diff options
-rw-r--r-- | firmware/common/dir.c | 2 | ||||
-rw-r--r-- | firmware/common/dircache.c | 351 | ||||
-rw-r--r-- | firmware/common/file.c | 57 | ||||
-rw-r--r-- | firmware/common/file_internal.c | 48 | ||||
-rw-r--r-- | firmware/common/fileobj_mgr.c | 126 | ||||
-rw-r--r-- | firmware/drivers/fat.c | 20 | ||||
-rw-r--r-- | firmware/export/fat.h | 6 | ||||
-rw-r--r-- | firmware/include/dircache.h | 40 | ||||
-rw-r--r-- | firmware/include/dircache_redirect.h | 19 | ||||
-rw-r--r-- | firmware/include/file_internal.h | 91 | ||||
-rw-r--r-- | firmware/include/fileobj_mgr.h | 5 |
11 files changed, 436 insertions, 329 deletions
diff --git a/firmware/common/dir.c b/firmware/common/dir.c index da798c71d5..59f7bd747a 100644 --- a/firmware/common/dir.c +++ b/firmware/common/dir.c @@ -56,7 +56,7 @@ static struct dirstr_desc * get_dirstr(DIR *dirp) { errnum = EFAULT; } - else if (dir->stream.flags == FV_NONEXIST) + else if (dir->stream.flags & FD_NONEXIST) { DEBUGF("dir #%d: nonexistant device\n", (int)(dir - open_streams)); errnum = ENXIO; diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c index b93ee73fc6..a3538ff96f 100644 --- a/firmware/common/dircache.c +++ b/firmware/common/dircache.c @@ -41,9 +41,7 @@ #include "audio.h" #include "rbpaths.h" #include "linked_list.h" -#ifdef HAVE_EEPROM_SETTINGS #include "crc32.h" -#endif /** * Cache memory layout: @@ -1457,7 +1455,7 @@ int dircache_readdir_dirent(struct filestr_base *stream, unsigned int direntry = scanp->fatscan.entry; while (1) { - if (idx == 0 || direntry == FAT_RW_VAL) /* rewound? */ + if (idx == 0 || direntry == FAT_DIRSCAN_RW_VAL) /* rewound? */ { idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down; break; @@ -1487,7 +1485,7 @@ int dircache_readdir_dirent(struct filestr_base *stream, return 0; /* end of dir */ } - if (ce->direntry > direntry || direntry == FAT_RW_VAL) + if (ce->direntry > direntry || direntry == FAT_DIRSCAN_RW_VAL) break; /* cache reader is caught up to FS scan */ idx = ce->next; @@ -1549,7 +1547,12 @@ int dircache_readdir_internal(struct filestr_base *stream, /* is parent cached? if not, readthrough because nothing is here yet */ if (!dirinfop->dcfile.serialnum) + { + if (stream->flags & FF_CACHEONLY) + goto read_eod; + return uncached_readdir_internal(stream, infop, fatent); + } int diridx = dirinfop->dcfile.idx; unsigned int frontier = diridx < 0 ? @@ -1562,7 +1565,8 @@ int dircache_readdir_internal(struct filestr_base *stream, idx = get_entry(idx)->next; struct dircache_entry *ce = get_entry(idx); - if (frontier != FRONTIER_SETTLED) + + if (frontier != FRONTIER_SETTLED && !(stream->flags & FF_CACHEONLY)) { /* the directory being read is reported to be incompletely cached; readthrough and if the entry exists, return it with its binding @@ -1577,9 +1581,7 @@ int dircache_readdir_internal(struct filestr_base *stream, else if (!ce) { /* end of dir */ - fat_empty_fat_direntry(fatent); - infop->fatfile.e.entries = 0; - return 0; + goto read_eod; } /* FS entry information that we maintain */ @@ -1612,6 +1614,11 @@ int dircache_readdir_internal(struct filestr_base *stream, } return rc; + +read_eod: + fat_empty_fat_direntry(fatent); + infop->fatfile.e.entries = 0; + return 0; } /** @@ -2451,30 +2458,41 @@ void dircache_fileop_sync(struct file_base_binding *bindp, /** Dircache paths and files **/ -#ifdef DIRCACHE_DUMPSTER -/* helper for dircache_get_path() */ -static ssize_t get_path_sub(int idx, char *buf, size_t size) +/** + * helper for returning a path and serial hash represented by an index + */ +struct get_path_sub_data +{ + char *buf; + size_t size; + dc_serial_t serialhash; +}; + +static ssize_t get_path_sub(int idx, struct get_path_sub_data *data) { if (idx == 0) - return -2; /* entry is an orphan split from any root */ + return -1; /* entry is an orphan split from any root */ - ssize_t offset; + ssize_t len; char *cename; if (idx > 0) { - /* go all the way up then move back down from the root */ struct dircache_entry *ce = get_entry(idx); - offset = get_path_sub(ce->up, buf, size) - 1; - if (offset < 0) - return -3; + + data->serialhash = dc_hash_serialnum(ce->serialnum, data->serialhash); + + /* go all the way up then move back down from the root */ + len = get_path_sub(ce->up, data) - 1; + if (len < 0) + return -2; cename = alloca(MAX_NAME + 1); entry_name_copy(cename, ce); } else /* idx < 0 */ { - offset = 0; + len = 0; cename = ""; #ifdef HAVE_MULTIVOLUME @@ -2486,14 +2504,14 @@ static ssize_t get_path_sub(int idx, char *buf, size_t size) get_volume_name(volume, cename); } #endif /* HAVE_MULTIVOLUME */ + + data->serialhash = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum, + data->serialhash); } - return offset + path_append(buf + offset, PA_SEP_HARD, cename, - size > (size_t)offset ? size - offset : 0); + return len + path_append(data->buf + len, PA_SEP_HARD, cename, + data->size > (size_t)len ? data->size - len : 0); } -#endif /* DIRCACHE_DUMPSTER */ - -#if 0 /** * retrieve and validate the file's entry/binding serial number @@ -2523,201 +2541,173 @@ static dc_serial_t get_file_serialnum(const struct dircache_file *dcfilep) } /** + * Obtain the hash of the serial numbers of the canonical path, index to root + */ +static dc_serial_t get_file_serialhash(const struct dircache_file *dcfilep) +{ + int idx = dcfilep->idx; + + dc_serial_t h = DC_SERHASH_START; + + while (idx > 0) + { + struct dircache_entry *ce = get_entry(idx); + h = dc_hash_serialnum(ce->serialnum, h); + idx = ce->up; + } + + h = dc_hash_serialnum(get_idx_dcvolp(idx)->serialnum, h); + + return h; +} + +/** + * Initialize the fileref + */ +void dircache_fileref_init(struct dircache_fileref *dcfrefp) +{ + dircache_dcfile_init(&dcfrefp->dcfile); + dcfrefp->serialhash = DC_SERHASH_START; +} + +/** * usermode function to construct a full absolute path from dircache into the * given buffer given the dircache file info * * returns: - * success - the length of the string, not including the trailing null + * success - the length of the string, not including the trailing null or the + * buffer length required if the buffer is too small (return is >= + * size) * failure - a negative value * - * successful return value is as strlcpy() - * * errors: - * ENOENT - the file or directory does not exist + * ENOENT - No such file or directory */ -ssize_t dircache_get_path(const struct dircache_file *dcfilep, char *buf, - size_t size) +ssize_t dircache_get_fileref_path(const struct dircache_fileref *dcfrefp, char *buf, + size_t size) { + ssize_t rc; + /* if missing buffer space, still return what's needed a la strlcpy */ if (!buf) size = 0; else if (size) *buf = '\0'; - ssize_t len = -1; - dircache_lock(); /* first and foremost, there must be a cache and the serial number must check out */ - if (dircache_runinfo.handle && get_file_serialnum(dcfilep)) - len = get_path_sub(dcfilep->idx, buf, size); - - if (len < 0) - errno = ENOENT; - - dircache_unlock(); - return len; -} + if (!dircache_runinfo.handle) + FILE_ERROR(ENOENT, -1); -/** - * searches the sublist starting at 'idx' for the named component - */ + if (get_file_serialnum(&dcfrefp->dcfile) == 0) + FILE_ERROR(ENOENT, -2); -/* helper for get_file_sub() */ -static struct dircache_entry * -get_file_sub_scan(int idx, const char *name, size_t length, int *idxp) -{ - struct dircache_entry *ce = get_entry(idx); - if (ce) + struct get_path_sub_data data = { - char entname[MAX_NAME+1]; - name = strmemdupa(name, length); - - do - { - entry_name_copy(entname, ce); - if (!strcasecmp(entname, name)) - { - *idxp = idx; - break; - } - - idx = ce->next; - } - while ((ce = get_entry(idx))); - } - - return ce; -} - -/** - * searches for the subcomponent of *pathp - */ - -/* helper for dircache_get_file() */ -static int get_file_sub(const char **pathp, int *downp, int *idxp) -{ - int rc; - const char *name; - rc = parse_path_component(pathp, &name, false); - if (rc <= 0) - return rc; - else if (rc >= MAX_PATH) - return ENAMETOOLONG; /* that's just unpossible, man */ + .buf = buf, + .size = size, + .serialhash = DC_SERHASH_START, + }; - struct dircache_entry *ce = get_file_sub_scan(*downp, name, rc, idxp); + rc = get_path_sub(dcfrefp->dcfile.idx, &data); + if (rc < 0) + FILE_ERROR(ENOENT, rc * 10 - 3); - if (!ce) - rc = RC_NOT_FOUND; /* not there; tellibry solly */ - else if (!*pathp) - rc = RC_PATH_ENDED; /* done */ - else if (!(ce->attr & ATTR_DIRECTORY)) - rc = ENOTDIR; /* a parent component must be a directory */ - else - while ((rc = get_file_sub(pathp, &ce->down, idxp)) == RC_CONTINUE); + if (data.serialhash != dcfrefp->serialhash) + FILE_ERROR(ENOENT, -4); - switch (rc) - { - case RC_GO_UP: /* hit ".."; drop to previous level */ - return RC_CONTINUE; - case RC_PATH_ENDED: /* success! */ - return RC_FOUND; - default: /* component not found or error */ - return rc; - } +file_error: + dircache_unlock(); + return rc; } /** - * usermode function to return dircache file info for the given path + * Test a path to various levels of rigor and optionally return dircache file + * info for the given path * * returns: - * success: the volume number that is specified for the file + * success: 0 * failure: a negative value * - * errors: - * ENOENT - the file or directory does not exist or path is empty - * ENAMETOOLONG - a component of the path is too long - * ENOTDIR - a component of the path is not a directory + * errors (including but not limited to): + * EFAULT - Bad address + * EINVAL - Invalid argument + * ENAMETOOLONG - File or path name too long + * ENOENT - No such file or directory + * ENOTDIR - Not a directory + * ENXIO - No such device or address */ -int dircache_get_file(const char *path, struct dircache_file *dcfilep) +int dircache_search(unsigned int flags, struct dircache_fileref *dcfrefp, const char *path) { - if (!path_is_absolute(path) || !dcfilep) - { - errno = ENOENT; - return -1; - } + int rc; + + if (!(flags & (DCS_FILEREF | DCS_CACHED_PATH))) + FILE_ERROR_RETURN(EINVAL, -1); /* search nothing? */ dircache_lock(); if (!dircache_runinfo.handle) + FILE_ERROR(ENOENT, -2); + + if (flags & DCS_FILEREF) { - dircache_unlock(); - errno = ENOENT; - return -2; - } + if (!dcfrefp) + FILE_ERROR(EFAULT, -3); - int volume = 0; - int idx = 0; - dc_serial_t serialnum = 0; - struct dircache_volume *dcvolp = NULL; - struct dircache_entry *ce = NULL; + if (get_file_serialnum(&dcfrefp->dcfile) != 0) + { + if (!(flags & _DCS_VERIFY_FLAG)) + goto file_success; /* no robust verification wanted */ + + if (get_file_serialhash(&dcfrefp->dcfile) == dcfrefp->serialhash) + goto file_success; /* reference is most likely still valid */ + } - int rc = RC_GO_UP; + if (!(flags & DCS_CACHED_PATH)) + FILE_ERROR(ENOENT, -4); /* no path search wanted */ + } - while (rc == RC_CONTINUE || rc == RC_GO_UP) + if (flags & DCS_CACHED_PATH) { - #ifdef HAVE_MULTIVOLUME - if (rc == RC_GO_UP) + const bool update = flags & DCS_UPDATE_FILEREF; + struct path_component_info *compinfop = NULL; + + if (update) { - volume = path_strip_volume(path, &path, false); - if (!CHECK_VOL(volume)) - { - rc = ENXIO; - break; - } - } - #endif /* HAVE_MULTIVOLUME */ + if (!dcfrefp) + FILE_ERROR(EFAULT, -5); - dcvolp = DCVOL(volume); + compinfop = alloca(sizeof (*compinfop)); + } - int *downp = &dcvolp->root_down; - if (*downp <= 0) + struct filestr_base stream; + rc = open_stream_internal(path, FF_ANYTYPE | FF_PROBE | FF_SELFINFO | + ((flags & _DCS_STORAGE_FLAG) ? 0 : FF_CACHEONLY), + &stream, compinfop); + if (rc <= 0) { - rc = ENXIO; - break; + if (update) + dircache_fileref_init(dcfrefp); + + FILE_ERROR(rc ? ERRNO : ENOENT, rc * 10 - 6); } - rc = get_file_sub(&path, downp, &idx); - } - - switch (rc) - { - case RC_FOUND: /* hit: component found */ - serialnum = ce->serialnum; - rc = volume; - break; - case RC_PATH_ENDED: /* hit: it's a root (volume or system) */ - idx = -volume - 1; - serialnum = dcvolp->serialnum; - rc = volume; - break; - case RC_NOT_FOUND: /* miss */ - rc = ENOENT; - default: - idx = 0; - errno = rc; - rc = -3; - break; + if (update) + { + dcfrefp->dcfile = compinfop->info.dcfile; + dcfrefp->serialhash = get_file_serialhash(&compinfop->info.dcfile); + } } - dcfilep->idx = idx; - dcfilep->serialnum = serialnum; +file_success: + rc = 0; +file_error: dircache_unlock(); - return rc; + return rc; } -#endif /* 0 */ /** Debug screen/info stuff **/ @@ -2737,13 +2727,12 @@ void dircache_get_info(struct dircache_info *info) if (!info) return; - memset(info, 0, sizeof (*info)); - dircache_lock(); enum dircache_status status = DIRCACHE_IDLE; + info->build_ticks = 0; - for (unsigned int volume = 0; volume < NUM_VOLUMES; volume++) + FOR_EACH_VOLUME(-1, volume) { struct dircache_volume *dcvolp = DCVOL(volume); enum dircache_status volstatus = dcvolp->status; @@ -2781,11 +2770,18 @@ void dircache_get_info(struct dircache_info *info) /* report usage only if there is something ready or being built */ if (status != DIRCACHE_IDLE) { - info->reserve_used = reserve_buf_used(); info->size = dircache.size; info->sizeused = dircache.sizeused; + info->reserve_used = reserve_buf_used(); info->entry_count = dircache.numentries; } + else + { + info->size = 0; + info->sizeused = 0; + info->reserve_used = 0; + info->entry_count = 0; + } dircache_unlock(); } @@ -2817,7 +2813,7 @@ void dircache_dump(void) dircache_runinfo.bufsize + 1); /* CSV */ - fdprintf(fdcsv, "\"Index\",\"Serialnum\"," + fdprintf(fdcsv, "\"Index\",\"Serialnum\",\"Serialhash\"," "\"Path\",\"Frontier\"," "\"Attribute\",\"File Size\"," "\"Mod Date\",\"Mod Time\"\n"); @@ -2833,11 +2829,12 @@ void dircache_dump(void) get_volume_name(volume, name); #endif fdprintf(fdcsv, - "%d,%lu," + "%d," DC_SERIAL_FMT "," DC_SERIAL_FMT "," "\"%c" IF_MV("%s") "\",%u," "0x%08X,0," "\"\",\"\"\n", -volume-1, dcvolp->serialnum, + dc_hash_serialnum(dcvolp->serialnum, DC_SERHASH_START), PATH_SEPCH, IF_MV(name,) dcvolp->frontier, ATTR_DIRECTORY | ATTR_VOLUME); } @@ -2855,15 +2852,23 @@ void dircache_dump(void) char buf[DC_MAX_NAME + 2]; *buf = '\0'; int idx = get_index(ce); - get_path_sub(idx, buf, sizeof (buf)); + + struct get_path_sub_data data = + { + .buf = buf, + .size = sizeof (buf), + .serialhash = DC_SERHASH_START, + }; + + get_path_sub(idx, &data); fdprintf(fdcsv, - "%d,%lu," + "%d," DC_SERIAL_FMT "," DC_SERIAL_FMT "," "\"%s\",%u," "0x%08X,%lu," "%04d/%02d/%02d," "%02d:%02d:%02d\n", - idx, ce->serialnum, + idx, ce->serialnum, data.serialhash, buf, ce->frontier, ce->attr, (ce->attr & ATTR_DIRECTORY) ? 0ul : (unsigned long)ce->filesize, diff --git a/firmware/common/file.c b/firmware/common/file.c index 6444918f1b..1f93824dc8 100644 --- a/firmware/common/file.c +++ b/firmware/common/file.c @@ -54,7 +54,7 @@ static struct filestr_desc * get_filestr(int fildes) return file; DEBUGF("fildes %d: bad file number\n", fildes); - errno = (file && file->stream.flags == FV_NONEXIST) ? ENXIO : EBADF; + errno = (file && (file->stream.flags & FD_NONEXIST)) ? ENXIO : EBADF; return NULL; } @@ -187,24 +187,28 @@ file_error: return rc; } -/* callback for each file stream to make sure all data is in sync with new - size */ -void ftruncate_internal_callback(struct filestr_base *stream, - struct filestr_base *s) +/* Handle syncing all file's streams to the truncation */ +static void handle_truncate(struct filestr_desc * const file, file_size_t size) { - struct filestr_desc *file = (struct filestr_desc *)s; - file_size_t size = *file->sizep; - - /* caches with data beyond new extents are invalid */ - unsigned long sector = file->stream.cachep->sector; - if (sector != INVALID_SECNUM && sector >= filesize_sectors(size)) - filestr_discard_cache(&file->stream); + unsigned long filesectors = filesize_sectors(size); - /* keep all positions within bounds */ - if (file->offset > size) - file->offset = size; - - (void)stream; + struct filestr_base *s = NULL; + while ((s = fileobj_get_next_stream(&file->stream, s))) + { + /* caches with data beyond new extents are invalid */ + unsigned long sector = s->cachep->sector; + if (sector != INVALID_SECNUM && sector >= filesectors) + filestr_discard_cache(s); + + /* files outside bounds must be rewound */ + if (fat_query_sectornum(&s->fatstr) > filesectors) + fat_seek_to_stream(&s->fatstr, &file->stream.fatstr); + + /* clip file offset too if needed */ + struct filestr_desc *f = (struct filestr_desc *)s; + if (f->offset > size) + f->offset = size; + } } /* truncate the file to the specified length */ @@ -246,13 +250,17 @@ static int ftruncate_internal(struct filestr_desc *file, file_size_t size, rc2 = fat_truncate(&file->stream.fatstr); if (rc2 < 0) FILE_ERROR(EIO, rc2 * 10 - 3); + + /* never needs to be done this way again since any data beyond the + cached size is now gone */ + fileobj_change_flags(&file->stream, 0, FO_TRUNC); } /* else just change the cached file size */ if (truncsize < cursize) { *file->sizep = truncsize; - fileop_ontruncate_internal(&file->stream); + handle_truncate(file, truncsize); } /* if truncation was partially successful, it effectively destroyed @@ -299,10 +307,6 @@ static int fsync_internal(struct filestr_desc *file) int rc2 = ftruncate_internal(file, size, rc == 0); if (rc2 < 0) FILE_ERROR(ERRNO, rc2 * 10 - 2); - - /* never needs to be done this way again since any data beyond the - cached size is now gone */ - fileobj_change_flags(&file->stream, 0, FO_TRUNC); } file_error:; @@ -327,8 +331,7 @@ static int close_internal(struct filestr_desc *file) /* call only when holding WRITER lock (updates directory entries) */ int rc; - if ((file->stream.flags & FD_WRITE) && - !(fileobj_get_flags(&file->stream) & FO_REMOVED)) + if ((file->stream.flags & (FD_WRITE|FD_NONEXIST)) == FD_WRITE) { rc = fsync_internal(file); if (rc < 0) @@ -786,6 +789,12 @@ int open_noiso_internal(const char *path, int oflag) return open_internal_locked(path, oflag, FF_ANYTYPE | FF_NOISO); } +void force_close_writer_internal(struct filestr_base *stream) +{ + /* only we do writers so we know this is our guy */ + close_internal((struct filestr_desc *)stream); +} + /** POSIX **/ diff --git a/firmware/common/file_internal.c b/firmware/common/file_internal.c index f9c6bfb570..aa0edb7ebb 100644 --- a/firmware/common/file_internal.c +++ b/firmware/common/file_internal.c @@ -292,7 +292,7 @@ struct pathwalk_component struct pathwalk_component *nextp; /* parent if in use else next free */ }; -#define WALK_RC_NOT_FOUND 0 /* successfully not found */ +#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_CONT_AT_ROOT 3 /* continue at root level */ @@ -351,7 +351,10 @@ static int fill_path_compinfo(struct pathwalk *walkp, compinfo->length = compp->length; compinfo->attr = compp->attr; compinfo->filesize = walkp->filesize; - compinfo->parentinfo = (compp->nextp ?: compp)->info; + if (!(walkp->callflags & FF_SELFINFO)) + compinfo->parentinfo = (compp->nextp ?: compp)->info; + else + compinfo->info = compp->info; } return rc; @@ -393,7 +396,10 @@ static int walk_open_info(struct pathwalk *walkp, else callflags &= ~FO_DIRECTORY; - fileop_onopen_internal(stream, &compp->info, callflags); + /* make open official if not simply probing for presence */ + if (!(callflags & FF_PROBE)) + fileop_onopen_internal(stream, &compp->info, callflags); + return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT; } @@ -511,7 +517,8 @@ walk_path(struct pathwalk *walkp, struct pathwalk_component *compp, { /* is ".." */ struct pathwalk_component *parentp = compp->nextp; - if (!parentp) + + if (!parentp IF_MV( || parentp->attr == ATTR_SYSTEM_ROOT )) return WALK_RC_CONT_AT_ROOT; compp->nextp = freep; @@ -567,6 +574,9 @@ int open_stream_internal(const char *path, unsigned int callflags, if (!compinfo) callflags &= ~FF_CHECKPREFIX; + /* This lets it be passed quietly to directory scanning */ + stream->flags = callflags & FF_MASK; + struct pathwalk walk; walk.path = path; walk.callflags = callflags; @@ -575,7 +585,7 @@ int open_stream_internal(const char *path, unsigned int callflags, struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL); rootp->nextp = NULL; - rootp->attr = ATTR_DIRECTORY; + rootp->attr = ATTR_SYSTEM_ROOT; #ifdef HAVE_MULTIVOLUME int volume = 0, rootrc = WALK_RC_FOUND; @@ -584,7 +594,7 @@ int open_stream_internal(const char *path, unsigned int callflags, 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") */ @@ -596,8 +606,19 @@ int open_stream_internal(const char *path, unsigned int callflags, FILE_ERROR(ENXIO, -2); } - /* the root of this subpath is the system root? */ - rootrc = p == pathptr ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND; + 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 */ @@ -633,11 +654,9 @@ int open_stream_internal(const char *path, unsigned int callflags, default: /* utter, abject failure :`( */ DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno); filestr_base_destroy(stream); - FILE_ERROR(-rc, -2); + FILE_ERROR(-rc, -3); } - file_cache_reset(stream->cachep); - file_error: return rc; } @@ -757,9 +776,10 @@ int test_stream_exists_internal(const char *path, unsigned int callflags) { /* only FF_* flags should be in callflags */ struct filestr_base stream; - int rc = open_stream_internal(path, callflags, &stream, NULL); - if (rc > 0) - close_stream_internal(&stream); + int rc = open_stream_internal(path, callflags | FF_PROBE, &stream, NULL); + + if (rc >= 0) + filestr_base_destroy(&stream); return rc; } diff --git a/firmware/common/fileobj_mgr.c b/firmware/common/fileobj_mgr.c index 8e7831d36c..e34a460e10 100644 --- a/firmware/common/fileobj_mgr.c +++ b/firmware/common/fileobj_mgr.c @@ -187,7 +187,8 @@ void fileobj_fileop_open(struct filestr_base *stream, ll_insert_last(&fobp->list, &stream->node); /* initiate the new stream into the enclave */ - stream->flags = FDO_BUSY | (callflags & (FD_WRITE|FD_WRONLY|FD_APPEND)); + stream->flags = FDO_BUSY | + (callflags & (FF_MASK|FD_WRITE|FD_WRONLY|FD_APPEND)); stream->infop = &fobp->bind.info; stream->fatstr.fatfilep = &fobp->bind.info.fatfile; stream->bindp = &fobp->bind; @@ -202,14 +203,6 @@ void fileobj_fileop_open(struct filestr_base *stream, fobp->writers = 0; fobp->size = 0; - if (callflags & FD_WRITE) - { - /* first one is a writer */ - fobp->writers = 1; - file_cache_init(&fobp->cache); - filestr_assign_cache(stream, &fobp->cache); - } - fileobj_bind_file(&fobp->bind); } else @@ -225,32 +218,33 @@ void fileobj_fileop_open(struct filestr_base *stream, DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", __func__, stream, callflags); } + } - if (fobp->writers) - { - /* already writers present */ - fobp->writers++; - filestr_assign_cache(stream, &fobp->cache); - } - else if (callflags & FD_WRITE) - { - /* first writer */ - fobp->writers = 1; - file_cache_init(&fobp->cache); - FOR_EACH_STREAM(FIRST, fobp, s) - filestr_assign_cache(s, &fobp->cache); - } - /* else another reader */ + if ((callflags & FD_WRITE) && ++fobp->writers == 1) + { + /* first writer */ + file_cache_init(&fobp->cache); + FOR_EACH_STREAM(FIRST, fobp, s) + filestr_assign_cache(s, &fobp->cache); + } + else if (fobp->writers) + { + /* already writers present */ + filestr_assign_cache(stream, &fobp->cache); + } + else + { + /* another reader and no writers present */ + file_cache_reset(stream->cachep); } } /* close the stream and free associated resources */ void fileobj_fileop_close(struct filestr_base *stream) { - switch (stream->flags) + if (!(stream->flags & FDO_BUSY)) { - case 0: /* not added to manager */ - case FV_NONEXIST: /* forced-closed by unmounting */ + /* not added to manager or forced-closed by unmounting */ filestr_base_destroy(stream); return; } @@ -260,30 +254,30 @@ void fileobj_fileop_close(struct filestr_base *stream) ll_remove(&fobp->list, &stream->node); - if ((foflags & FO_SINGLE) || fobp->writers == 0) + if (foflags & FO_SINGLE) { - if (foflags & FO_SINGLE) - { - /* last stream for file; close everything */ - fileobj_unbind_file(&fobp->bind); + /* last stream for file; close everything */ + fileobj_unbind_file(&fobp->bind); - if (fobp->writers) - file_cache_free(&fobp->cache); + if (fobp->writers) + file_cache_free(&fobp->cache); - binding_add_to_free_list(fobp); - } + binding_add_to_free_list(fobp); } - else if ((stream->flags & FD_WRITE) && --fobp->writers == 0) + else { - /* only readers remain; switch back to stream-local caching */ - FOR_EACH_STREAM(FIRST, fobp, s) - filestr_copy_cache(s, &fobp->cache); + if ((stream->flags & FD_WRITE) && --fobp->writers == 0) + { + /* only readers remain; switch back to stream-local caching */ + FOR_EACH_STREAM(FIRST, fobp, s) + filestr_copy_cache(s, &fobp->cache); - file_cache_free(&fobp->cache); - } + file_cache_free(&fobp->cache); + } - if (!(foflags & FO_SINGLE) && fobp->list.head == fobp->list.tail) - fobp->flags |= FO_SINGLE; /* only one open stream remaining */ + if (fobp->list.head == fobp->list.tail) + fobp->flags |= FO_SINGLE; /* only one open stream remaining */ + } filestr_base_destroy(stream); } @@ -320,15 +314,10 @@ void fileobj_fileop_rename(struct filestr_base *stream, /* informs manager than directory entries have been updated */ void fileobj_fileop_sync(struct filestr_base *stream) { - fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); -} + if (((struct fileobj_binding *)stream->bindp)->flags & FO_REMOVED) + return; /* no dir to sync */ -/* inform manager that file has been truncated */ -void fileobj_fileop_truncate(struct filestr_base *stream) -{ - /* let caller update internal info */ - FOR_EACH_STREAM(FIRST, (struct fileobj_binding *)stream->bindp, s) - ftruncate_internal_callback(stream, s); + fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); } /* query for the pointer to the size storage for the file object */ @@ -340,6 +329,16 @@ file_size_t * fileobj_get_sizep(const struct filestr_base *stream) return &((struct fileobj_binding *)stream->bindp)->size; } +/* iterate the list of streams for this stream's file */ +struct filestr_base * fileobj_get_next_stream(const struct filestr_base *stream, + const struct filestr_base *s) +{ + if (!stream->bindp) + return NULL; + + return s ? STREAM_NEXT(s) : STREAM_FIRST((struct fileobj_binding *)stream->bindp); +} + /* query manager bitflags for the file object */ unsigned int fileobj_get_flags(const struct filestr_base *stream) { @@ -349,20 +348,21 @@ unsigned int fileobj_get_flags(const struct filestr_base *stream) return ((struct fileobj_binding *)stream->bindp)->flags; } -/* change manager bitflags for the file object */ +/* change manager bitflags for the file object (permitted only) */ void fileobj_change_flags(struct filestr_base *stream, unsigned int flags, unsigned int mask) { struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp; - if (fobp) - fobp->flags = (fobp->flags & ~mask) | (flags & mask); + if (!fobp) + return; + + mask &= FDO_CHG_MASK; + fobp->flags = (fobp->flags & ~mask) | (flags & mask); } /* mark all open streams on a device as "nonexistant" */ void fileobj_mgr_unmount(IF_MV_NONVOID(int volume)) { - /* right now, there is nothing else to be freed when marking a descriptor - as "nonexistant" but a callback could be added if that changes */ FOR_EACH_VOLUME(volume, v) { struct fileobj_binding *fobp; @@ -371,11 +371,17 @@ void fileobj_mgr_unmount(IF_MV_NONVOID(int volume)) struct filestr_base *s; while ((s = STREAM_FIRST(fobp))) { + /* last ditch effort to preserve FS integrity; we could still + be alive (soft unmount, maybe); we get informed early */ + fileop_onunmount_internal(s); + + if (STREAM_FIRST(fobp) == s) + fileop_onclose_internal(s); /* above didn't close it */ + /* keep it "busy" to avoid races; any valid file/directory descriptor returned by an open call should always be - closed by whomever opened it (of course!) */ - fileop_onclose_internal(s); - s->flags = FV_NONEXIST; + closed by whoever opened it (of course!) */ + s->flags = (s->flags & ~FDO_BUSY) | FD_NONEXIST; } } } diff --git a/firmware/drivers/fat.c b/firmware/drivers/fat.c index a090bd5899..fed3baffd4 100644 --- a/firmware/drivers/fat.c +++ b/firmware/drivers/fat.c @@ -1927,7 +1927,7 @@ static int free_direntries(struct bpb *fat_bpb, struct fat_file *file) /* directory entry info is now gone */ file->dircluster = 0; - file->e.entry = FAT_RW_VAL; + file->e.entry = FAT_DIRSCAN_RW_VAL; file->e.entries = 0; return 1; @@ -2521,10 +2521,20 @@ void fat_rewind(struct fat_filestr *filestr) filestr->lastcluster = filestr->fatfilep->firstcluster; filestr->lastsector = 0; filestr->clusternum = 0; - filestr->sectornum = FAT_RW_VAL; + filestr->sectornum = FAT_FILE_RW_VAL; filestr->eof = false; } +void fat_seek_to_stream(struct fat_filestr *filestr, + const struct fat_filestr *filestr_seek_to) +{ + filestr->lastcluster = filestr_seek_to->lastcluster; + filestr->lastsector = filestr_seek_to->lastsector; + filestr->clusternum = filestr_seek_to->clusternum; + filestr->sectornum = filestr_seek_to->sectornum; + filestr->eof = filestr_seek_to->eof; +} + int fat_seek(struct fat_filestr *filestr, unsigned long seeksector) { const struct fat_file * const file = filestr->fatfilep; @@ -2536,7 +2546,7 @@ int fat_seek(struct fat_filestr *filestr, unsigned long seeksector) long cluster = file->firstcluster; unsigned long sector = 0; long clusternum = 0; - unsigned long sectornum = FAT_RW_VAL; + unsigned long sectornum = FAT_FILE_RW_VAL; #ifdef HAVE_FAT16SUPPORT if (fat_bpb->is_fat16 && cluster < 0) /* FAT16 root dir */ @@ -2710,7 +2720,7 @@ int fat_readdir(struct fat_filestr *dirstr, struct fat_dirscan_info *scan, dc_lock_cache(); - while (--scan->entry != FAT_RW_VAL) /* at beginning? */ + while (--scan->entry != FAT_DIRSCAN_RW_VAL) /* at beginning? */ { ent = cache_direntry(fat_bpb, dirstr, scan->entry); @@ -2761,7 +2771,7 @@ fat_error: void fat_rewinddir(struct fat_dirscan_info *scan) { /* rewind the directory scan counter to the beginning */ - scan->entry = FAT_RW_VAL; + scan->entry = FAT_DIRSCAN_RW_VAL; scan->entries = 0; } diff --git a/firmware/export/fat.h b/firmware/export/fat.h index 3aa1e254dc..963c1fe767 100644 --- a/firmware/export/fat.h +++ b/firmware/export/fat.h @@ -97,13 +97,15 @@ struct fat_direntry /* cursor structure used for scanning directories; holds the last-returned entry information */ +#define FAT_DIRSCAN_RW_VAL (0u - 1u) + struct fat_dirscan_info { unsigned int entry; /* short dir entry index in parent */ unsigned int entries; /* number of dir entries used */ }; -#define FAT_RW_VAL (0u - 1) +#define FAT_FILE_RW_VAL (0ul - 1ul) /* basic FAT file information about where to find a file and who houses it */ struct fat_file @@ -156,6 +158,8 @@ long fat_readwrite(struct fat_filestr *filestr, unsigned long sectorcount, void *buf, bool write); void fat_rewind(struct fat_filestr *filestr); int fat_seek(struct fat_filestr *filestr, unsigned long sector); +void fat_seek_to_stream(struct fat_filestr *filestr, + const struct fat_filestr *filestr_seek_to); int fat_truncate(const struct fat_filestr *filestr); /** Directory stream functions **/ diff --git a/firmware/include/dircache.h b/firmware/include/dircache.h index 7e8c764e7f..d73c6f6e81 100644 --- a/firmware/include/dircache.h +++ b/firmware/include/dircache.h @@ -57,6 +57,15 @@ figure pessimistic */ typedef uint32_t dc_serial_t; +/* these should agree with size of dc_serial_t */ +#define DC_SERHASH_START 0xffffffff + +/* I was originally using FNV hash but decided this is probably okay + (for now) */ +#define dc_hash_serialnum(s, h) \ + ({ dc_serial_t __x = (s); crc_32(&(__x), sizeof(dc_serial_t), (h)); }) +#define DC_SERIAL_FMT "0x%08lX" + /** ****************************************************************************/ @@ -132,10 +141,33 @@ void dircache_fileop_sync(struct file_base_binding *infop, const struct dirinfo_native *dinp); -/** Dircache paths and files **/ -ssize_t dircache_get_path(const struct dircache_file *dcfilep, char *buf, - size_t size); -int dircache_get_file(const char *path, struct dircache_file *dcfilep); +/** Dircache paths, files and shortcuts **/ +struct dircache_fileref +{ + struct dircache_file dcfile; + dc_serial_t serialhash; /* Hash of serialnumbers to root */ +}; + +void dircache_fileref_init(struct dircache_fileref *dcfrefp); +ssize_t dircache_get_fileref_path(const struct dircache_fileref *dcfrefp, + char *buf, size_t size); + +/* Bitflags for dircache_search() */ +enum dircache_search_flags +{ + DCS_FILEREF = 0x01, /* Check fileref existence and serial number */ + _DCS_VERIFY_FLAG = 0x02, /* Internal: Only valid with DCS_FILEREF */ + DCS_FILEREF_VERIFY = 0x03, /* Do DCS_FILEREF check + verify serial hash */ + DCS_CACHED_PATH = 0x04, /* Check only cache for provided path */ + _DCS_STORAGE_FLAG = 0x08, /* Internal: Only valid with DCS_CACHED_PATH */ + DCS_STORAGE_PATH = 0x0c, /* Read-through if needed for provided path */ + DCS_UPDATE_FILEREF = 0x10, /* If fileref is not valid but path is found or + searching a path, update the reference + information */ +}; + +int dircache_search(unsigned int flags, struct dircache_fileref *dcfrefp, + const char *path); /** Debug screen/info stuff **/ diff --git a/firmware/include/dircache_redirect.h b/firmware/include/dircache_redirect.h index 15fb4bc38d..9fae16b551 100644 --- a/firmware/include/dircache_redirect.h +++ b/firmware/include/dircache_redirect.h @@ -24,6 +24,8 @@ /*** ** Internal redirects that depend upon whether or not dircache is made + ** + ** Some stuff deals with it, some doesn't right now. This is a nexus point.. **/ /** File binding **/ @@ -119,11 +121,6 @@ static inline void fileop_onsync_internal(struct filestr_base *stream) #endif } -static inline void fileop_ontruncate_internal(struct filestr_base *stream) -{ - fileobj_fileop_truncate(stream); -} - static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) { #ifdef HAVE_DIRCACHE @@ -134,10 +131,20 @@ static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume)) { - fileobj_mgr_unmount(IF_MV(volume)); #ifdef HAVE_DIRCACHE + /* First, to avoid update of something about to be destroyed anyway */ dircache_unmount(IF_MV(volume)); #endif + fileobj_mgr_unmount(IF_MV(volume)); +} + +static inline void fileop_onunmount_internal(struct filestr_base *stream) +{ + + if (stream->flags & FD_WRITE) + force_close_writer_internal(stream); /* try to save stuff */ + else + fileop_onclose_internal(stream); /* just readers, bye */ } diff --git a/firmware/include/file_internal.h b/firmware/include/file_internal.h index d1bb67406a..acec81206e 100644 --- a/firmware/include/file_internal.h +++ b/firmware/include/file_internal.h @@ -81,6 +81,7 @@ #define ATTR_NEW_FILE (ATTR_ARCHIVE) #define ATTR_NEW_DIRECTORY (ATTR_DIRECTORY) +#define ATTR_SYSTEM_ROOT (ATTR_SYSROOT | ATTR_DIRECTORY) #define ATTR_MOUNT_POINT (ATTR_VOLUME | ATTR_DIRECTORY) /** File sector cache **/ @@ -110,33 +111,35 @@ void file_cache_free(struct filestr_cache *cachep); enum fildes_and_obj_flags { /* used in descriptor and common */ - FDO_BUSY = 0x0001, /* descriptor/object is in use */ + 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_WRITE = 0x0002, /* descriptor has write mode */ + FD_WRONLY = 0x0004, /* descriptor is write mode only */ + FD_APPEND = 0x0008, /* 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 */ + 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, - /* bitflags that instruct various 'open' functions how to behave */ - FF_FILE = 0x0000, /* expect file; accept file only */ - FF_DIR = 0x0100, /* expect dir; accept dir only */ - FF_ANYTYPE = 0x0200, /* succeed if either file or dir */ - FF_TYPEMASK = 0x0300, /* mask of typeflags */ - FF_CREAT = 0x0400, /* create if file doesn't exist */ - FF_EXCL = 0x0800, /* fail if creating and file exists */ - FF_CHECKPREFIX = 0x1000, /* detect if file is prefix of path */ - FF_NOISO = 0x2000, /* do not decode ISO filenames to UTF-8 */ - FF_MASK = 0x3f00, - /* special values used in isolation */ - FV_NONEXIST = 0x8000, /* closed but not freed (unmounted) */ - FV_OPENSYSROOT = 0xc001, /* open sysroot, volume 0 not mounted */ + 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 */ + FF_FILE = 0x00000000, /* expect file; accept file only */ + FF_DIR = 0x00010000, /* expect dir; accept dir only */ + FF_ANYTYPE = 0x00020000, /* succeed if either file or dir */ + FF_TYPEMASK = 0x00030000, /* mask of typeflags */ + FF_CREAT = 0x00040000, /* create if file doesn't exist */ + FF_EXCL = 0x00080000, /* fail if creating and file exists */ + FF_CHECKPREFIX = 0x00100000, /* detect if file is prefix of path */ + FF_NOISO = 0x00200000, /* do not decode ISO filenames to UTF-8 */ + FF_PROBE = 0x00400000, /* only test existence; don't open */ + FF_CACHEONLY = 0x00800000, /* succeed only if in dircache */ + FF_SELFINFO = 0x01000000, /* return info on self as well */ + FF_MASK = 0x01ff0000, }; - /** Common data structures used throughout **/ /* basic file information about its location */ @@ -183,8 +186,7 @@ struct dirscan_info struct filestr_base { struct ll_node node; /* list item node (first!) */ - uint16_t flags; /* FD_* bits of this stream */ - uint16_t unused; /* not used */ + uint32_t flags; /* F[DF]_* bits of this stream */ struct filestr_cache cache; /* stream-local cache */ struct filestr_cache *cachep; /* the cache in use (local or shared) */ struct file_base_info *infop; /* base file information */ @@ -235,17 +237,25 @@ static inline void filestr_unlock(struct filestr_base *stream) #define FILESTR_UNLOCK(type, stream) \ ({ if (FILESTR_##type) filestr_unlock(stream); }) -#define ATTR_PREFIX (0x8000) /* out of the way of all ATTR_* bits */ +/* auxilliary attributes - out of the way of regular ATTR_* bits */ +#define ATTR_SYSROOT (0x8000) +#define ATTR_PREFIX (0x4000) /* structure to return detailed information about what you opened */ struct path_component_info { - const char *name; /* pointer to name within 'path' */ + const char *name; /* pointer to name within 'path' (OUT) */ size_t length; /* length of component within 'path' */ file_size_t filesize; /* size of the opened file (0 if dir) */ unsigned int attr; /* attributes of this component */ - struct file_base_info *prefixp; /* base info to check as prefix (IN) */ - struct file_base_info parentinfo; /* parent directory info of file */ + struct file_base_info *prefixp; /* base info to check as prefix + (IN if FF_CHECKPREFIX) */ + union { + struct file_base_info parentinfo; /* parent directory base info of file + (if not FF_SELFINFO) */ + struct file_base_info info; /* base info of file itself + (if FF_SELFINFO) */ + }; }; int open_stream_internal(const char *path, unsigned int callflags, @@ -261,6 +271,7 @@ int remove_stream_internal(const char *path, struct filestr_base *stream, int test_stream_exists_internal(const char *path, unsigned int callflags); int open_noiso_internal(const char *path, int oflag); /* file.c */ +void force_close_writer_internal(struct filestr_base *stream); /* file.c */ struct dirent; int uncached_readdir_dirent(struct filestr_base *stream, @@ -326,22 +337,26 @@ static inline void file_internal_unlock_WRITER(void) * not in the macro */ +#define FILE_SET_CODE(_name, _keepcode, _value) \ + ({ __builtin_constant_p(_value) ? \ + ({ if ((_value) != (_keepcode)) _name = (_value); }) : \ + ({ _name = (_value); }); }) + /* set errno and rc and proceed to the "file_error:" label */ #define FILE_ERROR(_errno, _rc) \ - ({ __builtin_constant_p(_errno) ? \ - ({ if ((_errno) != ERRNO) errno = (_errno); }) : \ - ({ errno = (_errno); }); \ - __builtin_constant_p(_rc) ? \ - ({ if ((_rc) != RC) rc = (_rc); }) : \ - ({ rc = (_rc); }); \ + ({ FILE_SET_CODE(errno, ERRNO, (_errno)); \ + FILE_SET_CODE(rc, RC, (_rc)); \ goto file_error; }) /* set errno and return a value at the point of invocation */ #define FILE_ERROR_RETURN(_errno, _rc...) \ - ({ __builtin_constant_p(_errno) ? \ - ({ if ((_errno) != ERRNO) errno = (_errno); }) : \ - ({ errno = (_errno); }); \ - return _rc; }) + ({ FILE_SET_CODE(errno, ERRNO, _errno); \ + return _rc; }) + +/* set errno and return code, no branching */ +#define FILE_ERROR_SET(_errno, _rc) \ + ({ FILE_SET_CODE(errno, ERRNO, (_errno)); \ + FILE_SET_CODE(rc, RC, (_rc)); }) /** Misc. stuff **/ diff --git a/firmware/include/fileobj_mgr.h b/firmware/include/fileobj_mgr.h index c90a59bea0..627d2df341 100644 --- a/firmware/include/fileobj_mgr.h +++ b/firmware/include/fileobj_mgr.h @@ -41,12 +41,11 @@ void fileobj_fileop_rename(struct filestr_base *stream, void fileobj_fileop_remove(struct filestr_base *stream, const struct file_base_info *oldinfop); void fileobj_fileop_sync(struct filestr_base *stream); -void fileobj_fileop_truncate(struct filestr_base *stream); -extern void ftruncate_internal_callback(struct filestr_base *stream, - struct filestr_base *s); file_size_t * fileobj_get_sizep(const struct filestr_base *stream); unsigned int fileobj_get_flags(const struct filestr_base *stream); +struct filestr_base * fileobj_get_next_stream(const struct filestr_base *stream, + const struct filestr_base *s); void fileobj_change_flags(struct filestr_base *stream, unsigned int flags, unsigned int mask); void fileobj_mgr_unmount(IF_MV_NONVOID(int volume)); |