file.c (9324B)
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 <direct.h> 13 #include <libiberty/libiberty.h> 14 #define realpath(s, dummy) lrealpath(s) 15 #define DIR_SEP_CHAR '\\' 16 #define DIR_SEP_STR "\\" 17 #define pathcmp(path1, path2, length) strncasecmp(path1, path2, length) /* strncasecmp provided by libiberty */ 18 #define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR && ((strlen(file_name) > 2 && file_name[1] != ':') || strlen(file_name) <= 2)) 19 #define mkdir(file_name) (_mkdir(file_name) == 0) 20 #else 21 #define DIR_SEP_CHAR '/' 22 #define DIR_SEP_STR "/" 23 #define pathcmp(path1, path2, length) strncmp(path1, path2, length) 24 #define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR) 25 #define mkdir(file_name) (mkdir(file_name, 0755) == 0) 26 #endif 27 28 #ifndef PATH_MAX 29 #define PATH_MAX 4096 30 #endif 31 32 #include "../uxn.h" 33 #include "file.h" 34 35 /* 36 Copyright (c) 2021-2023 Devine Lu Linvega, Andrew Alderwick 37 38 Permission to use, copy, modify, and distribute this software for any 39 purpose with or without fee is hereby granted, provided that the above 40 copyright notice and this permission notice appear in all copies. 41 42 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 43 WITH REGARD TO THIS SOFTWARE. 44 */ 45 46 typedef struct { 47 FILE *f; 48 DIR *dir; 49 char current_filename[4096]; 50 struct dirent *de; 51 enum { IDLE, 52 FILE_READ, 53 FILE_WRITE, 54 DIR_READ, 55 DIR_WRITE 56 } state; 57 int outside_sandbox; 58 } UxnFile; 59 60 static UxnFile uxn_file[POLYFILEY]; 61 62 static void 63 reset(UxnFile *c) 64 { 65 if(c->f != NULL) { 66 fclose(c->f); 67 c->f = NULL; 68 } 69 if(c->dir != NULL) { 70 closedir(c->dir); 71 c->dir = NULL; 72 } 73 c->de = NULL; 74 c->state = IDLE; 75 c->outside_sandbox = 0; 76 } 77 78 static Uint16 79 get_entry(char *p, Uint16 len, const char *pathname, const char *basename, int fail_nonzero) 80 { 81 struct stat st; 82 if(len < strlen(basename) + 8) 83 return 0; 84 if(stat(pathname, &st)) 85 return fail_nonzero ? snprintf(p, len, "!!!! %s\n", basename) : 0; 86 else if(S_ISDIR(st.st_mode)) 87 return snprintf(p, len, "---- %s/\n", basename); 88 else if(st.st_size < 0x10000) 89 return snprintf(p, len, "%04x %s\n", (unsigned int)st.st_size, basename); 90 else 91 return snprintf(p, len, "???? %s\n", basename); 92 } 93 94 static Uint16 95 file_read_dir(UxnFile *c, char *dest, Uint16 len) 96 { 97 static char pathname[4352]; 98 char *p = dest; 99 if(c->de == NULL) c->de = readdir(c->dir); 100 for(; c->de != NULL; c->de = readdir(c->dir)) { 101 Uint16 n; 102 if(c->de->d_name[0] == '.' && c->de->d_name[1] == '\0') 103 continue; 104 if(strcmp(c->de->d_name, "..") == 0) { 105 /* hide "sandbox/.." */ 106 char cwd[PATH_MAX] = {'\0'}, *t; 107 /* Note there's [currently] no way of chdir()ing from uxn, so $PWD 108 * is always the sandbox top level. */ 109 getcwd(cwd, sizeof(cwd)); 110 /* We already checked that c->current_filename exists so don't need a wrapper. */ 111 t = realpath(c->current_filename, NULL); 112 if(strcmp(cwd, t) == 0) { 113 free(t); 114 continue; 115 } 116 free(t); 117 } 118 if(strlen(c->current_filename) + 1 + strlen(c->de->d_name) < sizeof(pathname)) 119 snprintf(pathname, sizeof(pathname), "%s/%s", c->current_filename, c->de->d_name); 120 else 121 pathname[0] = '\0'; 122 n = get_entry(p, len, pathname, c->de->d_name, 1); 123 if(!n) break; 124 p += n; 125 len -= n; 126 } 127 return p - dest; 128 } 129 130 static char * 131 retry_realpath(const char *file_name) 132 { 133 char *r, p[PATH_MAX] = {'\0'}, *x; 134 int fnlen; 135 if(file_name == NULL) { 136 errno = EINVAL; 137 return NULL; 138 } else if((fnlen = strlen(file_name)) >= PATH_MAX) { 139 errno = ENAMETOOLONG; 140 return NULL; 141 } 142 if(notdriveroot(file_name)) { 143 /* TODO: use a macro instead of '/' for absolute path first character so that other systems can work */ 144 /* if a relative path, prepend cwd */ 145 getcwd(p, sizeof(p)); 146 if(strlen(p) + strlen(DIR_SEP_STR) + fnlen >= PATH_MAX) { 147 errno = ENAMETOOLONG; 148 return NULL; 149 } 150 strcat(p, DIR_SEP_STR); /* TODO: use a macro instead of '/' for the path delimiter */ 151 } 152 strcat(p, file_name); 153 while((r = realpath(p, NULL)) == NULL) { 154 if(errno != ENOENT) 155 return NULL; 156 x = strrchr(p, DIR_SEP_CHAR); /* TODO: path delimiter macro */ 157 if(x) 158 *x = '\0'; 159 else 160 return NULL; 161 } 162 return r; 163 } 164 165 static void 166 file_check_sandbox(UxnFile *c) 167 { 168 char *x, *rp, cwd[PATH_MAX] = {'\0'}; 169 x = getcwd(cwd, sizeof(cwd)); 170 rp = retry_realpath(c->current_filename); 171 if(rp == NULL || (x && pathcmp(cwd, rp, strlen(cwd)) != 0)) { 172 c->outside_sandbox = 1; 173 fprintf(stderr, "file warning: blocked attempt to access %s outside of sandbox\n", c->current_filename); 174 } 175 free(rp); 176 } 177 178 static Uint16 179 file_init(UxnFile *c, char *filename, size_t max_len, int override_sandbox) 180 { 181 char *p = c->current_filename; 182 size_t len = sizeof(c->current_filename); 183 reset(c); 184 if(len > max_len) len = max_len; 185 while(len) { 186 if((*p++ = *filename++) == '\0') { 187 if(!override_sandbox) /* override sandbox for loading roms */ 188 file_check_sandbox(c); 189 return 0; 190 } 191 len--; 192 } 193 c->current_filename[0] = '\0'; 194 return 0; 195 } 196 197 static Uint16 198 file_read(UxnFile *c, void *dest, int len) 199 { 200 if(c->outside_sandbox) return 0; 201 if(c->state != FILE_READ && c->state != DIR_READ) { 202 reset(c); 203 if((c->dir = opendir(c->current_filename)) != NULL) 204 c->state = DIR_READ; 205 else if((c->f = fopen(c->current_filename, "rb")) != NULL) 206 c->state = FILE_READ; 207 } 208 if(c->state == FILE_READ) 209 return fread(dest, 1, len, c->f); 210 if(c->state == DIR_READ) 211 return file_read_dir(c, dest, len); 212 return 0; 213 } 214 215 static int 216 is_dir_path(char *p) 217 { 218 char c; 219 int saw_slash = 0; 220 while((c = *p++)) 221 saw_slash = c == DIR_SEP_CHAR; 222 return saw_slash; 223 } 224 225 int 226 dir_exists(char *p) 227 { 228 struct stat st; 229 return stat(p, &st) == 0 && S_ISDIR(st.st_mode); 230 } 231 232 int 233 ensure_parent_dirs(char *p) 234 { 235 int ok = 1; 236 char c, *s = p; 237 for(; ok && (c = *p); p++) { 238 if(c == DIR_SEP_CHAR) { 239 *p = '\0'; 240 ok = dir_exists(s) || mkdir(s); 241 *p = c; 242 } 243 } 244 return ok; 245 } 246 247 static Uint16 248 file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags) 249 { 250 Uint16 ret = 0; 251 if(c->outside_sandbox) return 0; 252 ensure_parent_dirs(c->current_filename); 253 if(c->state != FILE_WRITE && c->state != DIR_WRITE) { 254 reset(c); 255 if(is_dir_path(c->current_filename)) 256 c->state = DIR_WRITE; 257 else if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL) 258 c->state = FILE_WRITE; 259 } 260 if(c->state == FILE_WRITE) { 261 if((ret = fwrite(src, 1, len, c->f)) > 0 && fflush(c->f) != 0) 262 ret = 0; 263 } 264 if(c->state == DIR_WRITE) { 265 ret = dir_exists(c->current_filename); 266 } 267 return ret; 268 } 269 270 static Uint16 271 stat_fill(Uint8 *dest, Uint16 len, char c) 272 { 273 Uint16 i; 274 for(i = 0; i < len; i++) 275 *(dest++) = c; 276 return len; 277 } 278 279 static Uint16 280 stat_size(Uint8 *dest, Uint16 len, off_t size) 281 { 282 Uint16 i; 283 dest += len - 1; 284 for(i = 0; i < len; i++) { 285 char c = '0' + (Uint8)(size & 0xf); 286 if(c > '9') c += 39; 287 *(dest--) = c; 288 size = size >> 4; 289 } 290 return size == 0 ? len : stat_fill(dest, len, '?'); 291 } 292 293 static Uint16 294 file_stat(UxnFile *c, void *dest, Uint16 len) 295 { 296 struct stat st; 297 if(c->outside_sandbox) 298 return 0; 299 else if(stat(c->current_filename, &st)) 300 return stat_fill(dest, len, '!'); 301 else if(S_ISDIR(st.st_mode)) 302 return stat_fill(dest, len, '-'); 303 else 304 return stat_size(dest, len, st.st_size); 305 } 306 307 static Uint16 308 file_delete(UxnFile *c) 309 { 310 return c->outside_sandbox ? 0 : unlink(c->current_filename); 311 } 312 313 /* IO */ 314 315 void 316 file_deo(Uint8 port) 317 { 318 Uint16 addr, len, res; 319 switch(port) { 320 case 0xa5: 321 addr = PEEK2(&uxn.dev[0xa4]); 322 len = PEEK2(&uxn.dev[0xaa]); 323 if(len > 0x10000 - addr) 324 len = 0x10000 - addr; 325 res = file_stat(&uxn_file[0], &uxn.ram[addr], len); 326 POKE2(&uxn.dev[0xa2], res); 327 break; 328 case 0xa6: 329 res = file_delete(&uxn_file[0]); 330 POKE2(&uxn.dev[0xa2], res); 331 break; 332 case 0xa9: 333 addr = PEEK2(&uxn.dev[0xa8]); 334 res = file_init(&uxn_file[0], (char *)&uxn.ram[addr], 0x10000 - addr, 0); 335 POKE2(&uxn.dev[0xa2], res); 336 break; 337 case 0xad: 338 addr = PEEK2(&uxn.dev[0xac]); 339 len = PEEK2(&uxn.dev[0xaa]); 340 if(len > 0x10000 - addr) 341 len = 0x10000 - addr; 342 res = file_read(&uxn_file[0], &uxn.ram[addr], len); 343 POKE2(&uxn.dev[0xa2], res); 344 break; 345 case 0xaf: 346 addr = PEEK2(&uxn.dev[0xae]); 347 len = PEEK2(&uxn.dev[0xaa]); 348 if(len > 0x10000 - addr) 349 len = 0x10000 - addr; 350 res = file_write(&uxn_file[0], &uxn.ram[addr], len, uxn.dev[0xa7]); 351 POKE2(&uxn.dev[0xa2], res); 352 break; 353 /* File 2 */ 354 case 0xb5: 355 addr = PEEK2(&uxn.dev[0xb4]); 356 len = PEEK2(&uxn.dev[0xba]); 357 if(len > 0x10000 - addr) 358 len = 0x10000 - addr; 359 res = file_stat(&uxn_file[1], &uxn.ram[addr], len); 360 POKE2(&uxn.dev[0xb2], res); 361 break; 362 case 0xb6: 363 res = file_delete(&uxn_file[1]); 364 POKE2(&uxn.dev[0xb2], res); 365 break; 366 case 0xb9: 367 addr = PEEK2(&uxn.dev[0xb8]); 368 res = file_init(&uxn_file[1], (char *)&uxn.ram[addr], 0x10000 - addr, 0); 369 POKE2(&uxn.dev[0xb2], res); 370 break; 371 case 0xbd: 372 addr = PEEK2(&uxn.dev[0xbc]); 373 len = PEEK2(&uxn.dev[0xba]); 374 if(len > 0x10000 - addr) 375 len = 0x10000 - addr; 376 res = file_read(&uxn_file[1], &uxn.ram[addr], len); 377 POKE2(&uxn.dev[0xb2], res); 378 break; 379 case 0xbf: 380 addr = PEEK2(&uxn.dev[0xbe]); 381 len = PEEK2(&uxn.dev[0xba]); 382 if(len > 0x10000 - addr) 383 len = 0x10000 - addr; 384 res = file_write(&uxn_file[1], &uxn.ram[addr], len, uxn.dev[0xb7]); 385 POKE2(&uxn.dev[0xb2], res); 386 break; 387 } 388 }