file.c (7477B)
1 #define _XOPEN_SOURCE 500 2 #include <stdio.h> 3 #include <dirent.h> 4 #include <errno.h> 5 #include <limits.h> 6 #include <string.h> 7 #include <stdlib.h> 8 #include <sys/stat.h> 9 #include <unistd.h> 10 11 #ifdef _WIN32 12 #include <libiberty/libiberty.h> 13 #define realpath(s, dummy) lrealpath(s) 14 #define DIR_SEP_CHAR '\\' 15 #define DIR_SEP_STR "\\" 16 #define pathcmp(path1, path2, length) strncasecmp(path1, path2, length) /* strncasecmp provided by libiberty */ 17 #define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR && ((strlen(file_name) > 2 && file_name[1] != ':') || strlen(file_name) <= 2)) 18 #else 19 #define DIR_SEP_CHAR '/' 20 #define DIR_SEP_STR "/" 21 #define pathcmp(path1, path2, length) strncmp(path1, path2, length) 22 #define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR) 23 #endif 24 25 #ifndef PATH_MAX 26 #define PATH_MAX 4096 27 #endif 28 29 #include "../uxn.h" 30 #include "file.h" 31 32 /* 33 Copyright (c) 2021-2023 Devine Lu Linvega, Andrew Alderwick 34 35 Permission to use, copy, modify, and distribute this software for any 36 purpose with or without fee is hereby granted, provided that the above 37 copyright notice and this permission notice appear in all copies. 38 39 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 40 WITH REGARD TO THIS SOFTWARE. 41 */ 42 43 typedef struct { 44 FILE *f; 45 DIR *dir; 46 char current_filename[4096]; 47 struct dirent *de; 48 enum { IDLE, 49 FILE_READ, 50 FILE_WRITE, 51 DIR_READ } state; 52 int outside_sandbox; 53 } UxnFile; 54 55 static UxnFile uxn_file[POLYFILEY]; 56 57 static char 58 inthex(int n) 59 { 60 n &= 0xf; 61 return n < 10 ? '0' + n : 'a' + (n - 10); 62 } 63 64 static void 65 reset(UxnFile *c) 66 { 67 if(c->f != NULL) 68 fclose(c->f), c->f = NULL; 69 if(c->dir != NULL) 70 closedir(c->dir), c->dir = NULL; 71 c->de = NULL; 72 c->state = IDLE; 73 c->outside_sandbox = 0; 74 } 75 76 static Uint16 77 put_line(char *p, Uint16 len, const char *pathname, const char *basename, int fail_nonzero) 78 { 79 struct stat st; 80 if(len < strlen(basename) + 8) 81 return 0; 82 if(stat(pathname, &st)) 83 return fail_nonzero ? snprintf(p, len, "!!!! %s\n", basename) : 0; 84 else if(S_ISDIR(st.st_mode)) 85 return snprintf(p, len, "---- %s/\n", basename); 86 else if(st.st_size < 0x10000) 87 return snprintf(p, len, "%04x %s\n", (unsigned int)st.st_size, basename); 88 else 89 return snprintf(p, len, "???? %s\n", basename); 90 } 91 92 static Uint16 93 file_read_dir(UxnFile *c, char *dest, Uint16 len) 94 { 95 static char pathname[4352]; 96 char *p = dest; 97 if(c->de == NULL) c->de = readdir(c->dir); 98 for(; c->de != NULL; c->de = readdir(c->dir)) { 99 Uint16 n; 100 if(c->de->d_name[0] == '.' && c->de->d_name[1] == '\0') 101 continue; 102 if(strcmp(c->de->d_name, "..") == 0) { 103 /* hide "sandbox/.." */ 104 char cwd[PATH_MAX] = {'\0'}, *t; 105 /* Note there's [currently] no way of chdir()ing from uxn, so $PWD 106 * is always the sandbox top level. */ 107 getcwd(cwd, sizeof(cwd)); 108 /* We already checked that c->current_filename exists so don't need a wrapper. */ 109 t = realpath(c->current_filename, NULL); 110 if(strcmp(cwd, t) == 0) { 111 free(t); 112 continue; 113 } 114 free(t); 115 } 116 if(strlen(c->current_filename) + 1 + strlen(c->de->d_name) < sizeof(pathname)) 117 snprintf(pathname, sizeof(pathname), "%s/%s", c->current_filename, c->de->d_name); 118 else 119 pathname[0] = '\0'; 120 n = put_line(p, len, pathname, c->de->d_name, 1); 121 if(!n) break; 122 p += n; 123 len -= n; 124 } 125 return p - dest; 126 } 127 128 static char * 129 retry_realpath(const char *file_name) 130 { 131 char *r, p[PATH_MAX] = {'\0'}, *x; 132 int fnlen; 133 if(file_name == NULL) { 134 errno = EINVAL; 135 return NULL; 136 } else if((fnlen = strlen(file_name)) >= PATH_MAX) { 137 errno = ENAMETOOLONG; 138 return NULL; 139 } 140 if(notdriveroot(file_name)) { 141 /* TODO: use a macro instead of '/' for absolute path first character so that other systems can work */ 142 /* if a relative path, prepend cwd */ 143 getcwd(p, sizeof(p)); 144 if(strlen(p) + strlen(DIR_SEP_STR) + fnlen >= PATH_MAX) { 145 errno = ENAMETOOLONG; 146 return NULL; 147 } 148 strcat(p, DIR_SEP_STR); /* TODO: use a macro instead of '/' for the path delimiter */ 149 } 150 strcat(p, file_name); 151 while((r = realpath(p, NULL)) == NULL) { 152 if(errno != ENOENT) 153 return NULL; 154 x = strrchr(p, DIR_SEP_CHAR); /* TODO: path delimiter macro */ 155 if(x) 156 *x = '\0'; 157 else 158 return NULL; 159 } 160 return r; 161 } 162 163 static void 164 file_check_sandbox(UxnFile *c) 165 { 166 char *x, *rp, cwd[PATH_MAX] = {'\0'}; 167 x = getcwd(cwd, sizeof(cwd)); 168 rp = retry_realpath(c->current_filename); 169 if(rp == NULL || (x && pathcmp(cwd, rp, strlen(cwd)) != 0)) { 170 c->outside_sandbox = 1; 171 fprintf(stderr, "file warning: blocked attempt to access %s outside of sandbox\n", c->current_filename); 172 } 173 free(rp); 174 } 175 176 static Uint16 177 file_init(UxnFile *c, char *filename, size_t max_len, int override_sandbox) 178 { 179 char *p = c->current_filename; 180 size_t len = sizeof(c->current_filename); 181 reset(c); 182 if(len > max_len) len = max_len; 183 while(len) { 184 if((*p++ = *filename++) == '\0') { 185 if(!override_sandbox) /* override sandbox for loading roms */ 186 file_check_sandbox(c); 187 return 0; 188 } 189 len--; 190 } 191 c->current_filename[0] = '\0'; 192 return 0; 193 } 194 195 static Uint16 196 file_read(UxnFile *c, void *dest, int len) 197 { 198 if(c->outside_sandbox) return 0; 199 if(c->state != FILE_READ && c->state != DIR_READ) { 200 reset(c); 201 if((c->dir = opendir(c->current_filename)) != NULL) 202 c->state = DIR_READ; 203 else if((c->f = fopen(c->current_filename, "rb")) != NULL) 204 c->state = FILE_READ; 205 } 206 if(c->state == FILE_READ) 207 return fread(dest, 1, len, c->f); 208 if(c->state == DIR_READ) 209 return file_read_dir(c, dest, len); 210 return 0; 211 } 212 213 static Uint16 214 file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags) 215 { 216 Uint16 ret = 0; 217 if(c->outside_sandbox) return 0; 218 if(c->state != FILE_WRITE) { 219 reset(c); 220 if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL) 221 c->state = FILE_WRITE; 222 } 223 if(c->state == FILE_WRITE) { 224 if((ret = fwrite(src, 1, len, c->f)) > 0 && fflush(c->f) != 0) 225 ret = 0; 226 } 227 return ret; 228 } 229 230 static Uint16 231 file_stat(UxnFile *c, char *p, Uint16 len) 232 { 233 unsigned int i, size; 234 struct stat st; 235 if(c->outside_sandbox || !len) 236 return 0; 237 if(stat(c->current_filename, &st)) 238 for(i = 0; i < len; i++) 239 p[i] = '!'; 240 else if(S_ISDIR(st.st_mode)) 241 for(i = 0; i < len; i++) 242 p[i] = '-'; 243 else if(st.st_size >= 1 << (len << 2)) 244 for(i = 0; i < len; i++) 245 p[i] = '?'; 246 else 247 for(i = 0, size = st.st_size; i < len; i++) 248 p[i] = inthex(size >> ((len - i - 1) << 2)); 249 return len; 250 } 251 252 static Uint16 253 file_delete(UxnFile *c) 254 { 255 return c->outside_sandbox ? 0 : unlink(c->current_filename); 256 } 257 258 /* file registers */ 259 260 static Uint16 rL; 261 262 /* IO */ 263 264 void 265 file_deo(Uint8 id, Uint8 *ram, Uint8 *d, Uint8 port) 266 { 267 UxnFile *c = &uxn_file[id]; 268 Uint16 addr, res; 269 switch(port) { 270 case 0x5: 271 addr = (d[0x4] << 8) | d[0x5]; 272 if(rL > 0x10000 - addr) rL = 0x10000 - addr; 273 res = file_stat(c, (char *)&ram[addr], rL > 0x10 ? 0x10 : rL); 274 d[0x2] = res >> 8, d[0x3] = res; 275 return; 276 case 0x6: 277 res = file_delete(c); 278 d[0x2] = res >> 8, d[0x3] = res; 279 return; 280 case 0x9: 281 addr = (d[0x8] << 8) | d[0x9]; 282 res = file_init(c, (char *)&ram[addr], 0x10000 - addr, 0); 283 d[0x2] = res >> 8, d[0x3] = res; 284 return; 285 case 0xa: 286 case 0xb: 287 rL = (d[0xa] << 8) | d[0xb]; 288 return; 289 case 0xd: 290 addr = (d[0xc] << 8) | d[0xd]; 291 if(rL > 0x10000 - addr) rL = 0x10000 - addr; 292 res = file_read(c, &ram[addr], rL); 293 d[0x2] = res >> 8, d[0x3] = res; 294 return; 295 case 0xf: 296 addr = (d[0xe] << 8) | d[0xf]; 297 if(rL > 0x10000 - addr) rL = 0x10000 - addr; 298 res = file_write(c, &ram[addr], rL, d[0x7]); 299 d[0x2] = res >> 8, d[0x3] = res; 300 return; 301 } 302 }