uxn

Varvara Ordinator, written in ANSI C(SDL2)
git clone https://git.eamoncaddigan.net/uxn.git
Log | Files | Refs | README | LICENSE

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 }