Ensure that all file operations correctly wrap in memory.

The Varvara spec says that memory accesses should wrap.
Previously the file device ignored this by attempting to
truncate all reads and writes that would go beyond 0xffff.
There may be cases where memory outside uxn.ram could be
accessed.

After this change, all addresses read from and written to
should correctly be wrapped (i.e. uxn.ram[addr & 0xffff]).

This change also fixes a bug with hexadecimal sizes which
the previous patch introduced.
This commit is contained in:
~d6 2024-09-20 20:56:39 -04:00
parent df51651789
commit 6dabcecafb
1 changed files with 111 additions and 78 deletions

View File

@ -76,26 +76,70 @@ reset(UxnFile *c)
}
static Uint16
get_entry(char *p, Uint16 len, const char *pathname, const char *basename, int fail_nonzero)
stat_fill(Uint16 addr, Uint16 len, char c)
{
struct stat st;
if(len < strlen(basename) + 8)
return 0;
if(stat(pathname, &st))
return fail_nonzero ? snprintf(p, len, "!!!! %s\n", basename) : 0;
else if(S_ISDIR(st.st_mode))
return snprintf(p, len, "---- %s/\n", basename);
else if(st.st_size < 0x10000)
return snprintf(p, len, "%04x %s\n", (unsigned int)st.st_size, basename);
else
return snprintf(p, len, "???? %s\n", basename);
Uint16 i;
for (i = 0; i < len; i++)
uxn.ram[(addr + i) & 0xffff] = c;
return len;
}
static Uint16
file_read_dir(UxnFile *c, char *dest, Uint16 len)
stat_size(Uint16 addr, Uint16 len, off_t size)
{
Uint16 i;
for (i = 1; i <= len; i++) {
char c = '0' + (Uint8)(size & 0xf);
if (c > '9') c += 39;
uxn.ram[(addr + len - i) & 0xffff] = c;
size = size >> 4;
}
if (size > 0)
stat_fill(addr, len, '?');
return len;
}
static Uint16
stat_entry(Uint16 addr, char sym, off_t size, const char *s)
{
Uint16 pos = addr;
pos += sym ? stat_fill(pos, 4, sym) : stat_size(pos, 4, size);
uxn.ram[(pos++) & 0xffff] = ' ';
while(*s)
uxn.ram[(pos++) & 0xffff] = *(s++);
if (sym == '-')
uxn.ram[(pos++) & 0xffff] = '/';
uxn.ram[(pos++) & 0xffff] = '\n';
return pos - addr;
}
static Uint16
get_entry(Uint16 addr, Uint16 len, const char *pathname, const char *basename)
{
struct stat st;
int row_size = strlen(basename) + 6; /* 4 bytes, ' ', basename, '\n' */
char sym = 0;
off_t size = 0;
if(stat(pathname, &st))
sym = '!';
else if(S_ISDIR(st.st_mode)) {
sym = '-';
row_size++; /* directories have a '/' suffix */
} else if(st.st_size >= 0x10000)
sym = '?';
else
size = st.st_size;
return len < row_size ? 0 : stat_entry(addr, sym, size, basename);
}
static Uint16
file_read_dir(UxnFile *c, Uint16 addr, Uint16 len)
{
static char pathname[4352];
char *p = dest;
Uint16 pos = addr;
char cwd[PATH_MAX] = {'\0'}, *t;
if(c->de == NULL) c->de = readdir(c->dir);
for(; c->de != NULL; c->de = readdir(c->dir)) {
Uint16 n;
@ -103,7 +147,6 @@ file_read_dir(UxnFile *c, char *dest, Uint16 len)
continue;
if(strcmp(c->de->d_name, "..") == 0) {
/* hide "sandbox/.." */
char cwd[PATH_MAX] = {'\0'}, *t;
/* Note there's [currently] no way of chdir()ing from uxn, so $PWD
* is always the sandbox top level. */
getcwd(cwd, sizeof(cwd));
@ -119,12 +162,12 @@ file_read_dir(UxnFile *c, char *dest, Uint16 len)
snprintf(pathname, sizeof(pathname), "%s/%s", c->current_filename, c->de->d_name);
else
pathname[0] = '\0';
n = get_entry(p, len, pathname, c->de->d_name, 1);
n = get_entry(pos, len, pathname, c->de->d_name);
if(!n) break;
p += n;
pos += n;
len -= n;
}
return p - dest;
return pos - addr;
}
static char *
@ -176,26 +219,35 @@ file_check_sandbox(UxnFile *c)
}
static Uint16
file_init(UxnFile *c, char *filename, size_t max_len, int override_sandbox)
file_init(UxnFile *c, Uint16 addr)
{
char *p = c->current_filename;
size_t len = sizeof(c->current_filename);
Uint16 i = addr;
reset(c);
if(len > max_len) len = max_len;
while(len) {
if((*p++ = *filename++) == '\0') {
if(!override_sandbox) /* override sandbox for loading roms */
for(;; p++, i++) {
if((*p = uxn.ram[i & 0xffff]) == '\0') {
file_check_sandbox(c);
return 0;
}
len--;
}
c->current_filename[0] = '\0';
return 0;
}
static Uint16
file_read(UxnFile *c, void *dest, int len)
file_read_file(Uint16 addr, int len, FILE *f)
{
int total = addr + len;
if (total <= 0x10000) {
return fread(&uxn.ram[addr], 1, len, f);
} else {
size_t n1 = fread(&uxn.ram[addr], 1, 0x10000 - addr, f);
size_t n2 = fread(&uxn.ram[0], 1, total - 0x10000, f);
return n1 + n2;
}
}
static Uint16
file_read(UxnFile *c, Uint16 addr, int len)
{
if(c->outside_sandbox) return 0;
if(c->state != FILE_READ && c->state != DIR_READ) {
@ -206,9 +258,9 @@ file_read(UxnFile *c, void *dest, int len)
c->state = FILE_READ;
}
if(c->state == FILE_READ)
return fread(dest, 1, len, c->f);
return file_read_file(addr, len, c->f);
if(c->state == DIR_READ)
return file_read_dir(c, dest, len);
return file_read_dir(c, addr, len);
return 0;
}
@ -245,7 +297,22 @@ ensure_parent_dirs(char *p)
}
static Uint16
file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags)
file_write_file(Uint16 addr, int len, FILE *f)
{
int total = addr + len;
fflush(stdout);
if (total <= 0x10000) {
Uint16 n = fwrite(&uxn.ram[addr], 1, len, f);
return fflush(f) == 0 ? n : 0;
} else {
Uint16 n1 = fwrite(&uxn.ram[addr], 1, 0x10000 - addr, f);
Uint16 n2 = fwrite(&uxn.ram[0], 1, total - 0x10000, f);
return fflush(f) == 0 ? n1 + n2 : 0;
}
}
static Uint16
file_write(UxnFile *c, Uint16 addr, Uint16 len, Uint8 flags)
{
Uint16 ret = 0;
if(c->outside_sandbox) return 0;
@ -258,8 +325,7 @@ file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags)
c->state = FILE_WRITE;
}
if(c->state == FILE_WRITE) {
if((ret = fwrite(src, 1, len, c->f)) > 0 && fflush(c->f) != 0)
ret = 0;
ret = file_write_file(addr, len, c->f);
}
if (c->state == DIR_WRITE) {
ret = dir_exists(c->current_filename);
@ -268,38 +334,17 @@ file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags)
}
static Uint16
stat_fill(Uint8 *dest, Uint16 len, char c)
{
Uint16 i;
for (i = 0; i < len; i++)
*(dest++) = c;
return len;
}
static Uint16
stat_size(Uint8 *dest, Uint16 len, off_t size)
{
Uint16 i;
dest += len - 1;
for (i = 0; i < len; i++) {
*(dest--) = '0' + (Uint8)(size & 0xf);
size = size >> 4;
}
return size == 0 ? len : stat_fill(dest, len, '?');
}
static Uint16
file_stat(UxnFile *c, void *dest, Uint16 len)
file_stat(UxnFile *c, Uint16 addr, Uint16 len)
{
struct stat st;
if(c->outside_sandbox)
return 0;
else if(stat(c->current_filename, &st))
return stat_fill(dest, len, '!');
return stat_fill(addr, len, '!');
else if(S_ISDIR(st.st_mode))
return stat_fill(dest, len, '-');
return stat_fill(addr, len, '-');
else
return stat_size(dest, len, st.st_size);
return stat_size(addr, len, st.st_size);
}
static Uint16
@ -318,9 +363,7 @@ file_deo(Uint8 port)
case 0xa5:
addr = PEEK2(&uxn.dev[0xa4]);
len = PEEK2(&uxn.dev[0xaa]);
if(len > 0x10000 - addr)
len = 0x10000 - addr;
res = file_stat(&uxn_file[0], &uxn.ram[addr], len);
res = file_stat(&uxn_file[0], addr, len);
POKE2(&uxn.dev[0xa2], res);
break;
case 0xa6:
@ -329,32 +372,26 @@ file_deo(Uint8 port)
break;
case 0xa9:
addr = PEEK2(&uxn.dev[0xa8]);
res = file_init(&uxn_file[0], (char *)&uxn.ram[addr], 0x10000 - addr, 0);
res = file_init(&uxn_file[0], addr);
POKE2(&uxn.dev[0xa2], res);
break;
case 0xad:
addr = PEEK2(&uxn.dev[0xac]);
len = PEEK2(&uxn.dev[0xaa]);
if(len > 0x10000 - addr)
len = 0x10000 - addr;
res = file_read(&uxn_file[0], &uxn.ram[addr], len);
res = file_read(&uxn_file[0], addr, len);
POKE2(&uxn.dev[0xa2], res);
break;
case 0xaf:
addr = PEEK2(&uxn.dev[0xae]);
len = PEEK2(&uxn.dev[0xaa]);
if(len > 0x10000 - addr)
len = 0x10000 - addr;
res = file_write(&uxn_file[0], &uxn.ram[addr], len, uxn.dev[0xa7]);
res = file_write(&uxn_file[0], addr, len, uxn.dev[0xa7]);
POKE2(&uxn.dev[0xa2], res);
break;
/* File 2 */
case 0xb5:
addr = PEEK2(&uxn.dev[0xb4]);
len = PEEK2(&uxn.dev[0xba]);
if(len > 0x10000 - addr)
len = 0x10000 - addr;
res = file_stat(&uxn_file[1], &uxn.ram[addr], len);
res = file_stat(&uxn_file[1], addr, len);
POKE2(&uxn.dev[0xb2], res);
break;
case 0xb6:
@ -363,23 +400,19 @@ file_deo(Uint8 port)
break;
case 0xb9:
addr = PEEK2(&uxn.dev[0xb8]);
res = file_init(&uxn_file[1], (char *)&uxn.ram[addr], 0x10000 - addr, 0);
res = file_init(&uxn_file[1], addr);
POKE2(&uxn.dev[0xb2], res);
break;
case 0xbd:
addr = PEEK2(&uxn.dev[0xbc]);
len = PEEK2(&uxn.dev[0xba]);
if(len > 0x10000 - addr)
len = 0x10000 - addr;
res = file_read(&uxn_file[1], &uxn.ram[addr], len);
res = file_read(&uxn_file[1], addr, len);
POKE2(&uxn.dev[0xb2], res);
break;
case 0xbf:
addr = PEEK2(&uxn.dev[0xbe]);
len = PEEK2(&uxn.dev[0xba]);
if(len > 0x10000 - addr)
len = 0x10000 - addr;
res = file_write(&uxn_file[1], &uxn.ram[addr], len, uxn.dev[0xb7]);
res = file_write(&uxn_file[1], addr, len, uxn.dev[0xb7]);
POKE2(&uxn.dev[0xb2], res);
break;
}