-

Files at this revision

API Documentation at this revision

Comitter:
aguscahya
Date:
Thu Apr 22 03:49:45 2021 +0000
Commit message:
-

Changed in this revision

spiffs.h Show annotated file Show diff for this revision Revisions of this file
spiffs_cache.c Show annotated file Show diff for this revision Revisions of this file
spiffs_check.c Show annotated file Show diff for this revision Revisions of this file
spiffs_config.h Show annotated file Show diff for this revision Revisions of this file
spiffs_gc.c Show annotated file Show diff for this revision Revisions of this file
spiffs_hydrogen.c Show annotated file Show diff for this revision Revisions of this file
spiffs_nucleus.c Show annotated file Show diff for this revision Revisions of this file
spiffs_nucleus.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spiffs.h	Thu Apr 22 03:49:45 2021 +0000
@@ -0,0 +1,429 @@
+/*
+ * spiffs.h
+ *
+ *  Created on: May 26, 2013
+ *      Author: petera
+ */
+
+
+
+#ifndef SPIFFS_H_
+#define SPIFFS_H_
+
+#include "spiffs_config.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define SPIFFS_OK                       0
+#define SPIFFS_ERR_NOT_MOUNTED          -10000
+#define SPIFFS_ERR_FULL                 -10001
+#define SPIFFS_ERR_NOT_FOUND            -10002
+#define SPIFFS_ERR_END_OF_OBJECT        -10003
+#define SPIFFS_ERR_DELETED              -10004
+#define SPIFFS_ERR_NOT_FINALIZED        -10005
+#define SPIFFS_ERR_NOT_INDEX            -10006
+#define SPIFFS_ERR_OUT_OF_FILE_DESCS    -10007
+#define SPIFFS_ERR_FILE_CLOSED          -10008
+#define SPIFFS_ERR_FILE_DELETED         -10009
+#define SPIFFS_ERR_BAD_DESCRIPTOR       -10010
+#define SPIFFS_ERR_IS_INDEX             -10011
+#define SPIFFS_ERR_IS_FREE              -10012
+#define SPIFFS_ERR_INDEX_SPAN_MISMATCH  -10013
+#define SPIFFS_ERR_DATA_SPAN_MISMATCH   -10014
+#define SPIFFS_ERR_INDEX_REF_FREE       -10015
+#define SPIFFS_ERR_INDEX_REF_LU         -10016
+#define SPIFFS_ERR_INDEX_REF_INVALID    -10017
+#define SPIFFS_ERR_INDEX_FREE           -10018
+#define SPIFFS_ERR_INDEX_LU             -10019
+#define SPIFFS_ERR_INDEX_INVALID        -10020
+#define SPIFFS_ERR_NOT_WRITABLE         -10021
+#define SPIFFS_ERR_NOT_READABLE         -10022
+
+#define SPIFFS_ERR_INTERNAL             -10050
+
+#define SPIFFS_ERR_TEST                 -10100
+
+
+// spiffs file descriptor index type. must be signed
+typedef s16_t spiffs_file;
+// spiffs file descriptor flags
+typedef u16_t spiffs_flags;
+// spiffs file mode
+typedef u16_t spiffs_mode;
+// object type
+typedef u8_t spiffs_obj_type;
+
+/* 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);
+
+/* file system check callback report operation */
+typedef enum {
+  SPIFFS_CHECK_LOOKUP = 0,
+  SPIFFS_CHECK_INDEX,
+  SPIFFS_CHECK_PAGE
+} spiffs_check_type;
+
+/* file system check callback report type */
+typedef enum {
+  SPIFFS_CHECK_PROGRESS = 0,
+  SPIFFS_CHECK_ERROR,
+  SPIFFS_CHECK_FIX_INDEX,
+  SPIFFS_CHECK_FIX_LOOKUP,
+  SPIFFS_CHECK_DELETE_ORPHANED_INDEX,
+  SPIFFS_CHECK_DELETE_PAGE,
+  SPIFFS_CHECK_DELETE_BAD_FILE,
+} spiffs_check_report;
+
+/* file system check callback function */
+typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_report report,
+    u32_t arg1, u32_t arg2);
+
+#ifndef SPIFFS_DBG
+#define SPIFFS_DBG(...) \
+    print(__VA_ARGS__)
+#endif
+#ifndef SPIFFS_GC_DBG
+#define SPIFFS_GC_DBG(...) printf(__VA_ARGS__)
+#endif
+#ifndef SPIFFS_CACHE_DBG
+#define SPIFFS_CACHE_DBG(...) printf(__VA_ARGS__)
+#endif
+#ifndef SPIFFS_CHECK_DBG
+#define SPIFFS_CHECK_DBG(...) printf(__VA_ARGS__)
+#endif
+
+/* Any write to the filehandle is appended to end of the file */
+#define SPIFFS_APPEND                   (1<<0)
+/* If the opened file exists, it will be truncated to zero length before opened */
+#define SPIFFS_TRUNC                    (1<<1)
+/* If the opened file does not exist, it will be created before opened */
+#define SPIFFS_CREAT                    (1<<2)
+/* The opened file may only be read */
+#define SPIFFS_RDONLY                   (1<<3)
+/* The opened file may only be writted */
+#define SPIFFS_WRONLY                   (1<<4)
+/* The opened file may be both read and writted */
+#define SPIFFS_RDWR                     (SPIFFS_RDONLY | SPIFFS_WRONLY)
+/* Any writes to the filehandle will never be cached */
+#define SPIFFS_DIRECT                   (1<<5)
+
+#define SPIFFS_SEEK_SET                 (0)
+#define SPIFFS_SEEK_CUR                 (1)
+#define SPIFFS_SEEK_END                 (2)
+
+#define SPIFFS_TYPE_FILE                (1)
+#define SPIFFS_TYPE_DIR                 (2)
+#define SPIFFS_TYPE_HARD_LINK           (3)
+#define SPIFFS_TYPE_SOFT_LINK           (4)
+
+#ifndef SPIFFS_LOCK
+#define SPIFFS_LOCK(fs)
+#endif
+
+#ifndef SPIFFS_UNLOCK
+#define SPIFFS_UNLOCK(fs)
+#endif
+
+// phys structs
+
+// spiffs spi configuration struct
+typedef struct {
+  // physical read function
+  spiffs_read hal_read_f;
+  // physical write function
+  spiffs_write hal_write_f;
+  // physical erase function
+  spiffs_erase hal_erase_f;
+#if SPIFFS_SINGLETON == 0
+  // physical size of the spi flash
+  u32_t phys_size;
+  // physical offset in spi flash used for spiffs,
+  // must be on block boundary
+  u32_t phys_addr;
+  // physical size when erasing a block
+  u32_t phys_erase_block;
+
+  // logical size of a block, must be on physical
+  // block size boundary and must never be less than
+  // a physical block
+  u32_t log_block_size;
+  // logical size of a page, must be at least
+  // log_block_size / 8
+  u32_t log_page_size;
+#endif
+} spiffs_config;
+
+typedef struct {
+  // file system configuration
+  spiffs_config cfg;
+  // number of logical blocks
+  u32_t block_count;
+
+  // cursor for free blocks, block index
+  spiffs_block_ix free_cursor_block_ix;
+  // cursor for free blocks, entry index
+  int free_cursor_obj_lu_entry;
+  // cursor when searching, block index
+  spiffs_block_ix cursor_block_ix;
+  // cursor when searching, entry index
+  int cursor_obj_lu_entry;
+
+  // primary work buffer, size of a logical page
+  u8_t *lu_work;
+  // secondary work buffer, size of a logical page
+  u8_t *work;
+  // file descriptor memory area
+  u8_t *fd_space;
+  // available file descriptors
+  u32_t fd_count;
+
+  // last error
+  s32_t errno;
+
+  // current number of free blocks
+  u32_t free_blocks;
+  // current number of busy pages
+  u32_t stats_p_allocated;
+  // current number of deleted pages
+  u32_t stats_p_deleted;
+  // flag indicating that garbage collector is cleaning
+  u8_t cleaning;
+  // max erase count amongst all blocks
+  spiffs_obj_id max_erase_count;
+
+#if SPIFFS_GC_STATS
+  u32_t stats_gc_runs;
+#endif
+
+#if SPIFFS_CACHE
+  // cache memory
+  u8_t *cache;
+  // cache size
+  u32_t cache_size;
+#if SPIFFS_CACHE_STATS
+  u32_t cache_hits;
+  u32_t cache_misses;
+#endif
+#endif
+
+  // check callback function
+  spiffs_check_callback check_cb_f;
+} spiffs;
+
+/* spiffs file status struct */
+typedef struct {
+  spiffs_obj_id obj_id;
+  u32_t size;
+  spiffs_obj_type type;
+  u8_t name[SPIFFS_OBJ_NAME_LEN];
+} spiffs_stat;
+
+struct spiffs_dirent {
+  spiffs_obj_id obj_id;
+  u8_t name[SPIFFS_OBJ_NAME_LEN];
+  spiffs_obj_type type;
+  u32_t size;
+};
+
+typedef struct {
+  spiffs *fs;
+  spiffs_block_ix block;
+  int entry;
+} spiffs_DIR;
+
+// functions
+
+/**
+ * Initializes the file system dynamic parameters and mounts the filesystem
+ * @param fs            the file system struct
+ * @param config        the physical and logical configuration of the file system
+ * @param work          a memory work buffer comprising 2*config->log_page_size
+ *                      bytes used throughout all file system operations
+ * @param fd_space      memory for file descriptors
+ * @param fd_space_size memory size of file descriptors
+ * @param cache         memory for cache, may be null
+ * @param cache_size    memory size of cache
+ * @param check_cb_f    callback function for reporting during consistency checks
+ */
+s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work,
+    u8_t *fd_space, u32_t fd_space_size,
+    u8_t *cache, u32_t cache_size,
+    spiffs_check_callback check_cb_f);
+
+/**
+ * Unmounts the file system. All file handles will be flushed of any
+ * cached writes and closed.
+ * @param fs            the file system struct
+ */
+void SPIFFS_unmount(spiffs *fs);
+
+/**
+ * Creates a new file.
+ * @param fs            the file system struct
+ * @param path          the path of the new file
+ * @param mode          ignored, for posix compliance
+ */
+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
+ * @param mode          ignored, for posix compliance
+ */
+spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode);
+
+/**
+ * Reads from given filehandle.
+ * @param fs            the file system struct
+ * @param fh            the filehandle
+ * @param buf           where to put read data
+ * @param len           how much to read
+ * @returns number of bytes read, or -1 if error
+ */
+s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len);
+
+/**
+ * Writes to given filehandle.
+ * @param fs            the file system struct
+ * @param fh            the filehandle
+ * @param buf           the data to write
+ * @param len           how much to write
+ * @returns number of bytes written, or -1 if error
+ */
+s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len);
+
+/**
+ * Moves the read/write file 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
+ */
+s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence);
+
+/**
+ * Removes a file by path
+ * @param fs            the file system struct
+ * @param path          the path of the file to remove
+ */
+s32_t SPIFFS_remove(spiffs *fs, const char *path);
+
+/**
+ * Removes a file by filehandle
+ * @param fs            the file system struct
+ * @param fh            the filehandle of the file to remove
+ */
+s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh);
+
+/**
+ * Gets file status by path
+ * @param fs            the file system struct
+ * @param path          the path of the file to stat
+ * @param s             the stat struct to populate
+ */
+s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s);
+
+/**
+ * Gets file status by filehandle
+ * @param fs            the file system struct
+ * @param fh            the filehandle of the file to stat
+ * @param s             the stat struct to populate
+ */
+s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s);
+
+/**
+ * Flushes all pending write operations from cache for given file
+ * @param fs            the file system struct
+ * @param fh            the filehandle of the file to flush
+ */
+s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh);
+
+/**
+ * Closes a filehandle. If there are pending write operations, these are finalized before closing.
+ * @param fs            the file system struct
+ * @param fh            the filehandle of the file to close
+ */
+void SPIFFS_close(spiffs *fs, spiffs_file fh);
+
+/**
+ * Returns last error of last file operation.
+ * @param fs            the file system struct
+ */
+s32_t SPIFFS_errno(spiffs *fs);
+
+/**
+ * Opens a directory stream corresponding to the given name.
+ * The stream is positioned at the first entry in the directory.
+ * On hydrogen builds the name argument is ignored as hydrogen builds always correspond
+ * to a flat file structure - no directories.
+ * @param fs            the file system struct
+ * @param name          the name of the directory
+ * @param d             pointer the directory stream to be populated
+ */
+spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d);
+
+/**
+ * Closes a directory stream
+ * @param d             the directory stream to close
+ */
+s32_t SPIFFS_closedir(spiffs_DIR *d);
+
+/**
+ * Reads a directory into given spifs_dirent struct.
+ * @param d             pointer to the directory stream
+ * @param e             the dirent struct to be populated
+ * @returns null if error or end of stream, else given dirent is returned
+ */
+struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e);
+
+/**
+ * Runs a consistency check on given filesystem.
+ * @param fs            the file system struct
+ */
+s32_t SPIFFS_check(spiffs *fs);
+
+#if SPIFFS_TEST_VISUALISATION
+/**
+ * Prints out a visualization of the filesystem.
+ * @param fs            the file system struct
+ */
+s32_t SPIFFS_vis(spiffs *fs);
+#endif
+
+#if SPIFFS_BUFFER_HELP
+/**
+ * Returns number of bytes needed for the filedescriptor buffer given
+ * amount of file descriptors.
+ */
+u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs);
+
+#if SPIFFS_CACHE
+/**
+ * Returns number of bytes needed for the cache buffer given
+ * amount of cache pages.
+ */
+u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages);
+#endif
+#endif
+
+#if SPIFFS_CHACHE
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPIFFS_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spiffs_cache.c	Thu Apr 22 03:49:45 2021 +0000
@@ -0,0 +1,301 @@
+/*
+ * spiffs_cache.c
+ *
+ *  Created on: Jun 23, 2013
+ *      Author: petera
+ */
+
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+#if SPIFFS_CACHE
+
+// returns cached page for give page index, or null if no such cached page
+static spiffs_cache_page *spiffs_cache_page_get(spiffs *fs, spiffs_page_ix pix) {
+  spiffs_cache *cache = spiffs_get_cache(fs);
+  if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) return 0;
+  int i;
+  for (i = 0; i < cache->cpage_count; i++) {
+    spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+    if ((cache->cpage_use_map & (1<<i)) &&
+        (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 &&
+        cp->pix == pix ) {
+      SPIFFS_CACHE_DBG("CACHE_GET: have cache page %i for %04x\n", i, pix);
+      cp->last_access = cache->last_access;
+      return cp;
+    }
+  }
+  //SPIFFS_CACHE_DBG("CACHE_GET: no cache for %04x\n", pix);
+  return 0;
+}
+
+// frees cached page
+static s32_t spiffs_cache_page_free(spiffs *fs, int ix, u8_t write_back) {
+  s32_t res = SPIFFS_OK;
+  spiffs_cache *cache = spiffs_get_cache(fs);
+  spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, ix);
+  if (cache->cpage_use_map & (1<<ix)) {
+    if (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);
+    }
+
+    cp->flags = 0;
+    cache->cpage_use_map &= ~(1 << ix);
+
+    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);
+    }
+  }
+
+  return res;
+}
+
+// removes the oldest accessed cached page
+static s32_t spiffs_cache_page_remove_oldest(spiffs *fs, u8_t flag_mask, u8_t flags) {
+  s32_t res = SPIFFS_OK;
+  spiffs_cache *cache = spiffs_get_cache(fs);
+
+  if ((cache->cpage_use_map & cache->cpage_use_mask) != cache->cpage_use_mask) {
+    // at least one free cpage
+    return SPIFFS_OK;
+  }
+
+  // all busy, scan thru all to find the cpage which has oldest access
+  int i;
+  int cand_ix = -1;
+  u32_t oldest_val = 0;
+  for (i = 0; i < cache->cpage_count; i++) {
+    spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+    if ((cache->last_access - cp->last_access) > oldest_val &&
+        (cp->flags & flag_mask) == flags) {
+      oldest_val = cache->last_access - cp->last_access;
+      cand_ix = i;
+    }
+  }
+
+  if (cand_ix >= 0) {
+    res = spiffs_cache_page_free(fs, cand_ix, 1);
+  }
+
+  return res;
+}
+
+// allocates a new cached page and returns it, or null if all cache pages are busy
+static spiffs_cache_page *spiffs_cache_page_allocate(spiffs *fs) {
+  spiffs_cache *cache = spiffs_get_cache(fs);
+  if (cache->cpage_use_map == 0xffffffff) {
+    // out of cache memory
+    return 0;
+  }
+  int i;
+  for (i = 0; i < cache->cpage_count; i++) {
+    if ((cache->cpage_use_map & (1<<i)) == 0) {
+      spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+      cache->cpage_use_map |= (1<<i);
+      cp->last_access = cache->last_access;
+      SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page %i\n", i);
+      return cp;
+    }
+  }
+  // out of cache entries
+  return 0;
+}
+
+// drops the cache page for give page index
+void spiffs_cache_drop_page(spiffs *fs, spiffs_page_ix pix) {
+  spiffs_cache_page *cp =  spiffs_cache_page_get(fs, pix);
+  if (cp) {
+    spiffs_cache_page_free(fs, cp->ix, 0);
+  }
+}
+
+// ------------------------------
+
+// reads from spi flash or the cache
+s32_t spiffs_phys_rd(
+    spiffs *fs,
+    u8_t op,
+    spiffs_file fh,
+    u32_t addr,
+    u32_t len,
+    u8_t *dst) {
+  s32_t res = SPIFFS_OK;
+  spiffs_cache *cache = spiffs_get_cache(fs);
+  spiffs_cache_page *cp =  spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr));
+  cache->last_access++;
+  if (cp) {
+#if SPIFFS_CACHE_STATS
+    fs->cache_hits++;
+#endif
+    cp->last_access = cache->last_access;
+  } 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);
+    }
+#if SPIFFS_CACHE_STATS
+    fs->cache_misses++;
+#endif
+    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);
+    }
+
+    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;
+    }
+  }
+  u8_t *mem =  spiffs_get_cache_page(fs, cache, cp->ix);
+  memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len);
+  return res;
+}
+
+// writes to spi flash and/or the cache
+s32_t spiffs_phys_wr(
+    spiffs *fs,
+    u8_t op,
+    spiffs_file fh,
+    u32_t addr,
+    u32_t len,
+    u8_t *src) {
+  spiffs_page_ix pix = SPIFFS_PADDR_TO_PAGE(fs, addr);
+  spiffs_cache *cache = spiffs_get_cache(fs);
+  spiffs_cache_page *cp =  spiffs_cache_page_get(fs, pix);
+
+  if (cp && (op & SPIFFS_OP_COM_MASK) != SPIFFS_OP_C_WRTHRU) {
+    // have a cache page
+    // copy in data to cache page
+
+    if ((op & SPIFFS_OP_COM_MASK) == SPIFFS_OP_C_DELE &&
+        (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);
+    }
+
+    u8_t *mem =  spiffs_get_cache_page(fs, cache, cp->ix);
+    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);
+    } else {
+      return SPIFFS_OK;
+    }
+  } else {
+    // no cache page, no write cache - just write thru
+    return fs->cfg.hal_write_f(addr, len, src);
+  }
+}
+
+#if SPIFFS_CACHE_WR
+// returns the cache page that this fd refers, or null if no cache page
+spiffs_cache_page *spiffs_cache_page_get_by_fd(spiffs *fs, spiffs_fd *fd) {
+  spiffs_cache *cache = spiffs_get_cache(fs);
+
+  if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) {
+    // all cpages free, no cpage cannot be assigned to obj_id
+    return 0;
+  }
+
+  int i;
+  for (i = 0; i < cache->cpage_count; i++) {
+    spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+    if ((cache->cpage_use_map & (1<<i)) &&
+        (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) &&
+        cp->obj_id == fd->obj_id) {
+      return cp;
+    }
+  }
+
+  return 0;
+}
+
+// allocates a new cache page and refers this to given fd - flushes an old cache
+// page if all cache is busy
+spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd) {
+  // before this function is called, it is ensured that there is no already existing
+  // cache page with same object id
+  spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0);
+  spiffs_cache_page *cp = spiffs_cache_page_allocate(fs);
+  if (cp == 0) {
+    // could not get cache page
+    return 0;
+  }
+
+  cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR;
+  cp->obj_id = fd->obj_id;
+  fd->cache_page = cp;
+  return cp;
+}
+
+// unrefers all fds that this cache page refers to and releases the cache page
+void spiffs_cache_fd_release(spiffs *fs, spiffs_cache_page *cp) {
+  if (cp == 0) return;
+  int i;
+  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->file_nbr != 0 && cur_fd->cache_page == cp) {
+      cur_fd->cache_page = 0;
+    }
+  }
+  spiffs_cache_page_free(fs, cp->ix, 0);
+
+  cp->obj_id = 0;
+}
+
+#endif
+
+// initializes the cache
+void spiffs_cache_init(spiffs *fs) {
+  if (fs->cache == 0) return;
+  u32_t sz = fs->cache_size;
+  u32_t cache_mask = 0;
+  int i;
+  int cache_entries =
+      (sz - sizeof(spiffs_cache)) / (SPIFFS_CACHE_PAGE_SIZE(fs));
+  if (cache_entries <= 0) return;
+
+  for (i = 0; i < cache_entries; i++) {
+    cache_mask <<= 1;
+    cache_mask |= 1;
+  }
+
+  spiffs_cache cache;
+  memset(&cache, 0, sizeof(spiffs_cache));
+  cache.cpage_count = cache_entries;
+  cache.cpages = (u8_t *)(fs->cache + sizeof(spiffs_cache));
+
+  cache.cpage_use_map = 0xffffffff;
+  cache.cpage_use_mask = cache_mask;
+  memcpy(fs->cache, &cache, sizeof(spiffs_cache));
+
+  spiffs_cache *c = spiffs_get_cache(fs);
+
+  memset(c->cpages, 0, c->cpage_count * SPIFFS_CACHE_PAGE_SIZE(fs));
+
+  c->cpage_use_map &= ~(c->cpage_use_mask);
+  for (i = 0; i < cache.cpage_count; i++) {
+    spiffs_get_cache_page_hdr(fs, c, i)->ix = i;
+  }
+}
+
+#endif // SPIFFS_CACHE
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spiffs_check.c	Thu Apr 22 03:49:45 2021 +0000
@@ -0,0 +1,966 @@
+/*
+ * spiffs_check.c
+ *
+ * Contains functionality for checking file system consistency
+ * and mending problems.
+ * Three levels of consistency checks are implemented:
+ *
+ * Look up consistency
+ *   Checks if indices in lookup pages are coherent with page headers
+ * Object index consistency
+ *   Checks if there are any orphaned object indices (missing object index headers).
+ *   If an object index is found but not its header, the object index is deleted.
+ *   This is critical for the following page consistency check.
+ * Page consistency
+ *   Checks for pages that ought to be indexed, ought not to be indexed, are multiple indexed
+ *
+ *
+ *  Created on: Jul 7, 2013
+ *      Author: petera
+ */
+
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+//---------------------------------------
+// Look up consistency
+
+// searches in the object indices and returns the referenced page index given
+// the object id and the data span index
+// destroys fs->lu_work
+static s32_t spiffs_object_get_data_page_index_reference(
+  spiffs *fs,
+  spiffs_obj_id obj_id,
+  spiffs_span_ix data_spix,
+  spiffs_page_ix *pix,
+  spiffs_page_ix *objix_pix) {
+  s32_t res;
+
+  // calculate object index span index for given data page span index
+  spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+
+  // find obj index for obj id and span index
+  res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, objix_pix);
+  SPIFFS_CHECK_RES(res);
+
+  // load obj index entry
+  u32_t addr = SPIFFS_PAGE_TO_PADDR(fs, *objix_pix);
+  if (objix_spix == 0) {
+    // get referenced page from object index header
+    addr += sizeof(spiffs_page_object_ix_header) + data_spix * sizeof(spiffs_page_ix);
+  } else {
+    // get referenced page from object index
+    addr += sizeof(spiffs_page_object_ix) + SPIFFS_OBJ_IX_ENTRY(fs, data_spix) * sizeof(spiffs_page_ix);
+  }
+
+  res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, addr, sizeof(spiffs_page_ix), (u8_t *)pix);
+
+  return res;
+}
+
+// copies page contents to a new page
+static s32_t spiffs_rewrite_page(spiffs *fs, spiffs_page_ix cur_pix, spiffs_page_header *p_hdr, spiffs_page_ix *new_pix) {
+  s32_t res;
+  res = spiffs_page_allocate_data(fs, p_hdr->obj_id, p_hdr, 0,0,0,0, new_pix);
+  SPIFFS_CHECK_RES(res);
+  res = spiffs_phys_cpy(fs, 0,
+      SPIFFS_PAGE_TO_PADDR(fs, *new_pix) + sizeof(spiffs_page_header),
+      SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header),
+      SPIFFS_DATA_PAGE_SIZE(fs));
+  SPIFFS_CHECK_RES(res);
+  return res;
+}
+
+// rewrites the object index for given object id and replaces the
+// data page index to a new page index
+static s32_t spiffs_rewrite_index(spiffs *fs, spiffs_obj_id obj_id, spiffs_span_ix data_spix, spiffs_page_ix new_data_pix, spiffs_page_ix objix_pix) {
+  s32_t res;
+  spiffs_block_ix bix;
+  int entry;
+  spiffs_page_ix free_pix;
+  obj_id |= SPIFFS_OBJ_ID_IX_FLAG;
+
+  // 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);
+  free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+
+  // calculate object index span index for given data page span index
+  spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+  if (objix_spix == 0) {
+    // calc index in index header
+    entry = data_spix;
+  } 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,
+      0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+  SPIFFS_CHECK_RES(res);
+  spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work;
+
+  // be ultra safe, double check header against provided data
+  if (objix_p_hdr->obj_id != obj_id) {
+    spiffs_page_delete(fs, free_pix);
+    return SPIFFS_ERR_CHECK_OBJ_ID_MISM;
+  }
+  if (objix_p_hdr->span_ix != objix_spix) {
+    spiffs_page_delete(fs, free_pix);
+    return SPIFFS_ERR_CHECK_SPIX_MISM;
+  }
+  if ((objix_p_hdr->flags & (SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_INDEX |
+                            SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) !=
+                                (SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_DELET)) {
+    spiffs_page_delete(fs, free_pix);
+    return SPIFFS_ERR_CHECK_FLAGS_BAD;
+  }
+
+  // rewrite in mem
+  if (objix_spix == 0) {
+    ((spiffs_page_ix*)((u8_t*)fs->lu_work + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix;
+  } else {
+    ((spiffs_page_ix*)((u8_t*)fs->lu_work + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix;
+  }
+
+  res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+      0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+  SPIFFS_CHECK_RES(res);
+  res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+      0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix),
+      sizeof(spiffs_obj_id),
+      (u8_t *)&obj_id);
+  SPIFFS_CHECK_RES(res);
+  res = spiffs_page_delete(fs, objix_pix);
+
+  return res;
+}
+
+// deletes an object just by marking object index header as deleted
+static s32_t spiffs_delete_obj_lazy(spiffs *fs, spiffs_obj_id obj_id) {
+  spiffs_page_ix objix_hdr_pix;
+  s32_t res;
+  res = spiffs_obj_lu_find_id_and_span(fs, obj_id, 0, 0, &objix_hdr_pix);
+  if (res == SPIFFS_ERR_NOT_FOUND) {
+    return SPIFFS_OK;
+  }
+  SPIFFS_CHECK_RES(res);
+  u8_t flags = 0xff & ~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);
+  return res;
+}
+
+// validates the given look up entry
+static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, spiffs_page_header *p_hdr,
+    spiffs_page_ix cur_pix, spiffs_block_ix cur_block, int cur_entry, int *reload_lu) {
+  u8_t delete_page = 0;
+  s32_t res = SPIFFS_OK;
+  spiffs_page_ix objix_pix;
+  spiffs_page_ix ref_pix;
+  // check validity, take actions
+  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);
+    *reload_lu = 1;
+    delete_page = 1;
+    if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) {
+      // header says data page
+      // data page can be removed if not referenced by some object index
+      res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix);
+      if (res == SPIFFS_ERR_NOT_FOUND) {
+        // no object with this id, so remove page safely
+        res = SPIFFS_OK;
+      } else {
+        SPIFFS_CHECK_RES(res);
+        if (ref_pix == cur_pix) {
+          // data page referenced by object index but deleted in lu
+          // 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_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);
+          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);
+            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);
+          } 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);
+          }
+          SPIFFS_CHECK_RES(res);
+        }
+      }
+    } else {
+      // header says index page
+      // index page can be removed if other index with same obj_id and spanix is found
+      res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, 0);
+      if (res == SPIFFS_ERR_NOT_FOUND) {
+        // no such index page found, check for a data page amongst page headers
+        // lu cannot be trusted
+        res = spiffs_obj_lu_find_id_and_span_by_phdr(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, 0);
+        if (res == SPIFFS_OK) { // ignore other errors
+          // 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_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);
+        }
+      } else {
+        SPIFFS_CHECK_RES(res);
+      }
+    }
+  }
+  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);
+      delete_page = 1;
+      if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0 ||
+          (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) ||
+          (p_hdr->flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_IXDELE)) == 0) {
+        // page deleted or not finalized, just remove it
+      } else {
+        if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) {
+          // if data page, check for reference to this page
+          res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix);
+          if (res == SPIFFS_ERR_NOT_FOUND) {
+            // no object with this id, so remove page safely
+            res = SPIFFS_OK;
+          } else {
+            SPIFFS_CHECK_RES(res);
+            //   if found, rewrite page with object id, update index, and delete current
+            if (ref_pix == cur_pix) {
+              spiffs_page_ix new_pix;
+              res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix);
+              SPIFFS_CHECK_RES(res);
+              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);
+                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);
+              }
+              SPIFFS_CHECK_RES(res);
+            }
+          }
+        } else {
+          // else if index, check for other pages with both obj_id's and spanix
+          spiffs_page_ix objix_pix_lu, objix_pix_ph;
+          // see if other object index page exists for lookup 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, 0, &objix_pix_lu);
+          if (res == SPIFFS_ERR_NOT_FOUND) {
+            res = SPIFFS_OK;
+            objix_pix_lu = 0;
+          }
+          SPIFFS_CHECK_RES(res);
+          // see if other object index exists for page header obj id and span index
+          res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_ph);
+          if (res == SPIFFS_ERR_NOT_FOUND) {
+            res = SPIFFS_OK;
+            objix_pix_ph = 0;
+          }
+          SPIFFS_CHECK_RES(res);
+          //   if both obj_id's found, just delete current
+          if (objix_pix_ph == 0 || objix_pix_lu == 0) {
+            // otherwise try finding first corresponding data pages
+            spiffs_page_ix data_pix_lu, data_pix_ph;
+            // see if other data page exists for look up obj id and span index
+            res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_lu);
+            if (res == SPIFFS_ERR_NOT_FOUND) {
+              res = SPIFFS_OK;
+              objix_pix_lu = 0;
+            }
+            SPIFFS_CHECK_RES(res);
+            // see if other data page exists for page header obj id and span index
+            res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_ph);
+            if (res == SPIFFS_ERR_NOT_FOUND) {
+              res = SPIFFS_OK;
+              objix_pix_ph = 0;
+            }
+            SPIFFS_CHECK_RES(res);
+
+            spiffs_page_header new_ph;
+            new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL);
+            new_ph.span_ix = p_hdr->span_ix;
+            spiffs_page_ix new_pix;
+            if ((objix_pix_lu && data_pix_lu && data_pix_ph && objix_pix_ph == 0) ||
+                (objix_pix_lu == 0 && data_pix_ph && objix_pix_ph == 0)) {
+              //   got a data page for page header obj id
+              //   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_RES(res);
+              *reload_lu = 1;
+            } else if ((objix_pix_ph && data_pix_ph && data_pix_lu && objix_pix_lu == 0) ||
+                (objix_pix_ph == 0 && data_pix_lu && objix_pix_lu == 0)) {
+              //   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);
+              res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix);
+              SPIFFS_CHECK_RES(res);
+              *reload_lu = 1;
+            } else {
+              // cannot safely do anything
+              SPIFFS_CHECK_DBG("LU: FIXUP: nothing to do, just delete\n");
+            }
+          }
+        }
+      }
+    } 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_page_ix data_pix, objix_pix;
+      // 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);
+      if (res == SPIFFS_ERR_NOT_FOUND) {
+        res = SPIFFS_OK;
+        data_pix = 0;
+      }
+      SPIFFS_CHECK_RES(res);
+      // see if other object index 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, &objix_pix);
+      if (res == SPIFFS_ERR_NOT_FOUND) {
+        res = SPIFFS_OK;
+        objix_pix = 0;
+      }
+      SPIFFS_CHECK_RES(res);
+
+      delete_page = 1;
+      // if other data page exists and object index exists, just delete page
+      if (data_pix && objix_pix) {
+        SPIFFS_CHECK_DBG("LU: FIXUP: other index and data page exists, simply remove\n");
+      } else
+      // if only data page exists, make this page index
+      if (data_pix && objix_pix == 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);
+        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);
+        new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG;
+        new_ph.span_ix = p_hdr->span_ix;
+        res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix);
+        SPIFFS_CHECK_RES(res);
+        res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header),
+            SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header),
+            SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header));
+        SPIFFS_CHECK_RES(res);
+      } else
+      // if only index exists, make data page
+      if (data_pix == 0 && objix_pix) {
+        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);
+        spiffs_page_header new_ph;
+        spiffs_page_ix new_pix;
+        new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL);
+        new_ph.obj_id = lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+        new_ph.span_ix = p_hdr->span_ix;
+        res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix);
+        SPIFFS_CHECK_RES(res);
+        res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header),
+            SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header),
+            SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header));
+        SPIFFS_CHECK_RES(res);
+      } else {
+        // if nothing exists, we cannot safely make a decision - delete
+      }
+    }
+    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);
+      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);
+      // 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);
+      if (res == SPIFFS_ERR_NOT_FOUND) {
+        // no object with this id, so remove page safely
+        res = SPIFFS_OK;
+        delete_page = 1;
+      } else {
+        SPIFFS_CHECK_RES(res);
+        if (ref_pix != cur_pix) {
+          SPIFFS_CHECK_DBG("LU: FIXUP: other finalized page is referred, just delete\n");
+          delete_page = 1;
+        } else {
+          // 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;
+          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);
+        }
+      }
+    }
+  }
+
+  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);
+    res = spiffs_page_delete(fs, cur_pix);
+    SPIFFS_CHECK_RES(res);
+  }
+
+  return res;
+}
+
+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) {
+  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,
+      (cur_block * 256)/fs->block_count, 0);
+
+  // load header
+  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);
+
+  int reload_lu = 0;
+
+  res = spiffs_lookup_check_validate(fs, obj_id, &p_hdr, cur_pix, cur_block, cur_entry, &reload_lu);
+  SPIFFS_CHECK_RES(res);
+
+  if (res == SPIFFS_OK) {
+    return reload_lu ? SPIFFS_VIS_COUNTINUE_RELOAD : SPIFFS_VIS_COUNTINUE;
+  }
+  return res;
+}
+
+
+// Scans all object look up. For each entry, corresponding page header is checked for validity.
+// If an object index header page is found, this is also checked
+s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t 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);
+
+  res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_lookup_check_v, 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_LOOKUP, SPIFFS_CHECK_ERROR, res, 0);
+  }
+
+  if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0);
+
+  return res;
+}
+
+//---------------------------------------
+// Page consistency
+
+// Scans all pages (except lu pages), reserves 4 bits in working memory for each page
+// bit 0: 0 == FREE|DELETED, 1 == USED
+// bit 1: 0 == UNREFERENCED, 1 == REFERENCED
+// bit 2: 0 == NOT_INDEX,    1 == INDEX
+// bit 3: unused
+// A consistent file system will have only pages being
+//  * x000 free, unreferenced, not index
+//  * x011 used, referenced only once, not index
+//  * x101 used, unreferenced, index
+// The working memory might not fit all pages so several scans might be needed
+static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
+  const u32_t bits = 4;
+  const spiffs_page_ix pages_per_scan = SPIFFS_CFG_LOG_PAGE_SZ(fs) * 8 / bits;
+
+  s32_t res = SPIFFS_OK;
+  spiffs_page_ix pix_offset = 0;
+
+  // for each range of pages fitting into work memory
+  while (pix_offset < SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) {
+    // set this flag to abort all checks and rescan the page range
+    u8_t restart = 0;
+    memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(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,
+          (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)) {
+        // read header
+        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);
+
+        u8_t within_range = (cur_pix >= pix_offset && cur_pix < pix_offset + pages_per_scan);
+        const u32_t pix_byte_ix = (cur_pix - pix_offset) / (8/bits);
+        const u8_t pix_bit_ix = (cur_pix & ((8/bits)-1)) * bits;
+
+        if (within_range &&
+            (p_hdr.flags & SPIFFS_PH_FLAG_DELET) && (p_hdr.flags & SPIFFS_PH_FLAG_USED) == 0) {
+          // used
+          fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 0));
+        }
+        if ((p_hdr.flags & SPIFFS_PH_FLAG_DELET) &&
+            (p_hdr.flags & SPIFFS_PH_FLAG_IXDELE) &&
+            (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) == 0) {
+          // found non-deleted index
+          if (within_range) {
+            fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 2));
+          }
+
+          // load non-deleted index
+          res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+              0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+          SPIFFS_CHECK_RES(res);
+
+          // traverse index for referenced pages
+          spiffs_page_ix *object_page_index;
+          spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work;
+
+          int entries;
+          int i;
+          spiffs_span_ix data_spix_offset;
+          if (p_hdr.span_ix == 0) {
+            // object header page index
+            entries = SPIFFS_OBJ_HDR_IX_LEN(fs);
+            data_spix_offset = 0;
+            object_page_index = (spiffs_page_ix *)((u8_t*)fs->lu_work + sizeof(spiffs_page_object_ix_header));
+          } else {
+            // object page index
+            entries = SPIFFS_OBJ_IX_LEN(fs);
+            data_spix_offset = SPIFFS_OBJ_HDR_IX_LEN(fs) + SPIFFS_OBJ_IX_LEN(fs) * (p_hdr.span_ix - 1);
+            object_page_index = (spiffs_page_ix *)((u8_t*)fs->lu_work + sizeof(spiffs_page_object_ix));
+          }
+
+          // for all entries in index
+          for (i = 0; !restart && i < entries; i++) {
+            spiffs_page_ix rpix = object_page_index[i];
+            u8_t rpix_within_range = rpix >= pix_offset && rpix < pix_offset + pages_per_scan;
+
+            if ((rpix != (spiffs_page_ix)-1 && rpix > SPIFFS_MAX_PAGES(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",
+                  rpix, cur_pix);
+              // check for data page elsewhere
+              spiffs_page_ix data_pix;
+              res = spiffs_obj_lu_find_id_and_span(fs, objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+                  data_spix_offset + i, 0, &data_pix);
+              if (res == SPIFFS_ERR_NOT_FOUND) {
+                res = SPIFFS_OK;
+                data_pix = 0;
+              }
+              SPIFFS_CHECK_RES(res);
+              if (data_pix == 0) {
+                // if not, allocate free page
+                spiffs_page_header new_ph;
+                new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL);
+                new_ph.obj_id = objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+                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);
+              }
+              // remap index
+              SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix %04x\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);
+                // 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);
+              }
+              SPIFFS_CHECK_RES(res);
+              restart = 1;
+
+            } else if (rpix_within_range) {
+
+              // valid reference
+              // read referenced page header
+              spiffs_page_header rp_hdr;
+              res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+                  0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr);
+              SPIFFS_CHECK_RES(res);
+
+              // cross reference page header check
+              if (rp_hdr.obj_id != (p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) ||
+                  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",
+                    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
+               spiffs_page_ix data_pix;
+               res = spiffs_obj_lu_find_id_and_span(fs, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+                   data_spix_offset + i, rpix, &data_pix);
+               if (res == SPIFFS_ERR_NOT_FOUND) {
+                 res = SPIFFS_OK;
+                 data_pix = 0;
+               }
+               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);
+                 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",
+                     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);
+                   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);
+                 }
+                 SPIFFS_CHECK_RES(res);
+                 restart = 1;
+               }
+              }
+              else {
+                // mark rpix as referenced
+                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",
+                      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",
+                      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);
+                  res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
+                  SPIFFS_CHECK_RES(res);
+                  // extra precaution, delete this page also
+                  res = spiffs_page_delete(fs, cur_pix);
+                  SPIFFS_CHECK_RES(res);
+                  restart = 1;
+                }
+                fs->work[rpix_byte_ix] |= (1<<(rpix_bit_ix + 1));
+              }
+            }
+          } // for all index entries
+        } // found index
+
+        // next page
+        cur_pix++;
+      }
+      // next block
+      cur_block++;
+    }
+    // check consistency bitmap
+    if (!restart) {
+      spiffs_page_ix objix_pix;
+      spiffs_page_ix rpix;
+
+      int byte_ix;
+      int bit_ix;
+      for (byte_ix = 0; !restart && byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs); byte_ix++) {
+        for (bit_ix = 0; !restart && bit_ix < 8/bits; bit_ix ++) {
+          u8_t bitmask = (fs->work[byte_ix] >> (bit_ix * bits)) & 0x7;
+          spiffs_page_ix cur_pix = pix_offset + byte_ix * (8/bits) + bit_ix;
+
+          // 000 ok - free, unreferenced, not index
+
+          if (bitmask == 0x1) {
+
+            // 001
+            SPIFFS_CHECK_DBG("PA: pix %04x USED, UNREFERENCED, not index\n", cur_pix);
+
+            u8_t rewrite_ix_to_this = 0;
+            u8_t delete_page = 0;
+            // check corresponding object index entry
+            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);
+
+            res = spiffs_object_get_data_page_index_reference(fs, p_hdr.obj_id, p_hdr.span_ix,
+                &rpix, &objix_pix);
+            if (res == SPIFFS_OK) {
+              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);
+              } else {
+                // pointing to something else, check what
+                spiffs_page_header rp_hdr;
+                res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+                    0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr);
+                SPIFFS_CHECK_RES(res);
+                if (((p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) == rp_hdr.obj_id) &&
+                    ((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);
+                  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,
+                        (rp_hdr.flags & SPIFFS_PH_FLAG_INDEX) ? "" : "INDEX ",
+                            (rp_hdr.flags & SPIFFS_PH_FLAG_DELET) ? "" : "DELETED ",
+                                (rp_hdr.flags & SPIFFS_PH_FLAG_USED) ? "NOTUSED " : "",
+                                    (rp_hdr.flags & SPIFFS_PH_FLAG_FINAL) ? "NOTFINAL " : "",
+                        cur_pix);
+                    rewrite_ix_to_this = 1;
+                  } else {
+                    // should not happen, destined for fubar
+                  }
+                }
+              }
+            } else if (res == SPIFFS_ERR_NOT_FOUND) {
+              SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete %04x\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",
+                  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);
+                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);
+              }
+              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);
+              res = spiffs_page_delete(fs, cur_pix);
+            }
+            SPIFFS_CHECK_RES(res);
+          }
+          if (bitmask == 0x2) {
+
+            // 010
+            SPIFFS_CHECK_DBG("PA: pix %04x FREE, REFERENCED, not index\n", cur_pix);
+
+            // no op, this should be taken care of when checking valid references
+          }
+
+          // 011 ok - busy, referenced, not index
+
+          if (bitmask == 0x4) {
+
+            // 100
+            SPIFFS_CHECK_DBG("PA: pix %04x FREE, unreferenced, INDEX\n", cur_pix);
+
+            // this should never happen, major fubar
+          }
+
+          // 101 ok - busy, unreferenced, index
+
+          if (bitmask == 0x6) {
+
+            // 110
+            SPIFFS_CHECK_DBG("PA: pix %04x 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);
+
+            // no op, this should be taken care of when checking valid references
+          }
+        }
+      }
+    }
+    // next page range
+    if (!restart) {
+      pix_offset += pages_per_scan;
+    }
+  } // while page range not reached end
+  return res;
+}
+
+// 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);
+  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);
+  }
+  if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0);
+  return res;
+}
+
+//---------------------------------------
+// Object index consistency
+
+// searches for given object id in temporary object id index,
+// returns the index or -1
+static int spiffs_object_index_search(spiffs *fs, spiffs_obj_id obj_id) {
+  int i;
+  spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work;
+  obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG;
+  for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id); i++) {
+    if ((obj_table[i] & ~SPIFFS_OBJ_ID_IX_FLAG) == obj_id) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+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) {
+  s32_t res_c = SPIFFS_VIS_COUNTINUE;
+  s32_t res = SPIFFS_OK;
+  u32_t *log_ix = (u32_t *)user_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,
+      (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)) {
+    spiffs_page_header p_hdr;
+    spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry);
+
+    // load header
+    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);
+
+    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",
+          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);
+      res = spiffs_page_delete(fs, cur_pix);
+      SPIFFS_CHECK_RES(res);
+      return res_c;
+    }
+
+    if ((p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) ==
+        (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) {
+      return res_c;
+    }
+
+    if (p_hdr.span_ix == 0) {
+      // objix header page, register objid as reachable
+      int r = spiffs_object_index_search(fs, obj_id);
+      if (r == -1) {
+        // not registered, do it
+        obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+        (*log_ix)++;
+        if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) {
+          *log_ix = 0;
+        }
+      }
+    } else { // span index
+      // objix page, see if header can be found
+      int r = spiffs_object_index_search(fs, obj_id);
+      u8_t delete = 0;
+      if (r == -1) {
+        // not in temporary index, try finding it
+        spiffs_page_ix objix_hdr_pix;
+        res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &objix_hdr_pix);
+        res_c = SPIFFS_VIS_COUNTINUE_RELOAD;
+        if (res == SPIFFS_OK) {
+          // found, register as reachable
+          obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+        } else if (res == SPIFFS_ERR_NOT_FOUND) {
+          // not found, register as unreachable
+          delete = 1;
+          obj_table[*log_ix] = obj_id | SPIFFS_OBJ_ID_IX_FLAG;
+        } else {
+          SPIFFS_CHECK_RES(res);
+        }
+        (*log_ix)++;
+        if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) {
+          *log_ix = 0;
+        }
+      } else {
+        // in temporary index, check reachable flag
+        if ((obj_table[r] & SPIFFS_OBJ_ID_IX_FLAG)) {
+          // registered as unreachable
+          delete = 1;
+        }
+      }
+
+      if (delete) {
+        SPIFFS_CHECK_DBG("IX: FIXUP: pix %04x, obj id:%04x spix:%04x 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);
+        res = spiffs_page_delete(fs, cur_pix);
+        SPIFFS_CHECK_RES(res);
+      }
+    } // span index
+  } // valid object index id
+
+  return res_c;
+}
+
+// Removes orphaned and partially deleted index pages.
+// Scans for index pages. When an index page is found, corresponding index header is searched for.
+// If no such page exists, the index page cannot be reached as no index header exists and must be
+// deleted.
+s32_t spiffs_object_index_consistency_check(spiffs *fs) {
+  s32_t res = SPIFFS_OK;
+  // impl note:
+  // fs->work is used for a temporary object index memory, listing found object ids and
+  // indicating whether they can be reached or not. Acting as a fifo if object ids cannot fit.
+  // In the temporary object index memory, SPIFFS_OBJ_ID_IX_FLAG bit is used to indicate
+  // 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);
+  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);
+  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);
+  }
+  if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0);
+  return res;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spiffs_config.h	Thu Apr 22 03:49:45 2021 +0000
@@ -0,0 +1,208 @@
+/*
+ * spiffs_config.h
+ *
+ *  Created on: Jul 3, 2013
+ *      Author: petera
+ */
+
+#ifndef SPIFFS_CONFIG_H_
+#define SPIFFS_CONFIG_H_
+
+// ----------- 8< ------------
+// Following includes are for the linux test build of spiffs
+// These may/should/must be removed/altered/replaced in your target
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+// ----------- >8 ------------
+
+typedef signed int          s32_t;
+typedef unsigned int        u32_t;
+typedef signed short        s16_t;
+typedef unsigned short      u16_t;
+typedef signed char         s8_t;;
+typedef unsigned char       u8_t;
+
+// compile time switches
+
+// Set generic spiffs debug output call.
+#ifndef SPIFFS_DGB
+#define SPIFFS_DBG(...)
+#endif
+// Set spiffs debug output call for garbage collecting.
+#ifndef SPIFFS_GC_DGB
+#define SPIFFS_GC_DBG(...)
+#endif
+// Set spiffs debug output call for caching.
+#ifndef SPIFFS_CACHE_DGB
+#define SPIFFS_CACHE_DBG(...)
+#endif
+// Set spiffs debug output call for system consistency checks.
+#ifndef SPIFFS_CHECK_DGB
+#define SPIFFS_CHECK_DBG(...)
+#endif
+
+// Enable/disable API functions to determine exact number of bytes
+// for filedescriptor and cache buffers. Once decided for a configuration,
+// this can be disabled to reduce flash.
+#ifndef SPIFFS_BUFFER_HELP
+#define SPIFFS_BUFFER_HELP              0
+#endif
+
+// Enables/disable memory read caching of nucleus file system operations.
+// If enabled, memory area must be provided for cache in SPIFFS_mount.
+#ifndef  SPIFFS_CACHE
+#define SPIFFS_CACHE                    1
+#endif
+#if SPIFFS_CACHE
+// Enables memory write caching for file descriptors in hydrogen
+#ifndef  SPIFFS_CACHE_WR
+#define SPIFFS_CACHE_WR                 1
+#endif
+
+// Enable/disable statistics on caching. Debug/test purpose only.
+#ifndef  SPIFFS_CACHE_STATS
+#define SPIFFS_CACHE_STATS              0
+#endif
+#endif
+
+// Always check header of each accessed page to ensure consistent state.
+// If enabled it will increase number of reads, will increase flash.
+#ifndef SPIFFS_PAGE_CHECK
+#define SPIFFS_PAGE_CHECK               1
+#endif
+
+// Define maximum number of gc runs to perform to reach desired free pages.
+#ifndef SPIFFS_GC_MAX_RUNS
+#define SPIFFS_GC_MAX_RUNS              3
+#endif
+
+// Enable/disable statistics on gc. Debug/test purpose only.
+#ifndef SPIFFS_GC_STATS
+#define SPIFFS_GC_STATS                 0
+#endif
+
+// Garbage collecting examines all pages in a block which and sums up
+// to a block score. Deleted pages normally gives positive score and
+// used pages normally gives a negative score (as these must be moved).
+// To have a fair wear-leveling, the erase age is also included in score,
+// whose factor normally is the most positive.
+// The larger the score, the more likely it is that the block will
+// picked for garbage collection.
+
+// Garbage collecting heuristics - weight used for deleted pages.
+#ifndef SPIFFS_GC_HEUR_W_DELET
+#define SPIFFS_GC_HEUR_W_DELET          (5)
+#endif
+// Garbage collecting heuristics - weight used for used pages.
+#ifndef SPIFFS_GC_HEUR_W_USED
+#define SPIFFS_GC_HEUR_W_USED           (-1)
+#endif
+// Garbage collecting heuristics - weight used for time between
+// last erased and erase of this block.
+#ifndef SPIFFS_GC_HEUR_W_ERASE_AGE
+#define SPIFFS_GC_HEUR_W_ERASE_AGE      (50)
+#endif
+
+// Object name maximum length.
+#ifndef SPIFFS_OBJ_NAME_LEN
+#define SPIFFS_OBJ_NAME_LEN             (32)
+#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.
+#ifndef SPIFFS_COPY_BUFFER_STACK
+#define SPIFFS_COPY_BUFFER_STACK        (64)
+#endif
+
+// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level
+// These should be defined on a multithreaded system
+
+// define this to entering a mutex if you're running on a multithreaded system
+#ifndef SPIFFS_LOCK
+#define SPIFFS_LOCK(fs)
+#endif
+// define this to exiting a mutex if you're running on a multithreaded system
+#ifndef SPIFFS_UNLOCK
+#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.
+#ifndef SPIFFS_SINGLETON
+#define SPIFFS_SINGLETON 0
+#endif
+
+#if SPIFFS_SINGLETON
+// Instead of giving parameters in config struct, singleton build must
+// give parameters in defines below.
+#ifndef SPIFFS_CFG_PHYS_SZ
+#define SPIFFS_CFG_PHYS_SZ(ignore)        (1024*1024*2)
+#endif
+#ifndef SPIFFS_CFG_PHYS_ERASE_SZ
+#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore)  (65536)
+#endif
+#ifndef SPIFFS_CFG_PHYS_ADDR
+#define SPIFFS_CFG_PHYS_ADDR(ignore)      (0)
+#endif
+#ifndef SPIFFS_CFG_LOG_PAGE_SZ
+#define SPIFFS_CFG_LOG_PAGE_SZ(ignore)    (256)
+#endif
+#ifndef SPIFFS_CFG_LOG_BLOCK_SZ
+#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore)   (65536)
+#endif
+#endif
+
+// Set SPFIFS_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         1
+#endif
+#if SPIFFS_TEST_VISUALISATION
+#ifndef spiffs_printf
+#define spiffs_printf(...)                printf(__VA_ARGS__)
+#endif
+// spiffs_printf argument for a free page
+#ifndef SPIFFS_TEST_VIS_FREE_STR
+#define SPIFFS_TEST_VIS_FREE_STR          "_"
+#endif
+// spiffs_printf argument for a deleted page
+#ifndef SPIFFS_TEST_VIS_DELE_STR
+#define SPIFFS_TEST_VIS_DELE_STR          "/"
+#endif
+// spiffs_printf argument for an index page for given object id
+#ifndef SPIFFS_TEST_VIS_INDX_STR
+#define SPIFFS_TEST_VIS_INDX_STR(id)      "i"
+#endif
+// spiffs_printf argument for a data page for given object id
+#ifndef SPIFFS_TEST_VIS_DATA_STR
+#define SPIFFS_TEST_VIS_DATA_STR(id)      "d"
+#endif
+#endif
+
+// Types depending on configuration such as the amount of flash bytes
+// given to spiffs file system in total (spiffs_file_system_size),
+// the logical block size (log_block_size), and the logical page size
+// (log_page_size)
+
+// Block index type. Make sure the size of this type can hold
+// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size
+typedef u8_t spiffs_block_ix;
+// Page index type. Make sure the size of this type can hold
+// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size
+typedef u16_t spiffs_page_ix;
+// Object id type - most significant bit is reserved for index flag. Make sure the
+// size of this type can hold the highest object id on a full system,
+// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2
+typedef u16_t spiffs_obj_id;
+// Object span index type. Make sure the size of this type can
+// hold the largest possible span index on the system -
+// i.e. (spiffs_file_system_size / log_page_size) - 1
+typedef u16_t spiffs_span_ix;
+
+#endif /* SPIFFS_CONFIG_H_ */
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spiffs_gc.c	Thu Apr 22 03:49:45 2021 +0000
@@ -0,0 +1,549 @@
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+// Erases a logical block and updates the erase counter.
+// If cache is enabled, all pages that might be cached in this block
+// is dropped.
+static s32_t spiffs_gc_erase_block(
+    spiffs *fs,
+    spiffs_block_ix bix) {
+  s32_t res;
+  u32_t addr = SPIFFS_BLOCK_TO_PADDR(fs, bix);
+  s32_t size = SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+
+  SPIFFS_GC_DBG("gc: erase block %i\n", bix);
+
+  // here we ignore res, just try erasing the block
+  while (size > 0) {
+    SPIFFS_GC_DBG("gc: erase %08x:%08x\n", addr,  SPIFFS_CFG_PHYS_ERASE_SZ(fs));
+    (void)fs->cfg.hal_erase_f(addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs));
+    addr += SPIFFS_CFG_PHYS_ERASE_SZ(fs);
+    size -= SPIFFS_CFG_PHYS_ERASE_SZ(fs);
+  }
+  fs->free_blocks++;
+
+  // register erase count for this block
+  res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0,
+      SPIFFS_ERASE_COUNT_PADDR(fs, bix),
+      sizeof(spiffs_obj_id), (u8_t *)&fs->max_erase_count);
+  SPIFFS_CHECK_RES(res);
+
+  fs->max_erase_count++;
+  if (fs->max_erase_count == SPIFFS_OBJ_ID_IX_FLAG) {
+    fs->max_erase_count = 0;
+  }
+
+#if SPIFFS_CACHE
+  {
+    int i;
+    for (i = 0; i < SPIFFS_PAGES_PER_BLOCK(fs); i++) {
+      spiffs_cache_drop_page(fs, SPIFFS_PAGE_FOR_BLOCK(fs, bix) + i);
+    }
+  }
+#endif
+  return res;
+}
+
+// Searches for blocks where all entries are deleted - if one is found,
+// the block is erased. Compared to the non-quick gc, the quick one ensures
+// that no updates are needed on existing objects on pages that are erased.
+s32_t spiffs_gc_quick(
+    spiffs *fs) {
+  s32_t res = SPIFFS_OK;
+  u32_t blocks = fs->block_count;
+  spiffs_block_ix cur_block = 0;
+  u32_t cur_block_addr = 0;
+  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);
+#if SPIFFS_GC_STATS
+  fs->stats_gc_runs++;
+#endif
+
+  u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+
+  // find fully deleted blocks
+  // check each block
+  while (res == SPIFFS_OK && blocks--) {
+    u16_t deleted_pages_in_block = 0;
+
+    int obj_lookup_page = 0;
+    // check each object lookup page
+    while (res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+      int entry_offset = obj_lookup_page * entries_per_page;
+      res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+          0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+      // check each entry
+      while (res == SPIFFS_OK &&
+          cur_entry - entry_offset < entries_per_page && cur_entry < SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+        spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+        if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+          deleted_pages_in_block++;
+        } else if (obj_id == SPIFFS_OBJ_ID_FREE) {
+          // kill scan, go for next block
+          obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs);
+          break;
+        }  else {
+          // kill scan, go for next block
+          obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs);
+          break;
+        }
+        cur_entry++;
+      } // per entry
+      obj_lookup_page++;
+    } // per object lookup page
+
+    if (res == SPIFFS_OK && deleted_pages_in_block == SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+      // found a fully deleted block
+      fs->stats_p_deleted -= deleted_pages_in_block;
+      res = spiffs_gc_erase_block(fs, cur_block);
+      return res;
+    }
+
+    cur_entry = 0;
+    cur_block++;
+    cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+  } // per block
+
+  return res;
+}
+
+// Checks if garbaga collecting is necessary. If so a candidate block is found,
+// cleansed and erased
+s32_t spiffs_gc_check(
+    spiffs *fs,
+    u32_t len) {
+  s32_t res;
+  u32_t free_pages =
+      (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * fs->block_count
+      - fs->stats_p_allocated - fs->stats_p_deleted;
+  int tries = 0;
+
+  if (fs->free_blocks > 3 &&
+      len < free_pages * SPIFFS_DATA_PAGE_SIZE(fs)) {
+    return SPIFFS_OK;
+  }
+
+  //printf("gcing started  %i dirty, blocks %i free, want %i bytes\n", fs->stats_p_allocated + fs->stats_p_deleted, fs->free_blocks, len);
+
+  do {
+    SPIFFS_GC_DBG("\ngc_check #%i: run gc free_blocks:%i pfree:%i pallo:%i pdele:%i [%i] len:%i of %i\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));
+
+    spiffs_block_ix *cands;
+    int count;
+    spiffs_block_ix cand;
+    res = spiffs_gc_find_candidate(fs, &cands, &count);
+    SPIFFS_CHECK_RES(res);
+    if (count == 0) {
+      SPIFFS_GC_DBG("gc_check: no candidates, return\n");
+      return res;
+    }
+#if SPIFFS_GC_STATS
+    fs->stats_gc_runs++;
+#endif
+    cand = cands[0];
+    fs->cleaning = 1;
+    //printf("gcing: cleaning block %i\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);
+    } else {
+      SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res);
+    }
+    SPIFFS_CHECK_RES(res);
+
+    res = spiffs_gc_erase_page_stats(fs, cand);
+    SPIFFS_CHECK_RES(res);
+
+    res = spiffs_gc_erase_block(fs, cand);
+    SPIFFS_CHECK_RES(res);
+
+    free_pages =
+          (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * fs->block_count
+          - fs->stats_p_allocated - fs->stats_p_deleted;
+
+  } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 ||
+      len > free_pages*SPIFFS_DATA_PAGE_SIZE(fs)));
+  SPIFFS_GC_DBG("gc_check: finished\n");
+
+  //printf("gcing finished %i dirty, blocks %i free, %i pages free, %i tries, res %i\n",
+  //    fs->stats_p_allocated + fs->stats_p_deleted,
+  //    fs->free_blocks, free_pages, tries, res);
+
+  return res;
+}
+
+// Updates page statistics for a block that is about to be erased
+s32_t spiffs_gc_erase_page_stats(
+    spiffs *fs,
+    spiffs_block_ix bix) {
+  s32_t res = SPIFFS_OK;
+  int obj_lookup_page = 0;
+  u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+  spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+  int cur_entry = 0;
+  u32_t dele = 0;
+  u32_t allo = 0;
+
+  // check each object lookup page
+  while (res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+    int entry_offset = obj_lookup_page * entries_per_page;
+    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
+    while (res == SPIFFS_OK &&
+        cur_entry - entry_offset < entries_per_page && cur_entry < SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+      spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+      if (obj_id == SPIFFS_OBJ_ID_FREE) {
+      } else if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+        dele++;
+      } else {
+        allo++;
+      }
+      cur_entry++;
+    } // per entry
+    obj_lookup_page++;
+  } // per object lookup page
+  SPIFFS_GC_DBG("gc_check: wipe pallo:%i pdele:%i\n", allo, dele);
+  fs->stats_p_allocated -= allo;
+  fs->stats_p_deleted -= dele;
+  return res;
+}
+
+// Finds block candidates to erase
+s32_t spiffs_gc_find_candidate(
+    spiffs *fs,
+    spiffs_block_ix **block_candidates,
+    int *candidate_count) {
+  s32_t res = SPIFFS_OK;
+  u32_t blocks = fs->block_count;
+  spiffs_block_ix cur_block = 0;
+  u32_t cur_block_addr = 0;
+  spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+  int cur_entry = 0;
+
+  // using fs->work area as sorted candidate memory, (spiffs_block_ix)cand_bix/(s32_t)score
+  int max_candidates = MIN(fs->block_count, (SPIFFS_CFG_LOG_PAGE_SZ(fs)-8)/(sizeof(spiffs_block_ix) + sizeof(s32_t)));
+  *candidate_count = 0;
+  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));
+
+  *block_candidates = cand_blocks;
+
+  u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+
+  // check each block
+  while (res == SPIFFS_OK && blocks--) {
+    u16_t deleted_pages_in_block = 0;
+    u16_t used_pages_in_block = 0;
+
+    int obj_lookup_page = 0;
+    // check each object lookup page
+    while (res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+      int entry_offset = obj_lookup_page * entries_per_page;
+      res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+          0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+      // check each entry
+      while (res == SPIFFS_OK &&
+          cur_entry - entry_offset < entries_per_page && cur_entry < SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+        spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+        if (obj_id == SPIFFS_OBJ_ID_FREE) {
+          // when a free entry is encountered, scan logic ensures that all following entries are free also
+          break;
+        } else  if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+          deleted_pages_in_block++;
+        } else {
+          used_pages_in_block++;
+        }
+        cur_entry++;
+      } // per entry
+      obj_lookup_page++;
+    } // per object lookup page
+
+    // calculate score and insert into candidate table
+    // stoneage sort, but probably not so many blocks
+    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,
+          SPIFFS_ERASE_COUNT_PADDR(fs, cur_block),
+          sizeof(spiffs_obj_id), (u8_t *)&erase_count);
+      SPIFFS_CHECK_RES(res);
+
+      spiffs_obj_id erase_age;
+      if (fs->max_erase_count > erase_count) {
+        erase_age = fs->max_erase_count - erase_count;
+      } else {
+        erase_age = SPIFFS_OBJ_ID_FREE - (erase_count - fs->max_erase_count);
+      }
+
+      s32_t score =
+          deleted_pages_in_block * SPIFFS_GC_HEUR_W_DELET +
+          used_pages_in_block * SPIFFS_GC_HEUR_W_USED +
+          erase_age * 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);
+      while (cand_ix < max_candidates) {
+        if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) {
+          cand_blocks[cand_ix] = cur_block;
+          cand_scores[cand_ix] = score;
+          break;
+        } else if (cand_scores[cand_ix] < score) {
+          int reorder_cand_ix = max_candidates - 2;
+          while (reorder_cand_ix >= cand_ix) {
+            cand_blocks[reorder_cand_ix + 1] = cand_blocks[reorder_cand_ix];
+            cand_scores[reorder_cand_ix + 1] = cand_scores[reorder_cand_ix];
+            reorder_cand_ix--;
+          }
+          cand_blocks[cand_ix] = cur_block;
+          cand_scores[cand_ix] = score;
+          break;
+        }
+        cand_ix++;
+      }
+      (*candidate_count)++;
+    }
+
+    cur_entry = 0;
+    cur_block++;
+    cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+  } // per block
+
+  return res;
+}
+
+typedef enum {
+  FIND_OBJ_DATA,
+  MOVE_OBJ_DATA,
+  MOVE_OBJ_IX,
+  FINISHED
+} spiffs_gc_clean_state;
+
+typedef struct {
+  spiffs_gc_clean_state state;
+  spiffs_obj_id cur_obj_id;
+  spiffs_span_ix cur_objix_spix;
+  spiffs_page_ix cur_objix_pix;
+  int stored_scan_entry_index;
+  u8_t obj_id_found;
+} spiffs_gc;
+
+// Empties given block by moving all data into free pages of another block
+// Strategy:
+//   loop:
+//   scan object lookup for object data pages
+//   for first found id, check spix and load corresponding object index page to memory
+//   push object scan lookup entry index
+//     rescan object lookup, find data pages with same id and referenced by same object index
+//     move data page, update object index in memory
+//     when reached end of lookup, store updated object index
+//   pop object scan lookup entry index
+//   repeat loop until end of object lookup
+//   scan object lookup again for remaining object index pages, move to new page in other block
+//
+s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
+  s32_t res = SPIFFS_OK;
+  u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+  int cur_entry = 0;
+  spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+  spiffs_gc gc;
+  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);
+
+  memset(&gc, 0, sizeof(spiffs_gc));
+  gc.state = FIND_OBJ_DATA;
+
+  if (fs->free_cursor_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);
+  }
+
+  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;
+
+    // scan through lookup pages
+    int obj_lookup_page = cur_entry / entries_per_page;
+    u8_t scan = 1;
+    // check each object lookup page
+    while (scan && res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+      int entry_offset = obj_lookup_page * entries_per_page;
+      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
+      while (scan && res == SPIFFS_OK &&
+          cur_entry - entry_offset < entries_per_page && cur_entry < SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+        spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+        cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, cur_entry);
+
+        // act upon object id depending on gc state
+        switch (gc.state) {
+        case FIND_OBJ_DATA:
+          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);
+            gc.obj_id_found = 1;
+            gc.cur_obj_id = obj_id;
+            scan = 0;
+          }
+          break;
+        case MOVE_OBJ_DATA:
+          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);
+            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 {
+              spiffs_page_ix new_data_pix;
+              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_CHECK_RES(res);
+                // 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_DATA wipe objix %04x:%04x page %04x\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;
+              }
+              // update memory representation of object index page with new data page
+              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));
+              } 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));
+              }
+            }
+          }
+          break;
+        case MOVE_OBJ_IX:
+          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
+            spiffs_page_header p_hdr;
+            spiffs_page_ix new_pix;
+            // load header
+            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);
+            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_CHECK_RES(res);
+              spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, 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);
+              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_CHECK_RES(res);
+          }
+          break;
+        default:
+          scan = 0;
+          break;
+        }
+        cur_entry++;
+      } // per entry
+      obj_lookup_page++;
+    } // 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) {
+        // 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.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);
+        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);
+        SPIFFS_CHECK_RES(res);
+        SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page %04x\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);
+        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 {
+        gc.state = MOVE_OBJ_IX;
+        cur_entry = 0; // restart entry scan index
+      }
+      break;
+    case MOVE_OBJ_DATA: {
+      // store modified objix (hdr) page
+      spiffs_page_ix new_objix_pix;
+      gc.state = FIND_OBJ_DATA;
+      cur_entry = gc.stored_scan_entry_index;
+      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);
+        SPIFFS_CHECK_RES(res);
+      } else {
+        // store object index page
+        spiffs_page_ix new_objix_pix;
+        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_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);
+      }
+    }
+    break;
+    case MOVE_OBJ_IX:
+      gc.state = FINISHED;
+      break;
+    default:
+      cur_entry = 0;
+      break;
+    }
+    SPIFFS_GC_DBG("gc_clean: state-> %i\n", gc.state);
+  } // while state != FINISHED
+
+
+  return res;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spiffs_hydrogen.c	Thu Apr 22 03:49:45 2021 +0000
@@ -0,0 +1,752 @@
+/*
+ * spiffs_hydrogen.c
+ *
+ *  Created on: Jun 16, 2013
+ *      Author: petera
+ */
+
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh);
+
+#if SPIFFS_BUFFER_HELP
+u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs) {
+  return num_descs * sizeof(spiffs_fd);
+}
+#if SPIFFS_CACHE
+u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages) {
+  return sizeof(spiffs_cache) + num_pages * (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs));
+}
+#endif
+#endif
+
+s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work,
+    u8_t *fd_space, u32_t fd_space_size,
+    u8_t *cache, u32_t cache_size,
+    spiffs_check_callback check_cb_f) {
+  SPIFFS_LOCK(fs);
+  memset(fs, 0, sizeof(spiffs));
+  memcpy(&fs->cfg, config, sizeof(spiffs_config));
+  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
+  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
+  if (addr_lsb) {
+    fd_space += (ptr_size-addr_lsb);
+    fd_space_size -= (ptr_size-addr_lsb);
+  }
+  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
+  if (addr_lsb) {
+    cache += (ptr_size-addr_lsb);
+    cache_size -= (ptr_size-addr_lsb);
+  }
+  if (cache_size & (ptr_size-1)) {
+    cache_size -= (cache_size & (ptr_size-1));
+  }
+#if SPIFFS_CACHE
+  fs->cache = cache;
+  fs->cache_size = cache_size;
+  spiffs_cache_init(fs);
+#endif
+
+  s32_t 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);
+
+  fs->check_cb_f = check_cb_f;
+
+  SPIFFS_UNLOCK(fs);
+
+  return 0;
+}
+
+void SPIFFS_unmount(spiffs *fs) {
+  if (!SPIFFS_CHECK_MOUNT(fs)) return;
+  SPIFFS_LOCK(fs);
+  int i;
+  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->file_nbr != 0) {
+#if SPIFFS_CACHE
+      (void)spiffs_fflush_cache(fs, cur_fd->file_nbr);
+#endif
+      spiffs_fd_return(fs, cur_fd->file_nbr);
+    }
+  }
+  fs->block_count = 0;
+  SPIFFS_UNLOCK(fs);
+}
+
+s32_t SPIFFS_errno(spiffs *fs) {
+  return fs->errno;
+}
+
+s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+  spiffs_obj_id obj_id;
+  s32_t res;
+
+  res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+  res = spiffs_object_create(fs, obj_id, (u8_t*)path, SPIFFS_TYPE_FILE, 0);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+  SPIFFS_UNLOCK(fs);
+  return 0;
+}
+
+spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  spiffs_fd *fd;
+  spiffs_page_ix pix;
+
+  s32_t res = spiffs_fd_find_new(fs, &fd);
+  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) {
+    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) {
+    spiffs_obj_id obj_id;
+    res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id);
+    if (res < SPIFFS_OK) {
+      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);
+    if (res < SPIFFS_OK) {
+      spiffs_fd_return(fs, fd->file_nbr);
+    }
+    SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+    flags &= ~SPIFFS_TRUNC;
+  } else {
+    if (res < SPIFFS_OK) {
+      spiffs_fd_return(fs, fd->file_nbr);
+    }
+    SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+  }
+  res = spiffs_object_open_by_page(fs, pix, fd, flags, flags);
+  if (res < SPIFFS_OK) {
+    spiffs_fd_return(fs, fd->file_nbr);
+  }
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+  if (flags & SPIFFS_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);
+  }
+
+  fd->fdoffset = 0;
+
+  SPIFFS_UNLOCK(fs);
+
+  return fd->file_nbr;
+}
+
+s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  spiffs_fd *fd;
+  s32_t res;
+
+  res = spiffs_fd_get(fs, fh, &fd);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+  if ((fd->flags & SPIFFS_RDONLY) == 0) {
+    res = SPIFFS_ERR_NOT_READABLE;
+    SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+  }
+
+#if SPIFFS_CACHE_WR
+  spiffs_fflush_cache(fs, fh);
+#endif
+
+  if (fd->fdoffset + len >= fd->size) {
+    // reading beyond file size
+    s32_t avail = fd->size - fd->fdoffset;
+    if (avail <= 0) {
+      SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_END_OF_OBJECT);
+    }
+    res = spiffs_object_read(fd, fd->fdoffset, avail, (u8_t*)buf);
+    if (res == SPIFFS_ERR_END_OF_OBJECT) {
+      fd->fdoffset += avail;
+      SPIFFS_UNLOCK(fs);
+      return avail;
+    } else {
+      SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+    }
+  } else {
+    // reading within file size
+    res = spiffs_object_read(fd, fd->fdoffset, len, (u8_t*)buf);
+    SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+  }
+  fd->fdoffset += len;
+
+  SPIFFS_UNLOCK(fs);
+
+  return len;
+}
+
+static s32_t spiffs_hydro_write(spiffs *fs, spiffs_fd *fd, void *buf, u32_t offset, s32_t len) {
+  s32_t res = SPIFFS_OK;
+  s32_t remaining = len;
+  if (fd->size != SPIFFS_UNDEFINED_LEN && offset < fd->size) {
+    s32_t m_len = MIN(fd->size - offset, len);
+    res = spiffs_object_modify(fd, offset, (u8_t *)buf, m_len);
+    SPIFFS_CHECK_RES(res);
+    remaining -= m_len;
+    buf = (u8_t*)buf + m_len;
+    offset += m_len;
+  }
+  if (remaining > 0) {
+    res = spiffs_object_append(fd, offset, (u8_t *)buf, remaining);
+    SPIFFS_CHECK_RES(res);
+  }
+  return len;
+
+}
+
+s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  spiffs_fd *fd;
+  s32_t res;
+  u32_t offset;
+
+  res = spiffs_fd_get(fs, fh, &fd);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+  if ((fd->flags & SPIFFS_WRONLY) == 0) {
+    res = SPIFFS_ERR_NOT_WRITABLE;
+    SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+  }
+
+  offset = fd->fdoffset;
+
+#if SPIFFS_CACHE_WR
+  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);
+  }
+#endif
+  if (fd->flags & SPIFFS_APPEND) {
+    if (fd->size == SPIFFS_UNDEFINED_LEN) {
+      offset = 0;
+    } else {
+      offset = fd->size;
+    }
+#if SPIFFS_CACHE_WR
+    if (fd->cache_page) {
+      offset = MAX(offset, fd->cache_page->offset + fd->cache_page->size);
+    }
+#endif
+  }
+
+  SPIFFS_DBG("SPIFFS_write %i %04x offs:%i len %i\n", fh, fd->obj_id, offset, len);
+
+#if SPIFFS_CACHE_WR
+  if ((fd->flags & SPIFFS_DIRECT) == 0) {
+    if (len < SPIFFS_CFG_LOG_PAGE_SZ(fs)) {
+      // small write, try to cache it
+      u8_t alloc_cpage = 1;
+      if (fd->cache_page) {
+        // have a cached page for this fd already, check cache page boundaries
+        if (offset < fd->cache_page->offset || // writing before cache
+            offset > fd->cache_page->offset + fd->cache_page->size || // writing after cache
+            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",
+              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);
+        } else {
+          // writing within cache
+          alloc_cpage = 0;
+        }
+      }
+
+      if (alloc_cpage) {
+        fd->cache_page = spiffs_cache_page_allocate_by_fd(fs, fd);
+        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",
+              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",
+            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);
+        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);
+        fd->fdoffset += len;
+        SPIFFS_UNLOCK(fs);
+        return res;
+      }
+    } else {
+      // 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",
+            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);
+        res = spiffs_hydro_write(fs, fd, buf, offset, len);
+        SPIFFS_API_CHECK_RES(fs, res);
+      }
+    }
+  }
+#endif
+
+  res = spiffs_hydro_write(fs, fd, buf, offset, len);
+  SPIFFS_API_CHECK_RES(fs, res);
+  fd->fdoffset += len;
+
+  SPIFFS_UNLOCK(fs);
+
+  return res;
+}
+
+s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  spiffs_fd *fd;
+  s32_t res;
+  res = spiffs_fd_get(fs, fh, &fd);
+  SPIFFS_API_CHECK_RES(fs, res);
+
+#if SPIFFS_CACHE_WR
+  spiffs_fflush_cache(fs, fh);
+#endif
+
+  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;
+    break;
+  }
+
+  if (offs > fd->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 objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+  if (fd->cursor_objix_spix != objix_spix) {
+    spiffs_page_ix pix;
+    res = spiffs_obj_lu_find_id_and_span(
+        fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, &pix);
+    SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+    fd->cursor_objix_spix = objix_spix;
+    fd->cursor_objix_pix = pix;
+  }
+  fd->fdoffset = offs;
+
+  SPIFFS_UNLOCK(fs);
+
+  return 0;
+}
+
+s32_t SPIFFS_remove(spiffs *fs, const char *path) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  spiffs_fd *fd;
+  spiffs_page_ix pix;
+  s32_t res;
+
+  res = spiffs_fd_find_new(fs, &fd);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+  res = spiffs_object_find_object_index_header_by_name(fs, (u8_t*)path, &pix);
+  if (res != SPIFFS_OK) {
+    spiffs_fd_return(fs, fd->file_nbr);
+  }
+  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_truncate(fd, 0, 1);
+  if (res != SPIFFS_OK) {
+    spiffs_fd_return(fs, fd->file_nbr);
+  }
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+  SPIFFS_UNLOCK(fs);
+  return 0;
+}
+
+s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  spiffs_fd *fd;
+  s32_t res;
+  res = spiffs_fd_get(fs, fh, &fd);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+  if ((fd->flags & SPIFFS_WRONLY) == 0) {
+    res = SPIFFS_ERR_NOT_WRITABLE;
+    SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+  }
+
+#if SPIFFS_CACHE_WR
+  spiffs_cache_fd_release(fs, fd->cache_page);
+#endif
+
+  res = spiffs_object_truncate(fd, 0, 1);
+
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+  SPIFFS_UNLOCK(fs);
+
+  return 0;
+}
+
+static s32_t spiffs_stat_pix(spiffs *fs, spiffs_page_ix pix, spiffs_file fh, spiffs_stat *s) {
+  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,
+      SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr);
+  SPIFFS_API_CHECK_RES(fs, res);
+
+  u32_t obj_id_addr = SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs , pix)) +
+      SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_obj_id);
+  res =_spiffs_rd(fs,  SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, fh,
+      obj_id_addr, sizeof(spiffs_obj_id), (u8_t *)&obj_id);
+  SPIFFS_API_CHECK_RES(fs, res);
+
+  s->obj_id = obj_id;
+  s->type = objix_hdr.type;
+  s->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size;
+  strncpy((char *)s->name, (char *)objix_hdr.name, SPIFFS_OBJ_NAME_LEN);
+
+  return res;
+}
+
+s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  s32_t res;
+  spiffs_page_ix pix;
+
+  res = spiffs_object_find_object_index_header_by_name(fs, (u8_t*)path, &pix);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+  res = spiffs_stat_pix(fs, pix, 0, s);
+
+  SPIFFS_UNLOCK(fs);
+
+  return res;
+}
+
+s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  spiffs_fd *fd;
+  s32_t res;
+
+  res = spiffs_fd_get(fs, fh, &fd);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+#if SPIFFS_CACHE_WR
+  spiffs_fflush_cache(fs, fh);
+#endif
+
+  res = spiffs_stat_pix(fs, fd->objix_hdr_pix, fh, s);
+
+  SPIFFS_UNLOCK(fs);
+
+  return res;
+}
+
+// Checks if there are any cached writes for the object id associated with
+// given filehandle. If so, these writes are flushed.
+static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh) {
+  s32_t res = SPIFFS_OK;
+#if 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->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",
+          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);
+      if (res < SPIFFS_OK) {
+        fs->errno = res;
+      }
+      spiffs_cache_fd_release(fs, fd->cache_page);
+    }
+  }
+#endif
+
+  return res;
+}
+
+s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh) {
+  SPIFFS_API_CHECK_MOUNT(fs);
+  s32_t res = SPIFFS_OK;
+#if SPIFFS_CACHE_WR
+  SPIFFS_LOCK(fs);
+  res = spiffs_fflush_cache(fs, fh);
+  SPIFFS_API_CHECK_RES_UNLOCK(fs,res);
+  SPIFFS_UNLOCK(fs);
+#endif
+
+  return res;
+}
+
+void SPIFFS_close(spiffs *fs, spiffs_file fh) {
+  if (!SPIFFS_CHECK_MOUNT(fs)) {
+    fs->errno = 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);
+}
+
+spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d) {
+  if (!SPIFFS_CHECK_MOUNT(fs)) {
+    fs->errno = SPIFFS_ERR_NOT_MOUNTED;
+    return 0;
+  }
+  d->fs = fs;
+  d->block = 0;
+  d->entry = 0;
+  return d;
+}
+
+static s32_t spiffs_read_dir_v(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_block_ix bix,
+    int ix_entry,
+    u32_t user_data,
+    void *user_p) {
+  s32_t res;
+  spiffs_page_object_ix_header objix_hdr;
+  if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED ||
+      (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) {
+    return SPIFFS_VIS_COUNTINUE;
+  }
+
+  spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry);
+  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_header), (u8_t *)&objix_hdr);
+  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)) ==
+          (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) {
+    struct spiffs_dirent *e = (struct spiffs_dirent *)user_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;
+    return SPIFFS_OK;
+  }
+
+  return SPIFFS_VIS_COUNTINUE;
+}
+
+struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e) {
+  if (!SPIFFS_CHECK_MOUNT(d->fs)) {
+    d->fs->errno = SPIFFS_ERR_NOT_MOUNTED;
+    return 0;
+  }
+  SPIFFS_LOCK(fs);
+
+  spiffs_block_ix bix;
+  int entry;
+  s32_t res;
+  struct spiffs_dirent *ret = 0;
+
+  res = spiffs_obj_lu_find_entry_visitor(d->fs,
+      d->block,
+      d->entry,
+      SPIFFS_VIS_NO_WRAP,
+      0,
+      spiffs_read_dir_v,
+      0,
+      e,
+      &bix,
+      &entry);
+  if (res == SPIFFS_OK) {
+    d->block = bix;
+    d->entry = entry + 1;
+    ret = e;
+  } else {
+    d->fs->errno = res;
+  }
+  SPIFFS_UNLOCK(fs);
+  return ret;
+}
+
+s32_t SPIFFS_closedir(spiffs_DIR *d) {
+  SPIFFS_API_CHECK_MOUNT(d->fs);
+  return 0;
+}
+
+s32_t SPIFFS_check(spiffs *fs) {
+  s32_t res;
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  res = spiffs_lookup_consistency_check(fs, 0);
+
+  res = spiffs_object_index_consistency_check(fs);
+
+  res = spiffs_page_consistency_check(fs);
+
+  res = spiffs_obj_lu_scan(fs);
+
+  SPIFFS_UNLOCK(fs);
+  return res;
+}
+
+#if SPIFFS_TEST_VISUALISATION
+s32_t SPIFFS_vis(spiffs *fs) {
+  s32_t res = SPIFFS_OK;
+  SPIFFS_API_CHECK_MOUNT(fs);
+  SPIFFS_LOCK(fs);
+
+  u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+  spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+  spiffs_block_ix bix = 0;
+
+  while (bix < fs->block_count) {
+    // check each object lookup page
+    int obj_lookup_page = 0;
+    int cur_entry = 0;
+
+    while (res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+      int entry_offset = obj_lookup_page * entries_per_page;
+      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
+      while (res == SPIFFS_OK &&
+          cur_entry - entry_offset < entries_per_page && cur_entry < 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);
+        } else if ((cur_entry & 0x3f) == 0) {
+          spiffs_printf("     ");
+        }
+        if (obj_id == SPIFFS_OBJ_ID_FREE) {
+          spiffs_printf(SPIFFS_TEST_VIS_FREE_STR);
+        } else if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+          spiffs_printf(SPIFFS_TEST_VIS_DELE_STR);
+        } else if (obj_id & SPIFFS_OBJ_ID_IX_FLAG){
+          spiffs_printf(SPIFFS_TEST_VIS_INDX_STR(obj_id));
+        } else {
+          spiffs_printf(SPIFFS_TEST_VIS_DATA_STR(obj_id));
+        }
+        cur_entry++;
+        if ((cur_entry & 0x3f) == 0) {
+          spiffs_printf("\n");
+        }
+      } // per entry
+      obj_lookup_page++;
+    } // per object lookup page
+
+    spiffs_obj_id erase_count;
+    res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0,
+        SPIFFS_ERASE_COUNT_PADDR(fs, bix),
+        sizeof(spiffs_obj_id), (u8_t *)&erase_count);
+    SPIFFS_CHECK_RES(res);
+
+    if (erase_count != (spiffs_obj_id)-1) {
+      spiffs_printf("\tera_cnt: %i\n", erase_count);
+    } else {
+      spiffs_printf("\tera_cnt: N/A\n");
+    }
+
+    bix++;
+  } // per block
+
+  spiffs_printf("era_cnt_max: %i\n", fs->max_erase_count);
+  spiffs_printf("last_errno:  %i\n", fs->errno);
+  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_UNLOCK(fs);
+  return res;
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spiffs_nucleus.c	Thu Apr 22 03:49:45 2021 +0000
@@ -0,0 +1,1760 @@
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+static s32_t spiffs_page_data_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) {
+    // referring to page 0xffff...., bad object index
+    return SPIFFS_ERR_INDEX_REF_FREE;
+  }
+  if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+    // referring to an object lookup page, bad object index
+    return SPIFFS_ERR_INDEX_REF_LU;
+  }
+  if (pix > SPIFFS_MAX_PAGES(fs)) {
+    // referring to a bad page
+    return SPIFFS_ERR_INDEX_REF_INVALID;
+  }
+#if SPIFFS_PAGE_CHECK
+  spiffs_page_header ph;
+  res = _spiffs_rd(
+      fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ,
+      fd->file_nbr,
+      SPIFFS_PAGE_TO_PADDR(fs, pix),
+      sizeof(spiffs_page_header),
+      (u8_t *)&ph);
+  SPIFFS_CHECK_RES(res);
+  SPIFFS_VALIDATE_DATA(ph, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, spix);
+#endif
+  return res;
+}
+
+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) {
+    // referring to page 0xffff...., bad object index
+    return SPIFFS_ERR_INDEX_FREE;
+  }
+  if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+    // referring to an object lookup page, bad object index
+    return SPIFFS_ERR_INDEX_LU;
+  }
+  if (pix > SPIFFS_MAX_PAGES(fs)) {
+    // referring to a bad page
+    return SPIFFS_ERR_INDEX_INVALID;
+  }
+#if SPIFFS_PAGE_CHECK
+  spiffs_page_header ph;
+  res = _spiffs_rd(
+      fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+      fd->file_nbr,
+      SPIFFS_PAGE_TO_PADDR(fs, pix),
+      sizeof(spiffs_page_header),
+      (u8_t *)&ph);
+  SPIFFS_CHECK_RES(res);
+  SPIFFS_VALIDATE_OBJIX(ph, fd->obj_id, spix);
+#endif
+  return res;
+}
+
+#if !SPIFFS_CACHE
+
+s32_t spiffs_phys_rd(
+    spiffs *fs,
+    u32_t addr,
+    u32_t len,
+    u8_t *dst) {
+  return fs->cfg.hal_read_f(addr, len, dst);
+}
+
+s32_t spiffs_phys_wr(
+    spiffs *fs,
+    u32_t addr,
+    u32_t len,
+    u8_t *src) {
+  return fs->cfg.hal_write_f(addr, len, src);
+}
+
+#endif
+
+s32_t spiffs_phys_cpy(
+    spiffs *fs,
+    spiffs_file fh,
+    u32_t dst,
+    u32_t src,
+    u32_t len) {
+  s32_t res;
+  u8_t b[SPIFFS_COPY_BUFFER_STACK];
+  while (len > 0) {
+    u32_t chunk_size = MIN(SPIFFS_COPY_BUFFER_STACK, len);
+    res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVS, fh, src, chunk_size, b);
+    SPIFFS_CHECK_RES(res);
+    res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVD,  fh, dst, chunk_size, b);
+    SPIFFS_CHECK_RES(res);
+    len -= chunk_size;
+    src += chunk_size;
+    dst += chunk_size;
+  }
+  return SPIFFS_OK;
+}
+
+// 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.
+// 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.
+// If no match is found in all look up, SPIFFS_VIS_END is returned.
+// @param fs                    the file system
+// @param starting_block        the starting block to start search in
+// @param starting_lu_entry     the look up index entry to start search in
+// @param flags                 ored combination of SPIFFS_VIS_CHECK_ID, SPIFFS_VIS_CHECK_PH,
+//                              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 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(
+    spiffs *fs,
+    spiffs_block_ix starting_block,
+    int starting_lu_entry,
+    u8_t flags,
+    spiffs_obj_id obj_id,
+    spiffs_visitor_f v,
+    u32_t user_data,
+    void *user_p,
+    spiffs_block_ix *block_ix,
+    int *lu_entry) {
+  s32_t res = SPIFFS_OK;
+  s32_t entry_count = fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs);
+  spiffs_block_ix cur_block = starting_block;
+  u32_t cur_block_addr = starting_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+
+  spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+  int cur_entry = starting_lu_entry;
+  u32_t entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+
+  // wrap initial
+  if (cur_entry >= SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) {
+    cur_entry = 0;
+    cur_block++;
+    cur_block_addr = cur_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+    if (cur_block >= fs->block_count) {
+      // block wrap
+      cur_block = 0;
+      cur_block_addr = 0;
+    }
+  }
+
+  // check each block
+  while (res == SPIFFS_OK && entry_count > 0) {
+    int obj_lookup_page = cur_entry / entries_per_page;
+    // check each object lookup page
+    while (res == SPIFFS_OK && obj_lookup_page < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+      int entry_offset = obj_lookup_page * entries_per_page;
+      res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+          0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+      // check each entry
+      while (res == SPIFFS_OK &&
+          cur_entry - entry_offset < entries_per_page && // for non-last obj lookup pages
+          cur_entry < SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) // for last obj lookup page
+      {
+        if ((flags & SPIFFS_VIS_CHECK_ID) == 0 || obj_lu_buf[cur_entry-entry_offset] == obj_id) {
+          if (block_ix) *block_ix = cur_block;
+          if (lu_entry) *lu_entry = cur_entry;
+          if (v) {
+            res = v(
+                fs,
+                (flags & SPIFFS_VIS_CHECK_PH) ? obj_id : obj_lu_buf[cur_entry-entry_offset],
+                cur_block,
+                cur_entry,
+                user_data,
+                user_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,
+                    0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+                SPIFFS_CHECK_RES(res);
+              }
+              res = SPIFFS_OK;
+              cur_entry++;
+              entry_count--;
+              continue;
+            } else {
+              return res;
+            }
+          } else {
+            return SPIFFS_OK;
+          }
+        }
+        entry_count--;
+        cur_entry++;
+      } // per entry
+      obj_lookup_page++;
+    } // per object lookup page
+    cur_entry = 0;
+    cur_block++;
+    cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+    if (cur_block >= fs->block_count) {
+      if (flags & SPIFFS_VIS_NO_WRAP) {
+        return SPIFFS_VIS_END;
+      } else {
+        // block wrap
+        cur_block = 0;
+        cur_block_addr = 0;
+      }
+    }
+  } // per block
+
+  SPIFFS_CHECK_RES(res);
+
+  return SPIFFS_VIS_END;
+}
+
+
+static s32_t spiffs_obj_lu_scan_v(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_block_ix bix,
+    int ix_entry,
+    u32_t user_data,
+    void *user_p) {
+  if (obj_id == SPIFFS_OBJ_ID_FREE) {
+    if (ix_entry == 0) {
+      fs->free_blocks++;
+      // todo optimize further, return SPIFFS_NEXT_BLOCK
+    }
+  } else if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+    fs->stats_p_deleted++;
+  } else {
+    fs->stats_p_allocated++;
+  }
+
+  return SPIFFS_VIS_COUNTINUE;
+}
+
+// Scans thru all obj lu and counts free, deleted and used pages
+// Find the maximum block erase count
+s32_t spiffs_obj_lu_scan(
+    spiffs *fs) {
+  s32_t res;
+  spiffs_block_ix bix;
+  int entry;
+
+  fs->free_blocks = 0;
+  fs->stats_p_allocated = 0;
+  fs->stats_p_deleted = 0;
+
+  res = spiffs_obj_lu_find_entry_visitor(fs,
+      0,
+      0,
+      0,
+      0,
+      spiffs_obj_lu_scan_v,
+      0,
+      0,
+      &bix,
+      &entry);
+
+  if (res == SPIFFS_VIS_END) {
+    res = SPIFFS_OK;
+  }
+
+  SPIFFS_CHECK_RES(res);
+
+  bix = 0;
+  spiffs_obj_id erase_count_final;
+  spiffs_obj_id erase_count_min = SPIFFS_OBJ_ID_FREE;
+  spiffs_obj_id erase_count_max = 0;
+  while (bix < fs->block_count) {
+    spiffs_obj_id erase_count;
+    res = _spiffs_rd(fs,
+        SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+        0, SPIFFS_ERASE_COUNT_PADDR(fs, bix) ,
+        sizeof(spiffs_obj_id), (u8_t *)&erase_count);
+    SPIFFS_CHECK_RES(res);
+    if (erase_count != SPIFFS_OBJ_ID_FREE) {
+      erase_count_min = MIN(erase_count_min, erase_count);
+      erase_count_max = MAX(erase_count_max, erase_count);
+    }
+    bix++;
+  }
+
+  if (erase_count_min == 0 && erase_count_max == SPIFFS_OBJ_ID_FREE) {
+    // clean system, set counter to zero
+    erase_count_final = 0;
+  } else if (erase_count_max - erase_count_min > (SPIFFS_OBJ_ID_FREE)/2) {
+    // wrap, take min
+    erase_count_final = erase_count_min+1;
+  } else {
+    erase_count_final = erase_count_max+1;
+  }
+
+  fs->max_erase_count = erase_count_final;
+
+  return res;
+}
+
+// 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(
+    spiffs *fs,
+    spiffs_block_ix starting_block,
+    int starting_lu_entry,
+    spiffs_block_ix *block_ix,
+    int *lu_entry) {
+  s32_t res;
+  if (!fs->cleaning && fs->free_blocks < 2) {
+    res = spiffs_gc_quick(fs);
+    SPIFFS_CHECK_RES(res);
+    if (fs->free_blocks < 2) {
+      return SPIFFS_ERR_FULL;
+    }
+  }
+  res = spiffs_obj_lu_find_id(fs, starting_block, starting_lu_entry,
+      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;
+    if (*lu_entry == 0) {
+      fs->free_blocks--;
+    }
+  }
+  if (res == SPIFFS_VIS_END) {
+    SPIFFS_DBG("fs full\n");
+  }
+
+  return res == SPIFFS_VIS_END ? SPIFFS_ERR_FULL : res;
+}
+
+// Find object lookup entry containing given id
+// Iterate over object lookup pages in each block until a given object id entry is found
+s32_t spiffs_obj_lu_find_id(
+    spiffs *fs,
+    spiffs_block_ix starting_block,
+    int starting_lu_entry,
+    spiffs_obj_id obj_id,
+    spiffs_block_ix *block_ix,
+    int *lu_entry) {
+  s32_t res = spiffs_obj_lu_find_entry_visitor(
+      fs, starting_block, starting_lu_entry, SPIFFS_VIS_CHECK_ID, obj_id, 0, 0, 0, block_ix, lu_entry);
+  if (res == SPIFFS_VIS_END) {
+    res = SPIFFS_ERR_NOT_FOUND;
+  }
+  return res;
+}
+
+
+static s32_t spiffs_obj_lu_find_id_and_span_v(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_block_ix bix,
+    int ix_entry,
+    u32_t user_data,
+    void *user_p) {
+  s32_t res;
+  spiffs_page_header ph;
+  spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry);
+  res = _spiffs_rd(fs, 0, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+      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.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)) {
+    return SPIFFS_OK;
+  } else {
+    return SPIFFS_VIS_COUNTINUE;
+  }
+}
+
+// Find object lookup entry containing given id and span index
+// Iterate over object lookup pages in each block until a given object id entry is found
+s32_t spiffs_obj_lu_find_id_and_span(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_span_ix spix,
+    spiffs_page_ix exclusion_pix,
+    spiffs_page_ix *pix) {
+  s32_t res;
+  spiffs_block_ix bix;
+  int entry;
+
+  res = spiffs_obj_lu_find_entry_visitor(fs,
+      fs->cursor_block_ix,
+      fs->cursor_obj_lu_entry,
+      SPIFFS_VIS_CHECK_ID,
+      obj_id,
+      spiffs_obj_lu_find_id_and_span_v,
+      (u32_t)spix,
+      exclusion_pix ? &exclusion_pix : 0,
+      &bix,
+      &entry);
+
+  if (res == SPIFFS_VIS_END) {
+    res = SPIFFS_ERR_NOT_FOUND;
+  }
+
+  SPIFFS_CHECK_RES(res);
+
+  if (pix) {
+    *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+  }
+
+  fs->cursor_block_ix = bix;
+  fs->cursor_obj_lu_entry = entry;
+
+  return res;
+}
+
+// Find object lookup entry containing given id and span index in page headers only
+// Iterate over object lookup pages in each block until a given object id entry is found
+s32_t spiffs_obj_lu_find_id_and_span_by_phdr(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_span_ix spix,
+    spiffs_page_ix exclusion_pix,
+    spiffs_page_ix *pix) {
+  s32_t res;
+  spiffs_block_ix bix;
+  int entry;
+
+  res = spiffs_obj_lu_find_entry_visitor(fs,
+      fs->cursor_block_ix,
+      fs->cursor_obj_lu_entry,
+      SPIFFS_VIS_CHECK_PH,
+      obj_id,
+      spiffs_obj_lu_find_id_and_span_v,
+      (u32_t)spix,
+      exclusion_pix ? &exclusion_pix : 0,
+      &bix,
+      &entry);
+
+  if (res == SPIFFS_VIS_END) {
+    res = SPIFFS_ERR_NOT_FOUND;
+  }
+
+  SPIFFS_CHECK_RES(res);
+
+  if (pix) {
+    *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+  }
+
+  fs->cursor_block_ix = bix;
+  fs->cursor_obj_lu_entry = entry;
+
+  return res;
+}
+
+// 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
+s32_t spiffs_page_allocate_data(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_page_header *ph,
+    u8_t *data,
+    u32_t len,
+    u32_t page_offs,
+    u8_t finalize,
+    spiffs_page_ix *pix) {
+  s32_t res = SPIFFS_OK;
+  spiffs_block_ix bix;
+  int entry;
+
+  // 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);
+
+  // occupy page in object lookup
+  res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+      0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id);
+  SPIFFS_CHECK_RES(res);
+
+  fs->stats_p_allocated++;
+
+  // write page header
+  ph->flags &= ~SPIFFS_PH_FLAG_USED;
+  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_header), (u8_t*)ph);
+  SPIFFS_CHECK_RES(res);
+
+  // write page data
+  if (data) {
+    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_header) + page_offs, len, data);
+    SPIFFS_CHECK_RES(res);
+  }
+
+  // finalize header if necessary
+  if (finalize && (ph->flags & SPIFFS_PH_FLAG_FINAL)) {
+    ph->flags &= ~SPIFFS_PH_FLAG_FINAL;
+    res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+        0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + offsetof(spiffs_page_header, flags),
+        sizeof(u8_t),
+        (u8_t *)&ph->flags);
+    SPIFFS_CHECK_RES(res);
+  }
+
+  // return written page
+  if (pix) {
+    *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+  }
+
+  return res;
+}
+
+// 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(
+    spiffs *fs,
+    spiffs_file fh,
+    u8_t *page_data,
+    spiffs_obj_id obj_id,
+    spiffs_page_header *page_hdr,
+    spiffs_page_ix src_pix,
+    spiffs_page_ix *dst_pix) {
+  s32_t res;
+  u8_t was_final = 0;
+  spiffs_page_header *p_hdr;
+  spiffs_block_ix bix;
+  int entry;
+  spiffs_page_ix free_pix;
+
+  // 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);
+  free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+
+  if (dst_pix) *dst_pix = free_pix;
+
+  p_hdr = page_data ? (spiffs_page_header *)page_data : page_hdr;
+  if (page_data) {
+    // got page data
+    was_final = (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) == 0;
+    // write unfinalized page
+    p_hdr->flags |= SPIFFS_PH_FLAG_FINAL;
+    p_hdr->flags &= ~SPIFFS_PH_FLAG_USED;
+    res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+        0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), page_data);
+  } else {
+    // copy page data
+    res = spiffs_phys_cpy(fs, fh, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_PAGE_TO_PADDR(fs, src_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs));
+  }
+  SPIFFS_CHECK_RES(res);
+
+  // mark entry in destination object lookup
+  res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+      0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix),
+      sizeof(spiffs_obj_id),
+      (u8_t *)&obj_id);
+  SPIFFS_CHECK_RES(res);
+
+  fs->stats_p_allocated++;
+
+  if (was_final) {
+    // mark finalized in destination page
+    p_hdr->flags &= ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_USED);
+    res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+        fh,
+        SPIFFS_PAGE_TO_PADDR(fs, free_pix) + offsetof(spiffs_page_header, flags),
+        sizeof(u8_t),
+        (u8_t *)&p_hdr->flags);
+    SPIFFS_CHECK_RES(res);
+  }
+  // mark source deleted
+  res = spiffs_page_delete(fs, src_pix);
+  return res;
+}
+
+// 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,
+      0,
+      SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_page_ix),
+      sizeof(spiffs_obj_id),
+      (u8_t *)&d_obj_id);
+  SPIFFS_CHECK_RES(res);
+
+  fs->stats_p_deleted++;
+  fs->stats_p_allocated--;
+
+  // mark deleted in source page
+  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);
+
+  return res;
+}
+
+// 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],
+    spiffs_obj_type type,
+    spiffs_page_ix *objix_hdr_pix) {
+  s32_t res = SPIFFS_OK;
+  spiffs_block_ix bix;
+  spiffs_page_object_ix_header oix_hdr;
+  int entry;
+
+  res = spiffs_gc_check(fs, 0);
+  SPIFFS_CHECK_RES(res);
+
+  obj_id |= SPIFFS_OBJ_ID_IX_FLAG;
+
+  // 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);
+
+  // occupy page in object lookup
+  res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+      0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id);
+  SPIFFS_CHECK_RES(res);
+
+  fs->stats_p_allocated++;
+
+  // write empty object index page
+  oix_hdr.p_hdr.obj_id = obj_id;
+  oix_hdr.p_hdr.span_ix = 0;
+  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);
+
+
+  // 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);
+
+  if (objix_hdr_pix) {
+    *objix_hdr_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+  }
+
+  return res;
+}
+
+// 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
+// size may be null, if so size is not changed
+s32_t spiffs_object_update_index_hdr(
+    spiffs *fs,
+    spiffs_fd *fd,
+    spiffs_obj_id obj_id,
+    spiffs_page_ix objix_hdr_pix,
+    u8_t *new_objix_hdr_data,
+    u8_t name[SPIFFS_OBJ_NAME_LEN],
+    u32_t size,
+    spiffs_page_ix *new_pix) {
+  s32_t res = SPIFFS_OK;
+  spiffs_page_object_ix_header *objix_hdr;
+  spiffs_page_ix new_objix_hdr_pix;
+
+  obj_id |=  SPIFFS_OBJ_ID_IX_FLAG;
+
+  if (new_objix_hdr_data) {
+    // object index header page already given to us, no need to load it
+    objix_hdr = (spiffs_page_object_ix_header *)new_objix_hdr_data;
+  } else {
+    // read object index header page
+    res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+        fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+    SPIFFS_CHECK_RES(res);
+    objix_hdr = (spiffs_page_object_ix_header *)fs->work;
+  }
+
+  SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, obj_id, 0);
+
+  // change name
+  if (name) {
+    strncpy((char *)objix_hdr->name, (char *)name, SPIFFS_OBJ_NAME_LEN);
+  }
+  if (size) {
+    objix_hdr->size = size;
+  }
+
+  // move and update page
+  res = spiffs_page_move(fs, fd == 0 ? 0 : fd->file_nbr, (u8_t*)objix_hdr, obj_id, 0, objix_hdr_pix, &new_objix_hdr_pix);
+
+  if (res == SPIFFS_OK) {
+    if (new_pix) {
+      *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);
+    if (fd) fd->objix_hdr_pix = new_objix_hdr_pix; // if this is not in the registered cluster
+  }
+
+  return res;
+}
+
+void spiffs_cb_object_event(
+    spiffs *fs,
+    spiffs_fd *fd,
+    int ev,
+    spiffs_obj_id obj_id,
+    spiffs_span_ix spix,
+    spiffs_page_ix new_pix,
+    u32_t new_size) {
+  // update index caches in all file descriptors
+  obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG;
+  int i;
+  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->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);
+        cur_fd->objix_hdr_pix = new_pix;
+        if (new_size != 0) {
+          cur_fd->size = new_size;
+        }
+      } else if (ev == SPIFFS_EV_IX_DEL) {
+        cur_fd->file_nbr = 0;
+        cur_fd->obj_id = SPIFFS_OBJ_ID_DELETED;
+      }
+    }
+    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);
+        cur_fd->cursor_objix_pix = new_pix;
+      } else {
+        cur_fd->cursor_objix_pix = 0;
+      }
+    }
+  }
+}
+
+// Open object by id
+s32_t spiffs_object_open_by_id(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_fd *fd,
+    spiffs_flags flags,
+    spiffs_mode mode) {
+  s32_t res = SPIFFS_OK;
+  spiffs_page_ix pix;
+
+  res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
+  SPIFFS_CHECK_RES(res);
+
+  res = spiffs_object_open_by_page(fs, pix, fd, flags, mode);
+
+  return res;
+}
+
+// Open object by page index
+s32_t spiffs_object_open_by_page(
+    spiffs *fs,
+    spiffs_page_ix pix,
+    spiffs_fd *fd,
+    spiffs_flags flags,
+    spiffs_mode mode) {
+  s32_t res = SPIFFS_OK;
+  spiffs_page_object_ix_header oix_hdr;
+  spiffs_obj_id obj_id;
+
+  res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+      fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&oix_hdr);
+  SPIFFS_CHECK_RES(res);
+
+  spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(fs, pix);
+  int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix);
+
+  res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+      0,  SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t *)&obj_id);
+
+  fd->fs = fs;
+  fd->objix_hdr_pix = pix;
+  fd->size = oix_hdr.size;
+  fd->offset = 0;
+  fd->cursor_objix_pix = pix;
+  fd->cursor_objix_spix = 0;
+  fd->obj_id = obj_id;
+  fd->flags = flags;
+
+  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);
+
+  return res;
+}
+
+// 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) {
+  spiffs *fs = fd->fs;
+  s32_t res = SPIFFS_OK;
+  u32_t written = 0;
+
+  res = spiffs_gc_check(fs, len);
+  SPIFFS_CHECK_RES(res);
+
+  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_page_header p_hdr;
+
+  spiffs_span_ix cur_objix_spix = 0;
+  spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1;
+  spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix;
+  spiffs_page_ix new_objix_hdr_page;
+
+  spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs);
+  spiffs_page_ix data_page;
+  u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs);
+
+  // write all data
+  while (res == SPIFFS_OK && written < len) {
+    // calculate object index page span index
+    cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+
+    // handle storing and loading of object indices
+    if (cur_objix_spix != prev_objix_spix) {
+      // new object index page
+      // 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,
+            cur_objix_pix, prev_objix_spix, written);
+        if (prev_objix_spix == 0) {
+          // this is an update to object index header page
+          objix_hdr->size = offset+written;
+          if (offset == 0) {
+            // was an empty object, update same page (size was 0xffffffff)
+            res = spiffs_page_index_check(fs, fd, cur_objix_pix, 0);
+            SPIFFS_CHECK_RES(res);
+            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);
+          } 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);
+            SPIFFS_CHECK_RES(res);
+            SPIFFS_DBG("append: %04x store new objix_hdr, %04x:%04x, written %i\n", fd->obj_id,
+                new_objix_hdr_page, 0, written);
+          }
+        } else {
+          // this is an update to an object index page
+          res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix);
+          SPIFFS_CHECK_RES(res);
+
+          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);
+          // 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);
+          SPIFFS_CHECK_RES(res);
+          SPIFFS_DBG("append: %04x store new size I %i in objix_hdr, %04x:%04x, written %i\n", fd->obj_id,
+              offset+written, new_objix_hdr_page, 0, written);
+        }
+        fd->size = offset+written;
+        fd->offset = offset+written;
+      }
+
+      // 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);
+        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);
+        SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+      } else {
+        spiffs_span_ix len_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, (fd->size-1)/SPIFFS_DATA_PAGE_SIZE(fs));
+        // on subsequent passes, create a new object index page
+        if (written > 0 || cur_objix_spix > len_objix_spix) {
+          p_hdr.obj_id = fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG;
+          p_hdr.span_ix = cur_objix_spix;
+          p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX);
+          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
+              , 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);
+          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\n", fd->obj_id, 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);
+          SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+          cur_objix_pix = pix;
+        }
+        fd->cursor_objix_pix = cur_objix_pix;
+        fd->cursor_objix_spix = cur_objix_spix;
+        fd->offset = offset+written;
+        fd->size = offset+written;
+      }
+      prev_objix_spix = cur_objix_spix;
+    }
+
+    // write data
+    u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs);
+    if (page_offs == 0) {
+      // at beginning of a page, allocate and write a new page of data
+      p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+      p_hdr.span_ix = data_spix;
+      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,
+          data_page, data_spix, page_offs, to_write, written);
+    } else {
+      // append to existing page, fill out free data in existing page
+      if (cur_objix_spix == 0) {
+        // get data page from object index header page
+        data_page = ((spiffs_page_ix*)((u8_t*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix];
+      } else {
+        // get data page from object index page
+        data_page = ((spiffs_page_ix*)((u8_t*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)];
+      }
+
+      res = spiffs_page_data_check(fs, fd, data_page, data_spix);
+      SPIFFS_CHECK_RES(res);
+
+      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
+          , data_page, data_spix, page_offs, to_write, written);
+    }
+
+    if (res != SPIFFS_OK) break;
+
+    // update memory representation of object index page with new data page
+    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
+          , 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));
+    }
+
+    // update internals
+    page_offs = 0;
+    data_spix++;
+    written += to_write;
+  } // while all data
+
+  fd->size = offset+written;
+  fd->offset = offset+written;
+  fd->cursor_objix_pix = cur_objix_pix;
+  fd->cursor_objix_spix = cur_objix_spix;
+
+  // finalize updated object indices
+  s32_t res2 = SPIFFS_OK;
+  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,
+        cur_objix_pix, cur_objix_spix, written);
+
+    res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix);
+    SPIFFS_CHECK_RES(res2);
+
+    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);
+
+    // 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\n", fd->obj_id
+        , offset+written, new_objix_hdr_page, 0, written);
+    SPIFFS_CHECK_RES(res2);
+  } else {
+    // wrote within object index header page
+    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
+          , cur_objix_pix, cur_objix_spix, written);
+
+      res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix);
+      SPIFFS_CHECK_RES(res2);
+
+      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);
+      // 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);
+    } 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
+          , new_objix_hdr_page, 0, written);
+      SPIFFS_CHECK_RES(res2);
+    }
+  }
+
+  return res;
+}
+
+// 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) {
+  spiffs *fs = fd->fs;
+  s32_t res = SPIFFS_OK;
+  u32_t written = 0;
+
+  res = spiffs_gc_check(fs, len);
+  SPIFFS_CHECK_RES(res);
+
+  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_page_header p_hdr;
+
+  spiffs_span_ix cur_objix_spix = 0;
+  spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1;
+  spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix;
+  spiffs_page_ix new_objix_hdr_pix;
+
+  spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs);
+  spiffs_page_ix data_pix;
+  u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs);
+
+
+  // write all data
+  while (res == SPIFFS_OK && written < len) {
+    // calculate object index page span index
+    cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+
+    // handle storing and loading of object indices
+    if (cur_objix_spix != prev_objix_spix) {
+      // new object index page
+      // within this clause we return directly if something fails, object index mess-up
+      if (written > 0) {
+        // store previous object index (header) page, unless first pass
+        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);
+          SPIFFS_CHECK_RES(res);
+        } else {
+          // store new version of previous object index page
+          spiffs_page_ix new_objix_pix;
+
+          res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix);
+          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_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);
+        }
+      }
+
+      // 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);
+        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);
+        SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+      } 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);
+        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);
+        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);
+        SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+        cur_objix_pix = pix;
+      }
+      fd->cursor_objix_pix = cur_objix_pix;
+      fd->cursor_objix_spix = cur_objix_spix;
+      fd->offset = offset+written;
+      prev_objix_spix = cur_objix_spix;
+    }
+
+    // write partial data
+    u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs);
+    spiffs_page_ix orig_data_pix;
+    if (cur_objix_spix == 0) {
+      // get data page from object index header page
+      orig_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
+      orig_data_pix = ((spiffs_page_ix*)((u8_t*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)];
+    }
+
+    p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+    p_hdr.span_ix = data_spix;
+    p_hdr.flags = 0xff;
+    if (page_offs == 0 && to_write == SPIFFS_DATA_PAGE_SIZE(fs)) {
+      // 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);
+    } else {
+      // write to existing page, allocate new and copy unmodified data
+
+      res = spiffs_page_data_check(fs, fd, orig_data_pix, data_spix);
+      SPIFFS_CHECK_RES(res);
+
+      res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+          &p_hdr, 0, 0, 0, 0, &data_pix);
+      if (res != SPIFFS_OK) break;
+
+      // copy unmodified data
+      if (page_offs > 0) {
+        // before modification
+        res = spiffs_phys_cpy(fs, fd->file_nbr,
+            SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header),
+            SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header),
+            page_offs);
+        if (res != SPIFFS_OK) break;
+      }
+      if (page_offs + to_write < SPIFFS_DATA_PAGE_SIZE(fs)) {
+        // after modification
+        res = spiffs_phys_cpy(fs, fd->file_nbr,
+            SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs + to_write,
+            SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header) + page_offs + to_write,
+            SPIFFS_DATA_PAGE_SIZE(fs) - (page_offs + to_write));
+        if (res != SPIFFS_OK) break;
+      }
+
+      res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+          fd->file_nbr,
+          SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]);
+      if (res != SPIFFS_OK) break;
+      p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL;
+      res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+          fd->file_nbr,
+          SPIFFS_PAGE_TO_PADDR(fs, data_pix) + offsetof(spiffs_page_header, flags),
+          sizeof(u8_t),
+          (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);
+    }
+
+    // delete original data page
+    res = spiffs_page_delete(fs, orig_data_pix);
+    if (res != SPIFFS_OK) break;
+    // update memory representation of object index page with new data page
+    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);
+    } 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));
+    }
+
+    // update internals
+    page_offs = 0;
+    data_spix++;
+    written += to_write;
+  } // while all data
+
+  fd->offset = offset+written;
+  fd->cursor_objix_pix = cur_objix_pix;
+  fd->cursor_objix_spix = cur_objix_spix;
+
+  // finalize updated object indices
+  s32_t res2 = SPIFFS_OK;
+  if (cur_objix_spix != 0) {
+    // wrote beyond object index header page
+    // write last modified object index page
+    // move and update page
+    spiffs_page_ix new_objix_pix;
+
+    res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix);
+    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);
+    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);
+
+  } 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);
+    SPIFFS_CHECK_RES(res2);
+  }
+
+  return res;
+}
+
+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) {
+  s32_t res;
+  spiffs_page_object_ix_header objix_hdr;
+  spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry);
+  if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED ||
+      (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) {
+    return SPIFFS_VIS_COUNTINUE;
+  }
+  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_header), (u8_t *)&objix_hdr);
+  SPIFFS_CHECK_RES(res);
+  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) {
+      return SPIFFS_OK;
+    }
+  }
+
+  return SPIFFS_VIS_COUNTINUE;
+}
+
+// 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],
+    spiffs_page_ix *pix) {
+  s32_t res;
+  spiffs_block_ix bix;
+  int entry;
+
+  res = spiffs_obj_lu_find_entry_visitor(fs,
+      fs->cursor_block_ix,
+      fs->cursor_obj_lu_entry,
+      0,
+      0,
+      spiffs_object_find_object_index_header_by_name_v,
+      0,
+      name,
+      &bix,
+      &entry);
+
+  if (res == SPIFFS_VIS_END) {
+    res = SPIFFS_ERR_NOT_FOUND;
+  }
+  SPIFFS_CHECK_RES(res);
+
+  if (pix) {
+    *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+  }
+
+  fs->cursor_block_ix = bix;
+  fs->cursor_obj_lu_entry = entry;
+
+  return res;
+}
+
+// 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) {
+  s32_t res = SPIFFS_OK;
+  spiffs *fs = fd->fs;
+
+  res = spiffs_gc_check(fs, 0);
+  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);
+  u32_t cur_size = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size ;
+  spiffs_span_ix cur_objix_spix = 0;
+  spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1;
+  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_page_ix data_pix;
+  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) {
+    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),
+        sizeof(u8_t),
+        (u8_t *)&flags);
+    SPIFFS_CHECK_RES(res);
+  }
+
+  // delete from end of object until desired len is reached
+  while (cur_size > new_size) {
+    cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+
+    // put object index for current data span index in work buffer
+    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);
+
+        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);
+        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);
+          fd->size = cur_size;
+        }
+      }
+      // load current object index (header) page
+      if (cur_objix_spix == 0) {
+        objix_pix = fd->objix_hdr_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("truncate: 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_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+      fd->cursor_objix_pix = objix_pix;
+      fd->cursor_objix_spix = cur_objix_spix;
+      fd->offset = cur_size;
+
+      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];
+      ((spiffs_page_ix*)((u8_t*)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = SPIFFS_OBJ_ID_FREE;
+    } 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)];
+      ((spiffs_page_ix*)((u8_t*)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = SPIFFS_OBJ_ID_FREE;
+    }
+
+    if (cur_size - SPIFFS_DATA_PAGE_SIZE(fs) >= new_size) {
+      // delete full data page
+      res = spiffs_page_data_check(fs, fd, data_pix, data_spix);
+      if (res != SPIFFS_OK) break;
+
+      res = spiffs_page_delete(fs, data_pix);
+      if (res != SPIFFS_OK) break;
+      // update current size
+      if (cur_size % SPIFFS_DATA_PAGE_SIZE(fs) == 0) {
+        cur_size -= SPIFFS_DATA_PAGE_SIZE(fs);
+      } else {
+        cur_size -= cur_size % SPIFFS_DATA_PAGE_SIZE(fs);
+      }
+      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);
+    } 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);
+
+      res = spiffs_page_data_check(fs, fd, data_pix, data_spix);
+      if (res != SPIFFS_OK) break;
+
+      p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+      p_hdr.span_ix = data_spix;
+      p_hdr.flags = 0xff;
+      // allocate new page and copy unmodified data
+      res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+          &p_hdr, 0, 0, 0, 0, &new_data_pix);
+      if (res != SPIFFS_OK) break;
+      res = spiffs_phys_cpy(fs, 0,
+          SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + sizeof(spiffs_page_header),
+          SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header),
+          SPIFFS_DATA_PAGE_SIZE(fs) - bytes_to_remove);
+      if (res != SPIFFS_OK) break;
+      // delete original data page
+      res = spiffs_page_delete(fs, data_pix);
+      if (res != SPIFFS_OK) break;
+      p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL;
+      res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+          fd->file_nbr,
+          SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + offsetof(spiffs_page_header, flags),
+          sizeof(u8_t),
+          (u8_t *)&p_hdr.flags);
+      if (res != SPIFFS_OK) break;
+
+      // update memory representation of object index page with new data page
+      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));
+      } 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));
+      }
+      cur_size = new_size;
+      fd->size = new_size;
+      fd->offset = cur_size;
+      break;
+    }
+    data_spix--;
+  } // while all data
+
+  // update object indices
+  if (cur_objix_spix == 0) {
+    // update object index header page
+    if (cur_size == 0) {
+      if (remove) {
+        // remove object altogether
+        SPIFFS_DBG("truncate: remove object index header page %04x\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);
+      } else {
+        // make uninitialized object
+        SPIFFS_DBG("truncate: reset objix_hdr page %04x\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);
+        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);
+      SPIFFS_CHECK_RES(res);
+    }
+  } else {
+    // update both current object index page and object index header page
+    spiffs_page_ix new_objix_pix;
+
+    res = spiffs_page_index_check(fs, fd, objix_pix, cur_objix_spix);
+    SPIFFS_CHECK_RES(res);
+
+    // 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);
+    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);
+    SPIFFS_CHECK_RES(res);
+  }
+  fd->size = cur_size;
+
+  return res;
+}
+
+s32_t spiffs_object_read(
+    spiffs_fd *fd,
+    u32_t offset,
+    u32_t len,
+    u8_t *dst) {
+  s32_t res = SPIFFS_OK;
+  spiffs *fs = fd->fs;
+  spiffs_page_ix objix_pix;
+  spiffs_page_ix data_pix;
+  spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs);
+  u32_t cur_offset = offset;
+  spiffs_span_ix cur_objix_spix;
+  spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1;
+  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;
+
+  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];
+    } 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)];
+    }
+
+    // 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)));
+    if (len_to_read <= 0) {
+      res = SPIFFS_ERR_END_OF_OBJECT;
+      break;
+    }
+    res = spiffs_page_data_check(fs, fd, data_pix, data_spix);
+    SPIFFS_CHECK_RES(res);
+    res = _spiffs_rd(
+        fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ,
+        fd->file_nbr,
+        SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)),
+        len_to_read,
+        dst);
+    SPIFFS_CHECK_RES(res);
+    dst += len_to_read;
+    cur_offset += len_to_read;
+    fd->offset = cur_offset;
+    data_spix++;
+  }
+
+  return res;
+}
+
+typedef struct {
+  spiffs_obj_id min_obj_id;
+  spiffs_obj_id max_obj_id;
+  u32_t compaction;
+} 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) {
+  if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED) {
+    spiffs_obj_id min_obj_id = user_data;
+    id &= ~SPIFFS_OBJ_ID_IX_FLAG;
+    int bit_ix = (id-min_obj_id) & 7;
+    int byte_ix = (id-min_obj_id) >> 3;
+    if (byte_ix >= 0 && byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs)) {
+      fs->work[byte_ix] |= (1<<bit_ix);
+    }
+  }
+  return SPIFFS_VIS_COUNTINUE;
+}
+
+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) {
+  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;
+    spiffs_page_header ph;
+
+    res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+        0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, ix_entry), sizeof(spiffs_page_header), (u8_t*)&ph);
+    if (res == SPIFFS_OK && ph.span_ix == 0 &&
+        ((ph.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) ==
+            (SPIFFS_PH_FLAG_DELET))) {
+      // ok object look up entry
+      id &= ~SPIFFS_OBJ_ID_IX_FLAG;
+      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);
+        map[ix]++;
+      }
+    }
+  }
+  return SPIFFS_VIS_COUNTINUE;
+}
+
+// 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) {
+  s32_t res = SPIFFS_OK;
+  u32_t max_objects = (SPIFFS_CFG_PHYS_SZ(fs) / (u32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)) / 2;
+  spiffs_free_obj_id_state state;
+  spiffs_obj_id free_obj_id = SPIFFS_OBJ_ID_FREE;
+  state.min_obj_id = 1;
+  state.max_obj_id = max_objects + 1;
+  if (state.max_obj_id & SPIFFS_OBJ_ID_IX_FLAG) {
+    state.max_obj_id = ((spiffs_obj_id)-1) & ~SPIFFS_OBJ_ID_IX_FLAG;
+  }
+  state.compaction = 0;
+  while (res == SPIFFS_OK && free_obj_id == SPIFFS_OBJ_ID_FREE) {
+    if (state.max_obj_id - state.min_obj_id <= SPIFFS_CFG_LOG_PAGE_SZ(fs)*8) {
+      // possible to represent in bitmap
+      int i, j;
+      SPIFFS_DBG("free_obj_id: BITM min:%04x max:%04x\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, 0, 0, 0);
+      if (res == SPIFFS_VIS_END) res = SPIFFS_OK;
+      SPIFFS_CHECK_RES(res);
+      // traverse bitmask until found free obj_id
+      for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs); i++) {
+        u8_t mask = fs->work[i];
+        if (mask == 0xff) {
+          continue;
+        }
+        for (j = 0; j < 8; j++) {
+          if ((mask & (1<<j)) == 0) {
+            *obj_id = (i<<3)+j+state.min_obj_id;
+            return SPIFFS_OK;
+          }
+        }
+      }
+      return SPIFFS_ERR_FULL;
+    } else {
+      // not possible to represent all ids in range in a bitmap, compact and count
+      if (state.compaction != 0) {
+        // select element in compacted table, decrease range and recompact
+        int i, min_i = 0;
+        u8_t *map = (u8_t *)fs->work;
+        u8_t min_count = 0xff;
+
+        for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(u8_t); i++) {
+          if (map[i] < min_count) {
+            min_count = map[i];
+            min_i = i;
+            if (min_count == 0) {
+              break;
+            }
+          }
+        }
+
+        if (min_count == state.compaction) {
+          // there are no free objids!
+          SPIFFS_DBG("free_obj_id: compacted table is full\n");
+          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);
+
+        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);
+          state.min_obj_id += min_i *  state.compaction;
+          state.max_obj_id = state.min_obj_id + state.compaction;
+          // decrease compaction
+        }
+        if ((state.max_obj_id - state.min_obj_id <= SPIFFS_CFG_LOG_PAGE_SZ(fs)*8)) {
+          // no need for compacting, use bitmap
+          continue;
+        }
+      }
+      // 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);
+
+      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);
+      if (res == SPIFFS_VIS_END) res = SPIFFS_OK;
+      SPIFFS_CHECK_RES(res);
+    }
+  }
+
+  return res;
+}
+
+s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd) {
+  int i;
+  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->file_nbr == 0) {
+      cur_fd->file_nbr = i+1;
+      *fd = cur_fd;
+      return SPIFFS_OK;
+    }
+  }
+  return SPIFFS_ERR_OUT_OF_FILE_DESCS;
+}
+
+s32_t spiffs_fd_return(spiffs *fs, spiffs_file f) {
+  if (f <= 0 || f > fs->fd_count) {
+    return SPIFFS_ERR_BAD_DESCRIPTOR;
+  }
+  spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+  spiffs_fd *fd = &fds[f-1];
+  if (fd->file_nbr == 0) {
+    return SPIFFS_ERR_FILE_CLOSED;
+  }
+  fd->file_nbr = 0;
+  return SPIFFS_OK;
+}
+
+s32_t spiffs_fd_get(spiffs *fs, spiffs_file f, spiffs_fd **fd) {
+  if (f <= 0 || f > fs->fd_count) {
+    return SPIFFS_ERR_BAD_DESCRIPTOR;
+  }
+  spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+  *fd = &fds[f-1];
+  if ((*fd)->file_nbr == 0) {
+    return SPIFFS_ERR_FILE_CLOSED;
+  }
+  return SPIFFS_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spiffs_nucleus.h	Thu Apr 22 03:49:45 2021 +0000
@@ -0,0 +1,696 @@
+/*
+ * spiffs_nucleus.h
+ *
+ *  Created on: Jun 15, 2013
+ *      Author: petera
+ */
+
+/* SPIFFS layout
+ *
+ * spiffs is designed for following spi flash characteristics:
+ *   - only big areas of data (blocks) can be erased
+ *   - erasing resets all bits in a block to ones
+ *   - writing pulls ones to zeroes
+ *   - zeroes cannot be pulled to ones, without erase
+ *   - wear leveling
+ *
+ * spiffs is also meant to be run on embedded, memory constraint devices.
+ *
+ * Entire area is divided in blocks. Entire area is also divided in pages.
+ * Each block contains same number of pages. A page cannot be erased, but a
+ * block can be erased.
+ *
+ * Entire area must be block_size * x
+ * page_size must be block_size / (2^y) where y > 2
+ *
+ * ex: area = 1024*1024 bytes, block size = 65536 bytes, page size = 256 bytes
+ *
+ * BLOCK 0  PAGE 0       object lookup 1
+ *          PAGE 1       object lookup 2
+ *          ...
+ *          PAGE n-1     object lookup n
+ *          PAGE n       object data 1
+ *          PAGE n+1     object data 2
+ *          ...
+ *          PAGE n+m-1   object data m
+ *
+ * BLOCK 1  PAGE n+m     object lookup 1
+ *          PAGE n+m+1   object lookup 2
+ *          ...
+ *          PAGE 2n+m-1  object lookup n
+ *          PAGE 2n+m    object data 1
+ *          PAGE 2n+m    object data 2
+ *          ...
+ *          PAGE 2n+2m-1 object data m
+ * ...
+ *
+ * n is number of object lookup pages, which is number of pages needed to index all pages
+ * in a block by object id
+ *   : block_size / page_size * sizeof(obj_id) / page_size
+ * m is number data pages, which is number of pages in block minus number of lookup pages
+ *   : block_size / page_size - block_size / page_size * sizeof(obj_id) / page_size
+ * thus, n+m is total number of pages in a block
+ *   : block_size / page_size
+ *
+ * ex: n = 65536/256*2/256 = 2, m = 65536/256 - 2 = 254 => n+m = 65536/256 = 256
+ *
+ * Object lookup pages contain object id entries. Each entry represent the corresponding
+ * data page.
+ * Assuming a 16 bit object id, an object id being 0xffff represents a free page.
+ * An object id being 0x0000 represents a deleted page.
+ *
+ * ex: page 0 : lookup : 0008 0001 0aaa ffff ffff ffff ffff ffff ..
+ *     page 1 : lookup : ffff ffff ffff ffff ffff ffff ffff ffff ..
+ *     page 2 : data   : data for object id 0008
+ *     page 3 : data   : data for object id 0001
+ *     page 4 : data   : data for object id 0aaa
+ *     ...
+ *
+ *
+ * Object data pages can be either object index pages or object content.
+ * All object data pages contains a data page header, containing object id and span index.
+ * The span index denotes the object page ordering amongst data pages with same object id.
+ * This applies to both object index pages (when index spans more than one page of entries),
+ * and object data pages.
+ * An object index page contains page entries pointing to object content page. The entry index
+ * in a object index page correlates to the span index in the actual object data page.
+ * The first object index page (span index 0) is called object index header page, and also
+ * contains object flags (directory/file), size, object name etc.
+ *
+ * ex:
+ *  BLOCK 1
+ *    PAGE 256: objectl lookup page 1
+ *      [*123] [ 123] [ 123] [ 123]
+ *      [ 123] [*123] [ 123] [ 123]
+ *      [free] [free] [free] [free] ...
+ *    PAGE 257: objectl lookup page 2
+ *      [free] [free] [free] [free] ...
+ *    PAGE 258: object index page (header)
+ *      obj.id:0123 span.ix:0000 flags:INDEX
+ *      size:1600 name:ex.txt type:file
+ *      [259] [260] [261] [262]
+ *    PAGE 259: object data page
+ *      obj.id:0123 span.ix:0000 flags:DATA
+ *    PAGE 260: object data page
+ *      obj.id:0123 span.ix:0001 flags:DATA
+ *    PAGE 261: object data page
+ *      obj.id:0123 span.ix:0002 flags:DATA
+ *    PAGE 262: object data page
+ *      obj.id:0123 span.ix:0003 flags:DATA
+ *    PAGE 263: object index page
+ *      obj.id:0123 span.ix:0001 flags:INDEX
+ *      [264] [265] [fre] [fre]
+ *      [fre] [fre] [fre] [fre]
+ *    PAGE 264: object data page
+ *      obj.id:0123 span.ix:0004 flags:DATA
+ *    PAGE 265: object data page
+ *      obj.id:0123 span.ix:0005 flags:DATA
+ *
+ */
+#ifndef SPIFFS_NUCLEUS_H_
+#define SPIFFS_NUCLEUS_H_
+
+#define _SPIFFS_ERR_CHECK_FIRST         (SPIFFS_ERR_INTERNAL - 1)
+#define SPIFFS_ERR_CHECK_OBJ_ID_MISM    (SPIFFS_ERR_INTERNAL - 1)
+#define SPIFFS_ERR_CHECK_SPIX_MISM      (SPIFFS_ERR_INTERNAL - 2)
+#define SPIFFS_ERR_CHECK_FLAGS_BAD      (SPIFFS_ERR_INTERNAL - 3)
+#define _SPIFFS_ERR_CHECK_LAST          (SPIFFS_ERR_INTERNAL - 4)
+
+#define SPIFFS_VIS_COUNTINUE            (SPIFFS_ERR_INTERNAL - 20)
+#define SPIFFS_VIS_COUNTINUE_RELOAD     (SPIFFS_ERR_INTERNAL - 21)
+#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
+
+#define SPIFFS_OBJ_ID_IX_FLAG           (1<<(8*sizeof(spiffs_obj_id)-1))
+
+#define SPIFFS_UNDEFINED_LEN            (-1)
+
+#define SPIFFS_OBJ_ID_DELETED           ((spiffs_obj_id)0)
+#define SPIFFS_OBJ_ID_FREE              ((spiffs_obj_id)-1)
+
+#if SPIFFS_SINGLETON == 0
+#define SPIFFS_CFG_LOG_PAGE_SZ(fs) \
+  ((fs)->cfg.log_page_size)
+#define SPIFFS_CFG_LOG_BLOCK_SZ(fs) \
+  ((fs)->cfg.log_block_size)
+#define SPIFFS_CFG_PHYS_SZ(fs) \
+  ((fs)->cfg.phys_size)
+#define SPIFFS_CFG_PHYS_ERASE_SZ(fs) \
+  ((fs)->cfg.phys_erase_block)
+#define SPIFFS_CFG_PHYS_ADDR(fs) \
+  ((fs)->cfg.phys_addr)
+#endif
+
+// total number of pages
+#define SPIFFS_MAX_PAGES(fs) \
+  ( SPIFFS_CFG_PHYS_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// total number of pages per block, including object lookup pages
+#define SPIFFS_PAGES_PER_BLOCK(fs) \
+  ( SPIFFS_CFG_LOG_BLOCK_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// number of object lookup pages per block
+#define SPIFFS_OBJ_LOOKUP_PAGES(fs)     \
+  (MAX(1, (SPIFFS_PAGES_PER_BLOCK(fs) * sizeof(spiffs_obj_id)) / SPIFFS_CFG_LOG_PAGE_SZ(fs)) )
+// checks if page index belongs to object lookup
+#define SPIFFS_IS_LOOKUP_PAGE(fs,pix)     \
+  (((pix) % SPIFFS_PAGES_PER_BLOCK(fs)) < SPIFFS_OBJ_LOOKUP_PAGES(fs))
+// number of object lookup entries in all object lookup pages
+#define SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) \
+  (SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))
+// converts a block to physical address
+#define SPIFFS_BLOCK_TO_PADDR(fs, block) \
+  ( SPIFFS_CFG_PHYS_ADDR(fs) + (block)* SPIFFS_CFG_LOG_BLOCK_SZ(fs) )
+// converts a object lookup entry to page index
+#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, block, entry) \
+  ((block)*SPIFFS_PAGES_PER_BLOCK(fs) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry))
+// converts a object lookup entry to physical address of corresponding page
+#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, block, entry) \
+  (SPIFFS_BLOCK_TO_PADDR(fs, block) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry) * SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// converts a page to physical address
+#define SPIFFS_PAGE_TO_PADDR(fs, page) \
+  ( SPIFFS_CFG_PHYS_ADDR(fs) + (page) * SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// converts a physical address to page
+#define SPIFFS_PADDR_TO_PAGE(fs, addr) \
+  ( ((addr) -  SPIFFS_CFG_PHYS_ADDR(fs)) / SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// gives index in page for a physical address
+#define SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr) \
+  ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) % SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// returns containing block for given page
+#define SPIFFS_BLOCK_FOR_PAGE(fs, page) \
+  ( (page) / SPIFFS_PAGES_PER_BLOCK(fs) )
+// returns starting page for block
+#define SPIFFS_PAGE_FOR_BLOCK(fs, block) \
+  ( (block) * SPIFFS_PAGES_PER_BLOCK(fs) )
+// converts page to entry in object lookup page
+#define SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, page) \
+  ( (page) % SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs) )
+// returns data size in a data page
+#define SPIFFS_DATA_PAGE_SIZE(fs) \
+    ( SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header) )
+// returns physical address for block's erase count
+#define SPIFFS_ERASE_COUNT_PADDR(fs, bix) \
+  ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id) )
+
+// define helpers object
+
+// entries in an object header page index
+#define SPIFFS_OBJ_HDR_IX_LEN(fs) \
+  ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header))/sizeof(spiffs_page_ix))
+// entries in an object page index
+#define SPIFFS_OBJ_IX_LEN(fs) \
+  ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix))/sizeof(spiffs_page_ix))
+// object index entry for given data span index
+#define SPIFFS_OBJ_IX_ENTRY(fs, spix) \
+  ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? (spix) : (((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))%SPIFFS_OBJ_IX_LEN(fs)))
+// 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)))
+
+
+#define SPIFFS_OP_T_OBJ_LU    (0<<0)
+#define SPIFFS_OP_T_OBJ_LU2   (1<<0)
+#define SPIFFS_OP_T_OBJ_IX    (2<<0)
+#define SPIFFS_OP_T_OBJ_DA    (3<<0)
+#define SPIFFS_OP_C_DELE      (0<<2)
+#define SPIFFS_OP_C_UPDT      (1<<2)
+#define SPIFFS_OP_C_MOVS      (2<<2)
+#define SPIFFS_OP_C_MOVD      (3<<2)
+#define SPIFFS_OP_C_FLSH      (4<<2)
+#define SPIFFS_OP_C_READ      (5<<2)
+#define SPIFFS_OP_C_WRTHRU    (6<<2)
+
+#define SPIFFS_OP_TYPE_MASK (3<<0)
+#define SPIFFS_OP_COM_MASK  (7<<2)
+
+
+// if 0, this page is written to, else clean
+#define SPIFFS_PH_FLAG_USED   (1<<0)
+// if 0, writing is finalized, else under modification
+#define SPIFFS_PH_FLAG_FINAL  (1<<1)
+// if 0, this is an index page, else a data page
+#define SPIFFS_PH_FLAG_INDEX  (1<<2)
+// if 0, page is deleted, else valid
+#define SPIFFS_PH_FLAG_DELET  (1<<7)
+// if 0, this index header is being deleted
+#define SPIFFS_PH_FLAG_IXDELE (1<<6)
+
+
+#define SPIFFS_CHECK_MOUNT(fs) \
+  ((fs)->block_count > 0)
+
+#define SPIFFS_CHECK_RES(res) \
+  do { \
+    if ((res) < SPIFFS_OK) return (res); \
+  } while (0);
+
+#define SPIFFS_API_CHECK_MOUNT(fs) \
+  if (!SPIFFS_CHECK_MOUNT((fs))) { \
+    (fs)->errno = SPIFFS_ERR_NOT_MOUNTED; \
+    return -1; \
+  }
+
+#define SPIFFS_API_CHECK_RES(fs, res) \
+  if ((res) < SPIFFS_OK) { \
+    (fs)->errno = (res); \
+    return -1; \
+  }
+
+#define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \
+  if ((res) < SPIFFS_OK) { \
+    (fs)->errno = (res); \
+    SPIFFS_UNLOCK(fs); \
+    return -1; \
+  }
+
+#define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \
+    if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \
+    if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \
+    if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \
+    if (((ph).flags & SPIFFS_PH_FLAG_INDEX) != 0) return SPIFFS_ERR_NOT_INDEX; \
+    if (((objid) & SPIFFS_OBJ_ID_IX_FLAG) == 0) return SPIFFS_ERR_NOT_INDEX; \
+    if ((ph).span_ix != (spix)) return SPIFFS_ERR_INDEX_SPAN_MISMATCH;
+    //if ((spix) == 0 && ((ph).flags & SPIFFS_PH_FLAG_IXDELE) == 0) return SPIFFS_ERR_DELETED;
+
+#define SPIFFS_VALIDATE_DATA(ph, objid, spix) \
+    if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \
+    if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \
+    if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \
+    if (((ph).flags & SPIFFS_PH_FLAG_INDEX) == 0) return SPIFFS_ERR_IS_INDEX; \
+    if ((objid) & SPIFFS_OBJ_ID_IX_FLAG) return SPIFFS_ERR_IS_INDEX; \
+    if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH;
+
+
+// check id
+#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_CACHE
+
+#define SPIFFS_CACHE_FLAG_DIRTY       (1<<0)
+#define SPIFFS_CACHE_FLAG_WRTHRU      (1<<1)
+#define SPIFFS_CACHE_FLAG_OBJLU       (1<<2)
+#define SPIFFS_CACHE_FLAG_OBJIX       (1<<3)
+#define SPIFFS_CACHE_FLAG_DATA        (1<<4)
+#define SPIFFS_CACHE_FLAG_TYPE_WR     (1<<7)
+
+#define SPIFFS_CACHE_PAGE_SIZE(fs) \
+  (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs))
+
+#define spiffs_get_cache(fs) \
+  ((spiffs_cache *)((fs)->cache))
+
+#define spiffs_get_cache_page_hdr(fs, c, ix) \
+  ((spiffs_cache_page *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)])))
+
+#define spiffs_get_cache_page(fs, c, ix) \
+  ((u8_t *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)])) + sizeof(spiffs_cache_page))
+
+// cache page struct
+typedef struct {
+  // cache flags
+  u8_t flags;
+  // cache page index
+  u8_t ix;
+  // last access of this cache page
+  u32_t last_access;
+  union {
+    // type read cache
+    struct {
+      // read cache page index
+      spiffs_page_ix pix;
+    };
+#if SPIFFS_CACHE_WR
+    // type write cache
+    struct {
+      // write cache
+      spiffs_obj_id obj_id;
+      // offset in cache page
+      u32_t offset;
+      // size of cache page
+      u16_t size;
+    };
+#endif
+  };
+} spiffs_cache_page;
+
+// cache struct
+typedef struct {
+  u8_t cpage_count;
+  u32_t last_access;
+  u32_t cpage_use_map;
+  u32_t cpage_use_mask;
+  u8_t *cpages;
+} spiffs_cache;
+
+#endif
+
+
+// spiffs nucleus file descriptor
+typedef struct {
+  // the filesystem of this descriptor
+  spiffs *fs;
+  // number of file descriptor - if 0, the file descriptor is closed
+  spiffs_file file_nbr;
+  // object id - if SPIFFS_OBJ_ID_ERASED, the file was deleted
+  spiffs_obj_id obj_id;
+  // size of the file
+  u32_t size;
+  // cached object index header page index
+  spiffs_page_ix objix_hdr_pix;
+  // cached offset object index page index
+  spiffs_page_ix cursor_objix_pix;
+  // cached offset object index span index
+  spiffs_span_ix cursor_objix_spix;
+  // current absolute offset
+  u32_t offset;
+  // current file descriptor offset
+  u32_t fdoffset;
+  // fd flags
+  spiffs_flags flags;
+#if SPIFFS_CACHE_WR
+  spiffs_cache_page *cache_page;
+#endif
+} spiffs_fd;
+
+
+// object structs
+
+// page header, part of each page except object lookup pages
+#ifdef __ICCARM__
+typedef __packed struct {
+#else
+typedef struct __attribute(( packed )) {
+#endif
+  // object id
+  spiffs_obj_id obj_id;
+  // object span index
+  spiffs_span_ix span_ix;
+  // flags
+  u8_t flags;
+} spiffs_page_header;
+
+// object index header page header
+#ifdef __ICCARM__
+typedef __packed struct {
+#else
+typedef struct __attribute(( packed )) {
+#endif
+  // 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)];
+  // size of object
+  u32_t size;
+  // type of object
+  spiffs_obj_type type;
+  // name of object
+  u8_t name[SPIFFS_OBJ_NAME_LEN];
+} spiffs_page_object_ix_header;
+
+// object index page header
+#ifdef __ICCARM__
+typedef __packed struct {
+#else
+typedef struct __attribute(( packed )) {
+#endif
+ spiffs_page_header p_hdr;
+ 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);
+
+
+#if SPIFFS_CACHE
+#define _spiffs_rd(fs, op, fh, addr, len, dst) \
+    spiffs_phys_rd((fs), (op), (fh), (addr), (len), (dst))
+#define _spiffs_wr(fs, op, fh, addr, len, src) \
+    spiffs_phys_wr((fs), (op), (fh), (addr), (len), (src))
+#else
+#define _spiffs_rd(fs, op, fh, addr, len, dst) \
+    spiffs_phys_rd((fs), (addr), (len), (dst))
+#define _spiffs_wr(fs, op, fh, addr, len, src) \
+    spiffs_phys_wr((fs), (addr), (len), (src))
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+// ---------------
+
+s32_t spiffs_phys_rd(
+    spiffs *fs,
+#if SPIFFS_CACHE
+    u8_t op,
+    spiffs_file fh,
+#endif
+    u32_t addr,
+    u32_t len,
+    u8_t *dst);
+
+s32_t spiffs_phys_wr(
+    spiffs *fs,
+#if SPIFFS_CACHE
+    u8_t op,
+    spiffs_file fh,
+#endif
+    u32_t addr,
+    u32_t len,
+    u8_t *src);
+
+s32_t spiffs_phys_cpy(
+    spiffs *fs,
+    spiffs_file fh,
+    u32_t dst,
+    u32_t src,
+    u32_t len);
+
+s32_t spiffs_phys_count_free_blocks(
+    spiffs *fs);
+
+s32_t spiffs_obj_lu_find_entry_visitor(
+    spiffs *fs,
+    spiffs_block_ix starting_block,
+    int starting_lu_entry,
+    u8_t flags,
+    spiffs_obj_id obj_id,
+    spiffs_visitor_f v,
+    u32_t user_data,
+    void *user_p,
+    spiffs_block_ix *block_ix,
+    int *lu_entry);
+
+// ---------------
+
+s32_t spiffs_obj_lu_scan(
+    spiffs *fs);
+
+s32_t spiffs_obj_lu_find_free_obj_id(
+    spiffs *fs,
+    spiffs_obj_id *obj_id);
+
+s32_t spiffs_obj_lu_find_free(
+    spiffs *fs,
+    spiffs_block_ix starting_block,
+    int starting_lu_entry,
+    spiffs_block_ix *block_ix,
+    int *lu_entry);
+
+s32_t spiffs_obj_lu_find_id(
+    spiffs *fs,
+    spiffs_block_ix starting_block,
+    int starting_lu_entry,
+    spiffs_obj_id obj_id,
+    spiffs_block_ix *block_ix,
+    int *lu_entry);
+
+s32_t spiffs_obj_lu_find_id_and_span(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_span_ix spix,
+    spiffs_page_ix exclusion_pix,
+    spiffs_page_ix *pix);
+
+s32_t spiffs_obj_lu_find_id_and_span_by_phdr(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_span_ix spix,
+    spiffs_page_ix exclusion_pix,
+    spiffs_page_ix *pix);
+
+// ---------------
+
+s32_t spiffs_page_allocate_data(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_page_header *ph,
+    u8_t *data,
+    u32_t len,
+    u32_t page_offs,
+    u8_t finalize,
+    spiffs_page_ix *pix);
+
+s32_t spiffs_page_move(
+    spiffs *fs,
+    spiffs_file fh,
+    u8_t *page_data,
+    spiffs_obj_id obj_id,
+    spiffs_page_header *page_hdr,
+    spiffs_page_ix src_pix,
+    spiffs_page_ix *dst_pix);
+
+s32_t spiffs_page_delete(
+    spiffs *fs,
+    spiffs_page_ix pix);
+
+// ---------------
+
+s32_t spiffs_object_create(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    u8_t name[SPIFFS_OBJ_NAME_LEN],
+    spiffs_obj_type type,
+    spiffs_page_ix *objix_hdr_pix);
+
+s32_t spiffs_object_update_index_hdr(
+    spiffs *fs,
+    spiffs_fd *fd,
+    spiffs_obj_id obj_id,
+    spiffs_page_ix objix_hdr_pix,
+    u8_t *new_objix_hdr_data,
+    u8_t name[SPIFFS_OBJ_NAME_LEN],
+    u32_t size,
+    spiffs_page_ix *new_pix);
+
+void spiffs_cb_object_event(
+    spiffs *fs,
+    spiffs_fd *fd,
+    int ev,
+    spiffs_obj_id obj_id,
+    spiffs_span_ix spix,
+    spiffs_page_ix new_pix,
+    u32_t new_size);
+
+s32_t spiffs_object_open_by_id(
+    spiffs *fs,
+    spiffs_obj_id obj_id,
+    spiffs_fd *f,
+    spiffs_flags flags,
+    spiffs_mode mode);
+
+s32_t spiffs_object_open_by_page(
+    spiffs *fs,
+    spiffs_page_ix pix,
+    spiffs_fd *f,
+    spiffs_flags flags,
+    spiffs_mode mode);
+
+s32_t spiffs_object_append(
+    spiffs_fd *fd,
+    u32_t offset,
+    u8_t *data,
+    u32_t len);
+
+s32_t spiffs_object_modify(
+    spiffs_fd *fd,
+    u32_t offset,
+    u8_t *data,
+    u32_t len);
+
+s32_t spiffs_object_read(
+    spiffs_fd *fd,
+    u32_t offset,
+    u32_t len,
+    u8_t *dst);
+
+s32_t spiffs_object_truncate(
+    spiffs_fd *fd,
+    u32_t new_len,
+    u8_t remove_object);
+
+s32_t spiffs_object_find_object_index_header_by_name(
+    spiffs *fs,
+    u8_t name[SPIFFS_OBJ_NAME_LEN],
+    spiffs_page_ix *pix);
+
+// ---------------
+
+s32_t spiffs_gc_check(
+    spiffs *fs,
+    u32_t len);
+
+s32_t spiffs_gc_erase_page_stats(
+    spiffs *fs,
+    spiffs_block_ix bix);
+
+s32_t spiffs_gc_find_candidate(
+    spiffs *fs,
+    spiffs_block_ix **block_candidate,
+    int *candidate_count);
+
+s32_t spiffs_gc_clean(
+    spiffs *fs,
+    spiffs_block_ix bix);
+
+s32_t spiffs_gc_quick(
+    spiffs *fs);
+
+// ---------------
+
+s32_t spiffs_fd_find_new(
+    spiffs *fs,
+    spiffs_fd **fd);
+
+s32_t spiffs_fd_return(
+    spiffs *fs,
+    spiffs_file f);
+
+s32_t spiffs_fd_get(
+    spiffs *fs,
+    spiffs_file f,
+    spiffs_fd **fd);
+
+#if SPIFFS_CACHE
+void spiffs_cache_init(
+    spiffs *fs);
+
+void spiffs_cache_drop_page(
+    spiffs *fs,
+    spiffs_page_ix pix);
+
+#if SPIFFS_CACHE_WR
+spiffs_cache_page *spiffs_cache_page_allocate_by_fd(
+    spiffs *fs,
+    spiffs_fd *fd);
+
+void spiffs_cache_fd_release(
+    spiffs *fs,
+    spiffs_cache_page *cp);
+
+spiffs_cache_page *spiffs_cache_page_get_by_fd(
+    spiffs *fs,
+    spiffs_fd *fd);
+#endif
+#endif
+
+s32_t spiffs_lookup_consistency_check(
+    spiffs *fs,
+    u8_t check_all_objects);
+
+s32_t spiffs_page_consistency_check(
+    spiffs *fs);
+
+s32_t spiffs_object_index_consistency_check(
+    spiffs *fs);
+
+#endif /* SPIFFS_NUCLEUS_H_ */