diff --git a/include/gint/fs.h b/include/gint/fs.h index e7b3d4c..77cec45 100644 --- a/include/gint/fs.h +++ b/include/gint/fs.h @@ -87,6 +87,26 @@ void fs_free_descriptor(int fd); itself. */ int open_generic(fs_descriptor_type_t *type, void *data, int reuse_fd); +/* fs_path_normalize(): Normalize a path to eliminate ., .. and redundant / + + This function creates a copy of the specified path which is an absolute path + with redundant / removed and . and .. components resolved. For instance: + + "" -> "/" + "/subfolder/./" -> "/subfolder" + "/../subfolder/../subfolder" -> "/subfolder" + "/../../x/y/../t" -> "/x/t" + + The new string is created with malloc() and should be free()'d after use. */ +char *fs_path_normalize(char const *path); + +/* fs_path_normalize_fc(): Normalize a path and translate it to FONTCHARACTER + + This function is similar to fs_path_normalize(), but it creates a 16-bit + string that starts with \\fls0\ rather than /, and can be used in direct + calls to BFile. */ +uint16_t *fs_path_normalize_fc(char const *path); + #ifdef __cplusplus } #endif diff --git a/src/fs/fugue/fugue_dir.c b/src/fs/fugue/fugue_dir.c index c568f06..85c8918 100644 --- a/src/fs/fugue/fugue_dir.c +++ b/src/fs/fugue/fugue_dir.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "util.h" #include "fugue.h" @@ -63,7 +64,8 @@ off_t fugue_dir_lseek(void *data, off_t offset, int whence) if(whence == SEEK_END) offset += dp->count; - if(offset < 0 || offset >= dp->count) { + /* dp->count, being at the end of the enumeration, is a valid offset */ + if(offset < 0 || offset >= dp->count + 1) { errno = EINVAL; return -1; } @@ -97,6 +99,7 @@ const fs_descriptor_type_t fugue_dir_descriptor_type = { void *fugue_dir_explore(char const *path) { struct BFile_FileInfo info; + char *wildcard=NULL; uint16_t *fc_path=NULL, *search=NULL; dir_t *dp = malloc(sizeof *dp); @@ -111,7 +114,12 @@ void *fugue_dir_explore(char const *path) fc_path = malloc(512 * sizeof *fc_path); if(!fc_path) goto alloc_failure; - search = utf8_to_fc_alloc(u"\\\\fls0\\", path, u"\\*"); + wildcard = malloc(strlen(path) + 3); + if(!wildcard) goto alloc_failure; + strcpy(wildcard, path); + strcat(wildcard, "/*"); + + search = fs_path_normalize_fc(wildcard); if(!search) goto alloc_failure; rc = BFile_FindFirst(search, &sd, fc_path, &info); @@ -142,6 +150,8 @@ void *fugue_dir_explore(char const *path) if(info.type == BFile_Type_File) ent->d_type = DT_REG; + if(info.type == BFile_Type_Archived) + ent->d_type = DT_REG; if(info.type == BFile_Type_Directory) ent->d_type = DT_DIR; if(info.type == BFile_Type_Dot) @@ -160,6 +170,7 @@ alloc_failure: errno = ENOMEM; fugue_dir_close(dp); end: + free(wildcard); free(search); free(fc_path); if(sd >= 0) diff --git a/src/fs/fugue/fugue_mkdir.c b/src/fs/fugue/fugue_mkdir.c index 4118eef..29f10b9 100644 --- a/src/fs/fugue/fugue_mkdir.c +++ b/src/fs/fugue/fugue_mkdir.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "util.h" @@ -7,7 +8,7 @@ int fugue_mkdir(char const *path, GUNUSED mode_t mode) { ENOTSUP_IF_NOT_FUGUE(-1); - uint16_t *fcpath = utf8_to_fc_alloc(u"\\\\fls0\\", path, NULL); + uint16_t *fcpath = fs_path_normalize_fc(path); if(!fcpath) { errno = ENOMEM; return -1; @@ -16,8 +17,10 @@ int fugue_mkdir(char const *path, GUNUSED mode_t mode) int rc = BFile_Create(fcpath, BFile_Folder, NULL); if(rc < 0) { errno = bfile_error_to_errno(rc); + free(fcpath); return -1; } + free(fcpath); return 0; } diff --git a/src/fs/fugue/fugue_open.c b/src/fs/fugue/fugue_open.c index 493c275..aa1cdc7 100644 --- a/src/fs/fugue/fugue_open.c +++ b/src/fs/fugue/fugue_open.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "util.h" #include "fugue.h" @@ -11,7 +12,7 @@ int fugue_open(char const *path, int flags, GUNUSED mode_t mode) { ENOTSUP_IF_NOT_FUGUE(-1); - uint16_t *fcpath = utf8_to_fc_alloc(u"\\\\fls0\\", path, NULL); + uint16_t *fcpath = fs_path_normalize_fc(path); int fugue_fd, err, rc=-1, type, fd=-1; if(!fcpath) { @@ -31,21 +32,32 @@ int fugue_open(char const *path, int flags, GUNUSED mode_t mode) /* Truncation requires the file to be removed/recreated */ bool trunc = (flags & O_TRUNC) && (flags & O_CREAT); - /* Stat the entry */ - bool exists = (BFile_Ext_Stat(fcpath, &type, NULL) == 0); - if(!exists) type = -1; + /* Stat the entry. A special case is needed for the root, which doesn't + respond well. fs_path_normalize_fc() normalizes the path so we just + have to check for the fixed string "\\fls0\". */ + bool exists; + if(!memcmp(fcpath, u"\\\\fls0\\", 16)) { + exists = true; + type = BFile_Type_Directory; + } + else { + exists = (BFile_Ext_Stat(fcpath, &type, NULL) == 0); + if(!exists) type = -1; + } /* If the entry exists and O_EXCL was requested, fail. */ if(exists && excl) { errno = EEXIST; - return -1; + rc = -1; + goto end; } /* If the entry is not a directory but O_DIRECTORY is set, fail. If the directory doesn't exist, we fail regardless of O_CREAT. */ if((flags & O_DIRECTORY) && type != BFile_Type_Directory) { errno = (exists ? ENOTDIR : ENOENT); - return -1; + rc = -1; + goto end; } /* If the entry is a directory, open it as such */ @@ -83,6 +95,7 @@ int fugue_open(char const *path, int flags, GUNUSED mode_t mode) err = BFile_Create(fcpath, BFile_File, &size); if(err < 0) { errno = bfile_error_to_errno(err); + rc = -1; goto end; } fugue_fd = BFile_Open(fcpath, bfile_mode); @@ -104,7 +117,7 @@ int fugue_open(char const *path, int flags, GUNUSED mode_t mode) .type = &fugue_descriptor_type, .data = (void *)fugue_fd, }; - fd = fs_create_descriptor(&data); + rc = fd = fs_create_descriptor(&data); if(fd == -1) { BFile_Close(fugue_fd); @@ -112,7 +125,6 @@ int fugue_open(char const *path, int flags, GUNUSED mode_t mode) goto end; } - rc = fd; end: free(fcpath); return rc; diff --git a/src/fs/fugue/fugue_unlink.c b/src/fs/fugue/fugue_unlink.c index 29216df..2d33647 100644 --- a/src/fs/fugue/fugue_unlink.c +++ b/src/fs/fugue/fugue_unlink.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "util.h" @@ -7,7 +8,7 @@ int fugue_unlink(char const *path) { ENOTSUP_IF_NOT_FUGUE(-1); - uint16_t *fcpath = utf8_to_fc_alloc(u"\\\\fls0\\", path, NULL); + uint16_t *fcpath = fs_path_normalize_fc(path); if(!fcpath) { errno = ENOMEM; return -1; @@ -16,9 +17,11 @@ int fugue_unlink(char const *path) int err = BFile_Remove(fcpath); if(err < 0) { errno = bfile_error_to_errno(err); + free(fcpath); return -1; } + free(fcpath); return 0; } diff --git a/src/fs/fugue/util.c b/src/fs/fugue/util.c index e4bf711..dd04203 100644 --- a/src/fs/fugue/util.c +++ b/src/fs/fugue/util.c @@ -2,6 +2,7 @@ #include #include #include +#include #include int bfile_error_to_errno(int e) @@ -52,7 +53,7 @@ void utf8_to_fc(uint16_t *fc, char const *utf8, size_t fc_len) fc[j++] = utf8[i]; } - while(j < fc_len) + if(j < fc_len) fc[j++] = 0; } @@ -67,7 +68,7 @@ void fc_to_utf8(char *utf8, uint16_t const *fc, size_t utf8_len) utf8[j++] = fc[i]; } - while(j < utf8_len) + if(j < utf8_len) utf8[j++] = 0; } @@ -106,3 +107,111 @@ char *fc_to_utf8_alloc(uint16_t const *fc) fc_to_utf8(utf8, fc, len+1); return utf8; } + +/* Split a path into components. Only the top array needs to be freed */ +char **fs_split_components(char *path, int *count) +{ + char **comps = NULL; + *count = 0; + int allocated = 0; + + char *token = strtok(path, "/"); + + while(token) { + if(*count + 1 > allocated) { + allocated += 8; + char **new_comps = realloc(comps, + allocated * sizeof *comps); + if(!new_comps) { + *count = -1; + return NULL; + } + comps = new_comps; + } + comps[*count] = token; + *count += 1; + + token = strtok(NULL, "/"); + } + + return comps; +} + +/* Generalization of fs_path_normalize(). Returns a [char *] if use_fc=false, + otherwise returns an [uint16_t *]. */ +static void *path_normalize(char const *path, bool use_fc) +{ + char *path_dup = strdup(path); + if(!path_dup) return NULL; + + int components=0; + char **comps = fs_split_components(path_dup, &components); + if(components == -1) { + free(path_dup); + return NULL; + } + + /* Now rewrite comps[] to skip "." and apply ".." */ + int wr = 0; + for(int rd=0; rd < components; rd++) { + char *comp = comps[rd]; + if(!strcmp(comp, ".")) + continue; + else if(!strcmp(comp, "..")) + wr -= (wr > 0); + else + comps[wr++] = comp; + } + + /* Count total length */ + int length = (use_fc ? 7 : 1) + (wr >= 1 ? wr - 1 : 0); + for(int i = 0; i < wr; i++) { + length += utf8_len(comps[i]); + } + + /* Allocate string then copy */ + if(use_fc) { + uint16_t *fc = malloc((length + 1) * sizeof *fc); + uint16_t *fc_init = fc; + + memcpy(fc, u"\\\\fls0\\", 7*2); + fc += 7; + + for(int i = 0; i < wr; i++) { + if(i > 0) *fc++ = '\\'; + utf8_to_fc(fc, comps[i], (size_t)-1); + fc += utf8_len(comps[i]); + } + + *fc++ = 0; + free(path_dup); + free(comps); + return fc_init; + } + else { + char *utf8 = malloc(length + 1); + char *utf8_init = utf8; + *utf8++ = '/'; + + for(int i = 0; i < wr; i++) { + if(i > 0) *utf8++ = '/'; + strcpy(utf8, comps[i]); + utf8 += utf8_len(comps[i]); + } + + *utf8++ = 0; + free(path_dup); + free(comps); + return utf8_init; + } +} + +char *fs_path_normalize(char const *path) +{ + return path_normalize(path, false); +} + +uint16_t *fs_path_normalize_fc(char const *path) +{ + return path_normalize(path, true); +} diff --git a/src/fs/fugue/util.h b/src/fs/fugue/util.h index 8d39d70..c0a8184 100644 --- a/src/fs/fugue/util.h +++ b/src/fs/fugue/util.h @@ -14,7 +14,7 @@ extern "C" { if(gint[HWFS] != HWFS_FUGUE) { errno = ENOTSUP; return (rc); } /* Translate common BFile error codes to errno values. */ -extern int bfile_error_to_errno(int bfile_error_code); +int bfile_error_to_errno(int bfile_error_code); /* TODO: These functions do not actually translate special characters between encodings, they simply strip them. */ @@ -26,17 +26,17 @@ size_t fc_len(uint16_t const *fc); /* Convert UTF-8 to FONTCHARACTER; outputs fc_len characters with padding. If fc[fc_len-1] is not 0 after the call, then fc is too short. */ -extern void utf8_to_fc(uint16_t *fc, char const *utf8, size_t fc_len); +void utf8_to_fc(uint16_t *fc, char const *utf8, size_t fc_len); /* Same in the other direction. */ -extern void fc_to_utf8(char *utf8, uint16_t const *fc, size_t utf8_len); +void fc_to_utf8(char *utf8, uint16_t const *fc, size_t utf8_len); /* Same as utf8_to_fc() but allocates a string with malloc(). */ -extern uint16_t *utf8_to_fc_alloc(uint16_t *prefix, char const *utf8, +uint16_t *utf8_to_fc_alloc(uint16_t *prefix, char const *utf8, uint16_t *suffix); /* Same as fc_to_utf8() but allocates a string with malloc(). */ -extern char *fc_to_utf8_alloc(uint16_t const *fc); +char *fc_to_utf8_alloc(uint16_t const *fc); #ifdef __cplusplus }