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.
This commit is contained in:
~d6 2024-09-16 15:34:16 -04:00
parent ea4aaca2a6
commit c3023a4a71
1 changed files with 46 additions and 3 deletions

View File

@ -9,17 +9,20 @@
#include <unistd.h> #include <unistd.h>
#ifdef _WIN32 #ifdef _WIN32
#include <direct.h>
#include <libiberty/libiberty.h> #include <libiberty/libiberty.h>
#define realpath(s, dummy) lrealpath(s) #define realpath(s, dummy) lrealpath(s)
#define DIR_SEP_CHAR '\\' #define DIR_SEP_CHAR '\\'
#define DIR_SEP_STR "\\" #define DIR_SEP_STR "\\"
#define pathcmp(path1, path2, length) strncasecmp(path1, path2, length) /* strncasecmp provided by libiberty */ #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 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 #else
#define DIR_SEP_CHAR '/' #define DIR_SEP_CHAR '/'
#define DIR_SEP_STR "/" #define DIR_SEP_STR "/"
#define pathcmp(path1, path2, length) strncmp(path1, path2, length) #define pathcmp(path1, path2, length) strncmp(path1, path2, length)
#define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR) #define notdriveroot(file_name) (file_name[0] != DIR_SEP_CHAR)
#define mkdir(file_name) (mkdir(file_name, 0755) == 0)
#endif #endif
#ifndef PATH_MAX #ifndef PATH_MAX
@ -48,7 +51,9 @@ typedef struct {
enum { IDLE, enum { IDLE,
FILE_READ, FILE_READ,
FILE_WRITE, FILE_WRITE,
DIR_READ } state; DIR_READ,
DIR_WRITE
} state;
int outside_sandbox; int outside_sandbox;
} UxnFile; } UxnFile;
@ -207,20 +212,58 @@ file_read(UxnFile *c, void *dest, int len)
return 0; 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 static Uint16
file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags) file_write(UxnFile *c, void *src, Uint16 len, Uint8 flags)
{ {
Uint16 ret = 0; Uint16 ret = 0;
if(c->outside_sandbox) return 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); 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; c->state = FILE_WRITE;
} }
if(c->state == FILE_WRITE) { if(c->state == FILE_WRITE) {
if((ret = fwrite(src, 1, len, c->f)) > 0 && fflush(c->f) != 0) if((ret = fwrite(src, 1, len, c->f)) > 0 && fflush(c->f) != 0)
ret = 0; ret = 0;
} }
if (c->state == DIR_WRITE) {
ret = dir_exists(c->current_filename);
}
return ret; return ret;
} }