diff --git a/firmware/SOURCES b/firmware/SOURCES index a68d10ec76..f1c7621244 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -238,6 +238,7 @@ common/dircache.c common/pathfuncs.c common/fdprintf.c common/linked_list.c +common/rb_namespace.c common/strcasecmp.c common/strcasestr.c common/strnatcmp.c diff --git a/firmware/common/dir.c b/firmware/common/dir.c index f89129ae34..85e6ff316b 100644 --- a/firmware/common/dir.c +++ b/firmware/common/dir.c @@ -27,17 +27,14 @@ #include "dir.h" #include "pathfuncs.h" #include "fileobj_mgr.h" -#include "dircache_redirect.h" +#include "rb_namespace.h" /* structure used for open directory streams */ static struct dirstr_desc { struct filestr_base stream; /* basic stream info (first!) */ - struct dirscan_info scan; /* directory scan cursor */ + struct ns_scan_info scan; /* directory scan cursor */ struct dirent entry; /* current parsed entry information */ -#ifdef HAVE_MULTIVOLUME - int volumecounter; /* counter for root volume entries */ -#endif } open_streams[MAX_OPEN_DIRS]; /* check and return a struct dirstr_desc* from a DIR* */ @@ -47,7 +44,7 @@ static struct dirstr_desc * get_dirstr(DIR *dirp) if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) dir = NULL; - else if (dir->stream.flags & FDO_BUSY) + else if (dir->stream.flags & (FDO_BUSY|FD_VALID)) return dir; int errnum; @@ -104,49 +101,6 @@ static struct dirstr_desc * alloc_dirstr(void) return NULL; } -#ifdef HAVE_MULTIVOLUME -static int readdir_volume_inner(struct dirstr_desc *dir, struct dirent *entry) -{ - /* Volumes (secondary file systems) get inserted into the system root - * directory. If the path specified volume 0, enumeration will not - * include other volumes, but just its own files and directories. - * - * Fake special directories, which don't really exist, that will get - * redirected upon opendir() - */ - while (++dir->volumecounter < NUM_VOLUMES) - { - /* on the system root */ - if (!fat_ismounted(dir->volumecounter)) - continue; - - get_volume_name(dir->volumecounter, entry->d_name); - dir->entry.info.attr = ATTR_MOUNT_POINT; - dir->entry.info.size = 0; - dir->entry.info.wrtdate = 0; - dir->entry.info.wrttime = 0; - return 1; - } - - /* do normal directory entry fetching */ - return 0; -} -#endif /* HAVE_MULTIVOLUME */ - -static inline int readdir_volume(struct dirstr_desc *dir, - struct dirent *entry) -{ -#ifdef HAVE_MULTIVOLUME - /* fetch virtual volume entries? */ - if (dir->volumecounter < NUM_VOLUMES) - return readdir_volume_inner(dir, entry); -#endif /* HAVE_MULTIVOLUME */ - - /* do normal directory entry fetching */ - return 0; - (void)dir; (void)entry; -} - /** POSIX interface **/ @@ -165,21 +119,13 @@ DIR * opendir(const char *dirname) if (!dir) FILE_ERROR(EMFILE, RC); - rc = open_stream_internal(dirname, FF_DIR, &dir->stream, NULL); + rc = ns_open_stream(dirname, FF_DIR, &dir->stream, &dir->scan); if (rc < 0) { DEBUGF("Open failed: %d\n", rc); FILE_ERROR(ERRNO, RC); } -#ifdef HAVE_MULTIVOLUME - /* volume counter is relevant only to the system root */ - dir->volumecounter = rc > 1 ? 0 : INT_MAX; -#endif /* HAVE_MULTIVOLUME */ - - fat_rewind(&dir->stream.fatstr); - rewinddir_dirent(&dir->scan); - dirp = (DIR *)dir; file_error: file_internal_unlock_WRITER(); @@ -204,7 +150,7 @@ int closedir(DIR *dirp) FILE_ERROR(EBADF, -2); } - rc = close_stream_internal(&dir->stream); + rc = ns_close_stream(&dir->stream); if (rc < 0) FILE_ERROR(ERRNO, rc * 10 - 3); @@ -222,16 +168,11 @@ struct dirent * readdir(DIR *dirp) struct dirent *res = NULL; - int rc = readdir_volume(dir, &dir->entry); - if (rc == 0) - { - rc = readdir_dirent(&dir->stream, &dir->scan, &dir->entry); - if (rc < 0) - FILE_ERROR(EIO, RC); - } - + int rc = ns_readdir_dirent(&dir->stream, &dir->scan, &dir->entry); if (rc > 0) res = &dir->entry; + else if (rc < 0) + FILE_ERROR(EIO, RC); file_error: RELEASE_DIRSTR(READER, dir); @@ -258,13 +199,9 @@ int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) if (!dir) FILE_ERROR_RETURN(ERRNO, -1); - int rc = readdir_volume(dir, entry); - if (rc == 0) - { - rc = readdir_dirent(&dir->stream, &dir->scan, entry); - if (rc < 0) - FILE_ERROR(EIO, rc * 10 - 4); - } + int rc = ns_readdir_dirent(&dir->stream, &dir->scan, entry); + if (rc < 0) + FILE_ERROR(EIO, rc * 10 - 4); file_error: RELEASE_DIRSTR(READER, dir); @@ -288,12 +225,7 @@ void rewinddir(DIR *dirp) if (!dir) FILE_ERROR_RETURN(ERRNO); - rewinddir_dirent(&dir->scan); - -#ifdef HAVE_MULTIVOLUME - if (dir->volumecounter != INT_MAX) - dir->volumecounter = 0; -#endif /* HAVE_MULTIVOLUME */ + ns_dirscan_rewind(&dir->scan); RELEASE_DIRSTR(READER, dir); } diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c index 0cdaf1bd4a..cc65d2d540 100644 --- a/firmware/common/dircache.c +++ b/firmware/common/dircache.c @@ -2541,13 +2541,10 @@ static ssize_t get_path_sub(int idx, struct get_path_sub_data *data) cename = ""; #ifdef HAVE_MULTIVOLUME + /* prepend the volume specifier */ int volume = IF_MV_VOL(-idx - 1); - if (volume > 0) - { - /* prepend the volume specifier for volumes > 0 */ - cename = alloca(VOL_MAX_LEN+1); - get_volume_name(volume, cename); - } + cename = alloca(VOL_MAX_LEN+1); + get_volume_name(volume, cename); #endif /* HAVE_MULTIVOLUME */ data->serialhash = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum, diff --git a/firmware/common/disk.c b/firmware/common/disk.c index 98c273b26d..3bd88f66a8 100644 --- a/firmware/common/disk.c +++ b/firmware/common/disk.c @@ -27,7 +27,7 @@ #include "disk_cache.h" #include "fileobj_mgr.h" #include "dir.h" -#include "dircache_redirect.h" +#include "rb_namespace.h" #include "disk.h" diff --git a/firmware/common/file.c b/firmware/common/file.c index cb918c6eab..893e475a32 100644 --- a/firmware/common/file.c +++ b/firmware/common/file.c @@ -28,7 +28,7 @@ #include "file.h" #include "fileobj_mgr.h" #include "disk_cache.h" -#include "dircache_redirect.h" +#include "rb_namespace.h" #include "string-extra.h" /** diff --git a/firmware/common/file_internal.c b/firmware/common/file_internal.c index fe18f90056..45f412e166 100644 --- a/firmware/common/file_internal.c +++ b/firmware/common/file_internal.c @@ -26,9 +26,7 @@ #include "pathfuncs.h" #include "disk_cache.h" #include "fileobj_mgr.h" -#include "dir.h" -#include "dircache_redirect.h" -#include "dircache.h" +#include "rb_namespace.h" #include "string-extra.h" #include "rbunicode.h" @@ -87,9 +85,10 @@ void file_cache_free(struct filestr_cache *cachep) /** Stream base APIs **/ -static inline void filestr_clear(struct filestr_base *stream) +static inline void filestr_clear(struct filestr_base *stream, + unsigned int flags) { - stream->flags = 0; + stream->flags = flags; stream->bindp = NULL; #if 0 stream->mtx = NULL; @@ -153,7 +152,7 @@ void filestr_discard_cache(struct filestr_base *stream) /* Initialize the base descriptor */ void filestr_base_init(struct filestr_base *stream) { - filestr_clear(stream); + filestr_clear(stream, FD_VALID); file_cache_init(&stream->cache); stream->cachep = &stream->cache; } @@ -161,7 +160,7 @@ void filestr_base_init(struct filestr_base *stream) /* free base descriptor resources */ void filestr_base_destroy(struct filestr_base *stream) { - filestr_clear(stream); + filestr_clear(stream, 0); filestr_free_cache(stream); } @@ -293,7 +292,7 @@ struct pathwalk_component #define WALK_RC_NOT_FOUND 0 /* successfully not found (aid for file creation) */ #define WALK_RC_FOUND 1 /* found and opened */ -#define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */ +#define WALK_RC_FOUND_ROOT 2 /* found and opened sys root */ #define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */ /* return another struct pathwalk_component from the pool, or NULL if the @@ -397,10 +396,10 @@ static int walk_open_info(struct pathwalk *walkp, /* make open official if not simply probing for presence - must do it here or compp->info on stack will get destroyed before it was copied */ - if (!(callflags & FF_PROBE)) + if (!(callflags & (FF_PROBE|FF_NOFS))) fileop_onopen_internal(stream, &compp->info, callflags); - return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT; + return compp->attr == ATTR_SYSTEM_ROOT ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND; } /* check the component against the prefix test info */ @@ -507,6 +506,10 @@ walk_path(struct pathwalk *walkp, struct pathwalk_component *compp, if (len > MAX_COMPNAME) return -ENAMETOOLONG; + /* no filesystem is mounted here */ + if (walkp->callflags & FF_NOFS) + return -ENOENT; + /* check for "." and ".." */ if (name[0] == '.') { @@ -575,7 +578,7 @@ int open_stream_internal(const char *path, unsigned int callflags, callflags &= ~(FF_INFO | FF_PARENTINFO | FF_CHECKPREFIX); /* This lets it be passed quietly to directory scanning */ - stream->flags = callflags & FF_MASK; + stream->flags |= callflags & FF_MASK; struct pathwalk walk; walk.path = path; @@ -585,80 +588,36 @@ int open_stream_internal(const char *path, unsigned int callflags, struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL); rootp->nextp = NULL; - rootp->attr = ATTR_SYSTEM_ROOT; - -#ifdef HAVE_MULTIVOLUME - int volume = 0, rootrc = WALK_RC_FOUND; -#endif /* HAVE_MULTIVOLUME */ while (1) { - const char *pathptr = walk.path; - - #ifdef HAVE_MULTIVOLUME - /* this seamlessly integrates secondary filesystems into the - root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */ - const char *p; - volume = path_strip_volume(pathptr, &p, false); - if (!CHECK_VOL(volume)) - { - DEBUGF("No such device or address: %d\n", volume); - FILE_ERROR(ENXIO, -2); - } - - if (p == pathptr) - { - /* the root of this subpath is the system root */ - rootp->attr = ATTR_SYSTEM_ROOT; - rootrc = WALK_RC_FOUND_ROOT; - } - else - { - /* this subpath specifies a mount point */ - rootp->attr = ATTR_MOUNT_POINT; - rootrc = WALK_RC_FOUND; - } - - walk.path = p; - #endif /* HAVE_MULTIVOLUME */ - - /* set name to start at last leading separator; names of volume - specifiers will be returned as "/" */ - rootp->name = GOBBLE_PATH_SEPCH(pathptr) - 1; - rootp->length = - IF_MV( rootrc == WALK_RC_FOUND ? p - rootp->name : ) 1; - - rc = fat_open_rootdir(IF_MV(volume,) &rootp->info.fatfile); + rc = ns_parse_root(walk.path, &rootp->name, &rootp->length); if (rc < 0) - { - /* not mounted */ - DEBUGF("No such device or address: %d\n", IF_MV_VOL(volume)); - rc = -ENXIO; break; - } - get_rootinfo_internal(&rootp->info); + rc = ns_open_root(IF_MV(rc,) &walk.callflags, &rootp->info, &rootp->attr); + if (rc < 0) + break; + + walk.path = rootp->name + rootp->length; + rc = walk_path(&walk, rootp, stream); if (rc != WALK_RC_CONT_AT_ROOT) break; } - switch (rc) + if (rc >= 0) { - case WALK_RC_FOUND_ROOT: - IF_MV( rc = rootrc; ) - case WALK_RC_NOT_FOUND: - case WALK_RC_FOUND: /* FF_PROBE leaves nothing for caller to clean up */ - if (callflags & FF_PROBE) + if (walk.callflags & FF_PROBE) filestr_base_destroy(stream); - - break; - - default: /* utter, abject failure :`( */ + } + else + { + /* utter, abject failure :`( */ DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno); filestr_base_destroy(stream); - FILE_ERROR(-rc, -3); + FILE_ERROR(-rc, -1); } file_error: diff --git a/firmware/common/fileobj_mgr.c b/firmware/common/fileobj_mgr.c index e34a460e10..37452fbbe1 100644 --- a/firmware/common/fileobj_mgr.c +++ b/firmware/common/fileobj_mgr.c @@ -20,12 +20,13 @@ ****************************************************************************/ #include "config.h" #include "system.h" +#include #include "debug.h" #include "file.h" #include "dir.h" #include "disk_cache.h" #include "fileobj_mgr.h" -#include "dircache_redirect.h" +#include "rb_namespace.h" /** * Manages file and directory streams on all volumes @@ -34,8 +35,8 @@ */ -/* there will always be enough of these for all user handles, thus these - functions don't return failure codes */ +/* there will always be enough of these for all user handles, thus most of + these functions don't return failure codes */ #define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS) /* describes the file as an image on the storage medium */ @@ -84,6 +85,15 @@ static struct ll_head busy_bindings[NUM_VOLUMES]; for (struct filestr_base *s = STREAM_##what(start); \ s; s = STREAM_NEXT(s)) +/* once a file/directory, always a file/directory; such a change + is a bug */ +#define CHECK_FO_DIRECTORY(callflags, fobp) \ + if (((callflags) ^ (fobp)->flags) & FO_DIRECTORY) \ + { \ + DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", \ + __func__, (fobp), (callflags)); \ + } + /* syncs information for the stream's old and new parent directory if any are currently opened */ @@ -96,6 +106,10 @@ static void fileobj_sync_parent(const struct file_base_info *infop[], continue; /* not directory or removed can't be parent of anything */ struct filestr_base *parentstrp = STREAM_FIRST(fobp); + + if (!parentstrp) + continue; + struct fat_file *parentfilep = &parentstrp->infop->fatfile; for (int i = 0; i < count; i++) @@ -111,8 +125,7 @@ static void fileobj_sync_parent(const struct file_base_info *infop[], } /* see if this file has open streams and return that fileobj_binding if so, - else grab a new one from the free list; returns true if this stream is - the only open one */ + else grab a new one from the free list; returns true if this is new */ static bool binding_assign(const struct file_base_info *srcinfop, struct fileobj_binding **fobpp) { @@ -123,7 +136,7 @@ static bool binding_assign(const struct file_base_info *srcinfop, if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile)) { - /* already has open streams */ + /* already has open streams/mounts */ *fobpp = fobp; return false; } @@ -143,6 +156,23 @@ static void binding_add_to_free_list(struct fileobj_binding *fobp) ll_insert_last(FREE_BINDINGS(), &fobp->bind.node); } +static void bind_source_info(const struct file_base_info *srcinfop, + struct fileobj_binding **fobpp) +{ + if (!binding_assign(srcinfop, fobpp)) + return; /* already in use */ + + /* is new */ + (*fobpp)->bind.info = *srcinfop; + fileobj_bind_file(&(*fobpp)->bind); +} + +static void release_binding(struct fileobj_binding *fobp) +{ + fileobj_unbind_file(&fobp->bind); + binding_add_to_free_list(fobp); +} + /** File and directory internal interface **/ void file_binding_insert_last(struct file_base_binding *bindp) @@ -169,6 +199,41 @@ void file_binding_remove_next(struct file_base_binding *prevp, } #endif /* HAVE_DIRCACHE */ +/* mounts a file object as a target from elsewhere */ +bool fileobj_mount(const struct file_base_info *srcinfop, + unsigned int callflags, + struct file_base_binding **bindpp) +{ + struct fileobj_binding *fobp; + bind_source_info(srcinfop, &fobp); + + CHECK_FO_DIRECTORY(callflags, fobp); + + if (fobp->flags & FO_MOUNTTARGET) + return false; /* already mounted */ + + fobp->flags |= FDO_BUSY | FO_MOUNTTARGET | + (callflags & FO_DIRECTORY); + + *bindpp = &fobp->bind; + + return true; +} + +/* unmounts the file object and frees it if now unusued */ +void fileobj_unmount(struct file_base_binding *bindp) +{ + struct fileobj_binding *fobp = (struct fileobj_binding *)bindp; + + if (!(fobp->flags & FO_MOUNTTARGET)) + return; /* not mounted */ + + if (STREAM_FIRST(fobp) == NULL) + release_binding(fobp); /* no longer in use */ + else + fobp->flags &= ~FO_MOUNTTARGET; +} + /* opens the file object for a new stream and sets up the caches; * the stream must already be opened at the FS driver level and *stream * initialized. @@ -180,10 +245,14 @@ void fileobj_fileop_open(struct filestr_base *stream, const struct file_base_info *srcinfop, unsigned int callflags) { + /* assign base file information */ struct fileobj_binding *fobp; - bool first = binding_assign(srcinfop, &fobp); + bind_source_info(srcinfop, &fobp); + + unsigned int foflags = fobp->flags; /* add stream to this file's list */ + bool first = STREAM_FIRST(fobp) == NULL; ll_insert_last(&fobp->list, &stream->node); /* initiate the new stream into the enclave */ @@ -197,27 +266,16 @@ void fileobj_fileop_open(struct filestr_base *stream, if (first) { /* first stream for file */ - fobp->bind.info = *srcinfop; - fobp->flags = FDO_BUSY | FO_SINGLE | - (callflags & (FO_DIRECTORY|FO_TRUNC)); - fobp->writers = 0; - fobp->size = 0; - - fileobj_bind_file(&fobp->bind); + fobp->flags = foflags | FDO_BUSY | FO_SINGLE | + (callflags & (FO_DIRECTORY|FO_TRUNC)); + fobp->writers = 0; + fobp->size = 0; } else { /* additional stream for file */ - fobp->flags &= ~FO_SINGLE; - fobp->flags |= callflags & FO_TRUNC; - - /* once a file/directory, always a file/directory; such a change - is a bug */ - if ((callflags ^ fobp->flags) & FO_DIRECTORY) - { - DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", - __func__, stream, callflags); - } + fobp->flags = (foflags & ~FO_SINGLE) | (callflags & FO_TRUNC); + CHECK_FO_DIRECTORY(callflags, fobp); } if ((callflags & FD_WRITE) && ++fobp->writers == 1) @@ -257,12 +315,14 @@ void fileobj_fileop_close(struct filestr_base *stream) if (foflags & FO_SINGLE) { /* last stream for file; close everything */ - fileobj_unbind_file(&fobp->bind); - if (fobp->writers) file_cache_free(&fobp->cache); - binding_add_to_free_list(fobp); + /* binding must stay valid if something is mounted to here */ + if (foflags & FO_MOUNTTARGET) + fobp->flags = foflags & (FDO_BUSY|FO_DIRECTORY|FO_MOUNTTARGET); + else + release_binding(fobp); } else { diff --git a/firmware/common/pathfuncs.c b/firmware/common/pathfuncs.c index 0935a9a6e3..078c0b6938 100644 --- a/firmware/common/pathfuncs.c +++ b/firmware/common/pathfuncs.c @@ -105,7 +105,7 @@ static const unsigned char storage_dec_indexes[STORAGE_NUM_TYPES+1] = */ int path_strip_volume(const char *name, const char **nameptr, bool greedy) { - int volume = 0; + int volume = ROOT_VOLUME; const char *t = name; int c, v = 0; @@ -114,9 +114,16 @@ int path_strip_volume(const char *name, const char **nameptr, bool greedy) * digits within the brackets is parsed as the volume number and of * those, only the last ones VOL_MUM_MAX allows. */ - c = *(t = GOBBLE_PATH_SEPCH(t)); /* skip all leading slashes */ + t = GOBBLE_PATH_SEPCH(t); /* skip all leading slashes */ + if (t == name) + { + volume = -1; /* relative path; don't know */ + goto psv_out; + } + + c = *t; if (c != VOL_START_TOK) /* missing start token? no volume */ - goto volume0; + goto psv_out; do { @@ -127,7 +134,7 @@ int path_strip_volume(const char *name, const char **nameptr, bool greedy) break; case '\0': case PATH_SEPCH: /* no closing bracket; no volume */ - goto volume0; + goto psv_out; default: /* something else; reset volume */ v = 0; } @@ -137,7 +144,7 @@ int path_strip_volume(const char *name, const char **nameptr, bool greedy) if (!(c = *++t)) /* no more path and no '/' is ok */ ; else if (c != PATH_SEPCH) /* more path and no separator after end */ - goto volume0; + goto psv_out; else if (greedy) t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */ @@ -146,7 +153,7 @@ int path_strip_volume(const char *name, const char **nameptr, bool greedy) volume = v; name = t; -volume0: +psv_out: if (nameptr) *nameptr = name; return volume; @@ -157,10 +164,14 @@ volume0: */ int get_volume_name(int volume, char *buffer) { - if (volume < 0) + if (volume < 0 || volume == ROOT_VOLUME) { - *buffer = '\0'; - return 0; + char *t = buffer; + if (volume == ROOT_VOLUME) + *t++ = PATH_ROOTCHR; + + *t = '\0'; + return t - buffer; } volume %= VOL_NUM_MAX; /* as path parser would have it */ @@ -173,6 +184,20 @@ int get_volume_name(int volume, char *buffer) return snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c", VOL_START_TOK, voldec, volume, VOL_END_TOK); } + +/* Returns volume name formatted with the root. Assumes buffer size is at + * least {VOL_MAX_LEN}+2 */ +int make_volume_root(int volume, char *buffer) +{ + char *t = buffer; + + if (volume >= 0 && volume != ROOT_VOLUME) + *t++ = PATH_ROOTCHR; + + t += get_volume_name(volume, t); + + return t - buffer; +} #endif /* HAVE_MULTIVOLUME */ /* Just like path_strip_volume() but strips a leading drive specifier and diff --git a/firmware/common/rb_namespace.c b/firmware/common/rb_namespace.c new file mode 100644 index 0000000000..04f92e97af --- /dev/null +++ b/firmware/common/rb_namespace.c @@ -0,0 +1,289 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 by Michael Sevakis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include +#include "fileobj_mgr.h" +#include "rb_namespace.h" + +#define ROOT_CONTENTS_INDEX (NUM_VOLUMES) +#define NUM_ROOT_ITEMS (NUM_VOLUMES+1) + +static uint8_t root_entry_flags[NUM_VOLUMES+1]; +static struct file_base_binding *root_bindp; + +static inline unsigned int get_root_item_state(int item) +{ + return root_entry_flags[item]; +} + +static inline void set_root_item_state(int item, unsigned int state) +{ + root_entry_flags[item] = state; +} + +static void get_mount_point_entry(IF_MV(int volume,) struct dirent *entry) +{ +#ifdef HAVE_MULTIVOLUME + get_volume_name(volume, entry->d_name); +#else /* */ + strcpy(entry->d_name, PATH_ROOTSTR); +#endif /* HAVE_MULTIVOLUME */ + + /* is dirinfo_native */ + entry->info.attr = ATTR_MOUNT_POINT; + entry->info.size = 0; + entry->info.wrtdate = 0; + entry->info.wrttime = 0; +} + +/* unmount the directory that enumerates into the root namespace */ +static void unmount_item(int item) +{ + unsigned int state = get_root_item_state(item); + if (!state) + return; + + if (state & NSITEM_CONTENTS) + { + fileobj_unmount(root_bindp); + root_bindp = NULL; + } + + set_root_item_state(item, 0); +} + +/* mount the directory that enumerates into the root namespace */ +int root_mount_path(const char *path, unsigned int flags) +{ +#ifdef HAVE_MULTIVOLUME + int volume = path_strip_volume(path, NULL, false); + if (volume == ROOT_VOLUME) + return -EINVAL; + + if (!CHECK_VOL(volume)) + return -ENOENT; +#else + if (!path_is_absolute(path)) + return -ENOENT; +#endif /* HAVE_MULTIVOLUME */ + + bool contents = flags & NSITEM_CONTENTS; + int item = contents ? ROOT_CONTENTS_INDEX : IF_MV_VOL(volume); + unsigned int state = get_root_item_state(item); + + if (state) + return -EBUSY; + + if (contents) + { + /* cache information about the target */ + struct filestr_base stream; + struct path_component_info compinfo; + + int e = errno; + int rc = open_stream_internal(path, FF_DIR | FF_PROBE | FF_INFO | + FF_DEVPATH, &stream, &compinfo); + if (rc <= 0) + { + rc = rc ? -errno : -ENOENT; + errno = e; + return rc; + } + + if (!fileobj_mount(&compinfo.info, FO_DIRECTORY, &root_bindp)) + return -EBUSY; + } + + state = NSITEM_MOUNTED | (flags & (NSITEM_HIDDEN|NSITEM_CONTENTS)); + set_root_item_state(item, state); + + return 0; +} + +/* inform root that an entire volume is being unmounted */ +void root_unmount_volume(IF_MV_NONVOID(int volume)) +{ + FOR_EACH_VOLUME(volume, item) + { + #ifdef HAVE_MULTIVOLUME + uint32_t state = get_root_item_state(item); + if (state && (volume < 0 || item == volume)) + #endif /* HAVE_MULTIVOLUME */ + unmount_item(item); + } + + /* if the volume unmounted contains the root directory contents then + the contents must also be unmounted */ +#ifdef HAVE_MULTIVOLUME + uint32_t state = get_root_item_state(ROOT_CONTENTS_INDEX); + if (state && (volume < 0 || BASEINFO_VOL(&root_bindp->info) == volume)) +#endif + unmount_item(ROOT_CONTENTS_INDEX); +} + +/* parse the root part of a path */ +int ns_parse_root(const char *path, const char **pathp, uint16_t *lenp) +{ + int volume = ROOT_VOLUME; + +#ifdef HAVE_MULTIVOLUME + /* this seamlessly integrates secondary filesystems into the + root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */ + const char *p; + volume = path_strip_volume(path, &p, false); + if (volume != ROOT_VOLUME && !CHECK_VOL(volume)) + return -ENOENT; +#endif /* HAVE_MULTIVOLUME */ + + /* set name to start at last leading separator; name of root will + * be returned as "/", volume specifiers as "/" */ + *pathp = GOBBLE_PATH_SEPCH(path) - 1; + *lenp = IF_MV( volume < NUM_VOLUMES ? p - *pathp : ) 1; + +#ifdef HAVE_MULTIVOLUME + if (*lenp > MAX_COMPNAME+1) + return -ENAMETOOLONG; +#endif + + return volume; +} + +/* open one of the items in the root */ +int ns_open_root(IF_MV(int volume,) unsigned int *callflagsp, + struct file_base_info *infop, uint16_t *attrp) +{ + unsigned int callflags = *callflagsp; + bool devpath = !!(callflags & FF_DEVPATH); +#ifdef HAVE_MULTIVOLUME + bool sysroot = volume == ROOT_VOLUME; + if (devpath && sysroot) + return -ENOENT; /* devpath needs volume spec */ +#else + bool sysroot = !devpath; /* always sysroot unless devpath */ +#endif + + int item = sysroot ? ROOT_CONTENTS_INDEX : IF_MV_VOL(volume); + unsigned int state = get_root_item_state(item); + + if (sysroot) + { + *attrp = ATTR_SYSTEM_ROOT; + + if (state) + *infop = root_bindp->info; + else + *callflagsp = callflags | FF_NOFS; /* contents not mounted */ + } + else + { + *attrp = ATTR_MOUNT_POINT; + + if (!devpath && !state) + return -ENOENT; /* regular open requires having been mounted */ + + if (fat_open_rootdir(IF_MV(volume,) &infop->fatfile) < 0) + return -ENOENT; /* not mounted */ + + get_rootinfo_internal(infop); + } + + return 0; +} + +/* read root directory entries */ +int root_readdir_dirent(struct filestr_base *stream, + struct ns_scan_info *scanp, struct dirent *entry) +{ + int rc = 0; + + int item = scanp->item; + + /* skip any not-mounted or hidden items */ + unsigned int state; + while (1) + { + if (item >= NUM_ROOT_ITEMS) + goto file_eod; + + state = get_root_item_state(item); + if ((state & (NSITEM_MOUNTED|NSITEM_HIDDEN)) == NSITEM_MOUNTED) + break; + + item++; + } + + if (item == ROOT_CONTENTS_INDEX) + { + rc = readdir_dirent(stream, &scanp->scan, entry); + if (rc < 0) + FILE_ERROR(ERRNO, rc * 10 - 1); + + if (rc == 0) + item++; + } + else + { + get_mount_point_entry(IF_MV(item,) entry); + item++; + rc = 1; + } + + scanp->item = item; + +file_eod: + if (rc == 0) + empty_dirent(entry); + +file_error: + return rc; +} + +/* opens a stream to enumerate items in a namespace container */ +int ns_open_stream(const char *path, unsigned int callflags, + struct filestr_base *stream, struct ns_scan_info *scanp) +{ + /* stream still needs synchronization even if we don't have a stream */ + static struct mutex no_contents_mtx SHAREDBSS_ATTR; + + int rc = open_stream_internal(path, callflags, stream, NULL); + if (rc < 0) + FILE_ERROR(ERRNO, rc * 10 - 1); + + scanp->item = rc > 1 ? 0 : -1; + + if (stream->flags & FDO_BUSY) + { + /* root contents are mounted */ + fat_rewind(&stream->fatstr); + } + else + { + /* root contents not mounted */ + mutex_init(&no_contents_mtx); + stream->mtx = &no_contents_mtx; + } + + ns_dirscan_rewind(scanp); + + rc = 0; +file_error: + return rc; +} diff --git a/firmware/export/mv.h b/firmware/export/mv.h index ec7b2efdbd..5aa0ff8b4d 100644 --- a/firmware/export/mv.h +++ b/firmware/export/mv.h @@ -84,6 +84,10 @@ #define VOL_MAX_LEN (1 + VOL_DEC_MAX_LEN + 2 + 1) #define VOL_NUM_MAX 100 +#ifndef ROOT_VOLUME +#define ROOT_VOLUME INT_MAX +#endif + #else /* empty definitions if no multi-volume */ #define IF_MV(x...) #define IF_MV_NONVOID(x...) void diff --git a/firmware/export/pathfuncs.h b/firmware/export/pathfuncs.h index 92539c54c1..8858d85d24 100644 --- a/firmware/export/pathfuncs.h +++ b/firmware/export/pathfuncs.h @@ -30,10 +30,15 @@ /* useful char constants that could be reconfigured if desired */ #define PATH_SEPCH '/' #define PATH_SEPSTR "/" +#define PATH_ROOTCHR '/' #define PATH_ROOTSTR "/" #define PATH_BADSEPCH '\\' #define PATH_DRVSEPCH ':' +#ifndef ROOT_VOLUME +#define ROOT_VOLUME INT_MAX +#endif + /* a nicer way to check for "." and ".." than two strcmp() calls */ static inline bool is_dotdir_name(const char *name) { @@ -75,6 +80,7 @@ static inline bool name_is_dot_dot(const char *name) #ifdef HAVE_MULTIVOLUME int path_strip_volume(const char *name, const char **nameptr, bool greedy); int get_volume_name(int volume, char *name); +int make_volume_root(int volume, char *dst); #endif int path_strip_drive(const char *name, const char **nameptr, bool greedy); diff --git a/firmware/export/rbpaths.h b/firmware/export/rbpaths.h index de591f0ec1..458a070f92 100644 --- a/firmware/export/rbpaths.h +++ b/firmware/export/rbpaths.h @@ -64,6 +64,9 @@ #define PLUGIN_DIR ROCKBOX_DIR "/rocks" #define CODECS_DIR ROCKBOX_DIR "/codecs" +#define RB_ROOT_VOL_HIDDEN(v) (IF_MV_VOL(v) == 0) +#define RB_ROOT_CONTENTS_DIR "/" IF_MV("<0>") + #else /* APPLICATION */ #define HOME_DIR "" /* replaced at runtime */ diff --git a/firmware/include/dircache_redirect.h b/firmware/include/dircache_redirect.h index 9fae16b551..9a8de2fecd 100644 --- a/firmware/include/dircache_redirect.h +++ b/firmware/include/dircache_redirect.h @@ -20,7 +20,10 @@ ****************************************************************************/ #ifndef _DIRCACHE_REDIRECT_H_ +#include "rbpaths.h" +#include "pathfuncs.h" #include "dir.h" +#include "dircache.h" /*** ** Internal redirects that depend upon whether or not dircache is made @@ -123,10 +126,20 @@ static inline void fileop_onsync_internal(struct filestr_base *stream) static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) { +#ifdef HAVE_MULTIVOLUME + char path[VOL_MAX_LEN+2]; + make_volume_root(volume, path); +#else + const char *path = PATH_ROOTSTR; +#endif + root_mount_path(path, RB_ROOT_VOL_HIDDEN(volume) ? NSITEM_HIDDEN : 0); +#ifdef HAVE_MULTIVOLUME + if (volume == path_strip_volume(RB_ROOT_CONTENTS_DIR, NULL, false)) +#endif + root_mount_path(RB_ROOT_CONTENTS_DIR, NSITEM_CONTENTS); #ifdef HAVE_DIRCACHE dircache_mount(); #endif - IF_MV( (void)volume; ) } static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume)) @@ -135,6 +148,7 @@ static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume)) /* First, to avoid update of something about to be destroyed anyway */ dircache_unmount(IF_MV(volume)); #endif + root_unmount_volume(IF_MV(volume)); fileobj_mgr_unmount(IF_MV(volume)); } diff --git a/firmware/include/file_internal.h b/firmware/include/file_internal.h index d62b5a8541..f4bd8bb8c2 100644 --- a/firmware/include/file_internal.h +++ b/firmware/include/file_internal.h @@ -72,16 +72,18 @@ enum fildes_and_obj_flags /* used in descriptor and common */ FDO_BUSY = 0x0001, /* descriptor/object is in use */ /* only used in individual stream descriptor */ - FD_WRITE = 0x0002, /* descriptor has write mode */ - FD_WRONLY = 0x0004, /* descriptor is write mode only */ - FD_APPEND = 0x0008, /* descriptor is append mode */ + FD_VALID = 0x0002, /* descriptor is valid but not registered */ + FD_WRITE = 0x0004, /* descriptor has write mode */ + FD_WRONLY = 0x0008, /* descriptor is write mode only */ + FD_APPEND = 0x0010, /* descriptor is append mode */ FD_NONEXIST = 0x8000, /* closed but not freed (uncombined) */ /* only used as common flags */ - FO_DIRECTORY = 0x0010, /* fileobj is a directory */ - FO_TRUNC = 0x0020, /* fileobj is opened to be truncated */ - FO_REMOVED = 0x0040, /* fileobj was deleted while open */ - FO_SINGLE = 0x0080, /* fileobj has only one stream open */ - FDO_MASK = 0x00ff, + FO_DIRECTORY = 0x0020, /* fileobj is a directory */ + FO_TRUNC = 0x0040, /* fileobj is opened to be truncated */ + FO_REMOVED = 0x0080, /* fileobj was deleted while open */ + FO_SINGLE = 0x0100, /* fileobj has only one stream open */ + FO_MOUNTTARGET = 0x0200, /* fileobj kept open as a mount target */ + FDO_MASK = 0x03ff, FDO_CHG_MASK = FO_TRUNC, /* fileobj permitted external change */ /* bitflags that instruct various 'open' functions how to behave; * saved in stream flags (only) but not used by manager */ @@ -95,7 +97,9 @@ enum fildes_and_obj_flags FF_CACHEONLY = 0x00200000, /* succeed only if in dircache */ FF_INFO = 0x00400000, /* return info on self */ FF_PARENTINFO = 0x00800000, /* return info on parent */ - FF_MASK = 0x00ff0000, + FF_DEVPATH = 0x01000000, /* path is a device path, not root-based */ + FF_NOFS = 0x02000000, /* no filesystem mounted here */ + FF_MASK = 0x03ff0000, }; /** Common data structures used throughout **/ diff --git a/firmware/include/fileobj_mgr.h b/firmware/include/fileobj_mgr.h index 627d2df341..0db3520d34 100644 --- a/firmware/include/fileobj_mgr.h +++ b/firmware/include/fileobj_mgr.h @@ -29,6 +29,11 @@ void file_binding_remove(struct file_base_binding *bindp); void file_binding_remove_next(struct file_base_binding *prevp, struct file_base_binding *bindp); +bool fileobj_mount(const struct file_base_info *srcinfop, + unsigned int callflags, + struct file_base_binding **bindpp); +void fileobj_unmount(struct file_base_binding *bindp); + void fileobj_fileop_open(struct filestr_base *stream, const struct file_base_info *srcinfop, unsigned int callflags); diff --git a/firmware/include/fs_defines.h b/firmware/include/fs_defines.h index 538c4b36cd..aee6daff6a 100644 --- a/firmware/include/fs_defines.h +++ b/firmware/include/fs_defines.h @@ -51,12 +51,19 @@ /* internal functions open streams as well; make sure they don't fail if all user descs are busy; this needs to be at least the greatest quantity needed at once by all internal functions */ +/* internal functions open streams as well; make sure they don't fail if all + user descs are busy; this needs to be at least the greatest quantity needed + at once by all internal functions */ +#define MOUNT_AUX_FILEOBJS 1 + #ifdef HAVE_DIRCACHE -#define AUX_FILEOBJS 3 +#define DIRCACHE_AUX_FILEOBJS 1 #else -#define AUX_FILEOBJS 2 +#define DIRCACHE_AUX_FILEOBJS 0 #endif +#define AUX_FILEOBJS (2+DIRCACHE_AUX_FILEOBJS+MOUNT_AUX_FILEOBJS) + /* number of components statically allocated to handle the vast majority of path depths; should maybe be tuned for >= 90th percentile but for now, imma just guessing based on something like: diff --git a/firmware/include/rb_namespace.h b/firmware/include/rb_namespace.h new file mode 100644 index 0000000000..4d7a125c7b --- /dev/null +++ b/firmware/include/rb_namespace.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 by Michael Sevakis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef RB_NAMESPACE_H +#define RB_NAMESPACE_H + +#include "file_internal.h" + +enum ns_item_flags +{ + NSITEM_MOUNTED = 0x01, /* item is mounted */ + NSITEM_HIDDEN = 0x02, /* item is not enumerated */ + NSITEM_CONTENTS = 0x04, /* contents enumerate */ +}; + +struct ns_scan_info +{ + struct dirscan_info scan; /* dirscan info - first! */ + int item; /* current item in parent */ +}; + +/* root functions */ +int root_mount_path(const char *path, unsigned int flags); +void root_unmount_volume(IF_MV_NONVOID(int volume)); +int root_readdir_dirent(struct filestr_base *stream, + struct ns_scan_info *scanp, + struct dirent *entry); + +/* namespace functions */ +int ns_parse_root(const char *path, const char **pathp, uint16_t *lenp); +int ns_open_root(IF_MV(int volume,) unsigned int *callflagsp, + struct file_base_info *infop, uint16_t *attrp); +int ns_open_stream(const char *path, unsigned int callflags, + struct filestr_base *stream, struct ns_scan_info *scanp); + +/* closes the namespace stream */ +static inline int ns_close_stream(struct filestr_base *stream) +{ + return close_stream_internal(stream); +} + +#include "dircache_redirect.h" + +static inline void ns_dirscan_rewind(struct ns_scan_info *scanp) +{ + rewinddir_dirent(&scanp->scan); + if (scanp->item != -1) + scanp->item = 0; +} + +static inline int ns_readdir_dirent(struct filestr_base *stream, + struct ns_scan_info *scanp, + struct dirent *entry) + +{ + if (scanp->item == -1) + return readdir_dirent(stream, &scanp->scan, entry); + else + return root_readdir_dirent(stream, scanp, entry); +} + +#endif /* RB_NAMESPACE_H */ diff --git a/uisimulator/common/filesystem-sim.c b/uisimulator/common/filesystem-sim.c index 766beb3fda..45483b7172 100644 --- a/uisimulator/common/filesystem-sim.c +++ b/uisimulator/common/filesystem-sim.c @@ -309,6 +309,8 @@ int sim_get_os_path(char *buffer, const char *path, size_t bufsize) const char *next; volume = path_strip_volume(p, &next, true); + if (volume == ROOT_VOLUME) + volume = 0; /* FIXME: root no longer implies volume 0 */ if (next > p) {