From c3023a4a713d77da29398cf7ee97ff8034285ac4 Mon Sep 17 00:00:00 2001 From: d_m Date: Mon, 16 Sep 2024 15:34:16 -0400 Subject: [PATCH] Support creating directories Previously Varvara did not have a way to create new directories. This change adds that capability without adding or modifying any device ports. The changes are: (1) When a filename ends in the directory separator, writing any value to File/write will create a directory. Unlike with regular files, the value is ignored. (2) When writing any path (regular files or directories) if there are missing parent directories Varvara will attempt to create them first. This behavior is similar to mkdir -p. The directory separator is / on most platforms, and \ on Windows. On POSIX platforms the directories will be created with permission 0755 (modified by the user's umask value). This change has been tested and works in both uxnemu and uxncli. --- src/devices/file.c | 49 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/devices/file.c b/src/devices/file.c index 13dd802..a7487d4 100644 --- a/src/devices/file.c +++ b/src/devices/file.c @@ -9,17 +9,20 @@ #include #ifdef _WIN32 +#include #include #define realpath(s, dummy) lrealpath(s) #define DIR_SEP_CHAR '\\' #define DIR_SEP_STR "\\" #define pathcmp(path1, path2, length) strncasecmp(path1, path2, length) /* strncasecmp provided by libiberty */ #define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR && ((strlen(file_name) > 2 && file_name[1] != ':') || strlen(file_name) <= 2)) +#define mkdir(file_name) (_mkdir(file_name) == 0) #else #define DIR_SEP_CHAR '/' #define DIR_SEP_STR "/" #define pathcmp(path1, path2, length) strncmp(path1, path2, length) #define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR) +#define mkdir(file_name) (mkdir(file_name, 0755) == 0) #endif #ifndef PATH_MAX @@ -48,7 +51,9 @@ typedef struct { enum { IDLE, FILE_READ, FILE_WRITE, - DIR_READ } state; + DIR_READ, + DIR_WRITE + } state; int outside_sandbox; } UxnFile; @@ -207,20 +212,58 @@ file_read(UxnFile *c, void *dest, int len) return 0; } +static int +is_dir_path(char *p) +{ + char c; + int saw_slash = 0; + while (c = *p++) + saw_slash = c == DIR_SEP_CHAR; + return saw_slash; +} + +int +dir_exists(char *p) +{ + struct stat st; + return stat(p, &st) == 0 && S_ISDIR(st.st_mode); +} + +int +ensure_parent_dirs(char *p) +{ + int ok = 1; + char c, *s = p; + for(; ok && (c = *p); p++) { + if (c == DIR_SEP_CHAR) { + *p = '\0'; + ok = dir_exists(s) || mkdir(s); + *p = c; + } + } + return ok; +} + static Uint16 file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags) { Uint16 ret = 0; if(c->outside_sandbox) return 0; - if(c->state != FILE_WRITE) { + ensure_parent_dirs(c->current_filename); + if(c->state != FILE_WRITE && c->state != DIR_WRITE) { reset(c); - if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL) + if (is_dir_path(c->current_filename)) + c->state = DIR_WRITE; + else if((c->f = fopen(c->current_filename, (flags & 0x01) ? "ab" : "wb")) != NULL) c->state = FILE_WRITE; } if(c->state == FILE_WRITE) { if((ret = fwrite(src, 1, len, c->f)) > 0 && fflush(c->f) != 0) ret = 0; } + if (c->state == DIR_WRITE) { + ret = dir_exists(c->current_filename); + } return ret; }