From cb772519aa276ba573b080583d14dbefcfc05a3b Mon Sep 17 00:00:00 2001 From: Bernard Gautier Date: Wed, 9 Oct 2019 11:37:25 +0200 Subject: [PATCH] fix(spiffs): fix bugs of spiffs 1. fix debug macro and parameters error 2. fix data no-align load/store error 3. update other function for user to develop easily Merges https://github.com/espressif/ESP8266_RTOS_SDK/pull/729 --- include/spiffs/spiffs.h | 314 +++++++++- include/spiffs/spiffs_config.h | 193 ++++++- include/spiffs/spiffs_nucleus.h | 172 +++++- lib/libspiffs.a | Bin 136952 -> 158758 bytes third_party/spiffs/spiffs_cache.c | 72 ++- third_party/spiffs/spiffs_check.c | 201 ++++--- third_party/spiffs/spiffs_gc.c | 134 +++-- third_party/spiffs/spiffs_hydrogen.c | 715 +++++++++++++++++++---- third_party/spiffs/spiffs_nucleus.c | 833 +++++++++++++++++++++------ 9 files changed, 2108 insertions(+), 526 deletions(-) diff --git a/include/spiffs/spiffs.h b/include/spiffs/spiffs.h index 2bf0a6e9..534c3df8 100644 --- a/include/spiffs/spiffs.h +++ b/include/spiffs/spiffs.h @@ -5,8 +5,6 @@ * Author: petera */ - - #ifndef SPIFFS_H_ #define SPIFFS_H_ #if defined(__cplusplus) @@ -49,6 +47,22 @@ extern "C" { #define SPIFFS_ERR_NO_DELETED_BLOCKS -10029 +#define SPIFFS_ERR_FILE_EXISTS -10030 + +#define SPIFFS_ERR_NOT_A_FILE -10031 +#define SPIFFS_ERR_RO_NOT_IMPL -10032 +#define SPIFFS_ERR_RO_ABORTED_OPERATION -10033 +#define SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS -10034 +#define SPIFFS_ERR_PROBE_NOT_A_FS -10035 +#define SPIFFS_ERR_NAME_TOO_LONG -10036 + +#define SPIFFS_ERR_IX_MAP_UNMAPPED -10037 +#define SPIFFS_ERR_IX_MAP_MAPPED -10038 +#define SPIFFS_ERR_IX_MAP_BAD_RANGE -10039 + +#define SPIFFS_ERR_SEEK_BOUNDS -10040 + + #define SPIFFS_ERR_INTERNAL -10050 #define SPIFFS_ERR_TEST -10100 @@ -63,12 +77,26 @@ typedef u16_t spiffs_mode; // object type typedef u8_t spiffs_obj_type; +struct spiffs_t; + +#if SPIFFS_HAL_CALLBACK_EXTRA + +/* spi read call function type */ +typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst); +/* spi write call function type */ +typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src); +/* spi erase call function type */ +typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size); + +#else // SPIFFS_HAL_CALLBACK_EXTRA + /* spi read call function type */ typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst); /* spi write call function type */ typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src); /* spi erase call function type */ typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size); +#endif // SPIFFS_HAL_CALLBACK_EXTRA /* file system check callback report operation */ typedef enum { @@ -85,16 +113,34 @@ typedef enum { SPIFFS_CHECK_FIX_LOOKUP, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, SPIFFS_CHECK_DELETE_PAGE, - SPIFFS_CHECK_DELETE_BAD_FILE, + SPIFFS_CHECK_DELETE_BAD_FILE } spiffs_check_report; /* file system check callback function */ +#if SPIFFS_HAL_CALLBACK_EXTRA +typedef void (*spiffs_check_callback)(struct spiffs_t *fs, spiffs_check_type type, spiffs_check_report report, + u32_t arg1, u32_t arg2); +#else // SPIFFS_HAL_CALLBACK_EXTRA typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_report report, u32_t arg1, u32_t arg2); +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +/* file system listener callback operation */ +typedef enum { + /* the file has been created */ + SPIFFS_CB_CREATED = 0, + /* the file has been updated or moved to another page */ + SPIFFS_CB_UPDATED, + /* the file has been deleted */ + SPIFFS_CB_DELETED +} spiffs_fileop_type; + +/* file system listener callback function */ +typedef void (*spiffs_file_callback)(struct spiffs_t *fs, spiffs_fileop_type op, spiffs_obj_id obj_id, spiffs_page_ix pix); #ifndef SPIFFS_DBG #define SPIFFS_DBG(...) \ - print(__VA_ARGS__) + printf(__VA_ARGS__) #endif #ifndef SPIFFS_GC_DBG #define SPIFFS_GC_DBG(...) printf(__VA_ARGS__) @@ -108,18 +154,28 @@ typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_repor /* Any write to the filehandle is appended to end of the file */ #define SPIFFS_APPEND (1<<0) +#define SPIFFS_O_APPEND SPIFFS_APPEND /* If the opened file exists, it will be truncated to zero length before opened */ #define SPIFFS_TRUNC (1<<1) +#define SPIFFS_O_TRUNC SPIFFS_TRUNC /* If the opened file does not exist, it will be created before opened */ #define SPIFFS_CREAT (1<<2) +#define SPIFFS_O_CREAT SPIFFS_CREAT /* The opened file may only be read */ #define SPIFFS_RDONLY (1<<3) -/* The opened file may only be writted */ +#define SPIFFS_O_RDONLY SPIFFS_RDONLY +/* The opened file may only be written */ #define SPIFFS_WRONLY (1<<4) -/* The opened file may be both read and writted */ +#define SPIFFS_O_WRONLY SPIFFS_WRONLY +/* The opened file may be both read and written */ #define SPIFFS_RDWR (SPIFFS_RDONLY | SPIFFS_WRONLY) -/* Any writes to the filehandle will never be cached */ +#define SPIFFS_O_RDWR SPIFFS_RDWR +/* Any writes to the filehandle will never be cached but flushed directly */ #define SPIFFS_DIRECT (1<<5) +#define SPIFFS_O_DIRECT SPIFFS_DIRECT +/* If SPIFFS_O_CREAT and SPIFFS_O_EXCL are set, SPIFFS_open() shall fail if the file exists */ +#define SPIFFS_EXCL (1<<6) +#define SPIFFS_O_EXCL SPIFFS_EXCL #define SPIFFS_SEEK_SET (0) #define SPIFFS_SEEK_CUR (1) @@ -164,10 +220,15 @@ typedef struct { // logical size of a page, must be at least // log_block_size / 8 u32_t log_page_size; + +#endif +#if SPIFFS_FILEHDL_OFFSET + // an integer offset added to each file handle + u16_t fh_ix_offset; #endif } spiffs_config; -typedef struct { +typedef struct spiffs_t { // file system configuration spiffs_config cfg; // number of logical blocks @@ -222,9 +283,12 @@ typedef struct { // check callback function spiffs_check_callback check_cb_f; - + // file callback function + spiffs_file_callback file_cb_f; // mounted flag u8_t mounted; + // user data + void *user_data; // config magic u32_t config_magic; } spiffs; @@ -234,7 +298,11 @@ typedef struct { spiffs_obj_id obj_id; u32_t size; spiffs_obj_type type; + spiffs_page_ix pix; u8_t name[SPIFFS_OBJ_NAME_LEN]; +#if SPIFFS_OBJ_META_LEN + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif } spiffs_stat; struct spiffs_dirent { @@ -243,6 +311,9 @@ struct spiffs_dirent { spiffs_obj_type type; u32_t size; spiffs_page_ix pix; +#if SPIFFS_OBJ_META_LEN + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif }; typedef struct { @@ -251,8 +322,57 @@ typedef struct { int entry; } spiffs_DIR; +#if SPIFFS_IX_MAP + +typedef struct { + // buffer with looked up data pixes + spiffs_page_ix *map_buf; + // precise file byte offset + u32_t offset; + // start data span index of lookup buffer + spiffs_span_ix start_spix; + // end data span index of lookup buffer + spiffs_span_ix end_spix; +} spiffs_ix_map; + +#endif + // functions +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 +/** + * Special function. This takes a spiffs config struct and returns the number + * of blocks this file system was formatted with. This function relies on + * that following info is set correctly in given config struct: + * + * phys_addr, log_page_size, and log_block_size. + * + * Also, hal_read_f must be set in the config struct. + * + * One must be sure of the correct page size and that the physical address is + * correct in the probed file system when calling this function. It is not + * checked if the phys_addr actually points to the start of the file system, + * so one might get a false positive if entering a phys_addr somewhere in the + * middle of the file system at block boundary. In addition, it is not checked + * if the page size is actually correct. If it is not, weird file system sizes + * will be returned. + * + * If this function detects a file system it returns the assumed file system + * size, which can be used to set the phys_size. + * + * Otherwise, it returns an error indicating why it is not regarded as a file + * system. + * + * Note: this function is not protected with SPIFFS_LOCK and SPIFFS_UNLOCK + * macros. It returns the error code directly, instead of as read by + * SPIFFS_errno. + * + * @param config essential parts of the physical and logical + * configuration of the file system. + */ +s32_t SPIFFS_probe_fs(spiffs_config *config); +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + /** * Initializes the file system dynamic parameters and mounts the filesystem. * If SPIFFS_USE_MAGIC is enabled the mounting may fail with SPIFFS_ERR_NOT_A_FS @@ -286,19 +406,18 @@ void SPIFFS_unmount(spiffs *fs); * @param path the path of the new file * @param mode ignored, for posix compliance */ -s32_t SPIFFS_creat(spiffs *fs, char *path, spiffs_mode mode); +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode); /** * Opens/creates a file. * @param fs the file system struct * @param path the path of the new file * @param flags the flags for the open command, can be combinations of - * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, - * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT + * SPIFFS_O_APPEND, SPIFFS_O_TRUNC, SPIFFS_O_CREAT, SPIFFS_O_RDONLY, + * SPIFFS_O_WRONLY, SPIFFS_O_RDWR, SPIFFS_O_DIRECT, SPIFFS_O_EXCL * @param mode ignored, for posix compliance */ -spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode mode); - +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode); /** * Opens a file by given dir entry. @@ -306,7 +425,7 @@ spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode * a normal SPIFFS_open would need to traverse the filesystem again to find * the file, whilst SPIFFS_open_by_dirent already knows where the file resides. * @param fs the file system struct - * @param path the dir entry to the file + * @param e the dir entry to the file * @param flags the flags for the open command, can be combinations of * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. @@ -315,6 +434,22 @@ spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode */ spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode); +/** + * Opens a file by given page index. + * Optimization purposes, opens a file by directly pointing to the page + * index in the spi flash. + * If the page index does not point to a file header SPIFFS_ERR_NOT_A_FILE + * is returned. + * @param fs the file system struct + * @param page_ix the page index + * @param flags the flags for the open command, can be combinations of + * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, + * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. + * SPIFFS_CREAT will have no effect in this case. + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode); + /** * Reads from given filehandle. * @param fs the file system struct @@ -336,13 +471,14 @@ s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len); s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len); /** - * Moves the read/write file offset + * Moves the read/write file offset. Resulting offset is returned or negative if error. + * lseek(fs, fd, 0, SPIFFS_SEEK_CUR) will thus return current offset. * @param fs the file system struct * @param fh the filehandle * @param offs how much/where to move the offset * @param whence if SPIFFS_SEEK_SET, the file offset shall be set to offset bytes * if SPIFFS_SEEK_CUR, the file offset shall be set to its current location plus offset - * if SPIFFS_SEEK_END, the file offset shall be set to the size of the file plus offset + * if SPIFFS_SEEK_END, the file offset shall be set to the size of the file plus offse, which should be negative */ s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence); @@ -351,7 +487,7 @@ s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence); * @param fs the file system struct * @param path the path of the file to remove */ -s32_t SPIFFS_remove(spiffs *fs, char *path); +s32_t SPIFFS_remove(spiffs *fs, const char *path); /** * Removes a file by filehandle @@ -366,7 +502,7 @@ s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh); * @param path the path of the file to stat * @param s the stat struct to populate */ -s32_t SPIFFS_stat(spiffs *fs, char *path, spiffs_stat *s); +s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s); /** * Gets file status by filehandle @@ -388,7 +524,7 @@ s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh); * @param fs the file system struct * @param fh the filehandle of the file to close */ -void SPIFFS_close(spiffs *fs, spiffs_file fh); +s32_t SPIFFS_close(spiffs *fs, spiffs_file fh); /** * Renames a file @@ -396,7 +532,25 @@ void SPIFFS_close(spiffs *fs, spiffs_file fh); * @param old path of file to rename * @param newPath new path of file */ -s32_t SPIFFS_rename(spiffs *fs, char *old, char *newPath); +s32_t SPIFFS_rename(spiffs *fs, const char *old, const char *newPath); + +#if SPIFFS_OBJ_META_LEN +/** + * Updates file's metadata + * @param fs the file system struct + * @param path path to the file + * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long. + */ +s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta); + +/** + * Updates file's metadata + * @param fs the file system struct + * @param fh file handle of the file + * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long. + */ +s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta); +#endif /** * Returns last error of last file operation. @@ -419,7 +573,7 @@ void SPIFFS_clearerr(spiffs *fs); * @param name the name of the directory * @param d pointer the directory stream to be populated */ -spiffs_DIR *SPIFFS_opendir(spiffs *fs, char *name, spiffs_DIR *d); +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d); /** * Closes a directory stream @@ -441,13 +595,6 @@ struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e); */ s32_t SPIFFS_check(spiffs *fs); -/** - * Searches for a block with only deleted entries. If found, it is erased. - * @param fs the file system struct - */ -s32_t SPIFFS_erase_deleted_block(spiffs *fs); - - /** * Returns number of total bytes available and number of used bytes. * This is an estimation, and depends on if there a many files with little @@ -527,6 +674,115 @@ s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages); */ s32_t SPIFFS_gc(spiffs *fs, u32_t size); +/** + * Check if EOF reached. + * @param fs the file system struct + * @param fh the filehandle of the file to check + */ +s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh); + +/** + * Get position in file. + * @param fs the file system struct + * @param fh the filehandle of the file to check + */ +s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh); + +/** + * Registers a callback function that keeps track on operations on file + * headers. Do note, that this callback is called from within internal spiffs + * mechanisms. Any operations on the actual file system being callbacked from + * in this callback will mess things up for sure - do not do this. + * This can be used to track where files are and move around during garbage + * collection, which in turn can be used to build location tables in ram. + * Used in conjuction with SPIFFS_open_by_page this may improve performance + * when opening a lot of files. + * Must be invoked after mount. + * + * @param fs the file system struct + * @param cb_func the callback on file operations + */ +s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func); + +#if SPIFFS_IX_MAP + +/** + * Maps the first level index lookup to a given memory map. + * This will make reading big files faster, as the memory map will be used for + * looking up data pages instead of searching for the indices on the physical + * medium. When mapping, all affected indicies are found and the information is + * copied to the array. + * Whole file or only parts of it may be mapped. The index map will cover file + * contents from argument offset until and including arguments (offset+len). + * It is valid to map a longer range than the current file size. The map will + * then be populated when the file grows. + * On garbage collections and file data page movements, the map array will be + * automatically updated. Do not tamper with the map array, as this contains + * the references to the data pages. Modifying it from outside will corrupt any + * future readings using this file descriptor. + * The map will no longer be used when the file descriptor closed or the file + * is unmapped. + * This can be useful to get faster and more deterministic timing when reading + * large files, or when seeking and reading a lot within a file. + * @param fs the file system struct + * @param fh the file handle of the file to map + * @param map a spiffs_ix_map struct, describing the index map + * @param offset absolute file offset where to start the index map + * @param len length of the mapping in actual file bytes + * @param map_buf the array buffer for the look up data - number of required + * elements in the array can be derived from function + * SPIFFS_bytes_to_ix_map_entries given the length + */ +s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map, + u32_t offset, u32_t len, spiffs_page_ix *map_buf); + +/** + * Unmaps the index lookup from this filehandle. All future readings will + * proceed as normal, requiring reading of the first level indices from + * physical media. + * The map and map buffer given in function SPIFFS_ix_map will no longer be + * referenced by spiffs. + * It is not strictly necessary to unmap a file before closing it, as closing + * a file will automatically unmap it. + * @param fs the file system struct + * @param fh the file handle of the file to unmap + */ +s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh); + +/** + * Moves the offset for the index map given in function SPIFFS_ix_map. Parts or + * all of the map buffer will repopulated. + * @param fs the file system struct + * @param fh the mapped file handle of the file to remap + * @param offset new absolute file offset where to start the index map + */ +s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offs); + +/** + * Utility function to get number of spiffs_page_ix entries a map buffer must + * contain on order to map given amount of file data in bytes. + * See function SPIFFS_ix_map and SPIFFS_ix_map_entries_to_bytes. + * @param fs the file system struct + * @param bytes number of file data bytes to map + * @return needed number of elements in a spiffs_page_ix array needed to + * map given amount of bytes in a file + */ +s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes); + +/** + * Utility function to amount of file data bytes that can be mapped when + * mapping a file with buffer having given number of spiffs_page_ix entries. + * See function SPIFFS_ix_map and SPIFFS_bytes_to_ix_map_entries. + * @param fs the file system struct + * @param map_page_ix_entries number of entries in a spiffs_page_ix array + * @return amount of file data in bytes that can be mapped given a map + * buffer having given amount of spiffs_page_ix entries + */ +s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries); + +#endif // SPIFFS_IX_MAP + + #if SPIFFS_TEST_VISUALISATION /** * Prints out a visualization of the filesystem. diff --git a/include/spiffs/spiffs_config.h b/include/spiffs/spiffs_config.h index ac19c17f..a87fd0e7 100644 --- a/include/spiffs/spiffs_config.h +++ b/include/spiffs/spiffs_config.h @@ -11,33 +11,78 @@ // ----------- 8< ------------ // Following includes are for the linux test build of spiffs // These may/should/must be removed/altered/replaced in your target -#include -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - +//#include "params_test.h" +//#include +//#include +//#include +//#include +//#include +//#ifdef _SPIFFS_TEST +//#include "testrunner.h" +//#endif // ----------- >8 ------------ +#include "freertos/FreeRTOS.h" + // compile time switches // Set generic spiffs debug output call. #ifndef SPIFFS_DBG -#define SPIFFS_DBG(...) //printf(__VA_ARGS__) +#define SPIFFS_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) #endif // Set spiffs debug output call for garbage collecting. #ifndef SPIFFS_GC_DBG -#define SPIFFS_GC_DBG(...) //printf(__VA_ARGS__) +#define SPIFFS_GC_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) #endif // Set spiffs debug output call for caching. #ifndef SPIFFS_CACHE_DBG -#define SPIFFS_CACHE_DBG(...) //printf(__VA_ARGS__) +#define SPIFFS_CACHE_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) #endif // Set spiffs debug output call for system consistency checks. #ifndef SPIFFS_CHECK_DBG -#define SPIFFS_CHECK_DBG(...) //printf(__VA_ARGS__) +#define SPIFFS_CHECK_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) #endif +// Set spiffs debug output call for all api invocations. +#ifndef SPIFFS_API_DBG +#define SPIFFS_API_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) +#endif + + + +// Defines spiffs debug print formatters +// some general signed number +#ifndef _SPIPRIi +#define _SPIPRIi "%d" +#endif +// address +#ifndef _SPIPRIad +#define _SPIPRIad "%08x" +#endif +// block +#ifndef _SPIPRIbl +#define _SPIPRIbl "%04x" +#endif +// page +#ifndef _SPIPRIpg +#define _SPIPRIpg "%04x" +#endif +// span index +#ifndef _SPIPRIsp +#define _SPIPRIsp "%04x" +#endif +// file descriptor +#ifndef _SPIPRIfd +#define _SPIPRIfd "%d" +#endif +// file object id +#ifndef _SPIPRIid +#define _SPIPRIid "%04x" +#endif +// file flags +#ifndef _SPIPRIfl +#define _SPIPRIfl "%02x" +#endif + // Enable/disable API functions to determine exact number of bytes // for filedescriptor and cache buffers. Once decided for a configuration, @@ -51,7 +96,6 @@ #ifndef SPIFFS_CACHE #define SPIFFS_CACHE 1 #endif - #if SPIFFS_CACHE // Enables memory write caching for file descriptors in hydrogen #ifndef SPIFFS_CACHE_WR @@ -60,7 +104,7 @@ // Enable/disable statistics on caching. Debug/test purpose only. #ifndef SPIFFS_CACHE_STATS -#define SPIFFS_CACHE_STATS 1 +#define SPIFFS_CACHE_STATS 0 #endif #endif @@ -72,12 +116,12 @@ // Define maximum number of gc runs to perform to reach desired free pages. #ifndef SPIFFS_GC_MAX_RUNS -#define SPIFFS_GC_MAX_RUNS 5 +#define SPIFFS_GC_MAX_RUNS 10 #endif // Enable/disable statistics on gc. Debug/test purpose only. #ifndef SPIFFS_GC_STATS -#define SPIFFS_GC_STATS 1 +#define SPIFFS_GC_STATS 0 #endif // Garbage collecting examines all pages in a block which and sums up @@ -102,11 +146,27 @@ #define SPIFFS_GC_HEUR_W_ERASE_AGE (50) #endif -// Object name maximum length. +// Object name maximum length. Note that this length include the +// zero-termination character, meaning maximum string of characters +// can at most be SPIFFS_OBJ_NAME_LEN - 1. #ifndef SPIFFS_OBJ_NAME_LEN #define SPIFFS_OBJ_NAME_LEN (32) #endif +// Maximum length of the metadata associated with an object. +// Setting to non-zero value enables metadata-related API but also +// changes the on-disk format, so the change is not backward-compatible. +// +// Do note: the meta length must never exceed +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64) +// +// This is derived from following: +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) + +// spiffs_object_ix_header fields + at least some LUT entries) +#ifndef SPIFFS_OBJ_META_LEN +#define SPIFFS_OBJ_META_LEN (0) +#endif + // Size of buffer allocated on stack used when copying data. // Lower value generates more read/writes. No meaning having it bigger // than logical page size. @@ -122,6 +182,17 @@ #define SPIFFS_USE_MAGIC (0) #endif +#if SPIFFS_USE_MAGIC +// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is +// enabled, the magic will also be dependent on the length of the filesystem. +// For example, a filesystem configured and formatted for 4 megabytes will not +// be accepted for mounting with a configuration defining the filesystem as 2 +// megabytes. +#ifndef SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_USE_MAGIC_LENGTH (1) +#endif +#endif + // SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level // These should be defined on a multithreaded system @@ -134,7 +205,6 @@ #define SPIFFS_UNLOCK(fs) #endif - // Enable if only one spiffs instance with constant configuration will exist // on the target. This will reduce calculations, flash and memory accesses. // Parts of configuration must be defined below instead of at time of mount. @@ -167,11 +237,98 @@ #define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 1 #endif +// Enable this if you want the HAL callbacks to be called with the spiffs struct +#ifndef SPIFFS_HAL_CALLBACK_EXTRA +#define SPIFFS_HAL_CALLBACK_EXTRA 0 +#endif + +// Enable this if you want to add an integer offset to all file handles +// (spiffs_file). This is useful if running multiple instances of spiffs on +// same target, in order to recognise to what spiffs instance a file handle +// belongs. +// NB: This adds config field fh_ix_offset in the configuration struct when +// mounting, which must be defined. +#ifndef SPIFFS_FILEHDL_OFFSET +#define SPIFFS_FILEHDL_OFFSET 0 +#endif + +// Enable this to compile a read only version of spiffs. +// This will reduce binary size of spiffs. All code comprising modification +// of the file system will not be compiled. Some config will be ignored. +// HAL functions for erasing and writing to spi-flash may be null. Cache +// can be disabled for even further binary size reduction (and ram savings). +// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL. +// If the file system cannot be mounted due to aborted erase operation and +// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be +// returned. +// Might be useful for e.g. bootloaders and such. +#ifndef SPIFFS_READ_ONLY +#define SPIFFS_READ_ONLY 0 +#endif + +// Enable this to add a temporal file cache using the fd buffer. +// The effects of the cache is that SPIFFS_open will find the file faster in +// certain cases. It will make it a lot easier for spiffs to find files +// opened frequently, reducing number of readings from the spi flash for +// finding those files. +// This will grow each fd by 6 bytes. If your files are opened in patterns +// with a degree of temporal locality, the system is optimized. +// Examples can be letting spiffs serve web content, where one file is the css. +// The css is accessed for each html file that is opened, meaning it is +// accessed almost every second time a file is opened. Another example could be +// a log file that is often opened, written, and closed. +// The size of the cache is number of given file descriptors, as it piggybacks +// on the fd update mechanism. The cache lives in the closed file descriptors. +// When closed, the fd know the whereabouts of the file. Instead of forgetting +// this, the temporal cache will keep handling updates to that file even if the +// fd is closed. If the file is opened again, the location of the file is found +// directly. If all available descriptors become opened, all cache memory is +// lost. +#ifndef SPIFFS_TEMPORAL_FD_CACHE +#define SPIFFS_TEMPORAL_FD_CACHE 1 +#endif + +// Temporal file cache hit score. Each time a file is opened, all cached files +// will lose one point. If the opened file is found in cache, that entry will +// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this +// value for the specific access patterns of the application. However, it must +// be between 1 (no gain for hitting a cached entry often) and 255. +#ifndef SPIFFS_TEMPORAL_CACHE_HIT_SCORE +#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 4 +#endif + +// Enable to be able to map object indices to memory. +// This allows for faster and more deterministic reading if cases of reading +// large files and when changing file offset by seeking around a lot. +// When mapping a file's index, the file system will be scanned for index pages +// and the info will be put in memory provided by user. When reading, the +// memory map can be looked up instead of searching for index pages on the +// medium. This way, user can trade memory against performance. +// Whole, parts of, or future parts not being written yet can be mapped. The +// memory array will be owned by spiffs and updated accordingly during garbage +// collecting or when modifying the indices. The latter is invoked by when the +// file is modified in some way. The index buffer is tied to the file +// descriptor. +#ifndef SPIFFS_IX_MAP +#define SPIFFS_IX_MAP 1 +#endif + +// By default SPIFFS in some cases relies on the property of NOR flash that bits +// cannot be set from 0 to 1 by writing and that controllers will ignore such +// bit changes. This results in fewer reads as SPIFFS can in some cases perform +// blind writes, with all bits set to 1 and only those it needs reset set to 0. +// Most of the chips and controllers allow this behavior, so the default is to +// use this technique. If your controller is one of the rare ones that don't, +// turn this option on and SPIFFS will perform a read-modify-write instead. +#ifndef SPIFFS_NO_BLIND_WRITES +#define SPIFFS_NO_BLIND_WRITES 0 +#endif + // Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function // in the api. This function will visualize all filesystem using given printf // function. #ifndef SPIFFS_TEST_VISUALISATION -#define SPIFFS_TEST_VISUALISATION 0 +#define SPIFFS_TEST_VISUALISATION 1 #endif #if SPIFFS_TEST_VISUALISATION #ifndef spiffs_printf diff --git a/include/spiffs/spiffs_nucleus.h b/include/spiffs/spiffs_nucleus.h index 80cc1cff..3af548bf 100644 --- a/include/spiffs/spiffs_nucleus.h +++ b/include/spiffs/spiffs_nucleus.h @@ -116,13 +116,23 @@ #define SPIFFS_ERR_CHECK_FLAGS_BAD (SPIFFS_ERR_INTERNAL - 3) #define _SPIFFS_ERR_CHECK_LAST (SPIFFS_ERR_INTERNAL - 4) +// visitor result, continue searching #define SPIFFS_VIS_COUNTINUE (SPIFFS_ERR_INTERNAL - 20) +// visitor result, continue searching after reloading lu buffer #define SPIFFS_VIS_COUNTINUE_RELOAD (SPIFFS_ERR_INTERNAL - 21) +// visitor result, stop searching #define SPIFFS_VIS_END (SPIFFS_ERR_INTERNAL - 22) -#define SPIFFS_EV_IX_UPD 0 -#define SPIFFS_EV_IX_NEW 1 -#define SPIFFS_EV_IX_DEL 2 +// updating an object index contents +#define SPIFFS_EV_IX_UPD (0) +// creating a new object index +#define SPIFFS_EV_IX_NEW (1) +// deleting an object index +#define SPIFFS_EV_IX_DEL (2) +// moving an object index without updating contents +#define SPIFFS_EV_IX_MOV (3) +// updating an object index header data only, not the table itself +#define SPIFFS_EV_IX_UPD_HDR (4) #define SPIFFS_OBJ_ID_IX_FLAG ((spiffs_obj_id)(1<<(8*sizeof(spiffs_obj_id)-1))) @@ -131,7 +141,31 @@ #define SPIFFS_OBJ_ID_DELETED ((spiffs_obj_id)0) #define SPIFFS_OBJ_ID_FREE ((spiffs_obj_id)-1) -#define SPIFFS_MAGIC(fs) ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs))) + + +#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__) + /* For GCC, clang and TI compilers */ +#define SPIFFS_PACKED __attribute__((packed)) +#elif defined(__ICCARM__) || defined(__CC_ARM) + /* For IAR ARM and Keil MDK-ARM compilers */ +#define SPIFFS_PACKED + +#else + /* Unknown compiler */ +#define SPIFFS_PACKED +#endif + + + +#if SPIFFS_USE_MAGIC +#if !SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_MAGIC(fs, bix) \ + ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs))) +#else // SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_MAGIC(fs, bix) \ + ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs) ^ ((fs)->block_count - (bix)))) +#endif // SPIFFS_USE_MAGIC_LENGTH +#endif // SPIFFS_USE_MAGIC #define SPIFFS_CONFIG_MAGIC (0x20090315) @@ -220,6 +254,17 @@ // object index span index number for given data span index or entry #define SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, spix) \ ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? 0 : (1+((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))/SPIFFS_OBJ_IX_LEN(fs))) +// get data span index for object index span index +#define SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, spix) \ + ( (spix) == 0 ? 0 : (SPIFFS_OBJ_HDR_IX_LEN(fs) + (((spix)-1) * SPIFFS_OBJ_IX_LEN(fs))) ) + +#if SPIFFS_FILEHDL_OFFSET +#define SPIFFS_FH_OFFS(fs, fh) ((fh) != 0 ? ((fh) + (fs)->cfg.fh_ix_offset) : 0) +#define SPIFFS_FH_UNOFFS(fs, fh) ((fh) != 0 ? ((fh) - (fs)->cfg.fh_ix_offset) : 0) +#else +#define SPIFFS_FH_OFFS(fs, fh) ((spiffs_file)(fh)) +#define SPIFFS_FH_UNOFFS(fs, fh) ((spiffs_file)(fh)) +#endif #define SPIFFS_OP_T_OBJ_LU (0<<0) @@ -264,26 +309,26 @@ #define SPIFFS_API_CHECK_MOUNT(fs) \ if (!SPIFFS_CHECK_MOUNT((fs))) { \ (fs)->err_code = SPIFFS_ERR_NOT_MOUNTED; \ - return -1; \ + return SPIFFS_ERR_NOT_MOUNTED; \ } #define SPIFFS_API_CHECK_CFG(fs) \ if (!SPIFFS_CHECK_CFG((fs))) { \ (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; \ - return -1; \ + return SPIFFS_ERR_NOT_CONFIGURED; \ } #define SPIFFS_API_CHECK_RES(fs, res) \ if ((res) < SPIFFS_OK) { \ (fs)->err_code = (res); \ - return -1; \ + return (res); \ } #define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \ if ((res) < SPIFFS_OK) { \ (fs)->err_code = (res); \ SPIFFS_UNLOCK(fs); \ - return -1; \ + return (res); \ } #define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \ @@ -304,13 +349,33 @@ if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH; -// check id +// check id, only visit matching objec ids #define SPIFFS_VIS_CHECK_ID (1<<0) // report argument object id to visitor - else object lookup id is reported #define SPIFFS_VIS_CHECK_PH (1<<1) // stop searching at end of all look up pages #define SPIFFS_VIS_NO_WRAP (1<<2) +#if SPIFFS_HAL_CALLBACK_EXTRA + +#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \ + (_fs)->cfg.hal_write_f((_fs), (_paddr), (_len), (_src)) +#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \ + (_fs)->cfg.hal_read_f((_fs), (_paddr), (_len), (_dst)) +#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \ + (_fs)->cfg.hal_erase_f((_fs), (_paddr), (_len)) + +#else // SPIFFS_HAL_CALLBACK_EXTRA + +#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \ + (_fs)->cfg.hal_write_f((_paddr), (_len), (_src)) +#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \ + (_fs)->cfg.hal_read_f((_paddr), (_len), (_dst)) +#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \ + (_fs)->cfg.hal_erase_f((_paddr), (_len)) + +#endif // SPIFFS_HAL_CALLBACK_EXTRA + #if SPIFFS_CACHE #define SPIFFS_CACHE_FLAG_DIRTY (1<<0) @@ -390,13 +455,23 @@ typedef struct { spiffs_span_ix cursor_objix_spix; // current absolute offset u32_t offset; - // current file descriptor offset + // current file descriptor offset (cached) u32_t fdoffset; // fd flags spiffs_flags flags; #if SPIFFS_CACHE_WR spiffs_cache_page *cache_page; #endif +#if SPIFFS_TEMPORAL_FD_CACHE + // djb2 hash of filename + u32_t name_hash; + // hit score (score == 0 indicates never used fd) + u16_t score; +#endif +#if SPIFFS_IX_MAP + // spiffs index map, if 0 it means unmapped + spiffs_ix_map *ix_map; +#endif } spiffs_fd; @@ -405,7 +480,7 @@ typedef struct { // page header, part of each page except object lookup pages // NB: this is always aligned when the data page is an object index, // as in this case struct spiffs_page_object_ix is used -typedef struct __attribute(( packed )) { +typedef struct SPIFFS_PACKED { // object id spiffs_obj_id obj_id; // object span index @@ -415,7 +490,7 @@ typedef struct __attribute(( packed )) { } spiffs_page_header; // object index header page header -typedef struct __attribute(( packed )) +typedef struct SPIFFS_PACKED #if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES __attribute(( aligned(sizeof(spiffs_page_ix)) )) #endif @@ -423,24 +498,28 @@ typedef struct __attribute(( packed )) // common page header spiffs_page_header p_hdr; // alignment - u8_t _align[4 - (sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3)]; + u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))]; // size of object u32_t size; // type of object spiffs_obj_type type; // name of object u8_t name[SPIFFS_OBJ_NAME_LEN]; +#if SPIFFS_OBJ_META_LEN + // metadata. not interpreted by SPIFFS in any way. + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif } spiffs_page_object_ix_header; // object index page header -typedef struct __attribute(( packed )) { +typedef struct SPIFFS_PACKED { spiffs_page_header p_hdr; - u8_t _align[4 - (sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3)]; + u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))]; } spiffs_page_object_ix; // callback func for object lookup visitor typedef s32_t (*spiffs_visitor_f)(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, - u32_t user_data, void *user_p); + const void *user_const_p, void *user_var_p); #if SPIFFS_CACHE @@ -501,8 +580,8 @@ s32_t spiffs_obj_lu_find_entry_visitor( u8_t flags, spiffs_obj_id obj_id, spiffs_visitor_f v, - u32_t user_data, - void *user_p, + const void *user_const_p, + void *user_var_p, spiffs_block_ix *block_ix, int *lu_entry); @@ -510,6 +589,11 @@ s32_t spiffs_erase_block( spiffs *fs, spiffs_block_ix bix); +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH +s32_t spiffs_probe( + spiffs_config *cfg); +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH + // --------------- s32_t spiffs_obj_lu_scan( @@ -518,7 +602,7 @@ s32_t spiffs_obj_lu_scan( s32_t spiffs_obj_lu_find_free_obj_id( spiffs *fs, spiffs_obj_id *obj_id, - u8_t *conflicting_name); + const u8_t *conflicting_name); s32_t spiffs_obj_lu_find_free( spiffs *fs, @@ -579,7 +663,8 @@ s32_t spiffs_page_delete( s32_t spiffs_object_create( spiffs *fs, spiffs_obj_id obj_id, - u8_t name[SPIFFS_OBJ_NAME_LEN], + const u8_t name[], + const u8_t meta[], spiffs_obj_type type, spiffs_page_ix *objix_hdr_pix); @@ -589,13 +674,24 @@ s32_t spiffs_object_update_index_hdr( spiffs_obj_id obj_id, spiffs_page_ix objix_hdr_pix, u8_t *new_objix_hdr_data, - u8_t name[SPIFFS_OBJ_NAME_LEN], + const u8_t name[], + const u8_t meta[], u32_t size, spiffs_page_ix *new_pix); -void spiffs_cb_object_event( +#if SPIFFS_IX_MAP + +s32_t spiffs_populate_ix_map( spiffs *fs, spiffs_fd *fd, + u32_t vec_entry_start, + u32_t vec_entry_end); + +#endif + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_page_object_ix *objix, int ev, spiffs_obj_id obj_id, spiffs_span_ix spix, @@ -641,7 +737,7 @@ s32_t spiffs_object_truncate( s32_t spiffs_object_find_object_index_header_by_name( spiffs *fs, - u8_t name[SPIFFS_OBJ_NAME_LEN], + const u8_t name[SPIFFS_OBJ_NAME_LEN], spiffs_page_ix *pix); // --------------- @@ -671,7 +767,8 @@ s32_t spiffs_gc_quick( s32_t spiffs_fd_find_new( spiffs *fs, - spiffs_fd **fd); + spiffs_fd **fd, + const char *name); s32_t spiffs_fd_return( spiffs *fs, @@ -682,6 +779,13 @@ s32_t spiffs_fd_get( spiffs_file f, spiffs_fd **fd); +#if SPIFFS_TEMPORAL_FD_CACHE +void spiffs_fd_temporal_cache_rehash( + spiffs *fs, + const char *old_path, + const char *new_path); +#endif + #if SPIFFS_CACHE void spiffs_cache_init( spiffs *fs); @@ -715,4 +819,24 @@ s32_t spiffs_page_consistency_check( s32_t spiffs_object_index_consistency_check( spiffs *fs); +// memcpy macro, +// checked in test builds, otherwise plain memcpy (unless already defined) +#ifdef _SPIFFS_TEST +#define _SPIFFS_MEMCPY(__d, __s, __l) do { \ + intptr_t __a1 = (intptr_t)((u8_t*)(__s)); \ + intptr_t __a2 = (intptr_t)((u8_t*)(__s)+(__l)); \ + intptr_t __b1 = (intptr_t)((u8_t*)(__d)); \ + intptr_t __b2 = (intptr_t)((u8_t*)(__d)+(__l)); \ + if (__a1 <= __b2 && __b1 <= __a2) { \ + printf("FATAL OVERLAP: memcpy from %lx..%lx to %lx..%lx\n", __a1, __a2, __b1, __b2); \ + ERREXIT(); \ + } \ + memcpy((__d),(__s),(__l)); \ +} while (0) +#else +#ifndef _SPIFFS_MEMCPY +#define _SPIFFS_MEMCPY(__d, __s, __l) do{memcpy((__d),(__s),(__l));}while(0) +#endif +#endif //_SPIFFS_TEST + #endif /* SPIFFS_NUCLEUS_H_ */ diff --git a/lib/libspiffs.a b/lib/libspiffs.a index deec65c22867f29ea14cf0e65b01784afef0c6c7..15070cb6fd3d9a6320991615011dfb6059ae13cc 100644 GIT binary patch literal 158758 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 literal 136952 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�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 diff --git a/third_party/spiffs/spiffs_cache.c b/third_party/spiffs/spiffs_cache.c index 4fde4d36..e7cd4b73 100644 --- a/third_party/spiffs/spiffs_cache.c +++ b/third_party/spiffs/spiffs_cache.c @@ -20,12 +20,12 @@ static spiffs_cache_page *spiffs_cache_page_get(spiffs *fs, spiffs_page_ix pix) if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && cp->pix == pix ) { - SPIFFS_CACHE_DBG("CACHE_GET: have cache page %i for %04x\n", i, pix); + //SPIFFS_CACHE_DBG("CACHE_GET: have cache page "_SPIPRIi" for "_SPIPRIpg"\n", i, pix); cp->last_access = cache->last_access; return cp; } } - //SPIFFS_CACHE_DBG("CACHE_GET: no cache for %04x\n", pix); + //SPIFFS_CACHE_DBG("CACHE_GET: no cache for "_SPIPRIpg"\n", pix); return 0; } @@ -39,17 +39,20 @@ static s32_t spiffs_cache_page_free(spiffs *fs, int ix, u8_t write_back) { (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && (cp->flags & SPIFFS_CACHE_FLAG_DIRTY)) { u8_t *mem = spiffs_get_cache_page(fs, cache, ix); - res = fs->cfg.hal_write_f(SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem); + SPIFFS_CACHE_DBG("CACHE_FREE: write cache page "_SPIPRIi" pix "_SPIPRIpg"\n", ix, cp->pix); + res = SPIFFS_HAL_WRITE(fs, SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem); } - cp->flags = 0; - cache->cpage_use_map &= ~(1 << ix); - +#if SPIFFS_CACHE_WR if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) { - SPIFFS_CACHE_DBG("CACHE_FREE: free cache page %i objid %04x\n", ix, cp->obj_id); - } else { - SPIFFS_CACHE_DBG("CACHE_FREE: free cache page %i pix %04x\n", ix, cp->pix); + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" objid "_SPIPRIid"\n", ix, cp->obj_id); + } else +#endif + { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" pix "_SPIPRIpg"\n", ix, cp->pix); } + cache->cpage_use_map &= ~(1 << ix); + cp->flags = 0; } return res; @@ -98,7 +101,7 @@ static spiffs_cache_page *spiffs_cache_page_allocate(spiffs *fs) { spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); cache->cpage_use_map |= (1<last_access = cache->last_access; - SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page %i\n", i); + //SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi"\n", i); return cp; } } @@ -130,38 +133,50 @@ s32_t spiffs_phys_rd( spiffs_cache_page *cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr)); cache->last_access++; if (cp) { + // we've already got one, you see #if SPIFFS_CACHE_STATS fs->cache_hits++; #endif cp->last_access = cache->last_access; + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + _SPIFFS_MEMCPY(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); } else { if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) { // for second layer lookup functions, we do not cache in order to prevent shredding - return fs->cfg.hal_read_f( - addr , - len, - dst); + return SPIFFS_HAL_READ(fs, addr, len, dst); } #if SPIFFS_CACHE_STATS fs->cache_misses++; #endif + // this operation will always free one cache page (unless all already free), + // the result code stems from the write operation of the possibly freed cache page res = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + cp = spiffs_cache_page_allocate(fs); if (cp) { cp->flags = SPIFFS_CACHE_FLAG_WRTHRU; cp->pix = SPIFFS_PADDR_TO_PAGE(fs, addr); - } + SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi" for pix "_SPIPRIpg "\n", cp->ix, cp->pix); - s32_t res2 = fs->cfg.hal_read_f( - addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr), - SPIFFS_CFG_LOG_PAGE_SZ(fs), - spiffs_get_cache_page(fs, cache, cp->ix)); - if (res2 != SPIFFS_OK) { - res = res2; + s32_t res2 = SPIFFS_HAL_READ(fs, + addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr), + SPIFFS_CFG_LOG_PAGE_SZ(fs), + spiffs_get_cache_page(fs, cache, cp->ix)); + if (res2 != SPIFFS_OK) { + // honor read failure before possible write failure (bad idea?) + res = res2; + } + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + _SPIFFS_MEMCPY(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); + } else { + // this will never happen, last resort for sake of symmetry + s32_t res2 = SPIFFS_HAL_READ(fs, addr, len, dst); + if (res2 != SPIFFS_OK) { + // honor read failure before possible write failure (bad idea?) + res = res2; + } } } - u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); - memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); return res; } @@ -186,24 +201,24 @@ s32_t spiffs_phys_wr( (op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) { // page is being deleted, wipe from cache - unless it is a lookup page spiffs_cache_page_free(fs, cp->ix, 0); - return fs->cfg.hal_write_f(addr, len, src); + return SPIFFS_HAL_WRITE(fs, addr, len, src); } u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); - memcpy(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len); + _SPIFFS_MEMCPY(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len); cache->last_access++; cp->last_access = cache->last_access; if (cp->flags & SPIFFS_CACHE_FLAG_WRTHRU) { // page is being updated, no write-cache, just pass thru - return fs->cfg.hal_write_f(addr, len, src); + return SPIFFS_HAL_WRITE(fs, addr, len, src); } else { return SPIFFS_OK; } } else { // no cache page, no write cache - just write thru - return fs->cfg.hal_write_f(addr, len, src); + return SPIFFS_HAL_WRITE(fs, addr, len, src); } } @@ -245,6 +260,7 @@ spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd) { cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR; cp->obj_id = fd->obj_id; fd->cache_page = cp; + SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi" for fd "_SPIPRIfd ":"_SPIPRIid "\n", cp->ix, fd->file_nbr, fd->obj_id); return cp; } @@ -288,7 +304,7 @@ void spiffs_cache_init(spiffs *fs) { cache.cpage_use_map = 0xffffffff; cache.cpage_use_mask = cache_mask; - memcpy(fs->cache, &cache, sizeof(spiffs_cache)); + _SPIFFS_MEMCPY(fs->cache, &cache, sizeof(spiffs_cache)); spiffs_cache *c = spiffs_get_cache(fs); diff --git a/third_party/spiffs/spiffs_check.c b/third_party/spiffs/spiffs_check.c index 2180a2a1..0331fb18 100644 --- a/third_party/spiffs/spiffs_check.c +++ b/third_party/spiffs/spiffs_check.c @@ -19,9 +19,24 @@ * Author: petera */ + #include "spiffs.h" #include "spiffs_nucleus.h" +#if !SPIFFS_READ_ONLY + +#if SPIFFS_HAL_CALLBACK_EXTRA +#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \ + do { \ + if ((_fs)->check_cb_f) (_fs)->check_cb_f((_fs), (_type), (_rep), (_arg1), (_arg2)); \ + } while (0) +#else +#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \ + do { \ + if ((_fs)->check_cb_f) (_fs)->check_cb_f((_type), (_rep), (_arg1), (_arg2)); \ + } while (0) +#endif + //--------------------------------------- // Look up consistency @@ -93,6 +108,7 @@ static s32_t spiffs_rewrite_index(spiffs *fs, spiffs_obj_id obj_id, spiffs_span_ } else { // calc entry in index entry = SPIFFS_OBJ_IX_ENTRY(fs, data_spix); + } // load index res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, @@ -145,11 +161,17 @@ static s32_t spiffs_delete_obj_lazy(spiffs *fs, spiffs_obj_id obj_id) { return SPIFFS_OK; } SPIFFS_CHECK_RES(res); - u8_t flags = 0xff & ~SPIFFS_PH_FLAG_IXDELE; + u8_t flags = 0xff; +#if SPIFFS_NO_BLIND_WRITES + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix) + offsetof(spiffs_page_header, flags), + sizeof(flags), &flags); + SPIFFS_CHECK_RES(res); +#endif + flags &= ~SPIFFS_PH_FLAG_IXDELE; res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, 0, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix) + offsetof(spiffs_page_header, flags), - sizeof(u8_t), - (u8_t *)&flags); + sizeof(flags), &flags); return res; } @@ -166,7 +188,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s if (((lu_obj_id == SPIFFS_OBJ_ID_DELETED) && (p_hdr->flags & SPIFFS_PH_FLAG_DELET)) || ((lu_obj_id == SPIFFS_OBJ_ID_FREE) && (p_hdr->flags & SPIFFS_PH_FLAG_USED) == 0)) { // look up entry deleted / free but used in page header - SPIFFS_CHECK_DBG("LU: pix %04x deleted/free in lu but not on page\n", cur_pix); + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" deleted/free in lu but not on page\n", cur_pix); *reload_lu = 1; delete_page = 1; if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) { @@ -183,20 +205,20 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s // copy page to new place and re-write the object index to new place spiffs_page_ix new_pix; res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); - SPIFFS_CHECK_DBG("LU: FIXUP: data page not found elsewhere, rewriting %04x to new page %04x\n", cur_pix, new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: data page not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix); SPIFFS_CHECK_RES(res); *reload_lu = 1; - SPIFFS_CHECK_DBG("LU: FIXUP: %04x rewritten to %04x, affected objix_pix %04x\n", cur_pix, new_pix, objix_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: "_SPIPRIpg" rewritten to "_SPIPRIpg", affected objix_pix "_SPIPRIpg"\n", cur_pix, new_pix, objix_pix); res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { // index bad also, cannot mend this file - SPIFFS_CHECK_DBG("LU: FIXUP: index bad %i, cannot mend!\n", res); + SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); res = spiffs_page_delete(fs, new_pix); SPIFFS_CHECK_RES(res); res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); } else { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, p_hdr->obj_id, p_hdr->span_ix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, p_hdr->obj_id, p_hdr->span_ix); } SPIFFS_CHECK_RES(res); } @@ -213,10 +235,10 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s // got a data page also, assume lu corruption only, rewrite to new page spiffs_page_ix new_pix; res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); - SPIFFS_CHECK_DBG("LU: FIXUP: ix page with data not found elsewhere, rewriting %04x to new page %04x\n", cur_pix, new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: ix page with data not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix); SPIFFS_CHECK_RES(res); *reload_lu = 1; - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); } } else { SPIFFS_CHECK_RES(res); @@ -226,7 +248,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s if (lu_obj_id != SPIFFS_OBJ_ID_FREE && lu_obj_id != SPIFFS_OBJ_ID_DELETED) { // look up entry used if ((p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG) != (lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG)) { - SPIFFS_CHECK_DBG("LU: pix %04x differ in obj_id lu:%04x ph:%04x\n", cur_pix, lu_obj_id, p_hdr->obj_id); + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" differ in obj_id lu:"_SPIPRIid" ph:"_SPIPRIid"\n", cur_pix, lu_obj_id, p_hdr->obj_id); delete_page = 1; if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0 || (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) || @@ -249,12 +271,12 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { // index bad also, cannot mend this file - SPIFFS_CHECK_DBG("LU: FIXUP: index bad %i, cannot mend!\n", res); + SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); res = spiffs_page_delete(fs, new_pix); SPIFFS_CHECK_RES(res); res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); *reload_lu = 1; - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); } SPIFFS_CHECK_RES(res); } @@ -305,8 +327,8 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s // rewrite as obj_id_ph new_ph.obj_id = p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG; res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); - SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page %04x as %04x to pix %04x\n", cur_pix, new_ph.obj_id, new_pix); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid" to pix "_SPIPRIpg"\n", cur_pix, new_ph.obj_id, new_pix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); SPIFFS_CHECK_RES(res); *reload_lu = 1; } else if ((objix_pix_ph && data_pix_ph && data_pix_lu && objix_pix_lu == 0) || @@ -314,8 +336,8 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s // got a data page for look up obj id // rewrite as obj_id_lu new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG; - SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page %04x as %04x\n", cur_pix, new_ph.obj_id); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid"\n", cur_pix, new_ph.obj_id); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); SPIFFS_CHECK_RES(res); *reload_lu = 1; @@ -328,7 +350,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s } } else if (((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX)) || ((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0 && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) == 0)) { - SPIFFS_CHECK_DBG("LU: %04x lu/page index marking differ\n", cur_pix); + SPIFFS_CHECK_DBG("LU: "_SPIPRIpg" lu/page index marking differ\n", cur_pix); spiffs_page_ix data_pix, objix_pix_d; // see if other data page exists for given obj id and span index res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &data_pix); @@ -353,7 +375,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s // if only data page exists, make this page index if (data_pix && objix_pix_d == 0) { SPIFFS_CHECK_DBG("LU: FIXUP: other data page exists, make this index\n"); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, lu_obj_id, p_hdr->span_ix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, lu_obj_id, p_hdr->span_ix); spiffs_page_header new_ph; spiffs_page_ix new_pix; new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); @@ -369,7 +391,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s // if only index exists, make data page if (data_pix == 0 && objix_pix_d) { SPIFFS_CHECK_DBG("LU: FIXUP: other index page exists, make this data\n"); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, lu_obj_id, p_hdr->span_ix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, lu_obj_id, p_hdr->span_ix); spiffs_page_header new_ph; spiffs_page_ix new_pix; new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL); @@ -386,10 +408,10 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s } } else if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0) { - SPIFFS_CHECK_DBG("LU: pix %04x busy in lu but deleted on page\n", cur_pix); + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy in lu but deleted on page\n", cur_pix); delete_page = 1; } else if ((p_hdr->flags & SPIFFS_PH_FLAG_FINAL)) { - SPIFFS_CHECK_DBG("LU: pix %04x busy but not final\n", cur_pix); + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy but not final\n", cur_pix); // page can be removed if not referenced by object index *reload_lu = 1; res = spiffs_object_get_data_page_index_reference(fs, lu_obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); @@ -406,19 +428,26 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s // page referenced by object index but not final // just finalize SPIFFS_CHECK_DBG("LU: FIXUP: unfinalized page is referred, finalizing\n"); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); - u8_t flags = 0xff & ~SPIFFS_PH_FLAG_FINAL; + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + u8_t flags = 0xff; +#if SPIFFS_NO_BLIND_WRITES + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + offsetof(spiffs_page_header, flags), + sizeof(flags), &flags); + SPIFFS_CHECK_RES(res); +#endif + flags &= ~SPIFFS_PH_FLAG_FINAL; res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + offsetof(spiffs_page_header, flags), - sizeof(u8_t), (u8_t*)&flags); + sizeof(flags), &flags); } } } } if (delete_page) { - SPIFFS_CHECK_DBG("LU: FIXUP: deleting page %04x\n", cur_pix); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); + SPIFFS_CHECK_DBG("LU: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); res = spiffs_page_delete(fs, cur_pix); SPIFFS_CHECK_RES(res); } @@ -427,14 +456,14 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s } static s32_t spiffs_lookup_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, int cur_entry, - u32_t user_data, void *user_p) { - (void)user_data; - (void)user_p; + const void *user_const_p, void *user_var_p) { + (void)user_const_p; + (void)user_var_p; s32_t res = SPIFFS_OK; spiffs_page_header p_hdr; spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, (cur_block * 256)/fs->block_count, 0); // load header @@ -460,7 +489,7 @@ s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t check_all_objects) { (void)check_all_objects; s32_t res = SPIFFS_OK; - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 0, 0); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 0, 0); res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_lookup_check_v, 0, 0, 0, 0); @@ -469,10 +498,10 @@ s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t check_all_objects) { } if (res != SPIFFS_OK) { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_ERROR, res, 0); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_ERROR, res, 0); } - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0); return res; } @@ -506,14 +535,17 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { spiffs_block_ix cur_block = 0; // build consistency bitmap for id range traversing all blocks while (!restart && cur_block < fs->block_count) { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, (pix_offset*256)/(SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) + ((((cur_block * pages_per_scan * 256)/ (SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count))) / fs->block_count), 0); - // traverse each page except for lookup pages spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_PAGES(fs) + SPIFFS_PAGES_PER_BLOCK(fs) * cur_block; while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block+1)) { + //if ((cur_pix & 0xff) == 0) + // SPIFFS_CHECK_DBG("PA: processing pix "_SPIPRIpg", block "_SPIPRIbl" of pix "_SPIPRIpg", block "_SPIPRIbl"\n", + // cur_pix, cur_block, SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count, fs->block_count); + // read header spiffs_page_header p_hdr; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, @@ -570,7 +602,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { || (rpix_within_range && SPIFFS_IS_LOOKUP_PAGE(fs, rpix))) { // bad reference - SPIFFS_CHECK_DBG("PA: pix %04x bad pix / LU referenced from page %04x\n", + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg"x bad pix / LU referenced from page "_SPIPRIpg"\n", rpix, cur_pix); // check for data page elsewhere spiffs_page_ix data_pix; @@ -589,20 +621,20 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { new_ph.span_ix = data_spix_offset + i; res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &data_pix); SPIFFS_CHECK_RES(res); - SPIFFS_CHECK_DBG("PA: FIXUP: found no existing data page, created new @ %04x\n", data_pix); + SPIFFS_CHECK_DBG("PA: FIXUP: found no existing data page, created new @ "_SPIPRIpg"\n", data_pix); } // remap index - SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix %04x\n", cur_pix); + SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix "_SPIPRIpg"\n", cur_pix); res = spiffs_rewrite_index(fs, objix_p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, data_spix_offset + i, data_pix, cur_pix); if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { // index bad also, cannot mend this file - SPIFFS_CHECK_DBG("PA: FIXUP: index bad %i, cannot mend - delete object\n", res); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, objix_p_hdr->obj_id, 0); + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend - delete object\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, objix_p_hdr->obj_id, 0); // delete file res = spiffs_page_delete(fs, cur_pix); } else { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, objix_p_hdr->obj_id, objix_p_hdr->span_ix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, objix_p_hdr->obj_id, objix_p_hdr->span_ix); } SPIFFS_CHECK_RES(res); restart = 1; @@ -621,7 +653,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { rp_hdr.span_ix != data_spix_offset + i || (rp_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) != (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX)) { - SPIFFS_CHECK_DBG("PA: pix %04x has inconsistent page header ix id/span:%04x/%04x, ref id/span:%04x/%04x flags:%02x\n", + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" has inconsistent page header ix id/span:"_SPIPRIid"/"_SPIPRIsp", ref id/span:"_SPIPRIid"/"_SPIPRIsp" flags:"_SPIPRIfl"\n", rpix, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, data_spix_offset + i, rp_hdr.obj_id, rp_hdr.span_ix, rp_hdr.flags); // try finding correct page @@ -635,23 +667,23 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { SPIFFS_CHECK_RES(res); if (data_pix == 0) { // not found, this index is badly borked - SPIFFS_CHECK_DBG("PA: FIXUP: index bad, delete object id %04x\n", p_hdr.obj_id); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + SPIFFS_CHECK_DBG("PA: FIXUP: index bad, delete object id "_SPIPRIid"\n", p_hdr.obj_id); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); SPIFFS_CHECK_RES(res); break; } else { // found it, so rewrite index - SPIFFS_CHECK_DBG("PA: FIXUP: found correct data pix %04x, rewrite ix pix %04x id %04x\n", + SPIFFS_CHECK_DBG("PA: FIXUP: found correct data pix "_SPIPRIpg", rewrite ix pix "_SPIPRIpg" id "_SPIPRIid"\n", data_pix, cur_pix, p_hdr.obj_id); res = spiffs_rewrite_index(fs, p_hdr.obj_id, data_spix_offset + i, data_pix, cur_pix); if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { // index bad also, cannot mend this file - SPIFFS_CHECK_DBG("PA: FIXUP: index bad %i, cannot mend!\n", res); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); } else { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); } SPIFFS_CHECK_RES(res); restart = 1; @@ -662,14 +694,14 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { const u32_t rpix_byte_ix = (rpix - pix_offset) / (8/bits); const u8_t rpix_bit_ix = (rpix & ((8/bits)-1)) * bits; if (fs->work[rpix_byte_ix] & (1<<(rpix_bit_ix + 1))) { - SPIFFS_CHECK_DBG("PA: pix %04x multiple referenced from page %04x\n", + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" multiple referenced from page "_SPIPRIpg"\n", rpix, cur_pix); // Here, we should have fixed all broken references - getting this means there // must be multiple files with same object id. Only solution is to delete // the object which is referring to this page - SPIFFS_CHECK_DBG("PA: FIXUP: removing object %04x and page %04x\n", + SPIFFS_CHECK_DBG("PA: FIXUP: removing object "_SPIPRIid" and page "_SPIPRIpg"\n", p_hdr.obj_id, cur_pix); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); SPIFFS_CHECK_RES(res); // extra precaution, delete this page also @@ -706,7 +738,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { if (bitmask == 0x1) { // 001 - SPIFFS_CHECK_DBG("PA: pix %04x USED, UNREFERENCED, not index\n", cur_pix); + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, UNREFERENCED, not index\n", cur_pix); u8_t rewrite_ix_to_this = 0; u8_t delete_page = 0; @@ -722,7 +754,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { if (((rpix == (spiffs_page_ix)-1 || rpix > SPIFFS_MAX_PAGES(fs)) || (SPIFFS_IS_LOOKUP_PAGE(fs, rpix)))) { // pointing to a bad page altogether, rewrite index to this rewrite_ix_to_this = 1; - SPIFFS_CHECK_DBG("PA: corresponding ref is bad: %04x, rewrite to this %04x\n", rpix, cur_pix); + SPIFFS_CHECK_DBG("PA: corresponding ref is bad: "_SPIPRIpg", rewrite to this "_SPIPRIpg"\n", rpix, cur_pix); } else { // pointing to something else, check what spiffs_page_header rp_hdr; @@ -733,12 +765,12 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { ((rp_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL)) == (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET))) { // pointing to something else valid, just delete this page then - SPIFFS_CHECK_DBG("PA: corresponding ref is good but different: %04x, delete this %04x\n", rpix, cur_pix); + SPIFFS_CHECK_DBG("PA: corresponding ref is good but different: "_SPIPRIpg", delete this "_SPIPRIpg"\n", rpix, cur_pix); delete_page = 1; } else { // pointing to something weird, update index to point to this page instead if (rpix != cur_pix) { - SPIFFS_CHECK_DBG("PA: corresponding ref is weird: %04x %s%s%s%s, rewrite this %04x\n", rpix, + SPIFFS_CHECK_DBG("PA: corresponding ref is weird: "_SPIPRIpg" %s%s%s%s, rewrite this "_SPIPRIpg"\n", rpix, (rp_hdr.flags & SPIFFS_PH_FLAG_INDEX) ? "" : "INDEX ", (rp_hdr.flags & SPIFFS_PH_FLAG_DELET) ? "" : "DELETED ", (rp_hdr.flags & SPIFFS_PH_FLAG_USED) ? "NOTUSED " : "", @@ -751,32 +783,32 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { } } } else if (res == SPIFFS_ERR_NOT_FOUND) { - SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete %04x\n", cur_pix); + SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete "_SPIPRIpg"\n", cur_pix); delete_page = 1; res = SPIFFS_OK; } if (rewrite_ix_to_this) { // if pointing to invalid page, redirect index to this page - SPIFFS_CHECK_DBG("PA: FIXUP: rewrite index id %04x data spix %04x to point to this pix: %04x\n", + SPIFFS_CHECK_DBG("PA: FIXUP: rewrite index id "_SPIPRIid" data spix "_SPIPRIsp" to point to this pix: "_SPIPRIpg"\n", p_hdr.obj_id, p_hdr.span_ix, cur_pix); res = spiffs_rewrite_index(fs, p_hdr.obj_id, p_hdr.span_ix, cur_pix, objix_pix); if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { // index bad also, cannot mend this file - SPIFFS_CHECK_DBG("PA: FIXUP: index bad %i, cannot mend!\n", res); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); res = spiffs_page_delete(fs, cur_pix); SPIFFS_CHECK_RES(res); res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); } else { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); } SPIFFS_CHECK_RES(res); restart = 1; continue; } else if (delete_page) { - SPIFFS_CHECK_DBG("PA: FIXUP: deleting page %04x\n", cur_pix); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); + SPIFFS_CHECK_DBG("PA: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); res = spiffs_page_delete(fs, cur_pix); } SPIFFS_CHECK_RES(res); @@ -784,7 +816,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { if (bitmask == 0x2) { // 010 - SPIFFS_CHECK_DBG("PA: pix %04x FREE, REFERENCED, not index\n", cur_pix); + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, not index\n", cur_pix); // no op, this should be taken care of when checking valid references } @@ -794,7 +826,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { if (bitmask == 0x4) { // 100 - SPIFFS_CHECK_DBG("PA: pix %04x FREE, unreferenced, INDEX\n", cur_pix); + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, unreferenced, INDEX\n", cur_pix); // this should never happen, major fubar } @@ -804,20 +836,22 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { if (bitmask == 0x6) { // 110 - SPIFFS_CHECK_DBG("PA: pix %04x FREE, REFERENCED, INDEX\n", cur_pix); + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, INDEX\n", cur_pix); // no op, this should be taken care of when checking valid references } if (bitmask == 0x7) { // 111 - SPIFFS_CHECK_DBG("PA: pix %04x USED, REFERENCED, INDEX\n", cur_pix); + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, REFERENCED, INDEX\n", cur_pix); // no op, this should be taken care of when checking valid references } } } } + + SPIFFS_CHECK_DBG("PA: processed "_SPIPRIpg", restart "_SPIPRIi"\n", pix_offset, restart); // next page range if (!restart) { pix_offset += pages_per_scan; @@ -828,12 +862,12 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { // Checks consistency amongst all pages and fixes irregularities s32_t spiffs_page_consistency_check(spiffs *fs) { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 0, 0); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 0, 0); s32_t res = spiffs_page_consistency_check_i(fs); if (res != SPIFFS_OK) { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_ERROR, res, 0); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_ERROR, res, 0); } - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0); return res; } @@ -855,14 +889,14 @@ static int spiffs_object_index_search(spiffs *fs, spiffs_obj_id obj_id) { } static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, - int cur_entry, u32_t user_data, void *user_p) { - (void)user_data; + int cur_entry, const void *user_const_p, void *user_var_p) { + (void)user_const_p; s32_t res_c = SPIFFS_VIS_COUNTINUE; s32_t res = SPIFFS_OK; - u32_t *log_ix = (u32_t *)user_p; + u32_t *log_ix = (u32_t*)user_var_p; spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work; - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, (cur_block * 256)/fs->block_count, 0); if (obj_id != SPIFFS_OBJ_ID_FREE && obj_id != SPIFFS_OBJ_ID_DELETED && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { @@ -877,9 +911,9 @@ static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id o if (p_hdr.span_ix == 0 && (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) == (SPIFFS_PH_FLAG_DELET)) { - SPIFFS_CHECK_DBG("IX: pix %04x, obj id:%04x spix:%04x header not fully deleted - deleting\n", + SPIFFS_CHECK_DBG("IX: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" header not fully deleted - deleting\n", cur_pix, obj_id, p_hdr.span_ix); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_PAGE, cur_pix, obj_id); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_PAGE, cur_pix, obj_id); res = spiffs_page_delete(fs, cur_pix); SPIFFS_CHECK_RES(res); return res_c; @@ -933,9 +967,9 @@ static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id o } if (delete) { - SPIFFS_CHECK_DBG("IX: FIXUP: pix %04x, obj id:%04x spix:%04x is orphan index - deleting\n", + SPIFFS_CHECK_DBG("IX: FIXUP: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" is orphan index - deleting\n", cur_pix, obj_id, p_hdr.span_ix); - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, cur_pix, obj_id); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, cur_pix, obj_id); res = spiffs_page_delete(fs, cur_pix); SPIFFS_CHECK_RES(res); } @@ -958,16 +992,17 @@ s32_t spiffs_object_index_consistency_check(spiffs *fs) { // a reachable/unreachable object id. memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); u32_t obj_id_log_ix = 0; - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 0, 0); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 0, 0); res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_object_index_consistency_check_v, 0, &obj_id_log_ix, - 0, 0); + 0, 0); if (res == SPIFFS_VIS_END) { res = SPIFFS_OK; } if (res != SPIFFS_OK) { - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_ERROR, res, 0); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_ERROR, res, 0); } - if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0); return res; } +#endif // !SPIFFS_READ_ONLY diff --git a/third_party/spiffs/spiffs_gc.c b/third_party/spiffs/spiffs_gc.c index 5752e708..db1af4cc 100644 --- a/third_party/spiffs/spiffs_gc.c +++ b/third_party/spiffs/spiffs_gc.c @@ -1,6 +1,8 @@ #include "spiffs.h" #include "spiffs_nucleus.h" +#if !SPIFFS_READ_ONLY + // Erases a logical block and updates the erase counter. // If cache is enabled, all pages that might be cached in this block // is dropped. @@ -9,7 +11,7 @@ static s32_t spiffs_gc_erase_block( spiffs_block_ix bix) { s32_t res; - SPIFFS_GC_DBG("gc: erase block %i\n", bix); + SPIFFS_GC_DBG("gc: erase block "_SPIPRIbl"\n", bix); res = spiffs_erase_block(fs, bix); SPIFFS_CHECK_RES(res); @@ -36,7 +38,7 @@ s32_t spiffs_gc_quick( int cur_entry = 0; spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; - SPIFFS_GC_DBG("gc_quick: running\n", cur_block); + SPIFFS_GC_DBG("gc_quick: running\n"); #if SPIFFS_GC_STATS fs->stats_gc_runs++; #endif @@ -120,19 +122,19 @@ s32_t spiffs_gc_check( u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); // if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) { -// SPIFFS_GC_DBG("gc: full freeblk:%i needed:%i free:%i dele:%i\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); +// SPIFFS_GC_DBG("gc: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); // return SPIFFS_ERR_FULL; // } if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) { - SPIFFS_GC_DBG("gc_check: full freeblk:%i needed:%i free:%i dele:%i\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); + SPIFFS_GC_DBG("gc_check: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); return SPIFFS_ERR_FULL; } do { - SPIFFS_GC_DBG("\ngc_check #%i: run gc free_blocks:%i pfree:%i pallo:%i pdele:%i [%i] len:%i of %i\n", + SPIFFS_GC_DBG("\ngc_check #"_SPIPRIi": run gc free_blocks:"_SPIPRIi" pfree:"_SPIPRIi" pallo:"_SPIPRIi" pdele:"_SPIPRIi" ["_SPIPRIi"] len:"_SPIPRIi" of "_SPIPRIi"\n", tries, fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted), - len, free_pages*SPIFFS_DATA_PAGE_SIZE(fs)); + len, (u32_t)(free_pages*SPIFFS_DATA_PAGE_SIZE(fs))); spiffs_block_ix *cands; int count; @@ -150,13 +152,13 @@ s32_t spiffs_gc_check( #endif cand = cands[0]; fs->cleaning = 1; - //printf("gcing: cleaning block %i\n", cand); + //SPIFFS_GC_DBG("gcing: cleaning block "_SPIPRIi"\n", cand); res = spiffs_gc_clean(fs, cand); fs->cleaning = 0; if (res < 0) { - SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); + SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); } else { - SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); + SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); } SPIFFS_CHECK_RES(res); @@ -186,7 +188,7 @@ s32_t spiffs_gc_check( res = SPIFFS_ERR_FULL; } - SPIFFS_GC_DBG("gc_check: finished, %i dirty, blocks %i free, %i pages free, %i tries, res %i\n", + SPIFFS_GC_DBG("gc_check: finished, "_SPIPRIi" dirty, blocks "_SPIPRIi" free, "_SPIPRIi" pages free, "_SPIPRIi" tries, res "_SPIPRIi"\n", fs->stats_p_allocated + fs->stats_p_deleted, fs->free_blocks, free_pages, tries, res); @@ -224,7 +226,7 @@ s32_t spiffs_gc_erase_page_stats( } // per entry obj_lookup_page++; } // per object lookup page - SPIFFS_GC_DBG("gc_check: wipe pallo:%i pdele:%i\n", allo, dele); + SPIFFS_GC_DBG("gc_check: wipe pallo:"_SPIPRIi" pdele:"_SPIPRIi"\n", allo, dele); fs->stats_p_allocated -= allo; fs->stats_p_deleted -= dele; return res; @@ -249,10 +251,12 @@ s32_t spiffs_gc_find_candidate( memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); // divide up work area into block indices and scores - // todo alignment? spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work; s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix)); + // align cand_scores on s32_t boundary + cand_scores = (s32_t*)(((intptr_t)cand_scores + sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1)); + *block_candidates = cand_blocks; int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); @@ -290,7 +294,7 @@ s32_t spiffs_gc_find_candidate( // calculate score and insert into candidate table // stoneage sort, but probably not so many blocks - if (res == SPIFFS_OK && deleted_pages_in_block > 0) { + if (res == SPIFFS_OK /*&& deleted_pages_in_block > 0*/) { // read erase count spiffs_obj_id erase_count; res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, @@ -310,7 +314,7 @@ s32_t spiffs_gc_find_candidate( used_pages_in_block * SPIFFS_GC_HEUR_W_USED + erase_age * (fs_crammed ? 0 : SPIFFS_GC_HEUR_W_ERASE_AGE); int cand_ix = 0; - SPIFFS_GC_DBG("gc_check: bix:%i del:%i use:%i score:%i\n", cur_block, deleted_pages_in_block, used_pages_in_block, score); + SPIFFS_GC_DBG("gc_check: bix:"_SPIPRIbl" del:"_SPIPRIi" use:"_SPIPRIi" score:"_SPIPRIi"\n", cur_block, deleted_pages_in_block, used_pages_in_block, score); while (cand_ix < max_candidates) { if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) { cand_blocks[cand_ix] = cur_block; @@ -352,6 +356,7 @@ typedef struct { spiffs_obj_id cur_obj_id; spiffs_span_ix cur_objix_spix; spiffs_page_ix cur_objix_pix; + spiffs_page_ix cur_data_pix; int stored_scan_entry_index; u8_t obj_id_found; } spiffs_gc; @@ -371,15 +376,16 @@ typedef struct { // s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { s32_t res = SPIFFS_OK; - int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + const int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + // this is the global localizer being pushed and popped int cur_entry = 0; spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; - spiffs_gc gc; + spiffs_gc gc; // our stack frame/state spiffs_page_ix cur_pix = 0; spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; - SPIFFS_GC_DBG("gc_clean: cleaning block %i\n", bix); + SPIFFS_GC_DBG("gc_clean: cleaning block "_SPIPRIbl"\n", bix); memset(&gc, 0, sizeof(spiffs_gc)); gc.state = FIND_OBJ_DATA; @@ -388,12 +394,12 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { // move free cursor to next block, cannot use free pages from the block we want to clean fs->free_cursor_block_ix = (bix+1)%fs->block_count; fs->free_cursor_obj_lu_entry = 0; - SPIFFS_GC_DBG("gc_clean: move free cursor to block %i\n", fs->free_cursor_block_ix); + SPIFFS_GC_DBG("gc_clean: move free cursor to block "_SPIPRIbl"\n", fs->free_cursor_block_ix); } while (res == SPIFFS_OK && gc.state != FINISHED) { - SPIFFS_GC_DBG("gc_clean: state = %i entry:%i\n", gc.state, cur_entry); - gc.obj_id_found = 0; + SPIFFS_GC_DBG("gc_clean: state = "_SPIPRIi" entry:"_SPIPRIi"\n", gc.state, cur_entry); + gc.obj_id_found = 0; // reset (to no found data page) // scan through lookup pages int obj_lookup_page = cur_entry / entries_per_page; @@ -404,7 +410,7 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); - // check each entry + // check each object lookup entry while (scan && res == SPIFFS_OK && cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; @@ -413,21 +419,26 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { // act upon object id depending on gc state switch (gc.state) { case FIND_OBJ_DATA: + // find a data page if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) { - SPIFFS_GC_DBG("gc_clean: FIND_DATA state:%i - found obj id %04x\n", gc.state, obj_id); + // found a data page, stop scanning and handle in switch case below + SPIFFS_GC_DBG("gc_clean: FIND_DATA state:"_SPIPRIi" - found obj id "_SPIPRIid"\n", gc.state, obj_id); gc.obj_id_found = 1; gc.cur_obj_id = obj_id; + gc.cur_data_pix = cur_pix; scan = 0; } break; case MOVE_OBJ_DATA: + // evacuate found data pages for corresponding object index we have in memory, + // update memory representation if (obj_id == gc.cur_obj_id) { spiffs_page_header p_hdr; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); SPIFFS_CHECK_RES(res); - SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page %04x:%04x @ %04x\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page "_SPIPRIid":"_SPIPRIsp" @ "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) { SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n"); } else { @@ -435,7 +446,7 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { // move page res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix); - SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix %04x:%04x page %04x to %04x\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); SPIFFS_CHECK_RES(res); // move wipes obj_lu, reload it res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, @@ -443,8 +454,10 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CHECK_RES(res); } else { - // page is deleted but not deleted in lookup, scrap it - SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix %04x:%04x page %04x\n", obj_id, p_hdr.span_ix, cur_pix); + // page is deleted but not deleted in lookup, scrap it - + // might seem unnecessary as we will erase this block, but + // we might get aborted + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); res = spiffs_page_delete(fs, cur_pix); SPIFFS_CHECK_RES(res); new_data_pix = SPIFFS_OBJ_ID_FREE; @@ -453,16 +466,17 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { if (gc.cur_objix_spix == 0) { // update object index header page ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix; - SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page %04x to objix_hdr entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); } else { // update object index page ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix; - SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page %04x to objix entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); } } } break; case MOVE_OBJ_IX: + // find and evacuate object index pages if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { // found an index object id @@ -475,20 +489,24 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { // move page res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix); - SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix %04x:%04x page %04x to %04x\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, obj_id, p_hdr.span_ix, new_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&p_hdr, + SPIFFS_EV_IX_MOV, obj_id, p_hdr.span_ix, new_pix, 0); // move wipes obj_lu, reload it res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CHECK_RES(res); } else { - // page is deleted but not deleted in lookup, scrap it - SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix %04x:%04x page %04x\n", obj_id, p_hdr.span_ix, cur_pix); + // page is deleted but not deleted in lookup, scrap it - + // might seem unnecessary as we will erase this block, but + // we might get aborted + SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); res = spiffs_page_delete(fs, cur_pix); if (res == SPIFFS_OK) { - spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); } } SPIFFS_CHECK_RES(res); @@ -497,72 +515,92 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { default: scan = 0; break; - } + } // switch gc state cur_entry++; } // per entry - obj_lookup_page++; + obj_lookup_page++; // no need to check scan variable here, obj_lookup_page is set in start of loop } // per object lookup page - if (res != SPIFFS_OK) break; // state finalization and switch switch (gc.state) { case FIND_OBJ_DATA: if (gc.obj_id_found) { + // handle found data page - // find out corresponding obj ix page and load it to memory spiffs_page_header p_hdr; spiffs_page_ix objix_pix; - gc.stored_scan_entry_index = cur_entry; - cur_entry = 0; + gc.stored_scan_entry_index = cur_entry; // push cursor + cur_entry = 0; // restart scan from start gc.state = MOVE_OBJ_DATA; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); SPIFFS_CHECK_RES(res); gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix); - SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:%04x\n", gc.cur_objix_spix); + SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:"_SPIPRIsp"\n", gc.cur_objix_spix); res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // on borked systems we might get an ERR_NOT_FOUND here - + // this is handled by simply deleting the page as it is not referenced + // from anywhere + SPIFFS_GC_DBG("gc_clean: FIND_OBJ_DATA objix not found! Wipe page "_SPIPRIpg"\n", gc.cur_data_pix); + res = spiffs_page_delete(fs, gc.cur_data_pix); + SPIFFS_CHECK_RES(res); + // then we restore states and continue scanning for data pages + cur_entry = gc.stored_scan_entry_index; // pop cursor + gc.state = FIND_OBJ_DATA; + break; // done + } SPIFFS_CHECK_RES(res); - SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page %04x\n", objix_pix); + SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page "_SPIPRIpg"\n", objix_pix); res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); + // cannot allow a gc if the presumed index in fact is no index, a + // check must run or lot of data may be lost SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix); gc.cur_objix_pix = objix_pix; } else { + // no more data pages found, passed thru all block, start evacuating object indices gc.state = MOVE_OBJ_IX; cur_entry = 0; // restart entry scan index } break; case MOVE_OBJ_DATA: { - // store modified objix (hdr) page + // store modified objix (hdr) page residing in memory now that all + // data pages belonging to this object index and residing in the block + // we want to evacuate spiffs_page_ix new_objix_pix; gc.state = FIND_OBJ_DATA; - cur_entry = gc.stored_scan_entry_index; + cur_entry = gc.stored_scan_entry_index; // pop cursor if (gc.cur_objix_spix == 0) { // store object index header page - res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, &new_objix_pix); - SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, %04x:%04x\n", new_objix_pix, 0); + res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, 0, &new_objix_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, 0); SPIFFS_CHECK_RES(res); } else { // store object index page res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix); - SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, %04x:%04x\n", new_objix_pix, objix->p_hdr.span_ix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, objix->p_hdr.span_ix); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); } } break; case MOVE_OBJ_IX: + // scanned thru all block, no more object indices found - our work here is done gc.state = FINISHED; break; default: cur_entry = 0; break; - } - SPIFFS_GC_DBG("gc_clean: state-> %i\n", gc.state); + } // switch gc.state + SPIFFS_GC_DBG("gc_clean: state-> "_SPIPRIi"\n", gc.state); } // while state != FINISHED return res; } +#endif // !SPIFFS_READ_ONLY diff --git a/third_party/spiffs/spiffs_hydrogen.c b/third_party/spiffs/spiffs_hydrogen.c index 977c0039..235aaaa6 100644 --- a/third_party/spiffs/spiffs_hydrogen.c +++ b/third_party/spiffs/spiffs_hydrogen.c @@ -8,7 +8,9 @@ #include "spiffs.h" #include "spiffs_nucleus.h" +#if SPIFFS_CACHE == 1 static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh); +#endif #if SPIFFS_BUFFER_HELP u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs) { @@ -26,6 +28,10 @@ u8_t SPIFFS_mounted(spiffs *fs) { } s32_t SPIFFS_format(spiffs *fs) { +#if SPIFFS_READ_ONLY + (void)fs; + return SPIFFS_ERR_RO_NOT_IMPL; +#else SPIFFS_API_CHECK_CFG(fs); if (SPIFFS_CHECK_MOUNT(fs)) { fs->err_code = SPIFFS_ERR_MOUNTED; @@ -49,25 +55,48 @@ s32_t SPIFFS_format(spiffs *fs) { SPIFFS_UNLOCK(fs); return 0; +#endif // SPIFFS_READ_ONLY } +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +s32_t SPIFFS_probe_fs(spiffs_config *config) { + SPIFFS_API_DBG("%s\n", __func__); + s32_t res = spiffs_probe(config); + return res; +} + +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, u8_t *fd_space, u32_t fd_space_size, void *cache, u32_t cache_size, spiffs_check_callback check_cb_f) { + SPIFFS_API_DBG("%s " + " sz:"_SPIPRIi " logpgsz:"_SPIPRIi " logblksz:"_SPIPRIi " perasz:"_SPIPRIi + " addr:"_SPIPRIad + " fdsz:"_SPIPRIi " cachesz:"_SPIPRIi + "\n", + __func__, + SPIFFS_CFG_PHYS_SZ(fs), + SPIFFS_CFG_LOG_PAGE_SZ(fs), + SPIFFS_CFG_LOG_BLOCK_SZ(fs), + SPIFFS_CFG_PHYS_ERASE_SZ(fs), + SPIFFS_CFG_PHYS_ADDR(fs), + fd_space_size, cache_size); + void *user_data; SPIFFS_LOCK(fs); + user_data = fs->user_data; memset(fs, 0, sizeof(spiffs)); - memcpy(&fs->cfg, config, sizeof(spiffs_config)); + _SPIFFS_MEMCPY(&fs->cfg, config, sizeof(spiffs_config)); + fs->user_data = user_data; fs->block_count = SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_BLOCK_SZ(fs); fs->work = &work[0]; fs->lu_work = &work[SPIFFS_CFG_LOG_PAGE_SZ(fs)]; memset(fd_space, 0, fd_space_size); - // align fd_space pointer to pointer size byte boundary, below is safe + // align fd_space pointer to pointer size byte boundary u8_t ptr_size = sizeof(void*); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpointer-to-int-cast" - u8_t addr_lsb = ((u8_t)fd_space) & (ptr_size-1); -#pragma GCC diagnostic pop + u8_t addr_lsb = ((u8_t)(intptr_t)fd_space) & (ptr_size-1); if (addr_lsb) { fd_space += (ptr_size-addr_lsb); fd_space_size -= (ptr_size-addr_lsb); @@ -75,11 +104,8 @@ s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, fs->fd_space = fd_space; fs->fd_count = (fd_space_size/sizeof(spiffs_fd)); - // align cache pointer to 4 byte boundary, below is safe -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpointer-to-int-cast" - addr_lsb = ((u8_t)cache) & (ptr_size-1); -#pragma GCC diagnostic pop + // align cache pointer to 4 byte boundary + addr_lsb = ((u8_t)(intptr_t)cache) & (ptr_size-1); if (addr_lsb) { u8_t *cache_8 = (u8_t *)cache; cache_8 += (ptr_size-addr_lsb); @@ -89,9 +115,10 @@ s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, if (cache_size & (ptr_size-1)) { cache_size -= (cache_size & (ptr_size-1)); } + #if SPIFFS_CACHE fs->cache = cache; - fs->cache_size = (cache_size > (config->log_page_size*32)) ? config->log_page_size*32 : cache_size; + fs->cache_size = (cache_size > (SPIFFS_CFG_LOG_PAGE_SZ(fs)*32)) ? SPIFFS_CFG_LOG_PAGE_SZ(fs)*32 : cache_size; spiffs_cache_init(fs); #endif @@ -107,14 +134,14 @@ s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, res = spiffs_obj_lu_scan(fs); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - SPIFFS_DBG("page index byte len: %i\n", SPIFFS_CFG_LOG_PAGE_SZ(fs)); - SPIFFS_DBG("object lookup pages: %i\n", SPIFFS_OBJ_LOOKUP_PAGES(fs)); - SPIFFS_DBG("page pages per block: %i\n", SPIFFS_PAGES_PER_BLOCK(fs)); - SPIFFS_DBG("page header length: %i\n", sizeof(spiffs_page_header)); - SPIFFS_DBG("object header index entries: %i\n", SPIFFS_OBJ_HDR_IX_LEN(fs)); - SPIFFS_DBG("object index entries: %i\n", SPIFFS_OBJ_IX_LEN(fs)); - SPIFFS_DBG("available file descriptors: %i\n", fs->fd_count); - SPIFFS_DBG("free blocks: %i\n", fs->free_blocks); + SPIFFS_DBG("page index byte len: "_SPIPRIi"\n", (u32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)); + SPIFFS_DBG("object lookup pages: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_LOOKUP_PAGES(fs)); + SPIFFS_DBG("page pages per block: "_SPIPRIi"\n", (u32_t)SPIFFS_PAGES_PER_BLOCK(fs)); + SPIFFS_DBG("page header length: "_SPIPRIi"\n", (u32_t)sizeof(spiffs_page_header)); + SPIFFS_DBG("object header index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_HDR_IX_LEN(fs)); + SPIFFS_DBG("object index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_IX_LEN(fs)); + SPIFFS_DBG("available file descriptors: "_SPIPRIi"\n", (u32_t)fs->fd_count); + SPIFFS_DBG("free blocks: "_SPIPRIi"\n", (u32_t)fs->free_blocks); fs->check_cb_f = check_cb_f; @@ -126,6 +153,7 @@ s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, } void SPIFFS_unmount(spiffs *fs) { + SPIFFS_API_DBG("%s\n", __func__); if (!SPIFFS_CHECK_CFG(fs) || !SPIFFS_CHECK_MOUNT(fs)) return; SPIFFS_LOCK(fs); u32_t i; @@ -149,46 +177,74 @@ s32_t SPIFFS_errno(spiffs *fs) { } void SPIFFS_clearerr(spiffs *fs) { + SPIFFS_API_DBG("%s\n", __func__); fs->err_code = SPIFFS_OK; } -s32_t SPIFFS_creat(spiffs *fs, char *path, spiffs_mode mode) { +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode) { + SPIFFS_API_DBG("%s '%s'\n", __func__, path); +#if SPIFFS_READ_ONLY + (void)fs; (void)path; (void)mode; + return SPIFFS_ERR_RO_NOT_IMPL; +#else (void)mode; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } SPIFFS_LOCK(fs); spiffs_obj_id obj_id; s32_t res; - res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, (u8_t *)path); + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, (const u8_t*)path); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - res = spiffs_object_create(fs, obj_id, (u8_t *)path, SPIFFS_TYPE_FILE, 0); + res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, 0); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); SPIFFS_UNLOCK(fs); return 0; +#endif // SPIFFS_READ_ONLY } -spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode mode) { +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_DBG("%s '%s' "_SPIPRIfl "\n", __func__, path, flags); (void)mode; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } SPIFFS_LOCK(fs); spiffs_fd *fd; spiffs_page_ix pix; - s32_t res = spiffs_fd_find_new(fs, &fd); +#if SPIFFS_READ_ONLY + // not valid flags in read only mode + flags &= ~(SPIFFS_WRONLY | SPIFFS_CREAT | SPIFFS_TRUNC); +#endif // SPIFFS_READ_ONLY + + s32_t res = spiffs_fd_find_new(fs, &fd, path); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - res = spiffs_object_find_object_index_header_by_name(fs, (u8_t*)path, &pix); - if ((flags & SPIFFS_CREAT) == 0) { + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + if ((flags & SPIFFS_O_CREAT) == 0) { if (res < SPIFFS_OK) { spiffs_fd_return(fs, fd->file_nbr); } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); } - if ((flags & SPIFFS_CREAT) && res == SPIFFS_ERR_NOT_FOUND) { + if (res == SPIFFS_OK && + (flags & (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) == (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) { + // creat and excl and file exists - fail + res = SPIFFS_ERR_FILE_EXISTS; + spiffs_fd_return(fs, fd->file_nbr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if ((flags & SPIFFS_O_CREAT) && res == SPIFFS_ERR_NOT_FOUND) { +#if !SPIFFS_READ_ONLY spiffs_obj_id obj_id; // no need to enter conflicting name here, already looked for it above res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, 0); @@ -196,12 +252,13 @@ spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode spiffs_fd_return(fs, fd->file_nbr); } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - res = spiffs_object_create(fs, obj_id, (u8_t*)path, SPIFFS_TYPE_FILE, &pix); + res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, &pix); if (res < SPIFFS_OK) { spiffs_fd_return(fs, fd->file_nbr); } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - flags &= ~SPIFFS_TRUNC; + flags &= ~SPIFFS_O_TRUNC; +#endif // !SPIFFS_READ_ONLY } else { if (res < SPIFFS_OK) { spiffs_fd_return(fs, fd->file_nbr); @@ -213,29 +270,32 @@ spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode spiffs_fd_return(fs, fd->file_nbr); } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - if (flags & SPIFFS_TRUNC) { +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { res = spiffs_object_truncate(fd, 0, 0); if (res < SPIFFS_OK) { spiffs_fd_return(fs, fd->file_nbr); } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); } +#endif // !SPIFFS_READ_ONLY fd->fdoffset = 0; SPIFFS_UNLOCK(fs); - return fd->file_nbr; + return SPIFFS_FH_OFFS(fs, fd->file_nbr); } spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_DBG("%s '%s':"_SPIPRIid " "_SPIPRIfl "\n", __func__, e->name, e->obj_id, flags); SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); spiffs_fd *fd; - s32_t res = spiffs_fd_find_new(fs, &fd); + s32_t res = spiffs_fd_find_new(fs, &fd, 0); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); res = spiffs_object_open_by_page(fs, e->pix, fd, flags, mode); @@ -243,22 +303,71 @@ spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_fl spiffs_fd_return(fs, fd->file_nbr); } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - if (flags & SPIFFS_TRUNC) { +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { res = spiffs_object_truncate(fd, 0, 0); if (res < SPIFFS_OK) { spiffs_fd_return(fs, fd->file_nbr); } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); } +#endif // !SPIFFS_READ_ONLY fd->fdoffset = 0; SPIFFS_UNLOCK(fs); - return fd->file_nbr; + return SPIFFS_FH_OFFS(fs, fd->file_nbr); } -s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { +spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_DBG("%s "_SPIPRIpg " "_SPIPRIfl "\n", __func__, page_ix, flags); + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + + s32_t res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (SPIFFS_IS_LOOKUP_PAGE(fs, page_ix)) { + res = SPIFFS_ERR_NOT_A_FILE; + spiffs_fd_return(fs, fd->file_nbr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + res = spiffs_object_open_by_page(fs, page_ix, fd, flags, mode); + if (res == SPIFFS_ERR_IS_FREE || + res == SPIFFS_ERR_DELETED || + res == SPIFFS_ERR_NOT_FINALIZED || + res == SPIFFS_ERR_NOT_INDEX || + res == SPIFFS_ERR_INDEX_SPAN_MISMATCH) { + res = SPIFFS_ERR_NOT_A_FILE; + } + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +static s32_t spiffs_hydro_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); @@ -266,14 +375,21 @@ s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { spiffs_fd *fd; s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); res = spiffs_fd_get(fs, fh, &fd); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - if ((fd->flags & SPIFFS_RDONLY) == 0) { + if ((fd->flags & SPIFFS_O_RDONLY) == 0) { res = SPIFFS_ERR_NOT_READABLE; SPIFFS_API_CHECK_RES_UNLOCK(fs, res); } + if (fd->size == SPIFFS_UNDEFINED_LEN && len > 0) { + // special case for zero sized files + res = SPIFFS_ERR_END_OF_OBJECT; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + #if SPIFFS_CACHE_WR spiffs_fflush_cache(fs, fh); #endif @@ -305,6 +421,17 @@ s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { return len; } +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + SPIFFS_API_DBG("%s "_SPIPRIfd " "_SPIPRIi "\n", __func__, fh, len); + s32_t res = spiffs_hydro_read(fs, fh, buf, len); + if (res == SPIFFS_ERR_END_OF_OBJECT) { + res = 0; + } + return res; +} + + +#if !SPIFFS_READ_ONLY static s32_t spiffs_hydro_write(spiffs *fs, spiffs_fd *fd, void *buf, u32_t offset, s32_t len) { (void)fs; s32_t res = SPIFFS_OK; @@ -326,8 +453,14 @@ static s32_t spiffs_hydro_write(spiffs *fs, spiffs_fd *fd, void *buf, u32_t offs return len; } +#endif // !SPIFFS_READ_ONLY s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + SPIFFS_API_DBG("%s "_SPIPRIfd " "_SPIPRIi "\n", __func__, fh, len); +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; (void)buf; (void)len; + return SPIFFS_ERR_RO_NOT_IMPL; +#else SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); @@ -336,14 +469,18 @@ s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { s32_t res; u32_t offset; + fh = SPIFFS_FH_UNOFFS(fs, fh); res = spiffs_fd_get(fs, fh, &fd); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - if ((fd->flags & SPIFFS_WRONLY) == 0) { + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { res = SPIFFS_ERR_NOT_WRITABLE; SPIFFS_API_CHECK_RES_UNLOCK(fs, res); } + if ((fd->flags & SPIFFS_O_APPEND)) { + fd->fdoffset = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size; + } offset = fd->fdoffset; #if SPIFFS_CACHE_WR @@ -352,7 +489,7 @@ s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); } #endif - if (fd->flags & SPIFFS_APPEND) { + if (fd->flags & SPIFFS_O_APPEND) { if (fd->size == SPIFFS_UNDEFINED_LEN) { offset = 0; } else { @@ -366,7 +503,7 @@ s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { } #if SPIFFS_CACHE_WR - if ((fd->flags & SPIFFS_DIRECT) == 0) { + if ((fd->flags & SPIFFS_O_DIRECT) == 0) { if (len < (s32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)) { // small write, try to cache it u8_t alloc_cpage = 1; @@ -377,13 +514,13 @@ s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { offset + len > fd->cache_page->offset + SPIFFS_CFG_LOG_PAGE_SZ(fs)) // writing beyond cache page { // boundary violation, write back cache first and allocate new - SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page %i for fd %i:%04x, boundary viol, offs:%i size:%i\n", + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIi" for fd "_SPIPRIfd":"_SPIPRIid", boundary viol, offs:"_SPIPRIi" size:"_SPIPRIi"\n", fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); res = spiffs_hydro_write(fs, fd, spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), fd->cache_page->offset, fd->cache_page->size); spiffs_cache_fd_release(fs, fd->cache_page); - SPIFFS_API_CHECK_RES(fs, res); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); } else { // writing within cache alloc_cpage = 0; @@ -395,26 +532,37 @@ s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { if (fd->cache_page) { fd->cache_page->offset = offset; fd->cache_page->size = 0; - SPIFFS_CACHE_DBG("CACHE_WR_ALLO: allocating cache page %i for fd %i:%04x\n", + SPIFFS_CACHE_DBG("CACHE_WR_ALLO: allocating cache page "_SPIPRIi" for fd "_SPIPRIfd":"_SPIPRIid"\n", fd->cache_page->ix, fd->file_nbr, fd->obj_id); } } if (fd->cache_page) { u32_t offset_in_cpage = offset - fd->cache_page->offset; - SPIFFS_CACHE_DBG("CACHE_WR_WRITE: storing to cache page %i for fd %i:%04x, offs %i:%i len %i\n", + SPIFFS_CACHE_DBG("CACHE_WR_WRITE: storing to cache page "_SPIPRIi" for fd "_SPIPRIfd":"_SPIPRIid", offs "_SPIPRIi":"_SPIPRIi" len "_SPIPRIi"\n", fd->cache_page->ix, fd->file_nbr, fd->obj_id, offset, offset_in_cpage, len); spiffs_cache *cache = spiffs_get_cache(fs); u8_t *cpage_data = spiffs_get_cache_page(fs, cache, fd->cache_page->ix); - memcpy(&cpage_data[offset_in_cpage], buf, len); +#ifdef _SPIFFS_TEST + { + intptr_t __a1 = (u8_t*)&cpage_data[offset_in_cpage]-(u8_t*)cache; + intptr_t __a2 = (u8_t*)&cpage_data[offset_in_cpage]+len-(u8_t*)cache; + intptr_t __b = sizeof(spiffs_cache) + cache->cpage_count * (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs)); + if (__a1 > __b || __a2 > __b) { + printf("FATAL OOB: CACHE_WR: memcpy to cache buffer ixs:%4ld..%4ld of %4ld\n", __a1, __a2, __b); + ERREXIT(); + } + } +#endif + _SPIFFS_MEMCPY(&cpage_data[offset_in_cpage], buf, len); fd->cache_page->size = MAX(fd->cache_page->size, offset_in_cpage + len); fd->fdoffset += len; SPIFFS_UNLOCK(fs); return len; } else { res = spiffs_hydro_write(fs, fd, buf, offset, len); - SPIFFS_API_CHECK_RES(fs, res); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); fd->fdoffset += len; SPIFFS_UNLOCK(fs); return res; @@ -423,58 +571,65 @@ s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { // big write, no need to cache it - but first check if there is a cached write already if (fd->cache_page) { // write back cache first - SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page %i for fd %i:%04x, big write, offs:%i size:%i\n", + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIi" for fd "_SPIPRIfd":"_SPIPRIid", big write, offs:"_SPIPRIi" size:"_SPIPRIi"\n", fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); res = spiffs_hydro_write(fs, fd, spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), fd->cache_page->offset, fd->cache_page->size); spiffs_cache_fd_release(fs, fd->cache_page); - SPIFFS_API_CHECK_RES(fs, res); - res = spiffs_hydro_write(fs, fd, buf, offset, len); - SPIFFS_API_CHECK_RES(fs, res); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + // data written below } } } #endif res = spiffs_hydro_write(fs, fd, buf, offset, len); - SPIFFS_API_CHECK_RES(fs, res); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); fd->fdoffset += len; SPIFFS_UNLOCK(fs); return res; +#endif // SPIFFS_READ_ONLY } s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) { + SPIFFS_API_DBG("%s "_SPIPRIfd " "_SPIPRIi " %s\n", __func__, fh, offs, (const char* []){"SET","CUR","END","???"}[MIN(whence,3)]); SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); spiffs_fd *fd; s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); res = spiffs_fd_get(fs, fh, &fd); - SPIFFS_API_CHECK_RES(fs, res); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); #if SPIFFS_CACHE_WR spiffs_fflush_cache(fs, fh); #endif + s32_t file_size = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size; + switch (whence) { case SPIFFS_SEEK_CUR: offs = fd->fdoffset+offs; break; case SPIFFS_SEEK_END: - offs = (fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size) + offs; + offs = file_size + offs; break; } - - if (offs > (s32_t)fd->size) { + if (offs < 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_SEEK_BOUNDS); + } + if (offs > file_size) { + fd->fdoffset = file_size; res = SPIFFS_ERR_END_OF_OBJECT; } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - spiffs_span_ix data_spix = offs / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_span_ix data_spix = (offs > 0 ? (offs-1) : 0) / SPIFFS_DATA_PAGE_SIZE(fs); spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); if (fd->cursor_objix_spix != objix_spix) { spiffs_page_ix pix; @@ -491,19 +646,27 @@ s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) { return offs; } -s32_t SPIFFS_remove(spiffs *fs, char *path) { +s32_t SPIFFS_remove(spiffs *fs, const char *path) { + SPIFFS_API_DBG("%s '%s'\n", __func__, path); +#if SPIFFS_READ_ONLY + (void)fs; (void)path; + return SPIFFS_ERR_RO_NOT_IMPL; +#else SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } SPIFFS_LOCK(fs); spiffs_fd *fd; spiffs_page_ix pix; s32_t res; - res = spiffs_fd_find_new(fs, &fd); + res = spiffs_fd_find_new(fs, &fd, 0); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - res = spiffs_object_find_object_index_header_by_name(fs, (u8_t *)path, &pix); + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); if (res != SPIFFS_OK) { spiffs_fd_return(fs, fd->file_nbr); } @@ -523,19 +686,26 @@ s32_t SPIFFS_remove(spiffs *fs, char *path) { SPIFFS_UNLOCK(fs); return 0; +#endif // SPIFFS_READ_ONLY } s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh) { + SPIFFS_API_DBG("%s "_SPIPRIfd "\n", __func__, fh); +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; + return SPIFFS_ERR_RO_NOT_IMPL; +#else SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); spiffs_fd *fd; s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); res = spiffs_fd_get(fs, fh, &fd); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - if ((fd->flags & SPIFFS_WRONLY) == 0) { + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { res = SPIFFS_ERR_NOT_WRITABLE; SPIFFS_API_CHECK_RES_UNLOCK(fs, res); } @@ -551,9 +721,11 @@ s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh) { SPIFFS_UNLOCK(fs); return 0; +#endif // SPIFFS_READ_ONLY } static s32_t spiffs_stat_pix(spiffs *fs, spiffs_page_ix pix, spiffs_file fh, spiffs_stat *s) { + (void)fh; spiffs_page_object_ix_header objix_hdr; spiffs_obj_id obj_id; s32_t res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fh, @@ -566,23 +738,31 @@ static s32_t spiffs_stat_pix(spiffs *fs, spiffs_page_ix pix, spiffs_file fh, spi obj_id_addr, sizeof(spiffs_obj_id), (u8_t *)&obj_id); SPIFFS_API_CHECK_RES(fs, res); - s->obj_id = obj_id; + s->obj_id = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; s->type = objix_hdr.type; s->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + s->pix = pix; strncpy((char *)s->name, (char *)objix_hdr.name, SPIFFS_OBJ_NAME_LEN); +#if SPIFFS_OBJ_META_LEN + _SPIFFS_MEMCPY(s->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN); +#endif return res; } -s32_t SPIFFS_stat(spiffs *fs, char *path, spiffs_stat *s) { +s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s) { + SPIFFS_API_DBG("%s '%s'\n", __func__, path); SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } SPIFFS_LOCK(fs); s32_t res; spiffs_page_ix pix; - res = spiffs_object_find_object_index_header_by_name(fs, (u8_t*)path, &pix); + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); res = spiffs_stat_pix(fs, pix, 0, s); @@ -593,6 +773,7 @@ s32_t SPIFFS_stat(spiffs *fs, char *path, spiffs_stat *s) { } s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) { + SPIFFS_API_DBG("%s "_SPIPRIfd "\n", __func__, fh); SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); @@ -600,6 +781,7 @@ s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) { spiffs_fd *fd; s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); res = spiffs_fd_get(fs, fh, &fd); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); @@ -616,21 +798,24 @@ s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) { // Checks if there are any cached writes for the object id associated with // given filehandle. If so, these writes are flushed. +#if SPIFFS_CACHE == 1 static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh) { + (void)fs; + (void)fh; s32_t res = SPIFFS_OK; -#if SPIFFS_CACHE_WR +#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR spiffs_fd *fd; res = spiffs_fd_get(fs, fh, &fd); SPIFFS_API_CHECK_RES(fs, res); - if ((fd->flags & SPIFFS_DIRECT) == 0) { + if ((fd->flags & SPIFFS_O_DIRECT) == 0) { if (fd->cache_page == 0) { // see if object id is associated with cache already fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); } if (fd->cache_page) { - SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page %i for fd %i:%04x, flush, offs:%i size:%i\n", + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIi" for fd "_SPIPRIfd":"_SPIPRIid", flush, offs:"_SPIPRIi" size:"_SPIPRIi"\n", fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); res = spiffs_hydro_write(fs, fd, spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), @@ -645,13 +830,17 @@ static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh) { return res; } +#endif s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh) { + SPIFFS_API_DBG("%s "_SPIPRIfd "\n", __func__, fh); + (void)fh; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); s32_t res = SPIFFS_OK; -#if SPIFFS_CACHE_WR +#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR SPIFFS_LOCK(fs); + fh = SPIFFS_FH_UNOFFS(fs, fh); res = spiffs_fflush_cache(fs, fh); SPIFFS_API_CHECK_RES_UNLOCK(fs,res); SPIFFS_UNLOCK(fs); @@ -660,38 +849,48 @@ s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh) { return res; } -void SPIFFS_close(spiffs *fs, spiffs_file fh) { - if (!SPIFFS_CHECK_CFG((fs))) { - (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; - return; - } - - if (!SPIFFS_CHECK_MOUNT(fs)) { - fs->err_code = SPIFFS_ERR_NOT_MOUNTED; - return; - } - SPIFFS_LOCK(fs); - -#if SPIFFS_CACHE - spiffs_fflush_cache(fs, fh); -#endif - spiffs_fd_return(fs, fh); - - SPIFFS_UNLOCK(fs); -} - -s32_t SPIFFS_rename(spiffs *fs, char *old, char *new) { +s32_t SPIFFS_close(spiffs *fs, spiffs_file fh) { + SPIFFS_API_DBG("%s "_SPIPRIfd "\n", __func__, fh); SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); + + s32_t res = SPIFFS_OK; + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); +#if SPIFFS_CACHE + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + res = spiffs_fd_return(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +} + +s32_t SPIFFS_rename(spiffs *fs, const char *old_path, const char *new_path) { + SPIFFS_API_DBG("%s %s %s\n", __func__, old_path, new_path); +#if SPIFFS_READ_ONLY + (void)fs; (void)old_path; (void)new_path; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(new_path) > SPIFFS_OBJ_NAME_LEN - 1 || + strlen(old_path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } SPIFFS_LOCK(fs); spiffs_page_ix pix_old, pix_dummy; spiffs_fd *fd; - s32_t res = spiffs_object_find_object_index_header_by_name(fs, (u8_t*)old, &pix_old); + s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)old_path, &pix_old); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - res = spiffs_object_find_object_index_header_by_name(fs, (u8_t*)new, &pix_dummy); + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)new_path, &pix_dummy); if (res == SPIFFS_ERR_NOT_FOUND) { res = SPIFFS_OK; } else if (res == SPIFFS_OK) { @@ -699,7 +898,7 @@ s32_t SPIFFS_rename(spiffs *fs, char *old, char *new) { } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - res = spiffs_fd_find_new(fs, &fd); + res = spiffs_fd_find_new(fs, &fd, 0); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); res = spiffs_object_open_by_page(fs, pix_old, fd, 0, 0); @@ -708,7 +907,50 @@ s32_t SPIFFS_rename(spiffs *fs, char *old, char *new) { } SPIFFS_API_CHECK_RES_UNLOCK(fs, res); - res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, (u8_t*)new, + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, (const u8_t*)new_path, + 0, 0, &pix_dummy); +#if SPIFFS_TEMPORAL_FD_CACHE + if (res == SPIFFS_OK) { + spiffs_fd_temporal_cache_rehash(fs, old_path, new_path); + } +#endif + + spiffs_fd_return(fs, fd->file_nbr); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +#if SPIFFS_OBJ_META_LEN +s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta) { +#if SPIFFS_READ_ONLY + (void)fs; (void)name; (void)meta; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_page_ix pix, pix_dummy; + spiffs_fd *fd; + + s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)name, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix, fd, 0, 0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta, 0, &pix_dummy); spiffs_fd_return(fs, fd->file_nbr); @@ -718,9 +960,45 @@ s32_t SPIFFS_rename(spiffs *fs, char *old, char *new) { SPIFFS_UNLOCK(fs); return res; +#endif // SPIFFS_READ_ONLY } -spiffs_DIR *SPIFFS_opendir(spiffs *fs, char *name, spiffs_DIR *d) { +s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; (void)meta; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + s32_t res; + spiffs_fd *fd; + spiffs_page_ix pix_dummy; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta, + 0, &pix_dummy); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} +#endif // SPIFFS_OBJ_META_LEN + +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d) { + SPIFFS_API_DBG("%s\n", __func__); (void)name; if (!SPIFFS_CHECK_CFG((fs))) { @@ -744,9 +1022,9 @@ static s32_t spiffs_read_dir_v( spiffs_obj_id obj_id, spiffs_block_ix bix, int ix_entry, - u32_t user_data, - void *user_p) { - (void)user_data; + const void *user_const_p, + void *user_var_p) { + (void)user_const_p; s32_t res; spiffs_page_object_ix_header objix_hdr; if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED || @@ -760,26 +1038,29 @@ static s32_t spiffs_read_dir_v( if (res != SPIFFS_OK) return res; if ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && objix_hdr.p_hdr.span_ix == 0 && - (objix_hdr.p_hdr.flags& (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { - struct spiffs_dirent *e = (struct spiffs_dirent *)user_p; + struct spiffs_dirent *e = (struct spiffs_dirent*)user_var_p; e->obj_id = obj_id; strcpy((char *)e->name, (char *)objix_hdr.name); e->type = objix_hdr.type; e->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; e->pix = pix; +#if SPIFFS_OBJ_META_LEN + _SPIFFS_MEMCPY(e->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN); +#endif return SPIFFS_OK; } - return SPIFFS_VIS_COUNTINUE; } struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e) { + SPIFFS_API_DBG("%s\n", __func__); if (!SPIFFS_CHECK_MOUNT(d->fs)) { d->fs->err_code = SPIFFS_ERR_NOT_MOUNTED; return 0; } - SPIFFS_LOCK(fs); + SPIFFS_LOCK(d->fs); spiffs_block_ix bix; int entry; @@ -799,21 +1080,28 @@ struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e) { if (res == SPIFFS_OK) { d->block = bix; d->entry = entry + 1; + e->obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; ret = e; } else { d->fs->err_code = res; } - SPIFFS_UNLOCK(fs); + SPIFFS_UNLOCK(d->fs); return ret; } s32_t SPIFFS_closedir(spiffs_DIR *d) { + SPIFFS_API_DBG("%s\n", __func__); SPIFFS_API_CHECK_CFG(d->fs); SPIFFS_API_CHECK_MOUNT(d->fs); return 0; } s32_t SPIFFS_check(spiffs *fs) { + SPIFFS_API_DBG("%s\n", __func__); +#if SPIFFS_READ_ONLY + (void)fs; + return SPIFFS_ERR_RO_NOT_IMPL; +#else s32_t res; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); @@ -829,9 +1117,11 @@ s32_t SPIFFS_check(spiffs *fs) { SPIFFS_UNLOCK(fs); return res; +#endif // SPIFFS_READ_ONLY } s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used) { + SPIFFS_API_DBG("%s\n", __func__); s32_t res = SPIFFS_OK; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); @@ -856,6 +1146,11 @@ s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used) { } s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages) { + SPIFFS_API_DBG("%s "_SPIPRIi "\n", __func__, max_free_pages); +#if SPIFFS_READ_ONLY + (void)fs; (void)max_free_pages; + return SPIFFS_ERR_RO_NOT_IMPL; +#else s32_t res; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); @@ -866,10 +1161,16 @@ s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages) { SPIFFS_API_CHECK_RES_UNLOCK(fs, res); SPIFFS_UNLOCK(fs); return 0; +#endif // SPIFFS_READ_ONLY } s32_t SPIFFS_gc(spiffs *fs, u32_t size) { + SPIFFS_API_DBG("%s "_SPIPRIi "\n", __func__, size); +#if SPIFFS_READ_ONLY + (void)fs; (void)size; + return SPIFFS_ERR_RO_NOT_IMPL; +#else s32_t res; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); @@ -880,8 +1181,199 @@ s32_t SPIFFS_gc(spiffs *fs, u32_t size) { SPIFFS_API_CHECK_RES_UNLOCK(fs, res); SPIFFS_UNLOCK(fs); return 0; +#endif // SPIFFS_READ_ONLY } +s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh) { + SPIFFS_API_DBG("%s "_SPIPRIfd "\n", __func__, fh); + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + res = (fd->fdoffset >= (fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size)); + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh) { + SPIFFS_API_DBG("%s "_SPIPRIfd "\n", __func__, fh); + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + res = fd->fdoffset; + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func) { + SPIFFS_API_DBG("%s\n", __func__); + SPIFFS_LOCK(fs); + fs->file_cb_f = cb_func; + SPIFFS_UNLOCK(fs); + return 0; +} + +#if SPIFFS_IX_MAP + +s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map, + u32_t offset, u32_t len, spiffs_page_ix *map_buf) { + SPIFFS_API_DBG("%s "_SPIPRIfd " "_SPIPRIi " "_SPIPRIi "\n", __func__, fh, offset, len); + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_MAPPED); + } + + map->map_buf = map_buf; + map->offset = offset; + // nb: spix range includes last + map->start_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + map->end_spix = (offset + len) / SPIFFS_DATA_PAGE_SIZE(fs); + memset(map_buf, 0, sizeof(spiffs_page_ix) * (map->end_spix - map->start_spix + 1)); + fd->ix_map = map; + + // scan for pixes + res = spiffs_populate_ix_map(fs, fd, 0, map->end_spix - map->start_spix + 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh) { + SPIFFS_API_DBG("%s "_SPIPRIfd "\n", __func__, fh); + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map == 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED); + } + + fd->ix_map = 0; + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offset) { + SPIFFS_API_DBG("%s "_SPIPRIfd " "_SPIPRIi "\n", __func__, fh, offset); + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map == 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED); + } + + spiffs_ix_map *map = fd->ix_map; + + s32_t spix_diff = offset / SPIFFS_DATA_PAGE_SIZE(fs) - map->start_spix; + map->offset = offset; + + // move existing pixes if within map offs + if (spix_diff != 0) { + // move vector + int i; + const s32_t vec_len = map->end_spix - map->start_spix + 1; // spix range includes last + map->start_spix += spix_diff; + map->end_spix += spix_diff; + if (spix_diff >= vec_len) { + // moving beyond range + memset(&map->map_buf, 0, vec_len * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, 0, vec_len-1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else if (spix_diff > 0) { + // diff positive + for (i = 0; i < vec_len - spix_diff; i++) { + map->map_buf[i] = map->map_buf[i + spix_diff]; + } + // memset is non-inclusive + memset(&map->map_buf[vec_len - spix_diff], 0, spix_diff * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, vec_len - spix_diff, vec_len-1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else { + // diff negative + for (i = vec_len - 1; i >= -spix_diff; i--) { + map->map_buf[i] = map->map_buf[i + spix_diff]; + } + // memset is non-inclusive + memset(&map->map_buf[0], 0, -spix_diff * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, 0, -spix_diff - 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + } + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes) { + SPIFFS_API_CHECK_CFG(fs); + // always add one extra page, the offset might change to the middle of a page + return (bytes + SPIFFS_DATA_PAGE_SIZE(fs) ) / SPIFFS_DATA_PAGE_SIZE(fs); +} + +s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries) { + SPIFFS_API_CHECK_CFG(fs); + return map_page_ix_entries * SPIFFS_DATA_PAGE_SIZE(fs); +} + +#endif // SPIFFS_IX_MAP #if SPIFFS_TEST_VISUALISATION s32_t SPIFFS_vis(spiffs *fs) { @@ -908,7 +1400,7 @@ s32_t SPIFFS_vis(spiffs *fs) { cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; if (cur_entry == 0) { - spiffs_printf("%4i ", bix); + spiffs_printf(_SPIPRIbl" ", bix); } else if ((cur_entry & 0x3f) == 0) { spiffs_printf(" "); } @@ -936,7 +1428,7 @@ s32_t SPIFFS_vis(spiffs *fs) { SPIFFS_CHECK_RES(res); if (erase_count != (spiffs_obj_id)-1) { - spiffs_printf("\tera_cnt: %i\n", erase_count); + spiffs_printf("\tera_cnt: "_SPIPRIi"\n", erase_count); } else { spiffs_printf("\tera_cnt: N/A\n"); } @@ -944,17 +1436,16 @@ s32_t SPIFFS_vis(spiffs *fs) { bix++; } // per block - spiffs_printf("era_cnt_max: %i\n", fs->max_erase_count); - spiffs_printf("last_errno: %i\n", fs->err_code); - spiffs_printf("blocks: %i\n", fs->block_count); - spiffs_printf("free_blocks: %i\n", fs->free_blocks); - spiffs_printf("page_alloc: %i\n", fs->stats_p_allocated); - spiffs_printf("page_delet: %i\n", fs->stats_p_deleted); + spiffs_printf("era_cnt_max: "_SPIPRIi"\n", fs->max_erase_count); + spiffs_printf("last_errno: "_SPIPRIi"\n", fs->err_code); + spiffs_printf("blocks: "_SPIPRIi"\n", fs->block_count); + spiffs_printf("free_blocks: "_SPIPRIi"\n", fs->free_blocks); + spiffs_printf("page_alloc: "_SPIPRIi"\n", fs->stats_p_allocated); + spiffs_printf("page_delet: "_SPIPRIi"\n", fs->stats_p_deleted); + SPIFFS_UNLOCK(fs); u32_t total, used; SPIFFS_info(fs, &total, &used); - spiffs_printf("used: %i of %i\n", used, total); - - SPIFFS_UNLOCK(fs); + spiffs_printf("used: "_SPIPRIi" of "_SPIPRIi"\n", used, total); return res; } #endif diff --git a/third_party/spiffs/spiffs_nucleus.c b/third_party/spiffs/spiffs_nucleus.c index bfd028fc..f811d932 100644 --- a/third_party/spiffs/spiffs_nucleus.c +++ b/third_party/spiffs/spiffs_nucleus.c @@ -29,6 +29,7 @@ static s32_t spiffs_page_data_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pi return res; } +#if !SPIFFS_READ_ONLY static s32_t spiffs_page_index_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { s32_t res = SPIFFS_OK; if (pix == (spiffs_page_ix)-1) { @@ -56,6 +57,7 @@ static s32_t spiffs_page_index_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix p #endif return res; } +#endif // !SPIFFS_READ_ONLY #if !SPIFFS_CACHE @@ -64,7 +66,7 @@ s32_t spiffs_phys_rd( u32_t addr, u32_t len, u8_t *dst) { - return fs->cfg.hal_read_f(addr, len, dst); + return SPIFFS_HAL_READ(fs, addr, len, dst); } s32_t spiffs_phys_wr( @@ -72,17 +74,19 @@ s32_t spiffs_phys_wr( u32_t addr, u32_t len, u8_t *src) { - return fs->cfg.hal_write_f(addr, len, src); + return SPIFFS_HAL_WRITE(fs, addr, len, src); } #endif +#if !SPIFFS_READ_ONLY s32_t spiffs_phys_cpy( spiffs *fs, spiffs_file fh, u32_t dst, u32_t src, u32_t len) { + (void)fh; s32_t res; u8_t b[SPIFFS_COPY_BUFFER_STACK]; while (len > 0) { @@ -97,10 +101,11 @@ s32_t spiffs_phys_cpy( } return SPIFFS_OK; } +#endif // !SPIFFS_READ_ONLY // Find object lookup entry containing given id with visitor. // Iterate over object lookup pages in each block until a given object id entry is found. -// When found, the visitor function is called with block index, entry index and user_data. +// When found, the visitor function is called with block index, entry index and user data. // If visitor returns SPIFFS_VIS_CONTINUE, the search goes on. Otherwise, the search will be // ended and visitor's return code is returned to caller. // If no visitor is given (0) the search returns on first entry with matching object id. @@ -112,8 +117,8 @@ s32_t spiffs_phys_cpy( // SPIFFS_VIS_NO_WRAP // @param obj_id argument object id // @param v visitor callback function -// @param user_data any data, passed to the callback visitor function -// @param user_p any pointer, passed to the callback visitor function +// @param user_const_p any const pointer, passed to the callback visitor function +// @param user_var_p any pointer, passed to the callback visitor function // @param block_ix reported block index where match was found // @param lu_entry reported look up index where match was found s32_t spiffs_obj_lu_find_entry_visitor( @@ -123,8 +128,8 @@ s32_t spiffs_obj_lu_find_entry_visitor( u8_t flags, spiffs_obj_id obj_id, spiffs_visitor_f v, - u32_t user_data, - void *user_p, + const void *user_const_p, + void *user_var_p, spiffs_block_ix *block_ix, int *lu_entry) { s32_t res = SPIFFS_OK; @@ -137,7 +142,7 @@ s32_t spiffs_obj_lu_find_entry_visitor( int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); // wrap initial - if (cur_entry >= (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) { + if (cur_entry > (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) { cur_entry = 0; cur_block++; cur_block_addr = cur_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs); @@ -174,8 +179,8 @@ s32_t spiffs_obj_lu_find_entry_visitor( (flags & SPIFFS_VIS_CHECK_PH) ? obj_id : obj_lu_buf[cur_entry-entry_offset], cur_block, cur_entry, - user_data, - user_p); + user_const_p, + user_var_p); if (res == SPIFFS_VIS_COUNTINUE || res == SPIFFS_VIS_COUNTINUE_RELOAD) { if (res == SPIFFS_VIS_COUNTINUE_RELOAD) { res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, @@ -217,6 +222,7 @@ s32_t spiffs_obj_lu_find_entry_visitor( return SPIFFS_VIS_END; } +#if !SPIFFS_READ_ONLY s32_t spiffs_erase_block( spiffs *fs, spiffs_block_ix bix) { @@ -226,8 +232,9 @@ s32_t spiffs_erase_block( // here we ignore res, just try erasing the block while (size > 0) { - SPIFFS_DBG("erase %08x:%08x\n", addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); - (void)fs->cfg.hal_erase_f(addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + SPIFFS_DBG("erase "_SPIPRIad":"_SPIPRIi"\n", addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + SPIFFS_HAL_ERASE(fs, addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + addr += SPIFFS_CFG_PHYS_ERASE_SZ(fs); size -= SPIFFS_CFG_PHYS_ERASE_SZ(fs); } @@ -241,7 +248,7 @@ s32_t spiffs_erase_block( #if SPIFFS_USE_MAGIC // finally, write magic - spiffs_obj_id magic = SPIFFS_MAGIC(fs); + spiffs_obj_id magic = SPIFFS_MAGIC(fs, bix); res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0, SPIFFS_MAGIC_PADDR(fs, bix), sizeof(spiffs_obj_id), (u8_t *)&magic); @@ -255,6 +262,59 @@ s32_t spiffs_erase_block( return res; } +#endif // !SPIFFS_READ_ONLY + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 +s32_t spiffs_probe( + spiffs_config *cfg) { + s32_t res; + u32_t paddr; + spiffs dummy_fs; // create a dummy fs struct just to be able to use macros + _SPIFFS_MEMCPY(&dummy_fs.cfg, cfg, sizeof(spiffs_config)); + dummy_fs.block_count = 0; + + // Read three magics, as one block may be in an aborted erase state. + // At least two of these must contain magic and be in decreasing order. + spiffs_obj_id magic[3]; + spiffs_obj_id bix_count[3]; + + spiffs_block_ix bix; + for (bix = 0; bix < 3; bix++) { + paddr = SPIFFS_MAGIC_PADDR(&dummy_fs, bix); +#if SPIFFS_HAL_CALLBACK_EXTRA + // not any proper fs to report here, so callback with null + // (cross fingers that no-one gets angry) + res = cfg->hal_read_f((void *)0, paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]); +#else + res = cfg->hal_read_f(paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]); +#endif + bix_count[bix] = magic[bix] ^ SPIFFS_MAGIC(&dummy_fs, 0); + SPIFFS_CHECK_RES(res); + } + + // check that we have sane number of blocks + if (bix_count[0] < 3) return SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS; + // check that the order is correct, take aborted erases in calculation + // first block aborted erase + if (magic[0] == (spiffs_obj_id)(-1) && bix_count[1] - bix_count[2] == 1) { + return (bix_count[1]+1) * cfg->log_block_size; + } + // second block aborted erase + if (magic[1] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[2] == 2) { + return bix_count[0] * cfg->log_block_size; + } + // third block aborted erase + if (magic[2] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[1] == 1) { + return bix_count[0] * cfg->log_block_size; + } + // no block has aborted erase + if (bix_count[0] - bix_count[1] == 1 && bix_count[1] - bix_count[2] == 1) { + return bix_count[0] * cfg->log_block_size; + } + + return SPIFFS_ERR_PROBE_NOT_A_FS; +} +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 static s32_t spiffs_obj_lu_scan_v( @@ -262,11 +322,11 @@ static s32_t spiffs_obj_lu_scan_v( spiffs_obj_id obj_id, spiffs_block_ix bix, int ix_entry, - u32_t user_data, - void *user_p) { + const void *user_const_p, + void *user_var_p) { (void)bix; - (void)user_data; - (void)user_p; + (void)user_const_p; + (void)user_var_p; if (obj_id == SPIFFS_OBJ_ID_FREE) { if (ix_entry == 0) { fs->free_blocks++; @@ -309,7 +369,7 @@ s32_t spiffs_obj_lu_scan( sizeof(spiffs_obj_id), (u8_t *)&magic); SPIFFS_CHECK_RES(res); - if (magic != SPIFFS_MAGIC(fs)) { + if (magic != SPIFFS_MAGIC(fs, bix)) { if (unerased_bix == (spiffs_block_ix)-1) { // allow one unerased block as it might be powered down during an erase unerased_bix = bix; @@ -347,8 +407,12 @@ s32_t spiffs_obj_lu_scan( #if SPIFFS_USE_MAGIC if (unerased_bix != (spiffs_block_ix)-1) { // found one unerased block, remedy - SPIFFS_DBG("mount: erase block %i\n", bix); + SPIFFS_DBG("mount: erase block "_SPIPRIbl"\n", bix); +#if SPIFFS_READ_ONLY + res = SPIFFS_ERR_RO_ABORTED_OPERATION; +#else res = spiffs_erase_block(fs, unerased_bix); +#endif // SPIFFS_READ_ONLY SPIFFS_CHECK_RES(res); } #endif @@ -379,6 +443,7 @@ s32_t spiffs_obj_lu_scan( return res; } +#if !SPIFFS_READ_ONLY // Find free object lookup entry // Iterate over object lookup pages in each block until a free object id entry is found s32_t spiffs_obj_lu_find_free( @@ -402,17 +467,18 @@ s32_t spiffs_obj_lu_find_free( SPIFFS_OBJ_ID_FREE, block_ix, lu_entry); if (res == SPIFFS_OK) { fs->free_cursor_block_ix = *block_ix; - fs->free_cursor_obj_lu_entry = *lu_entry; + fs->free_cursor_obj_lu_entry = (*lu_entry) + 1; if (*lu_entry == 0) { fs->free_blocks--; } } - if (res == SPIFFS_VIS_END) { + if (res == SPIFFS_ERR_FULL) { SPIFFS_DBG("fs full\n"); } - return res == SPIFFS_VIS_END ? SPIFFS_ERR_FULL : res; + return res; } +#endif // !SPIFFS_READ_ONLY // Find object lookup entry containing given id // Iterate over object lookup pages in each block until a given object id entry is found @@ -437,8 +503,8 @@ static s32_t spiffs_obj_lu_find_id_and_span_v( spiffs_obj_id obj_id, spiffs_block_ix bix, int ix_entry, - u32_t user_data, - void *user_p) { + const void *user_const_p, + void *user_var_p) { s32_t res; spiffs_page_header ph; spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); @@ -446,10 +512,10 @@ static s32_t spiffs_obj_lu_find_id_and_span_v( SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_header), (u8_t *)&ph); SPIFFS_CHECK_RES(res); if (ph.obj_id == obj_id && - ph.span_ix == (spiffs_span_ix)user_data && + ph.span_ix == *((spiffs_span_ix*)user_var_p) && (ph.flags & (SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED)) == SPIFFS_PH_FLAG_DELET && !((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (ph.flags & SPIFFS_PH_FLAG_IXDELE) == 0 && ph.span_ix == 0) && - (user_p == 0 || *((spiffs_page_ix *)user_p) != pix)) { + (user_const_p == 0 || *((const spiffs_page_ix*)user_const_p) != pix)) { return SPIFFS_OK; } else { return SPIFFS_VIS_COUNTINUE; @@ -474,8 +540,8 @@ s32_t spiffs_obj_lu_find_id_and_span( SPIFFS_VIS_CHECK_ID, obj_id, spiffs_obj_lu_find_id_and_span_v, - (u32_t)spix, exclusion_pix ? &exclusion_pix : 0, + &spix, &bix, &entry); @@ -513,8 +579,8 @@ s32_t spiffs_obj_lu_find_id_and_span_by_phdr( SPIFFS_VIS_CHECK_PH, obj_id, spiffs_obj_lu_find_id_and_span_v, - (u32_t)spix, exclusion_pix ? &exclusion_pix : 0, + &spix, &bix, &entry); @@ -534,6 +600,153 @@ s32_t spiffs_obj_lu_find_id_and_span_by_phdr( return res; } +#if SPIFFS_IX_MAP + +// update index map of given fd with given object index data +static void spiffs_update_ix_map(spiffs *fs, + spiffs_fd *fd, spiffs_span_ix objix_spix, spiffs_page_object_ix *objix) { +#if SPIFFS_SINGLETON + (void)fs; +#endif + spiffs_ix_map *map = fd->ix_map; + spiffs_span_ix map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix); + spiffs_span_ix map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->end_spix); + + // check if updated ix is within map range + if (objix_spix < map_objix_start_spix || objix_spix > map_objix_end_spix) { + return; + } + + // update memory mapped page index buffer to new pages + + // get range of updated object index map data span indices + spiffs_span_ix objix_data_spix_start = + SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, objix_spix); + spiffs_span_ix objix_data_spix_end = objix_data_spix_start + + (objix_spix == 0 ? SPIFFS_OBJ_HDR_IX_LEN(fs) : SPIFFS_OBJ_IX_LEN(fs)); + + // calc union of object index range and index map range array + spiffs_span_ix map_spix = MAX(map->start_spix, objix_data_spix_start); + spiffs_span_ix map_spix_end = MIN(map->end_spix + 1, objix_data_spix_end); + + while (map_spix < map_spix_end) { + spiffs_page_ix objix_data_pix; + if (objix_spix == 0) { + // get data page from object index header page + objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix_header)))[map_spix]; + } else { + // get data page from object index page + objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, map_spix)]; + } + + if (objix_data_pix == (spiffs_page_ix)-1) { + // reached end of object, abort + break; + } + + map->map_buf[map_spix - map->start_spix] = objix_data_pix; + SPIFFS_DBG("map "_SPIPRIid":"_SPIPRIsp" ("_SPIPRIsp"--"_SPIPRIsp") objix.spix:"_SPIPRIsp" to pix "_SPIPRIpg"\n", + fd->obj_id, map_spix - map->start_spix, + map->start_spix, map->end_spix, + objix->p_hdr.span_ix, + objix_data_pix); + + map_spix++; + } +} + +typedef struct { + spiffs_fd *fd; + u32_t remaining_objix_pages_to_visit; + spiffs_span_ix map_objix_start_spix; + spiffs_span_ix map_objix_end_spix; +} spiffs_ix_map_populate_state; + +static s32_t spiffs_populate_ix_map_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_const_p; + s32_t res; + spiffs_ix_map_populate_state *state = (spiffs_ix_map_populate_state *)user_var_p; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + + // load header to check it + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix), (u8_t *)objix); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, obj_id, objix->p_hdr.span_ix); + + // check if hdr is ok, and if objix range overlap with ix map range + if ((objix->p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE) && + objix->p_hdr.span_ix >= state->map_objix_start_spix && + objix->p_hdr.span_ix <= state->map_objix_end_spix) { + // ok, load rest of object index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix) + sizeof(spiffs_page_object_ix), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix), + (u8_t *)objix + sizeof(spiffs_page_object_ix)); + SPIFFS_CHECK_RES(res); + + spiffs_update_ix_map(fs, state->fd, objix->p_hdr.span_ix, objix); + + state->remaining_objix_pages_to_visit--; + SPIFFS_DBG("map "_SPIPRIid" ("_SPIPRIsp"--"_SPIPRIsp") remaining objix pages "_SPIPRIi"\n", + state->fd->obj_id, + state->fd->ix_map->start_spix, state->fd->ix_map->end_spix, + state->remaining_objix_pages_to_visit); + } + + if (res == SPIFFS_OK) { + res = state->remaining_objix_pages_to_visit ? SPIFFS_VIS_COUNTINUE : SPIFFS_VIS_END; + } + return res; +} + +// populates index map, from vector entry start to vector entry end, inclusive +s32_t spiffs_populate_ix_map(spiffs *fs, spiffs_fd *fd, u32_t vec_entry_start, u32_t vec_entry_end) { + s32_t res; + spiffs_ix_map *map = fd->ix_map; + spiffs_ix_map_populate_state state; + vec_entry_start = MIN((u32_t)(map->end_spix - map->start_spix), vec_entry_start); + vec_entry_end = MAX((u32_t)(map->end_spix - map->start_spix), vec_entry_end); + if (vec_entry_start > vec_entry_end) { + return SPIFFS_ERR_IX_MAP_BAD_RANGE; + } + state.map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_start); + state.map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_end); + state.remaining_objix_pages_to_visit = + state.map_objix_end_spix - state.map_objix_start_spix + 1; + state.fd = fd; + + res = spiffs_obj_lu_find_entry_visitor( + fs, + SPIFFS_BLOCK_FOR_PAGE(fs, fd->objix_hdr_pix), + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, fd->objix_hdr_pix), + SPIFFS_VIS_CHECK_ID, + fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + spiffs_populate_ix_map_v, + 0, + &state, + 0, + 0); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + return res; +} + +#endif + + +#if !SPIFFS_READ_ONLY // Allocates a free defined page with given obj_id // Occupies object lookup entry and page // data may be NULL; where only page header is stored, len and page_offs is ignored @@ -591,7 +804,9 @@ s32_t spiffs_page_allocate_data( return res; } +#endif // !SPIFFS_READ_ONLY +#if !SPIFFS_READ_ONLY // Moves a page from src to a free page and finalizes it. Updates page index. Page data is given in param page. // If page data is null, provided header is used for metainfo and page data is physically copied. s32_t spiffs_page_move( @@ -654,14 +869,14 @@ s32_t spiffs_page_move( res = spiffs_page_delete(fs, src_pix); return res; } +#endif // !SPIFFS_READ_ONLY +#if !SPIFFS_READ_ONLY // Deletes a page and removes it from object lookup. s32_t spiffs_page_delete( spiffs *fs, spiffs_page_ix pix) { s32_t res; - spiffs_page_header hdr; - hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED); // mark deleted entry in source object lookup spiffs_obj_id d_obj_id = SPIFFS_OBJ_ID_DELETED; res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_DELE, @@ -675,20 +890,30 @@ s32_t spiffs_page_delete( fs->stats_p_allocated--; // mark deleted in source page + u8_t flags = 0xff; +#if SPIFFS_NO_BLIND_WRITES + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix) + offsetof(spiffs_page_header, flags), + sizeof(flags), &flags); + SPIFFS_CHECK_RES(res); +#endif + flags &= ~(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED); res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_DELE, 0, SPIFFS_PAGE_TO_PADDR(fs, pix) + offsetof(spiffs_page_header, flags), - sizeof(u8_t), - (u8_t *)&hdr.flags); + sizeof(flags), &flags); return res; } +#endif // !SPIFFS_READ_ONLY +#if !SPIFFS_READ_ONLY // Create an object index header page with empty index and undefined length s32_t spiffs_object_create( spiffs *fs, spiffs_obj_id obj_id, - u8_t name[SPIFFS_OBJ_NAME_LEN], + const u8_t name[], + const u8_t meta[], spiffs_obj_type type, spiffs_page_ix *objix_hdr_pix) { s32_t res = SPIFFS_OK; @@ -704,7 +929,7 @@ s32_t spiffs_object_create( // find free entry res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); SPIFFS_CHECK_RES(res); - SPIFFS_DBG("create: found free page @ %04x bix:%i entry:%i\n", SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), bix, entry); + SPIFFS_DBG("create: found free page @ "_SPIPRIpg" bix:"_SPIPRIbl" entry:"_SPIPRIsp"\n", (spiffs_page_ix)SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), bix, entry); // occupy page in object lookup res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, @@ -719,15 +944,24 @@ s32_t spiffs_object_create( oix_hdr.p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED); oix_hdr.type = type; oix_hdr.size = SPIFFS_UNDEFINED_LEN; // keep ones so we can update later without wasting this page - strncpy((char *)&oix_hdr.name, (char *)name, SPIFFS_OBJ_NAME_LEN); - + strncpy((char*)oix_hdr.name, (const char*)name, SPIFFS_OBJ_NAME_LEN); +#if SPIFFS_OBJ_META_LEN + if (meta) { + _SPIFFS_MEMCPY(oix_hdr.meta, meta, SPIFFS_OBJ_META_LEN); + } else { + memset(oix_hdr.meta, 0xff, SPIFFS_OBJ_META_LEN); + } +#else + (void) meta; +#endif // update page res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_object_ix_header), (u8_t*)&oix_hdr); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_NEW, obj_id, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), SPIFFS_UNDEFINED_LEN); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&oix_hdr, + SPIFFS_EV_IX_NEW, obj_id, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), SPIFFS_UNDEFINED_LEN); if (objix_hdr_pix) { *objix_hdr_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); @@ -735,7 +969,9 @@ s32_t spiffs_object_create( return res; } +#endif // !SPIFFS_READ_ONLY +#if !SPIFFS_READ_ONLY // update object index header with any combination of name/size/index // new_objix_hdr_data may be null, if so the object index header page is loaded // name may be null, if so name is not changed @@ -746,7 +982,8 @@ s32_t spiffs_object_update_index_hdr( spiffs_obj_id obj_id, spiffs_page_ix objix_hdr_pix, u8_t *new_objix_hdr_data, - u8_t name[SPIFFS_OBJ_NAME_LEN], + const u8_t name[], + const u8_t meta[], u32_t size, spiffs_page_ix *new_pix) { s32_t res = SPIFFS_OK; @@ -770,8 +1007,15 @@ s32_t spiffs_object_update_index_hdr( // change name if (name) { - strncpy((char *)objix_hdr->name, (char *)name, SPIFFS_OBJ_NAME_LEN); + strncpy((char*)objix_hdr->name, (const char*)name, SPIFFS_OBJ_NAME_LEN); } +#if SPIFFS_OBJ_META_LEN + if (meta) { + _SPIFFS_MEMCPY(objix_hdr->meta, meta, SPIFFS_OBJ_META_LEN); + } +#else + (void) meta; +#endif if (size) { objix_hdr->size = size; } @@ -784,49 +1028,125 @@ s32_t spiffs_object_update_index_hdr( *new_pix = new_objix_hdr_pix; } // callback on object index update - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, obj_id, objix_hdr->p_hdr.span_ix, new_objix_hdr_pix, objix_hdr->size); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr, + new_objix_hdr_data ? SPIFFS_EV_IX_UPD : SPIFFS_EV_IX_UPD_HDR, + obj_id, objix_hdr->p_hdr.span_ix, new_objix_hdr_pix, objix_hdr->size); if (fd) fd->objix_hdr_pix = new_objix_hdr_pix; // if this is not in the registered cluster } return res; } +#endif // !SPIFFS_READ_ONLY void spiffs_cb_object_event( spiffs *fs, - spiffs_fd *fd, + spiffs_page_object_ix *objix, int ev, - spiffs_obj_id obj_id, + spiffs_obj_id obj_id_raw, spiffs_span_ix spix, spiffs_page_ix new_pix, u32_t new_size) { - (void)fd; +#if SPIFFS_IX_MAP == 0 + (void)objix; +#endif // update index caches in all file descriptors - obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; + spiffs_obj_id obj_id = obj_id_raw & ~SPIFFS_OBJ_ID_IX_FLAG; u32_t i; spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + SPIFFS_DBG(" CALLBACK %s obj_id:"_SPIPRIid" spix:"_SPIPRIsp" npix:"_SPIPRIpg" nsz:"_SPIPRIi"\n", (const char *[]){"UPD", "NEW", "DEL", "MOV", "HUP","???"}[MIN(ev,5)], + obj_id_raw, spix, new_pix, new_size); for (i = 0; i < fs->fd_count; i++) { spiffs_fd *cur_fd = &fds[i]; - if (cur_fd->file_nbr == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; - if (spix == 0) { - if (ev == SPIFFS_EV_IX_NEW || ev == SPIFFS_EV_IX_UPD) { - SPIFFS_DBG(" callback: setting fd %i:%04x objix_hdr_pix to %04x, size:%i\n", cur_fd->file_nbr, cur_fd->obj_id, new_pix, new_size); + if ((cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; // fd not related to updated file +#if !SPIFFS_TEMPORAL_FD_CACHE + if (cur_fd->file_nbr == 0) continue; // fd closed +#endif + if (spix == 0) { // object index header update + if (ev != SPIFFS_EV_IX_DEL) { +#if SPIFFS_TEMPORAL_FD_CACHE + if (cur_fd->score == 0) continue; // never used fd +#endif + SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid"(fdoffs:"_SPIPRIi" offs:"_SPIPRIi") objix_hdr_pix to "_SPIPRIpg", size:"_SPIPRIi"\n", + SPIFFS_FH_OFFS(fs, cur_fd->file_nbr), cur_fd->obj_id, cur_fd->fdoffset, cur_fd->offset, new_pix, new_size); cur_fd->objix_hdr_pix = new_pix; if (new_size != 0) { + // update size and offsets for fds to this file cur_fd->size = new_size; + u32_t act_new_size = new_size == SPIFFS_UNDEFINED_LEN ? 0 : new_size; +#if SPIFFS_CACHE_WR + if (act_new_size > 0 && cur_fd->cache_page) { + act_new_size = MAX(act_new_size, cur_fd->cache_page->offset + cur_fd->cache_page->size); + } +#endif + if (cur_fd->offset > act_new_size) { + cur_fd->offset = act_new_size; + } + if (cur_fd->fdoffset > act_new_size) { + cur_fd->fdoffset = act_new_size; + } +#if SPIFFS_CACHE_WR + if (cur_fd->cache_page && cur_fd->cache_page->offset > act_new_size+1) { + SPIFFS_CACHE_DBG("CACHE_DROP: file trunced, dropping cache page "_SPIPRIi", no writeback\n", cur_fd->cache_page->ix); + spiffs_cache_fd_release(fs, cur_fd->cache_page); + } +#endif } - } else if (ev == SPIFFS_EV_IX_DEL) { + } else { + // removing file +#if SPIFFS_CACHE_WR + if (cur_fd->file_nbr && cur_fd->cache_page) { + SPIFFS_CACHE_DBG("CACHE_DROP: file deleted, dropping cache page "_SPIPRIi", no writeback\n", cur_fd->cache_page->ix); + spiffs_cache_fd_release(fs, cur_fd->cache_page); + } +#endif + SPIFFS_DBG(" callback: release fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp" objix_pix to "_SPIPRIpg"\n", SPIFFS_FH_OFFS(fs, cur_fd->file_nbr), cur_fd->obj_id, spix, new_pix); cur_fd->file_nbr = 0; cur_fd->obj_id = SPIFFS_OBJ_ID_DELETED; } - } + } // object index header update if (cur_fd->cursor_objix_spix == spix) { - if (ev == SPIFFS_EV_IX_NEW || ev == SPIFFS_EV_IX_UPD) { - SPIFFS_DBG(" callback: setting fd %i:%04x span:%04x objix_pix to %04x\n", cur_fd->file_nbr, cur_fd->obj_id, spix, new_pix); + if (ev != SPIFFS_EV_IX_DEL) { + SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp" objix_pix to "_SPIPRIpg"\n", SPIFFS_FH_OFFS(fs, cur_fd->file_nbr), cur_fd->obj_id, spix, new_pix); cur_fd->cursor_objix_pix = new_pix; } else { cur_fd->cursor_objix_pix = 0; } } + } // fd update loop + +#if SPIFFS_IX_MAP + + // update index maps + if (ev == SPIFFS_EV_IX_UPD || ev == SPIFFS_EV_IX_NEW) { + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + // check fd opened, having ix map, match obj id + if (cur_fd->file_nbr == 0 || + cur_fd->ix_map == 0 || + (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; + SPIFFS_DBG(" callback: map ix update fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp"\n", SPIFFS_FH_OFFS(fs, cur_fd->file_nbr), cur_fd->obj_id, spix); + spiffs_update_ix_map(fs, cur_fd, spix, objix); + } + } + +#endif + + // callback to user if object index header + if (fs->file_cb_f && spix == 0 && (obj_id_raw & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_fileop_type op; + if (ev == SPIFFS_EV_IX_NEW) { + op = SPIFFS_CB_CREATED; + } else if (ev == SPIFFS_EV_IX_UPD || + ev == SPIFFS_EV_IX_MOV || + ev == SPIFFS_EV_IX_UPD_HDR) { + op = SPIFFS_CB_UPDATED; + } else if (ev == SPIFFS_EV_IX_DEL) { + op = SPIFFS_CB_DELETED; + } else { + SPIFFS_DBG(" callback: WARNING unknown callback event "_SPIPRIi"\n", ev); + return; // bail out + } + fs->file_cb_f(fs, op, obj_id, new_pix); } } @@ -881,11 +1201,12 @@ s32_t spiffs_object_open_by_page( SPIFFS_VALIDATE_OBJIX(oix_hdr.p_hdr, fd->obj_id, 0); - SPIFFS_DBG("open: fd %i is obj id %04x\n", fd->file_nbr, fd->obj_id); + SPIFFS_DBG("open: fd "_SPIPRIfd" is obj id "_SPIPRIid"\n", SPIFFS_FH_OFFS(fs, fd->file_nbr), fd->obj_id); return res; } +#if !SPIFFS_READ_ONLY // Append to object // keep current object index (header) page in fs->work buffer s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { @@ -893,7 +1214,7 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { s32_t res = SPIFFS_OK; u32_t written = 0; - SPIFFS_DBG("append: %i bytes @ offs %i of size %i\n", len, offset, fd->size); + SPIFFS_DBG("append: "_SPIPRIi" bytes @ offs "_SPIPRIi" of size "_SPIPRIi"\n", len, offset, fd->size); if (offset > fd->size) { SPIFFS_DBG("append: offset reversed to size\n"); @@ -902,7 +1223,7 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); // add an extra page of data worth for meta if (res != SPIFFS_OK) { - SPIFFS_DBG("append: gc check fail %i\n", res); + SPIFFS_DBG("append: gc check fail "_SPIPRIi"\n", res); } SPIFFS_CHECK_RES(res); @@ -930,7 +1251,7 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { // within this clause we return directly if something fails, object index mess-up if (written > 0) { // store previous object index page, unless first pass - SPIFFS_DBG("append: %04x store objix %04x:%04x, written %i\n", fd->obj_id, + SPIFFS_DBG("append: "_SPIPRIid" store objix "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, cur_objix_pix, prev_objix_spix, written); if (prev_objix_spix == 0) { // this is an update to object index header page @@ -945,9 +1266,9 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { } else { // was a nonempty object, update to new page res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - fd->objix_hdr_pix, fs->work, 0, offset+written, &new_objix_hdr_page); + fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page); SPIFFS_CHECK_RES(res); - SPIFFS_DBG("append: %04x store new objix_hdr, %04x:%04x, written %i\n", fd->obj_id, + SPIFFS_DBG("append: "_SPIPRIid" store new objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, new_objix_hdr_page, 0, written); } } else { @@ -958,12 +1279,13 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD,fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD,fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); // update length in object index header page res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - fd->objix_hdr_pix, 0, 0, offset+written, &new_objix_hdr_page); + fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page); SPIFFS_CHECK_RES(res); - SPIFFS_DBG("append: %04x store new size I %i in objix_hdr, %04x:%04x, written %i\n", fd->obj_id, + SPIFFS_DBG("append: "_SPIPRIid" store new size I "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, offset+written, new_objix_hdr_page, 0, written); } fd->size = offset+written; @@ -973,7 +1295,7 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { // create or load new object index page if (cur_objix_spix == 0) { // load object index header page, must always exist - SPIFFS_DBG("append: %04x load objixhdr page %04x:%04x\n", fd->obj_id, cur_objix_pix, cur_objix_spix); + SPIFFS_DBG("append: "_SPIPRIid" load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", fd->obj_id, cur_objix_pix, cur_objix_spix); res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); @@ -988,23 +1310,24 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { res = spiffs_page_allocate_data(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, &p_hdr, 0, 0, 0, 1, &cur_objix_pix); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_NEW, fd->obj_id, cur_objix_spix, cur_objix_pix, 0); // quick "load" of new object index page memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); - memcpy(fs->work, &p_hdr, sizeof(spiffs_page_header)); - SPIFFS_DBG("append: %04x create objix page, %04x:%04x, written %i\n", fd->obj_id + _SPIFFS_MEMCPY(fs->work, &p_hdr, sizeof(spiffs_page_header)); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_NEW, fd->obj_id, cur_objix_spix, cur_objix_pix, 0); + SPIFFS_DBG("append: "_SPIPRIid" create objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id , cur_objix_pix, cur_objix_spix, written); } else { // on first pass, we load existing object index page spiffs_page_ix pix; - SPIFFS_DBG("append: %04x find objix span_ix:%04x\n", fd->obj_id, cur_objix_spix); + SPIFFS_DBG("append: "_SPIPRIid" find objix span_ix:"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix); if (fd->cursor_objix_spix == cur_objix_spix) { pix = fd->cursor_objix_pix; } else { res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix); SPIFFS_CHECK_RES(res); } - SPIFFS_DBG("append: %04x found object index at page %04x [fd size %i]\n", fd->obj_id, pix, fd->size); + SPIFFS_DBG("append: "_SPIPRIid" found object index at page "_SPIPRIpg" [fd size "_SPIPRIi"]\n", fd->obj_id, pix, fd->size); res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); @@ -1028,7 +1351,7 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL); // finalize immediately res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, &p_hdr, &data[written], to_write, page_offs, 1, &data_page); - SPIFFS_DBG("append: %04x store new data page, %04x:%04x offset:%i, len %i, written %i\n", fd->obj_id, + SPIFFS_DBG("append: "_SPIPRIid" store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id, data_page, data_spix, page_offs, to_write, written); } else { // append to existing page, fill out free data in existing page @@ -1045,7 +1368,7 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, data_page) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]); - SPIFFS_DBG("append: %04x store to existing data page, %04x:%04x offset:%i, len %i, written %i\n", fd->obj_id + SPIFFS_DBG("append: "_SPIPRIid" store to existing data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id , data_page, data_spix, page_offs, to_write, written); } @@ -1055,14 +1378,14 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { if (cur_objix_spix == 0) { // update object index header page ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_page; - SPIFFS_DBG("append: %04x wrote page %04x to objix_hdr entry %02x in mem\n", fd->obj_id + SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", fd->obj_id , data_page, data_spix); objix_hdr->size = offset+written; } else { // update object index page ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_page; - SPIFFS_DBG("append: %04x wrote page %04x to objix entry %02x in mem\n", fd->obj_id - , data_page, SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", fd->obj_id + , data_page, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); } // update internals @@ -1081,7 +1404,7 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { if (cur_objix_spix != 0) { // wrote beyond object index header page // write last modified object index page, unless object header index page - SPIFFS_DBG("append: %04x store objix page, %04x:%04x, written %i\n", fd->obj_id, + SPIFFS_DBG("append: "_SPIPRIid" store objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, cur_objix_pix, cur_objix_spix, written); res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); @@ -1090,12 +1413,13 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res2); - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); // update size in object header index page res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - fd->objix_hdr_pix, 0, 0, offset+written, &new_objix_hdr_page); - SPIFFS_DBG("append: %04x store new size II %i in objix_hdr, %04x:%04x, written %i, res %i\n", fd->obj_id + fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: "_SPIPRIid" store new size II "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi", res "_SPIPRIi"\n", fd->obj_id , offset+written, new_objix_hdr_page, 0, written, res2); SPIFFS_CHECK_RES(res2); } else { @@ -1103,7 +1427,7 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { if (offset == 0) { // wrote to empty object - simply update size and write whole page objix_hdr->size = offset+written; - SPIFFS_DBG("append: %04x store fresh objix_hdr page, %04x:%04x, written %i\n", fd->obj_id + SPIFFS_DBG("append: "_SPIPRIid" store fresh objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id , cur_objix_pix, cur_objix_spix, written); res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); @@ -1113,20 +1437,23 @@ s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res2); // callback on object index update - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, fd->obj_id, objix_hdr->p_hdr.span_ix, cur_objix_pix, objix_hdr->size); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD_HDR, fd->obj_id, objix_hdr->p_hdr.span_ix, cur_objix_pix, objix_hdr->size); } else { // modifying object index header page, update size and make new copy res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - fd->objix_hdr_pix, fs->work, 0, offset+written, &new_objix_hdr_page); - SPIFFS_DBG("append: %04x store modified objix_hdr page, %04x:%04x, written %i\n", fd->obj_id + fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: "_SPIPRIid" store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id , new_objix_hdr_page, 0, written); SPIFFS_CHECK_RES(res2); } } return res; -} +} // spiffs_object_append +#endif // !SPIFFS_READ_ONLY +#if !SPIFFS_READ_ONLY // Modify object // keep current object index (header) page in fs->work buffer s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { @@ -1165,8 +1492,8 @@ s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { if (prev_objix_spix == 0) { // store previous object index header page res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - fd->objix_hdr_pix, fs->work, 0, 0, &new_objix_hdr_pix); - SPIFFS_DBG("modify: store modified objix_hdr page, %04x:%04x, written %i\n", new_objix_hdr_pix, 0, written); + fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written); SPIFFS_CHECK_RES(res); } else { // store new version of previous object index page @@ -1176,16 +1503,17 @@ s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { SPIFFS_CHECK_RES(res); res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix); - SPIFFS_DBG("modify: store previous modified objix page, %04x:%04x, written %i\n", new_objix_pix, objix->p_hdr.span_ix, written); + SPIFFS_DBG("modify: store previous modified objix page, "_SPIPRIid":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, objix->p_hdr.span_ix, written); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); } } // load next object index page if (cur_objix_spix == 0) { // load object index header page, must exist - SPIFFS_DBG("modify: load objixhdr page %04x:%04x\n", cur_objix_pix, cur_objix_spix); + SPIFFS_DBG("modify: load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", cur_objix_pix, cur_objix_spix); res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); @@ -1193,14 +1521,14 @@ s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { } else { // load existing object index page on first pass spiffs_page_ix pix; - SPIFFS_DBG("modify: find objix span_ix:%04x\n", cur_objix_spix); + SPIFFS_DBG("modify: find objix span_ix:"_SPIPRIsp"\n", cur_objix_spix); if (fd->cursor_objix_spix == cur_objix_spix) { pix = fd->cursor_objix_pix; } else { res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix); SPIFFS_CHECK_RES(res); } - SPIFFS_DBG("modify: found object index at page %04x\n", pix); + SPIFFS_DBG("modify: found object index at page "_SPIPRIpg"\n", pix); res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); @@ -1231,7 +1559,7 @@ s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { // a full page, allocate and write a new page of data res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, &p_hdr, &data[written], to_write, page_offs, 1, &data_pix); - SPIFFS_DBG("modify: store new data page, %04x:%04x offset:%i, len %i, written %i\n", data_pix, data_spix, page_offs, to_write, written); + SPIFFS_DBG("modify: store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", data_pix, data_spix, page_offs, to_write, written); } else { // write to existing page, allocate new and copy unmodified data @@ -1272,7 +1600,7 @@ s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { (u8_t *)&p_hdr.flags); if (res != SPIFFS_OK) break; - SPIFFS_DBG("modify: store to existing data page, src:%04x, dst:%04x:%04x offset:%i, len %i, written %i\n", orig_data_pix, data_pix, data_spix, page_offs, to_write, written); + SPIFFS_DBG("modify: store to existing data page, src:"_SPIPRIpg", dst:"_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", orig_data_pix, data_pix, data_spix, page_offs, to_write, written); } // delete original data page @@ -1282,11 +1610,11 @@ s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { if (cur_objix_spix == 0) { // update object index header page ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_pix; - SPIFFS_DBG("modify: wrote page %04x to objix_hdr entry %02x in mem\n", data_pix, data_spix); + SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", data_pix, data_spix); } else { // update object index page ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_pix; - SPIFFS_DBG("modify: wrote page %04x to objix entry %02x in mem\n", data_pix, SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); } // update internals @@ -1311,31 +1639,33 @@ s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { SPIFFS_CHECK_RES(res2); res2 = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix); - SPIFFS_DBG("modify: store modified objix page, %04x:%04x, written %i\n", new_objix_pix, cur_objix_spix, written); + SPIFFS_DBG("modify: store modified objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, cur_objix_spix, written); fd->cursor_objix_pix = new_objix_pix; fd->cursor_objix_spix = cur_objix_spix; SPIFFS_CHECK_RES(res2); - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); } else { // wrote within object index header page res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - fd->objix_hdr_pix, fs->work, 0, 0, &new_objix_hdr_pix); - SPIFFS_DBG("modify: store modified objix_hdr page, %04x:%04x, written %i\n", new_objix_hdr_pix, 0, written); + fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written); SPIFFS_CHECK_RES(res2); } return res; -} +} // spiffs_object_modify +#endif // !SPIFFS_READ_ONLY static s32_t spiffs_object_find_object_index_header_by_name_v( spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix bix, int ix_entry, - u32_t user_data, - void *user_p) { - (void)user_data; + const void *user_const_p, + void *user_var_p) { + (void)user_var_p; s32_t res; spiffs_page_object_ix_header objix_hdr; spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); @@ -1349,7 +1679,7 @@ static s32_t spiffs_object_find_object_index_header_by_name_v( if (objix_hdr.p_hdr.span_ix == 0 && (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { - if (strcmp((char *)user_p, (char *)objix_hdr.name) == 0) { + if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) { return SPIFFS_OK; } } @@ -1360,7 +1690,7 @@ static s32_t spiffs_object_find_object_index_header_by_name_v( // Finds object index header page by name s32_t spiffs_object_find_object_index_header_by_name( spiffs *fs, - u8_t name[SPIFFS_OBJ_NAME_LEN], + const u8_t name[SPIFFS_OBJ_NAME_LEN], spiffs_page_ix *pix) { s32_t res; spiffs_block_ix bix; @@ -1372,8 +1702,8 @@ s32_t spiffs_object_find_object_index_header_by_name( 0, 0, spiffs_object_find_object_index_header_by_name_v, - 0, name, + 0, &bix, &entry); @@ -1392,16 +1722,25 @@ s32_t spiffs_object_find_object_index_header_by_name( return res; } +#if !SPIFFS_READ_ONLY // Truncates object to new size. If new size is null, object may be removed totally s32_t spiffs_object_truncate( spiffs_fd *fd, u32_t new_size, - u8_t remove) { + u8_t remove_full) { s32_t res = SPIFFS_OK; spiffs *fs = fd->fs; - res = spiffs_gc_check(fs, remove ? 0 : SPIFFS_DATA_PAGE_SIZE(fs)); - SPIFFS_CHECK_RES(res); + if ((fd->size == SPIFFS_UNDEFINED_LEN || fd->size == 0) && !remove_full) { + // no op + return res; + } + + // need 2 pages if not removing: object index page + possibly chopped data page + if (remove_full == 0) { + res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs) * 2); + SPIFFS_CHECK_RES(res); + } spiffs_page_ix objix_pix = fd->objix_hdr_pix; spiffs_span_ix data_spix = (fd->size > 0 ? fd->size-1 : 0) / SPIFFS_DATA_PAGE_SIZE(fs); @@ -1414,7 +1753,7 @@ s32_t spiffs_object_truncate( spiffs_page_ix new_objix_hdr_pix; // before truncating, check if object is to be fully removed and mark this - if (remove && new_size == 0) { + if (remove_full && new_size == 0) { u8_t flags = ~( SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE); res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, fd->objix_hdr_pix) + offsetof(spiffs_page_header, flags), @@ -1431,20 +1770,28 @@ s32_t spiffs_object_truncate( if (prev_objix_spix != cur_objix_spix) { if (prev_objix_spix != (spiffs_span_ix)-1) { // remove previous object index page - SPIFFS_DBG("truncate: delete objix page %04x:%04x\n", objix_pix, prev_objix_spix); + SPIFFS_DBG("truncate: delete objix page "_SPIPRIpg":"_SPIPRIsp"\n", objix_pix, prev_objix_spix); res = spiffs_page_index_check(fs, fd, objix_pix, prev_objix_spix); SPIFFS_CHECK_RES(res); res = spiffs_page_delete(fs, objix_pix); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_DEL, fd->obj_id, objix->p_hdr.span_ix, objix_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, fd->obj_id, objix->p_hdr.span_ix, objix_pix, 0); if (prev_objix_spix > 0) { - // update object index header page - SPIFFS_DBG("truncate: update objix hdr page %04x:%04x to size %i\n", fd->objix_hdr_pix, prev_objix_spix, cur_size); - res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - fd->objix_hdr_pix, 0, 0, cur_size, &new_objix_hdr_pix); - SPIFFS_CHECK_RES(res); + // Update object index header page, unless we totally want to remove the file. + // If fully removing, we're not keeping consistency as good as when storing the header between chunks, + // would we be aborted. But when removing full files, a crammed system may otherwise + // report ERR_FULL a la windows. We cannot have that. + // Hence, take the risk - if aborted, a file check would free the lost pages and mend things + // as the file is marked as fully deleted in the beginning. + if (remove_full == 0) { + SPIFFS_DBG("truncate: update objix hdr page "_SPIPRIpg":"_SPIPRIsp" to size "_SPIPRIi"\n", fd->objix_hdr_pix, prev_objix_spix, cur_size); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } fd->size = cur_size; } } @@ -1456,7 +1803,7 @@ s32_t spiffs_object_truncate( SPIFFS_CHECK_RES(res); } - SPIFFS_DBG("truncate: load objix page %04x:%04x for data spix:%04x\n", objix_pix, cur_objix_spix, data_spix); + SPIFFS_DBG("truncate: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix); res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); @@ -1478,20 +1825,20 @@ s32_t spiffs_object_truncate( ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = SPIFFS_OBJ_ID_FREE; } - SPIFFS_DBG("truncate: got data pix %04x\n", data_pix); + SPIFFS_DBG("truncate: got data pix "_SPIPRIpg"\n", data_pix); - if (cur_size - SPIFFS_DATA_PAGE_SIZE(fs) >= new_size) { + if (new_size == 0 || remove_full || cur_size - new_size >= SPIFFS_DATA_PAGE_SIZE(fs)) { // delete full data page res = spiffs_page_data_check(fs, fd, data_pix, data_spix); if (res != SPIFFS_ERR_DELETED && res != SPIFFS_OK && res != SPIFFS_ERR_INDEX_REF_FREE) { - SPIFFS_DBG("truncate: err validating data pix %i\n", res); + SPIFFS_DBG("truncate: err validating data pix "_SPIPRIi"\n", res); break; } if (res == SPIFFS_OK) { res = spiffs_page_delete(fs, data_pix); if (res != SPIFFS_OK) { - SPIFFS_DBG("truncate: err deleting data pix %i\n", res); + SPIFFS_DBG("truncate: err deleting data pix "_SPIPRIi"\n", res); break; } } else if (res == SPIFFS_ERR_DELETED || res == SPIFFS_ERR_INDEX_REF_FREE) { @@ -1506,13 +1853,13 @@ s32_t spiffs_object_truncate( } fd->size = cur_size; fd->offset = cur_size; - SPIFFS_DBG("truncate: delete data page %04x for data spix:%04x, cur_size:%i\n", data_pix, data_spix, cur_size); + SPIFFS_DBG("truncate: delete data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", data_pix, data_spix, cur_size); } else { // delete last page, partially spiffs_page_header p_hdr; spiffs_page_ix new_data_pix; u32_t bytes_to_remove = SPIFFS_DATA_PAGE_SIZE(fs) - (new_size % SPIFFS_DATA_PAGE_SIZE(fs)); - SPIFFS_DBG("truncate: delete %i bytes from data page %04x for data spix:%04x, cur_size:%i\n", bytes_to_remove, data_pix, data_spix, cur_size); + SPIFFS_DBG("truncate: delete "_SPIPRIi" bytes from data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", bytes_to_remove, data_pix, data_spix, cur_size); res = spiffs_page_data_check(fs, fd, data_pix, data_spix); if (res != SPIFFS_OK) break; @@ -1544,11 +1891,11 @@ s32_t spiffs_object_truncate( if (cur_objix_spix == 0) { // update object index header page ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix; - SPIFFS_DBG("truncate: wrote page %04x to objix_hdr entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); } else { // update object index page ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix; - SPIFFS_DBG("truncate: wrote page %04x to objix entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); } cur_size = new_size; fd->size = new_size; @@ -1562,30 +1909,31 @@ s32_t spiffs_object_truncate( if (cur_objix_spix == 0) { // update object index header page if (cur_size == 0) { - if (remove) { + if (remove_full) { // remove object altogether - SPIFFS_DBG("truncate: remove object index header page %04x\n", objix_pix); + SPIFFS_DBG("truncate: remove object index header page "_SPIPRIpg"\n", objix_pix); res = spiffs_page_index_check(fs, fd, objix_pix, 0); SPIFFS_CHECK_RES(res); res = spiffs_page_delete(fs, objix_pix); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_DEL, fd->obj_id, 0, objix_pix, 0); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, fd->obj_id, 0, objix_pix, 0); } else { // make uninitialized object - SPIFFS_DBG("truncate: reset objix_hdr page %04x\n", objix_pix); + SPIFFS_DBG("truncate: reset objix_hdr page "_SPIPRIpg"\n", objix_pix); memset(fs->work + sizeof(spiffs_page_object_ix_header), 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header)); res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - objix_pix, fs->work, 0, SPIFFS_UNDEFINED_LEN, &new_objix_hdr_pix); + objix_pix, fs->work, 0, 0, SPIFFS_UNDEFINED_LEN, &new_objix_hdr_pix); SPIFFS_CHECK_RES(res); } } else { // update object index header page SPIFFS_DBG("truncate: update object index header page with indices and size\n"); res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - objix_pix, fs->work, 0, cur_size, &new_objix_hdr_pix); + objix_pix, fs->work, 0, 0, cur_size, &new_objix_hdr_pix); SPIFFS_CHECK_RES(res); } } else { @@ -1598,20 +1946,22 @@ s32_t spiffs_object_truncate( // move and update object index page res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix_hdr, fd->obj_id, 0, objix_pix, &new_objix_pix); SPIFFS_CHECK_RES(res); - spiffs_cb_object_event(fs, fd, SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); - SPIFFS_DBG("truncate: store modified objix page, %04x:%04x\n", new_objix_pix, cur_objix_spix); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + SPIFFS_DBG("truncate: store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, cur_objix_spix); fd->cursor_objix_pix = new_objix_pix; fd->cursor_objix_spix = cur_objix_spix; fd->offset = cur_size; // update object index header page with new size res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, - fd->objix_hdr_pix, 0, 0, cur_size, &new_objix_hdr_pix); + fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix); SPIFFS_CHECK_RES(res); } fd->size = cur_size; return res; -} +} // spiffs_object_truncate +#endif // !SPIFFS_READ_ONLY s32_t spiffs_object_read( spiffs_fd *fd, @@ -1630,45 +1980,59 @@ s32_t spiffs_object_read( spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; while (cur_offset < offset + len) { - cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); - if (prev_objix_spix != cur_objix_spix) { - // load current object index (header) page - if (cur_objix_spix == 0) { - objix_pix = fd->objix_hdr_pix; - } else { - SPIFFS_DBG("read: find objix %04x:%04x\n", fd->obj_id, cur_objix_spix); - res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix); - SPIFFS_CHECK_RES(res); - } - SPIFFS_DBG("read: load objix page %04x:%04x for data spix:%04x\n", objix_pix, cur_objix_spix, data_spix); - res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, - fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); - SPIFFS_CHECK_RES(res); - SPIFFS_VALIDATE_OBJIX(objix->p_hdr, fd->obj_id, cur_objix_spix); - - fd->offset = cur_offset; - fd->cursor_objix_pix = objix_pix; - fd->cursor_objix_spix = cur_objix_spix; - - prev_objix_spix = cur_objix_spix; - } - - if (cur_objix_spix == 0) { - // get data page from object index header page - data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; +#if SPIFFS_IX_MAP + // check if we have a memory, index map and if so, if we're within index map's range + // and if so, if the entry is populated + if (fd->ix_map && data_spix >= fd->ix_map->start_spix && data_spix <= fd->ix_map->end_spix + && fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]) { + data_pix = fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]; } else { - // get data page from object index page - data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; - } +#endif + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (prev_objix_spix != cur_objix_spix) { + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + SPIFFS_DBG("read: find objix "_SPIPRIid":"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + objix_pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix); + SPIFFS_CHECK_RES(res); + } + } + SPIFFS_DBG("read: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, fd->obj_id, cur_objix_spix); + fd->offset = cur_offset; + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } +#if SPIFFS_IX_MAP + } +#endif // all remaining data u32_t len_to_read = offset + len - cur_offset; // remaining data in page len_to_read = MIN(len_to_read, SPIFFS_DATA_PAGE_SIZE(fs) - (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs))); // remaining data in file - len_to_read = MIN(len_to_read, fd->size); - SPIFFS_DBG("read: offset:%i rd:%i data spix:%04x is data_pix:%04x addr:%08x\n", cur_offset, len_to_read, data_spix, data_pix, - SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs))); + len_to_read = MIN(len_to_read, fd->size - cur_offset); + SPIFFS_DBG("read: offset:"_SPIPRIi" rd:"_SPIPRIi" data spix:"_SPIPRIsp" is data_pix:"_SPIPRIpg" addr:"_SPIPRIad"\n", cur_offset, len_to_read, data_spix, data_pix, + (u32_t)(SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)))); if (len_to_read <= 0) { res = SPIFFS_ERR_END_OF_OBJECT; break; @@ -1691,6 +2055,7 @@ s32_t spiffs_object_read( return res; } +#if !SPIFFS_READ_ONLY typedef struct { spiffs_obj_id min_obj_id; spiffs_obj_id max_obj_id; @@ -1699,10 +2064,10 @@ typedef struct { } spiffs_free_obj_id_state; static s32_t spiffs_obj_lu_find_free_obj_id_bitmap_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, - u32_t user_data, void *user_p) { + const void *user_const_p, void *user_var_p) { if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED) { - spiffs_obj_id min_obj_id = user_data; - u8_t *conflicting_name = (u8_t *)user_p; + spiffs_obj_id min_obj_id = *((spiffs_obj_id*)user_var_p); + const u8_t *conflicting_name = (const u8_t*)user_const_p; // if conflicting name parameter is given, also check if this name is found in object index hdrs if (conflicting_name && (id & SPIFFS_OBJ_ID_IX_FLAG)) { @@ -1715,7 +2080,7 @@ static s32_t spiffs_obj_lu_find_free_obj_id_bitmap_v(spiffs *fs, spiffs_obj_id i if (objix_hdr.p_hdr.span_ix == 0 && (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { - if (strcmp((char *)user_p, (char *)objix_hdr.name) == 0) { + if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) { return SPIFFS_ERR_CONFLICTING_NAME; } } @@ -1732,11 +2097,11 @@ static s32_t spiffs_obj_lu_find_free_obj_id_bitmap_v(spiffs *fs, spiffs_obj_id i } static s32_t spiffs_obj_lu_find_free_obj_id_compact_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, - u32_t user_data, void *user_p) { - (void)user_data; + const void *user_const_p, void *user_var_p) { + (void)user_var_p; if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED && (id & SPIFFS_OBJ_ID_IX_FLAG)) { s32_t res; - spiffs_free_obj_id_state *state = (spiffs_free_obj_id_state *)user_p; + const spiffs_free_obj_id_state *state = (const spiffs_free_obj_id_state*)user_const_p; spiffs_page_object_ix_header objix_hdr; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, @@ -1753,7 +2118,7 @@ static s32_t spiffs_obj_lu_find_free_obj_id_compact_v(spiffs *fs, spiffs_obj_id if (id >= state->min_obj_id && id <= state->max_obj_id) { u8_t *map = (u8_t *)fs->work; int ix = (id - state->min_obj_id) / state->compaction; - //SPIFFS_DBG("free_obj_id: add ix %i for id %04x min:%04x max%04x comp:%i\n", ix, id, state->min_obj_id, state->max_obj_id, state->compaction); + //SPIFFS_DBG("free_obj_id: add ix "_SPIPRIi" for id "_SPIPRIid" min"_SPIPRIid" max"_SPIPRIid" comp:"_SPIPRIi"\n", ix, id, state->min_obj_id, state->max_obj_id, state->compaction); map[ix]++; } } @@ -1764,10 +2129,10 @@ static s32_t spiffs_obj_lu_find_free_obj_id_compact_v(spiffs *fs, spiffs_obj_id // Scans thru all object lookup for object index header pages. If total possible number of // object ids cannot fit into a work buffer, these are grouped. When a group containing free // object ids is found, the object lu is again scanned for object ids within group and bitmasked. -// Finally, the bitmasked is searched for a free id -s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, u8_t *conflicting_name) { +// Finally, the bitmask is searched for a free id +s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, const u8_t *conflicting_name) { s32_t res = SPIFFS_OK; - u32_t max_objects = (SPIFFS_CFG_PHYS_SZ(fs) / (u32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)) / 2; + u32_t max_objects = (fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) / 2; spiffs_free_obj_id_state state; spiffs_obj_id free_obj_id = SPIFFS_OBJ_ID_FREE; state.min_obj_id = 1; @@ -1781,11 +2146,11 @@ s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, u8_t *co if (state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8) { // possible to represent in bitmap u32_t i, j; - SPIFFS_DBG("free_obj_id: BITM min:%04x max:%04x\n", state.min_obj_id, state.max_obj_id); + SPIFFS_DBG("free_obj_id: BITM min:"_SPIPRIid" max:"_SPIPRIid"\n", state.min_obj_id, state.max_obj_id); memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); - res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_bitmap_v, state.min_obj_id, - conflicting_name, 0, 0); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_bitmap_v, + conflicting_name, &state.min_obj_id, 0, 0); if (res == SPIFFS_VIS_END) res = SPIFFS_OK; SPIFFS_CHECK_RES(res); // traverse bitmask until found free obj_id @@ -1826,14 +2191,14 @@ s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, u8_t *co return SPIFFS_ERR_FULL; } - SPIFFS_DBG("free_obj_id: COMP select index:%i min_count:%i min:%04x max:%04x compact:%i\n", min_i, min_count, state.min_obj_id, state.max_obj_id, state.compaction); + SPIFFS_DBG("free_obj_id: COMP select index:"_SPIPRIi" min_count:"_SPIPRIi" min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", min_i, min_count, state.min_obj_id, state.max_obj_id, state.compaction); if (min_count == 0) { // no id in this range, skip compacting and use directly *obj_id = min_i * state.compaction + state.min_obj_id; return SPIFFS_OK; } else { - SPIFFS_DBG("free_obj_id: COMP SEL chunk:%04x min:%04x -> %04x\n", state.compaction, state.min_obj_id, state.min_obj_id + min_i * state.compaction); + SPIFFS_DBG("free_obj_id: COMP SEL chunk:"_SPIPRIi" min:"_SPIPRIid" -> "_SPIPRIid"\n", state.compaction, state.min_obj_id, state.min_obj_id + min_i * state.compaction); state.min_obj_id += min_i * state.compaction; state.max_obj_id = state.min_obj_id + state.compaction; // decrease compaction @@ -1846,10 +2211,10 @@ s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, u8_t *co // in a work memory of log_page_size bytes, we may fit in log_page_size ids // todo what if compaction is > 255 - then we cannot fit it in a byte state.compaction = (state.max_obj_id-state.min_obj_id) / ((SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(u8_t))); - SPIFFS_DBG("free_obj_id: COMP min:%04x max:%04x compact:%i\n", state.min_obj_id, state.max_obj_id, state.compaction); + SPIFFS_DBG("free_obj_id: COMP min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", state.min_obj_id, state.max_obj_id, state.compaction); memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); - res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_compact_v, 0, &state, 0, 0); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_compact_v, &state, 0, 0, 0); if (res == SPIFFS_VIS_END) res = SPIFFS_OK; SPIFFS_CHECK_RES(res); state.conflicting_name = 0; // searched for conflicting name once, no need to do it again @@ -1858,8 +2223,86 @@ s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, u8_t *co return res; } +#endif // !SPIFFS_READ_ONLY -s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd) { +#if SPIFFS_TEMPORAL_FD_CACHE +// djb2 hash +static u32_t spiffs_hash(spiffs *fs, const u8_t *name) { + (void)fs; + u32_t hash = 5381; + u8_t c; + int i = 0; + while ((c = name[i++]) && i < SPIFFS_OBJ_NAME_LEN) { + hash = (hash * 33) ^ c; + } + return hash; +} +#endif + +s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd, const char *name) { +#if SPIFFS_TEMPORAL_FD_CACHE + u32_t i; + u16_t min_score = 0xffff; + u32_t cand_ix = (u32_t)-1; + u32_t name_hash = name ? spiffs_hash(fs, (const u8_t *)name) : 0; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + + if (name) { + // first, decrease score of all closed descriptors + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + if (cur_fd->score > 1) { // score == 0 indicates never used fd + cur_fd->score--; + } + } + } + } + + // find the free fd with least score or name match + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + if (name && cur_fd->name_hash == name_hash) { + cand_ix = i; + break; + } + if (cur_fd->score < min_score) { + min_score = cur_fd->score; + cand_ix = i; + } + } + } + + if (cand_ix != (u32_t)-1) { + spiffs_fd *cur_fd = &fds[cand_ix]; + if (name) { + if (cur_fd->name_hash == name_hash && cur_fd->score > 0) { + // opened an fd with same name hash, assume same file + // set search point to saved obj index page and hope we have a correct match directly + // when start searching - if not, we will just keep searching until it is found + fs->cursor_block_ix = SPIFFS_BLOCK_FOR_PAGE(fs, cur_fd->objix_hdr_pix); + fs->cursor_obj_lu_entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, cur_fd->objix_hdr_pix); + // update score + if (cur_fd->score < 0xffff-SPIFFS_TEMPORAL_CACHE_HIT_SCORE) { + cur_fd->score += SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + } else { + cur_fd->score = 0xffff; + } + } else { + // no hash hit, restore this fd to initial state + cur_fd->score = SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + cur_fd->name_hash = name_hash; + } + } + cur_fd->file_nbr = cand_ix+1; + *fd = cur_fd; + return SPIFFS_OK; + } else { + return SPIFFS_ERR_OUT_OF_FILE_DESCS; + } +#else + (void)name; u32_t i; spiffs_fd *fds = (spiffs_fd *)fs->fd_space; for (i = 0; i < fs->fd_count; i++) { @@ -1871,6 +2314,7 @@ s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd) { } } return SPIFFS_ERR_OUT_OF_FILE_DESCS; +#endif } s32_t spiffs_fd_return(spiffs *fs, spiffs_file f) { @@ -1883,6 +2327,9 @@ s32_t spiffs_fd_return(spiffs *fs, spiffs_file f) { return SPIFFS_ERR_FILE_CLOSED; } fd->file_nbr = 0; +#if SPIFFS_IX_MAP + fd->ix_map = 0; +#endif return SPIFFS_OK; } @@ -1897,3 +2344,21 @@ s32_t spiffs_fd_get(spiffs *fs, spiffs_file f, spiffs_fd **fd) { } return SPIFFS_OK; } + +#if SPIFFS_TEMPORAL_FD_CACHE +void spiffs_fd_temporal_cache_rehash( + spiffs *fs, + const char *old_path, + const char *new_path) { + u32_t i; + u32_t old_hash = spiffs_hash(fs, (const u8_t *)old_path); + u32_t new_hash = spiffs_hash(fs, (const u8_t *)new_path); + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->score > 0 && cur_fd->name_hash == old_hash) { + cur_fd->name_hash = new_hash; + } + } +} +#endif