/**************************************************************************** * fs/spiffs/src/spiffs_core.c * * Copyright (C) 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * This is a port of version 0.3.7 of SPIFFS by Peter Andersion. That * version was originally released under the MIT license but is here re- * released under the NuttX BSD license. * * Copyright (c) 2013-2017 Peter Andersson (pelleplutt1976@gmail.com) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include "spiffs.h" #include "spiffs_mtd.h" #include "spiffs_gc.h" #include "spiffs_cache.h" #include "spiffs_core.h" /**************************************************************************** * Private Types ****************************************************************************/ struct spiffs_free_objid_s { int16_t min_objid; int16_t max_objid; uint32_t compaction; FAR const uint8_t *conflicting_name; }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: spiffs_page_data_check * * Description: * ****************************************************************************/ static int spiffs_page_data_check(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj, int16_t pgndx, int16_t spndx) { #ifdef CONFIG_SPIFFS_PAGE_CHECK struct spiffs_page_header_s ph; #endif int ret = OK; if (pgndx == (int16_t)-1) { /* Referring to page 0xffff...., bad object index */ return SPIFFS_ERR_INDEX_REF_FREE; } if (pgndx % SPIFFS_GEO_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 (pgndx > SPIFFS_GEO_PAGE_COUNT(fs)) { /* Referring to a bad page */ return SPIFFS_ERR_INDEX_REF_INVALID; } #ifdef CONFIG_SPIFFS_PAGE_CHECK ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, pgndx), sizeof(struct spiffs_page_header_s), (FAR uint8_t *)&ph); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); } else if ((ph.flags & SPIFFS_PH_FLAG_USED) != 0) { ret = SPIFFS_ERR_IS_FREE; } else if ((ph.flags & SPIFFS_PH_FLAG_DELET) == 0) { ret = SPIFFS_ERR_DELETED; } else if ((ph.flags & SPIFFS_PH_FLAG_FINAL) != 0) { ret = SPIFFS_ERR_NOT_FINALIZED; } else if ((ph.flags & SPIFFS_PH_FLAG_INDEX) == 0) { ret = SPIFFS_ERR_IS_INDEX; } else if (ph.spndx != (spndx)) { ret = SPIFFS_ERR_DATA_SPAN_MISMATCH; } #endif return ret; } /**************************************************************************** * Name: spiffs_page_index_check * * Description: * ****************************************************************************/ static int spiffs_page_index_check(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj, int16_t pgndx, int16_t spndx) { #ifdef CONFIG_SPIFFS_PAGE_CHECK struct spiffs_page_header_s ph; int ret; #endif if (pgndx == (int16_t) - 1) { /* Referring to page 0xffff...., bad object index */ return SPIFFS_ERR_INDEX_FREE; } if (pgndx % SPIFFS_GEO_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { /* Referring to an object lookup page, bad object index */ return SPIFFS_ERR_INDEX_LU; } if (pgndx > SPIFFS_GEO_PAGE_COUNT(fs)) { /* Referring to a bad page */ return SPIFFS_ERR_INDEX_INVALID; } #ifdef CONFIG_SPIFFS_PAGE_CHECK ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, pgndx), sizeof(struct spiffs_page_header_s), (uint8_t *) & ph); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } ret = spiffs_validate_objndx(&ph, fobj->objid, spndx); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } #endif return OK; } /**************************************************************************** * Name: spiffs_objlu_scan_callback * * Description: * ****************************************************************************/ static int spiffs_objlu_scan_callback(FAR struct spiffs_s *fs, int16_t objid, int16_t blkndx, int entry, FAR const void *user_const, FAR void *user_var) { if (objid == SPIFFS_OBJID_FREE) { if (entry == 0) { /* TODO: Optimize further, return SPIFFS_NEXT_BLOCK */ fs->free_blocks++; } } else if (objid == SPIFFS_OBJID_DELETED) { fs->deleted_pages++; } else { fs->alloc_pages++; } return SPIFFS_VIS_COUNTINUE; } /**************************************************************************** * Name: spiffs_objlu_find_id_and_span_callback * * Description: * ****************************************************************************/ static int spiffs_objlu_find_id_and_span_callback(FAR struct spiffs_s *fs, int16_t objid, int16_t blkndx, int entry, FAR const void *user_const, FAR void *user_var) { struct spiffs_page_header_s ph; int16_t pgndx; int ret; pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); ret = spiffs_cache_read(fs, 0, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, SPIFFS_PAGE_TO_PADDR(fs, pgndx), sizeof(struct spiffs_page_header_s), (FAR uint8_t *)&ph); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } if (ph.objid == objid && ph.spndx == *((FAR int16_t *)user_var) && (ph.flags & (SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED)) == SPIFFS_PH_FLAG_DELET && !((objid & SPIFFS_OBJID_NDXFLAG) != 0 && (ph.flags & SPIFFS_PH_FLAG_NDXDELE) == 0 && ph.spndx == 0) && (user_const == NULL || *((FAR const int16_t *)user_const) != pgndx)) { return OK; } else { return SPIFFS_VIS_COUNTINUE; } } /**************************************************************************** * Name: spiffs_find_objhdr_pgndx_callback * * Description: * ****************************************************************************/ static int spiffs_find_objhdr_pgndx_callback(FAR struct spiffs_s *fs, int16_t objid, int16_t blkndx, int entry, FAR const void *user_const, FAR void *user_var) { struct spiffs_pgobj_ndxheader_s objhdr; int16_t pgndx; int ret; if (objid == SPIFFS_OBJID_FREE || objid == SPIFFS_OBJID_DELETED || (objid & SPIFFS_OBJID_NDXFLAG) == 0) { return SPIFFS_VIS_COUNTINUE; } pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, pgndx), sizeof(struct spiffs_pgobj_ndxheader_s), (FAR uint8_t *)&objhdr); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } if (objhdr.phdr.spndx == 0 && (objhdr.phdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_NDXDELE)) == (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_NDXDELE)) { if ( #ifdef CONFIG_SPIFFS_LEADING_SLASH ((FAR char *)objhdr.name)[0] == '/' && #endif strcmp((FAR const char *)user_const, (FAR char *)objhdr.name + SPIFFS_LEADING_SLASH_SIZE) == 0) { return OK; } } return SPIFFS_VIS_COUNTINUE; } /**************************************************************************** * Name: spiffs_objlu_find_free_objid_bitmap_callback * * Description: * ****************************************************************************/ static int spiffs_objlu_find_free_objid_bitmap_callback(FAR struct spiffs_s *fs, int16_t objid, int16_t blkndx, int entry, FAR const void *user_const, FAR void *user_var) { if (objid != SPIFFS_OBJID_FREE && objid != SPIFFS_OBJID_DELETED) { int16_t min_objid = *((FAR int16_t *)user_var); FAR const uint8_t *conflicting_name = (FAR const uint8_t *)user_const; uint32_t bitndx; int bytendx; /* If conflicting name parameter is given, also check if this name is * found in object index headers */ if (conflicting_name != NULL && (objid & SPIFFS_OBJID_NDXFLAG) != 0) { struct spiffs_pgobj_ndxheader_s objhdr; int16_t pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); int ret; ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, pgndx), sizeof(struct spiffs_pgobj_ndxheader_s), (FAR uint8_t *)&objhdr); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } if (objhdr.phdr.spndx == 0 && (objhdr.phdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_NDXDELE)) == (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_NDXDELE)) { if (strcmp((FAR const char *)user_const, (FAR char *)objhdr.name) == 0) { return -EEXIST; } } } objid &= ~SPIFFS_OBJID_NDXFLAG; bitndx = (objid - min_objid) & 7; bytendx = (objid - min_objid) >> 3; if (bytendx >= 0 && (uint32_t)bytendx < SPIFFS_GEO_PAGE_SIZE(fs)) { fs->work[bytendx] |= (1 << bitndx); } } return SPIFFS_VIS_COUNTINUE; } /**************************************************************************** * Name: spiffs_objlu_find_free_objid_compact_callback * * Description: * ****************************************************************************/ static int spiffs_objlu_find_free_objid_compact_callback(FAR struct spiffs_s *fs, int16_t objid, int16_t blkndx, int entry, FAR const void *user_const, FAR void *user_var) { if (objid != SPIFFS_OBJID_FREE && objid != SPIFFS_OBJID_DELETED && (objid & SPIFFS_OBJID_NDXFLAG) != 0) { FAR const struct spiffs_free_objid_s *state = (FAR const struct spiffs_free_objid_s *)user_const; struct spiffs_pgobj_ndxheader_s objhdr; int ret; ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, blkndx, entry), sizeof(struct spiffs_pgobj_ndxheader_s), (FAR uint8_t *)&objhdr); if (ret >= 0 && objhdr.phdr.spndx == 0 && ((objhdr.phdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) == (SPIFFS_PH_FLAG_DELET))) { /* OK object look up entry */ if (state->conflicting_name && strcmp((FAR const char *)state->conflicting_name, (FAR char *)objhdr.name) == 0) { return -EEXIST; } objid &= ~SPIFFS_OBJID_NDXFLAG; if (objid >= state->min_objid && objid <= state->max_objid) { FAR uint8_t *map = (FAR uint8_t *)fs->work; int ndx = (objid - state->min_objid) / state->compaction; map[ndx]++; } } } return SPIFFS_VIS_COUNTINUE; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: spiffs_validate_objndx * * Description: * Returns zero (OK) if everything checks out. Otherwise, a negated errno * value is return to indicate the nature of the problem * ****************************************************************************/ int spiffs_validate_objndx(FAR struct spiffs_page_header_s *ph, int16_t objid, int16_t spndx) { int ret = OK; if ((ph->flags & SPIFFS_PH_FLAG_USED) != 0) { ret = SPIFFS_ERR_IS_FREE; } else if ((ph->flags & SPIFFS_PH_FLAG_DELET) == 0) { ret = SPIFFS_ERR_DELETED; } else if ((ph->flags & SPIFFS_PH_FLAG_FINAL) != 0) { ret = SPIFFS_ERR_NOT_FINALIZED; } else if ((ph->flags & SPIFFS_PH_FLAG_INDEX) != 0) { ret = SPIFFS_ERR_NOT_INDEX; } else if ((objid & SPIFFS_OBJID_NDXFLAG) == 0) { ret = SPIFFS_ERR_NOT_INDEX; } else if (ph->spndx != spndx) { ret = SPIFFS_ERR_INDEX_SPAN_MISMATCH; } return ret; } /**************************************************************************** * Name: spiffs_phys_cpy * * Description: * ****************************************************************************/ int spiffs_phys_cpy(FAR struct spiffs_s *fs, int16_t objid, uint32_t dest, uint32_t src, uint32_t len) { uint8_t cpybuf[CONFIG_SPIFFS_COPYBUF_STACK]; int ret; while (len > 0) { uint32_t chunk_size = MIN(CONFIG_SPIFFS_COPYBUF_STACK, len); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVS, objid, src, chunk_size, cpybuf); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVD, objid, dest, chunk_size, cpybuf); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } len -= chunk_size; src += chunk_size; dest += chunk_size; } return OK; } /**************************************************************************** * Name: spiffs_foreach_objlu * * Description: * Find object lookup entry containing given objid with visitor. * Iterate over object lookup pages in each block until a given object * objid 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 objid. * * If no match is found in all look up, SPIFFS_VIS_END is returned. * * Input Parameters: * fs - The file system * starting_block - The starting block to start search in * starting_lu_entry - The look up index entry to start search in * flags - ORed combination of SPIFFS_VIS_CHECK_ID, * SPIFFS_VIS_CHECK_PH, SPIFFS_VIS_NO_WRAP * objid - Argument object ID * cb - Visitor callback function * user_const - Any const pointer, passed to the callback visitor * function * user_var - Any pointer, passed to the callback visitor function * blkndx - Reported block index where match was found * lu_entry - Reported look up index where match was found * ****************************************************************************/ int spiffs_foreach_objlu(FAR struct spiffs_s *fs, int16_t starting_block, int starting_lu_entry, uint8_t flags, int16_t objid, spiffs_callback_t cb, FAR const void *user_const, FAR void *user_var, FAR int16_t *blkndx, FAR int *lu_entry) { FAR int16_t *objlu_buf; uint32_t cur_block_addr; int32_t entry_count; int16_t cur_block; int cur_entry; int entries_per_page; int ret; entry_count = SPIFFS_GEO_BLOCK_COUNT(fs) * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs); cur_block = starting_block; cur_block_addr = starting_block * SPIFFS_GEO_BLOCK_SIZE(fs); objlu_buf = (FAR int16_t *)fs->lu_work; cur_entry = starting_lu_entry; entries_per_page = (SPIFFS_GEO_PAGE_SIZE(fs) / sizeof(int16_t)); ret = OK; /* Wrap initial */ if (cur_entry > (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) { cur_entry = 0; cur_block++; cur_block_addr = cur_block * SPIFFS_GEO_BLOCK_SIZE(fs); if (cur_block >= SPIFFS_GEO_BLOCK_COUNT(fs)) { if (flags & SPIFFS_VIS_NO_WRAP) { return SPIFFS_VIS_END; } else { /* Block wrap */ cur_block = 0; cur_block_addr = 0; } } } /* Check each block */ while (ret >= 0 && entry_count > 0) { int obj_lookup_page = cur_entry / entries_per_page; /* Check each object lookup page */ while (ret >= 0 && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { int entry_offset; off_t physoff; entry_offset = obj_lookup_page * entries_per_page; physoff = cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, physoff, SPIFFS_GEO_PAGE_SIZE(fs), fs->lu_work); /* Check each entry */ while (ret >= 0 && cur_entry - entry_offset < entries_per_page && cur_entry < (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) { if ((flags & SPIFFS_VIS_CHECK_ID) == 0 || objlu_buf[cur_entry - entry_offset] == objid) { if (blkndx) { *blkndx = cur_block; } if (lu_entry) { *lu_entry = cur_entry; } if (cb) { ret = cb(fs, (flags & SPIFFS_VIS_CHECK_PH) ? objid : objlu_buf[cur_entry - entry_offset], cur_block, cur_entry, user_const, user_var); if (ret == SPIFFS_VIS_COUNTINUE || ret == SPIFFS_VIS_COUNTINUE_RELOAD) { if (ret == SPIFFS_VIS_COUNTINUE_RELOAD) { physoff = cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, physoff, SPIFFS_GEO_PAGE_SIZE(fs), fs->lu_work); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: " "%d\n", ret); return ret; } } ret = OK; cur_entry++; entry_count--; continue; } else { return ret; } } else { return OK; } } entry_count--; cur_entry++; } obj_lookup_page++; } cur_entry = 0; cur_block++; cur_block_addr += SPIFFS_GEO_BLOCK_SIZE(fs); if (cur_block >= SPIFFS_GEO_BLOCK_COUNT(fs)) { if (flags & SPIFFS_VIS_NO_WRAP) { return SPIFFS_VIS_END; } else { /* Block wrap */ cur_block = 0; cur_block_addr = 0; } } } if (ret < 0) { return ret; } return SPIFFS_VIS_END; } /**************************************************************************** * Name: spiffs_erase_block * * Description: * ****************************************************************************/ int spiffs_erase_block(FAR struct spiffs_s *fs, int16_t blkndx) { uint32_t addr = SPIFFS_BLOCK_TO_PADDR(fs, blkndx); int32_t size = SPIFFS_GEO_BLOCK_SIZE(fs); int ret; /* Here we ignore the return value and just try erasing the block */ while (size > 0) { finfo("erase %08lx:%" PRIu32 "\n", (unsigned long)addr, SPIFFS_GEO_EBLOCK_SIZE(fs)); spiffs_mtd_erase(fs, addr, SPIFFS_GEO_EBLOCK_SIZE(fs)); addr += SPIFFS_GEO_EBLOCK_SIZE(fs); size -= SPIFFS_GEO_EBLOCK_SIZE(fs); } fs->free_blocks++; /* Register erase count for this block */ ret = spiffs_cache_write(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0, SPIFFS_ERASE_COUNT_PADDR(fs, blkndx), sizeof(int16_t), (FAR uint8_t *)&fs->max_erase_count); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } fs->max_erase_count++; if (fs->max_erase_count == SPIFFS_OBJID_NDXFLAG) { fs->max_erase_count = 0; } return ret; } /**************************************************************************** * Name: spiffs_objlu_scan * * Description: * Scans through all obj lu and counts free, deleted and used pages. * Find the maximum block erase count Checks magic if enabled * ****************************************************************************/ int spiffs_objlu_scan(FAR struct spiffs_s *fs) { int16_t blkndx; int16_t erase_count_final; int16_t erase_count_min; int16_t erase_count_max; int entry; int ret; /* Find out erase count. */ blkndx = 0; erase_count_min = SPIFFS_OBJID_FREE; erase_count_max = 0; while (blkndx < SPIFFS_GEO_BLOCK_COUNT(fs)) { int16_t erase_count; ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_ERASE_COUNT_PADDR(fs, blkndx), sizeof(int16_t), (FAR uint8_t *)&erase_count); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } if (erase_count != SPIFFS_OBJID_FREE) { erase_count_min = MIN(erase_count_min, erase_count); erase_count_max = MAX(erase_count_max, erase_count); } blkndx++; } if (erase_count_min == 0 && erase_count_max == SPIFFS_OBJID_FREE) { /* Clean system, set counter to zero */ erase_count_final = 0; } else if (erase_count_max - erase_count_min > (SPIFFS_OBJID_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; /* Count blocks */ fs->free_blocks = 0; fs->alloc_pages = 0; fs->deleted_pages = 0; ret = spiffs_foreach_objlu(fs, 0, 0, 0, 0, spiffs_objlu_scan_callback, 0, 0, &blkndx, &entry); if (ret == SPIFFS_VIS_END) { ret = OK; } else if (ret < 0) { ferr("ERROR: spiffs_foreach_objlu() failed: %d\n", ret); } return ret; } /**************************************************************************** * Name: spiffs_objlu_find_free * * Description: * Find free object lookup entry. Iterate over object lookup pages in each * block until a free object ID entry is found * ****************************************************************************/ int spiffs_objlu_find_free(FAR struct spiffs_s *fs, int16_t starting_block, int starting_lu_entry, FAR int16_t *blkndx, FAR int *lu_entry) { int ret; if (fs->free_blocks < 2) { ret = spiffs_gc_quick(fs, 0); /* spiffs_gc_quick() will return -ENODATA if no blocks were freed */ if (ret == -ENODATA) { ret = OK; } else if (ret < 0) { return ret; } if (fs->free_blocks < 2) { return -ENOSPC; } } ret = spiffs_objlu_find_id(fs, starting_block, starting_lu_entry, SPIFFS_OBJID_FREE, blkndx, lu_entry); if (ret >= 0) { fs->free_blkndx = *blkndx; fs->free_entry = (*lu_entry) + 1; if (*lu_entry == 0) { fs->free_blocks--; } } if (ret == -ENOSPC) { fwarn("WARNING: Filesystem full\n"); } return ret; } /**************************************************************************** * Name: spiffs_objlu_find_id * * Description: * Find object lookup entry containing given objid. Iterate over object * lookup pages in each block until a given object objid entry is found * ****************************************************************************/ int spiffs_objlu_find_id(FAR struct spiffs_s *fs, int16_t starting_block, int starting_lu_entry, int16_t objid, FAR int16_t *blkndx, FAR int *lu_entry) { int ret = spiffs_foreach_objlu(fs, starting_block, starting_lu_entry, SPIFFS_VIS_CHECK_ID, objid, 0, 0, 0, blkndx, lu_entry); if (ret == SPIFFS_VIS_END) { ret = -ENOENT; } return ret; } /**************************************************************************** * Name: spiffs_objlu_find_id_and_span * * Description: * Find object lookup entry containing given objid and span index. * Iterate over object lookup pages in each block until a given object * objid entry is found * ****************************************************************************/ int spiffs_objlu_find_id_and_span(FAR struct spiffs_s *fs, int16_t objid, int16_t spndx, int16_t exclusion_pgndx, FAR int16_t *pgndx) { int16_t blkndx; int entry; int ret; ret = spiffs_foreach_objlu(fs, fs->lu_blkndx, fs->lu_entry, SPIFFS_VIS_CHECK_ID, objid, spiffs_objlu_find_id_and_span_callback, exclusion_pgndx ? &exclusion_pgndx : 0, &spndx, &blkndx, &entry); if (ret == SPIFFS_VIS_END) { ret = -ENOENT; } else if (ret < 0) { ferr("ERROR: spiffs_foreach_objlu() failed: %d\n", ret); return ret; } if (pgndx != NULL) { *pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); } fs->lu_blkndx = blkndx; fs->lu_entry = entry; return ret; } /**************************************************************************** * Name: spiffs_objlu_find_id_and_span_byphdr * * Description: * Find object lookup entry containing given objid and span index in page * headers only. Iterate over object lookup pages in each block until a * given object objid entry is found * ****************************************************************************/ int spiffs_objlu_find_id_and_span_byphdr(FAR struct spiffs_s *fs, int16_t objid, int16_t spndx, int16_t exclusion_pgndx, FAR int16_t *pgndx) { int16_t blkndx; int entry; int ret; ret = spiffs_foreach_objlu(fs, fs->lu_blkndx, fs->lu_entry, SPIFFS_VIS_CHECK_PH, objid, spiffs_objlu_find_id_and_span_callback, exclusion_pgndx ? &exclusion_pgndx : 0, &spndx, &blkndx, &entry); if (ret == SPIFFS_VIS_END) { ret = -ENOENT; } if (ret < 0) { return ret; } if (pgndx != NULL) { *pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); } fs->lu_blkndx = blkndx; fs->lu_entry = entry; return ret; } /**************************************************************************** * Name: spiffs_page_allocate_data * * Description: * Allocates a free defined page with given objid. Occupies object * lookup entry and page. Data may be NULL; where only page header is * stored, len and page_offs is ignored * ****************************************************************************/ int spiffs_page_allocate_data(FAR struct spiffs_s *fs, int16_t objid, FAR struct spiffs_page_header_s *ph, FAR uint8_t *data, uint32_t len, uint32_t page_offs, bool finalize, FAR int16_t *pgndx) { int16_t blkndx; int entry; int ret = OK; /* Find free entry */ ret = spiffs_objlu_find_free(fs, fs->free_blkndx, fs->free_entry, &blkndx, &entry); if (ret < 0) { ferr("ERROR: spiffs_objlu_find_free() failed: %d\n", ret); return ret; } /* Occupy page in object lookup */ ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, 0, SPIFFS_BLOCK_TO_PADDR(fs, blkndx) + entry * sizeof(int16_t), sizeof(int16_t), (FAR uint8_t *)&objid); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } fs->alloc_pages++; /* Write page header */ ph->flags &= ~SPIFFS_PH_FLAG_USED; ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, blkndx, entry), sizeof(struct spiffs_page_header_s), (FAR uint8_t *)ph); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } /* Write page data */ if (data) { ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, blkndx, entry) + sizeof(struct spiffs_page_header_s) + page_offs, len, data); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } } /* Finalize header if necessary */ if (finalize && (ph->flags & SPIFFS_PH_FLAG_FINAL) != 0) { ph->flags &= ~SPIFFS_PH_FLAG_FINAL; ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, blkndx, entry) + offsetof(struct spiffs_page_header_s, flags), sizeof(uint8_t), (FAR uint8_t *)&ph->flags); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } } /* Return written page */ if (pgndx != NULL) { *pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); } return ret; } /**************************************************************************** * Name: spiffs_page_move * * Description: * 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 meta-info and page data is physically * copied. * ****************************************************************************/ int spiffs_page_move(FAR struct spiffs_s *fs, int16_t objid, FAR uint8_t *page_data, int16_t ndx, FAR struct spiffs_page_header_s *page_hdr, int16_t src_pgndx, FAR int16_t *dst_pgndx) { struct spiffs_page_header_s *phdr; bool was_final = false; int16_t free_pgndx; int16_t blkndx; int entry; int ret; /* Find free entry */ ret = spiffs_objlu_find_free(fs, fs->free_blkndx, fs->free_entry, &blkndx, &entry); if (ret < 0) { ferr("ERROR: spiffs_gc_epage_stats() failed: %d\n", ret); return ret; } free_pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); if (dst_pgndx != NULL) { *dst_pgndx = free_pgndx; } phdr = page_data ? (struct spiffs_page_header_s *) page_data : page_hdr; if (page_data != NULL) { /* Got page data */ was_final = (phdr->flags & SPIFFS_PH_FLAG_FINAL) == 0; /* Write unfinalized page */ phdr->flags |= SPIFFS_PH_FLAG_FINAL; phdr->flags &= ~SPIFFS_PH_FLAG_USED; ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, 0, SPIFFS_PAGE_TO_PADDR(fs, free_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), page_data); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } } else { /* Copy page data */ ret = spiffs_phys_cpy(fs, objid, SPIFFS_PAGE_TO_PADDR(fs, free_pgndx), SPIFFS_PAGE_TO_PADDR(fs, src_pgndx), SPIFFS_GEO_PAGE_SIZE(fs)); if (ret < 0) { ferr("ERROR: spiffs_phys_cpy() failed: %d\n", ret); return ret; } } /* Mark entry in destination object lookup */ ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pgndx)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pgndx) * sizeof(int16_t), sizeof(int16_t), (FAR uint8_t *)&ndx); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } fs->alloc_pages++; if (was_final) { /* Mark finalized in destination page */ phdr->flags &= ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_USED); ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, objid, SPIFFS_PAGE_TO_PADDR(fs, free_pgndx) + offsetof(struct spiffs_page_header_s, flags), sizeof(uint8_t), (FAR uint8_t *)&phdr->flags); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } } /* Mark source deleted */ ret = spiffs_page_delete(fs, src_pgndx); return ret; } /**************************************************************************** * Name: spiffs_page_delete * * Description: * Deletes a page and removes it from object lookup. * ****************************************************************************/ int spiffs_page_delete(FAR struct spiffs_s *fs, int16_t pgndx) { uint8_t flags; int ret; /* Mark deleted entry in source object lookup */ int16_t d_objid = SPIFFS_OBJID_DELETED; ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_DELE, 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, pgndx)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pgndx) * sizeof(int16_t), sizeof(int16_t), (FAR uint8_t *)&d_objid); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } fs->deleted_pages++; fs->alloc_pages--; /* Mark deleted in source page */ flags = 0xff; #ifdef CONFIG_SPIFFS_NO_BLIND_WRITES /* Perform read-modify-write operation */ physoff = SPIFFS_PAGE_TO_PADDR(fs, pgndx) + offsetof(struct spiffs_page_header_s, flags) ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, 0, physoff, sizeof(flags), &flags); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } #endif flags &= ~(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED); ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_DELE, 0, SPIFFS_PAGE_TO_PADDR(fs, pgndx) + offsetof(struct spiffs_page_header_s, flags), sizeof(flags), &flags); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); } return ret; } /**************************************************************************** * Name: spiffs_fobj_create * * Description: * Create an object index header page with empty index and undefined length * ****************************************************************************/ int spiffs_fobj_create(FAR struct spiffs_s *fs, int16_t objid, const uint8_t name[], uint8_t type, FAR int16_t *objhdr_pgndx) { struct spiffs_pgobj_ndxheader_s objndx_hdr; int16_t blkndx; int entry; int ret = OK; ret = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs)); if (ret < 0) { ferr("ERROR: spiffs_gc_check() failed: %d\n", ret); return ret; } objid |= SPIFFS_OBJID_NDXFLAG; /* Find free entry */ ret = spiffs_objlu_find_free(fs, fs->free_blkndx, fs->free_entry, &blkndx, &entry); if (ret < 0) { ferr("ERROR: spiffs_objlu_find_free() failed: %d\n", ret); return ret; } finfo("Found free page @ %04x blkndx=%04x entry=%04x\n", (int16_t) SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry), blkndx, entry); /* Occupy page in object lookup */ ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, 0, SPIFFS_BLOCK_TO_PADDR(fs, blkndx) + entry * sizeof(int16_t), sizeof(int16_t), (FAR uint8_t *)&objid); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } fs->alloc_pages++; /* Write empty object index page */ objndx_hdr.phdr.objid = objid; objndx_hdr.phdr.spndx = 0; objndx_hdr.phdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED); objndx_hdr.type = type; objndx_hdr.size = SPIFFS_UNDEFINED_LEN; #ifdef CONFIG_SPIFFS_LEADING_SLASH objndx_hdr.name[0] = '/'; #endif strlcpy((char *)objndx_hdr.name + SPIFFS_LEADING_SLASH_SIZE, (const char *)name, CONFIG_SPIFFS_NAME_MAX); /* Update page */ ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, blkndx, entry), sizeof(struct spiffs_pgobj_ndxheader_s), (FAR uint8_t *)&objndx_hdr); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *)&objndx_hdr, SPIFFS_EV_NDXNEW, objid, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry), SPIFFS_UNDEFINED_LEN); if (objhdr_pgndx) { *objhdr_pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); } return ret < 0 ? ret : OK; } /**************************************************************************** * Name: spiffs_fobj_update_ndxhdr * * Description: * Update object index header with any combination of name/size/index. * new_objhdr_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 * ****************************************************************************/ int spiffs_fobj_update_ndxhdr(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj, int16_t objid, int16_t objhdr_pgndx, FAR uint8_t *new_objhdr_data, const uint8_t name[], uint32_t size, FAR int16_t *new_pgndx) { FAR struct spiffs_pgobj_ndxheader_s *objhdr; int16_t new_objhdr_pgndx; int ret = OK; objid |= SPIFFS_OBJID_NDXFLAG; if (new_objhdr_data) { /* object index header page already given to us, no need to load it */ objhdr = (FAR struct spiffs_pgobj_ndxheader_s *) new_objhdr_data; } else { /* Read object index header page */ ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, objhdr_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } objhdr = (FAR struct spiffs_pgobj_ndxheader_s *)fs->work; } ret = spiffs_validate_objndx(&objhdr->phdr, objid, 0); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } /* Change name */ if (name != NULL) { strlcpy((FAR char *)objhdr->name + SPIFFS_LEADING_SLASH_SIZE, (FAR const char *)name, CONFIG_SPIFFS_NAME_MAX); } if (size != 0) { objhdr->size = size; } /* Move and update page */ ret = spiffs_page_move(fs, fobj == NULL ? 0 : fobj->objid, (FAR uint8_t *)objhdr, objid, 0, objhdr_pgndx, &new_objhdr_pgndx); if (ret >= 0) { if (new_pgndx != NULL) { *new_pgndx = new_objhdr_pgndx; } /* Object index update */ spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *)objhdr, new_objhdr_data ? SPIFFS_EV_NDXUPD : SPIFFS_EV_NDXUPD_HDR, objid, objhdr->phdr.spndx, new_objhdr_pgndx, objhdr->size); if (fobj != NULL) { fobj->objhdr_pgndx = new_objhdr_pgndx; /* If this is not in the * registered cluster */ } } return ret; } /**************************************************************************** * Name: spiffs_fobj_event * * Description: * ****************************************************************************/ void spiffs_fobj_event(FAR struct spiffs_s *fs, FAR struct spiffs_page_objndx_s *objndx, int ev, int16_t objid_raw, int16_t spndx, int16_t new_pgndx, uint32_t new_size) { FAR static const char *evname[] = { "UPD", "NEW", "DEL", "MOV", "HUP", "???" }; FAR struct spiffs_file_s *fobj; FAR struct spiffs_file_s *next; int16_t objid = objid_raw & ~SPIFFS_OBJID_NDXFLAG; finfo("Event=%s objid=%04x spndx=%04x npgndx=%04x nsz=%" PRIu32 "\n", evname[MIN(ev, 5)], objid_raw, spndx, new_pgndx, new_size); /* Update index caches in all file descriptors */ for (fobj = (FAR struct spiffs_file_s *)dq_peek(&fs->objq); fobj != NULL; fobj = next) { /* Set up for the next time through the loop (in case fobj is * deleted) */ next = (FAR struct spiffs_file_s *)dq_next((FAR dq_entry_t *)fobj); /* Is this the object we are looking for? */ if ((fobj->objid & ~SPIFFS_OBJID_NDXFLAG) != objid) { continue; /* Object not related to updated file */ } if (spndx == 0) { /* Object index header update */ if (ev != SPIFFS_EV_NDXDEL) { finfo("Setting objid=%d (offset=%" PRIuOFF ") objhdr_pgndx " "to %04x size=%" PRIu32 "\n", fobj->objid, fobj->offset, new_pgndx, new_size); fobj->objhdr_pgndx = new_pgndx; if (new_size != 0) { uint32_t act_new_size = 0; /* Update size and offsets for fobj to this file */ fobj->size = new_size; if (new_size != SPIFFS_UNDEFINED_LEN) { act_new_size = new_size; } if (act_new_size > 0 && fobj->cache_page) { act_new_size = MAX(act_new_size, fobj->cache_page->offset + fobj->cache_page->size); } if (fobj->offset > act_new_size) { fobj->offset = act_new_size; } if (fobj->cache_page && fobj->cache_page->offset > act_new_size + 1) { spiffs_cacheinfo("File truncated, " "dropping cache page=%d, " "no writeback\n", fobj->cache_page->cpndx); spiffs_cache_page_release(fs, fobj->cache_page); } } } else { /* Removing file */ if (fobj->cache_page) { spiffs_cacheinfo("File deleted, dropping cache page=%d, " "no writeback\n", fobj->cache_page->cpndx); spiffs_cache_page_release(fs, fobj->cache_page); } finfo("Release objid=%d span=%04x objndx_pgndx to %04x\n", fobj->objid, spndx, new_pgndx); /* Remove the file object */ spiffs_fobj_free(fs, fobj, true); } } if (fobj->objndx_spndx == spndx) { if (ev != SPIFFS_EV_NDXDEL) { finfo("Setting objid=%d span=%04x objndx_pgndx to %04x\n", fobj->objid, spndx, new_pgndx); fobj->objndx_pgndx = new_pgndx; } else { fobj->objndx_pgndx = 0; } } } } /**************************************************************************** * Name: spiffs_fobj_open_bypage * * Description: * Open object by page index * ****************************************************************************/ int spiffs_fobj_open_bypage(FAR struct spiffs_s *fs, int16_t pgndx, FAR struct spiffs_file_s *fobj) { struct spiffs_pgobj_ndxheader_s objndx_hdr; off_t physoff; int16_t objid; int16_t blkndx; int entry; int ret = OK; physoff = SPIFFS_PAGE_TO_PADDR(fs, pgndx); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, physoff, sizeof(struct spiffs_pgobj_ndxheader_s), (FAR uint8_t *)&objndx_hdr); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } blkndx = SPIFFS_BLOCK_FOR_PAGE(fs, pgndx); entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pgndx); physoff = SPIFFS_BLOCK_TO_PADDR(fs, blkndx) + entry * sizeof(int16_t); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, physoff, sizeof(int16_t), (FAR uint8_t *)&objid); /* Fill in the parts of the open file structure known only to the core * logic. */ fobj->objhdr_pgndx = pgndx; fobj->size = objndx_hdr.size; fobj->offset = 0; fobj->objndx_pgndx = pgndx; fobj->objndx_spndx = 0; fobj->objid = objid; ret = spiffs_validate_objndx(&objndx_hdr.phdr, fobj->objid, 0); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } finfo("objid=%d\n", fobj->objid); return ret; } /**************************************************************************** * Name: spiffs_fobj_append * * Description: * Append to object. Deep current object index (header) page in fs->work * buffer * ****************************************************************************/ ssize_t spiffs_fobj_append(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj, off_t offset, FAR uint8_t *data, size_t len) { struct spiffs_page_header_s phdr; FAR struct spiffs_pgobj_ndxheader_s *objhdr; FAR struct spiffs_page_objndx_s *objndx; ssize_t nwritten = 0; uint32_t page_offs; int16_t cur_objndx_spndx; int16_t prev_objndx_spndx; int16_t cur_objndx_pgndx; int16_t new_objhdr_page; int16_t data_spndx; int16_t data_page; int ret = OK; int ret2; finfo("Append %zu bytes @ offs=%" PRIdOFF " of size=%" PRIdOFF "\n", len, offset, fobj->size); if (offset > fobj->size) { finfo("Offset replaced with size\n"); offset = fobj->size < 0 ? 0 : fobj->size; } /* Add an extra page of data worth for meta */ ret = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); if (ret < 0) { ferr("ERROR: spiffs_gc_epage_stats() failed: %d\n", ret); return ret; } objhdr = (FAR struct spiffs_pgobj_ndxheader_s *)fs->work; objndx = (FAR struct spiffs_page_objndx_s *)fs->work; cur_objndx_spndx = 0; prev_objndx_spndx = (int16_t)-1; cur_objndx_pgndx = fobj->objhdr_pgndx; data_spndx = offset / SPIFFS_DATA_PAGE_SIZE(fs); page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); /* Write all data */ while (ret >= 0 && nwritten < len) { size_t to_write; /* Calculate object index page span index */ cur_objndx_spndx = SPIFFS_OBJNDX_ENTRY_SPNDX(fs, data_spndx); /* Handle storing and loading of object indices */ if (cur_objndx_spndx != prev_objndx_spndx) { /* New object index page. Within this clause we return directly * if something fails, object index mess-up */ if (nwritten > 0) { /* Store previous object index page, unless first pass */ finfo("objid=%04x store objndx %04x:%04x, nwritten=%zu\n", fobj->objid, cur_objndx_pgndx, prev_objndx_spndx, nwritten); if (prev_objndx_spndx == 0) { /* This is an update to object index header page */ objhdr->size = offset + nwritten; if (offset == 0) { /* Was an empty object, update same page (size was * 0xffffffff) */ ret = spiffs_page_index_check(fs, fobj, cur_objndx_pgndx, 0); if (ret < 0) { ferr("ERROR: spiffs_page_index_check() failed: " "%d\n", ret); return ret; } ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, cur_objndx_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } } else { /* Was a nonempty object, update to new page */ ret = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, fobj->objhdr_pgndx, fs->work, 0, offset + nwritten, &new_objhdr_page); if (ret < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: " "%d\n", ret); return ret; } finfo("objid=%04x store new objhdr, " "%04x:%04x, nwritten=%zu\n", fobj->objid, new_objhdr_page, 0, nwritten); } } else { /* this is an update to an object index page */ ret = spiffs_page_index_check(fs, fobj, cur_objndx_pgndx, prev_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_page_index_check() failed: " "%d\n", ret); return ret; } ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, cur_objndx_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *) fs->work, SPIFFS_EV_NDXUPD, fobj->objid, objndx->phdr.spndx, cur_objndx_pgndx, 0); /* Update length in object index header page */ ret = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, fobj->objhdr_pgndx, 0, 0, offset + nwritten, &new_objhdr_page); if (ret < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", ret); return ret; } finfo("objid=%04x store new size I %" PRIdOFF " in objhdr, %04x:%04x, nwritten=%zu\n", fobj->objid, offset + nwritten, new_objhdr_page, 0, nwritten); } fobj->size = offset + nwritten; fobj->offset = offset + nwritten; } /* create or load new object index page */ if (cur_objndx_spndx == 0) { /* load object index header page, must always exist */ finfo("objid=%04x load objndxhdr page %04x:%04x\n", fobj->objid, cur_objndx_pgndx, cur_objndx_spndx); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, cur_objndx_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } ret = spiffs_validate_objndx(&objhdr->phdr, fobj->objid, cur_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } } else { int16_t len_objndx_spndx; /* On subsequent passes, create a new object index page */ len_objndx_spndx = SPIFFS_OBJNDX_ENTRY_SPNDX(fs, (fobj->size - 1) / SPIFFS_DATA_PAGE_SIZE(fs)); if (nwritten > 0 || cur_objndx_spndx > len_objndx_spndx) { phdr.objid = fobj->objid | SPIFFS_OBJID_NDXFLAG; phdr.spndx = cur_objndx_spndx; phdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); ret = spiffs_page_allocate_data(fs, fobj->objid | SPIFFS_OBJID_NDXFLAG, &phdr, 0, 0, 0, 1, &cur_objndx_pgndx); if (ret < 0) { ferr("ERROR: spiffs_page_allocate_data() failed: %d\n", ret); return ret; } /* Quick "load" of new object index page */ memset(fs->work, 0xff, SPIFFS_GEO_PAGE_SIZE(fs)); memcpy(fs->work, &phdr, sizeof(struct spiffs_page_header_s)); spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *) fs->work, SPIFFS_EV_NDXNEW, fobj->objid, cur_objndx_spndx, cur_objndx_pgndx, 0); finfo("objid=%04x create objndx page, " "%04x:%04x, nwritten=%zu\n", fobj->objid, cur_objndx_pgndx, cur_objndx_spndx, nwritten); } else { int16_t pgndx; /* On first pass, we load existing object index page */ finfo("objid=%04x find objndx spndx=%04x\n", fobj->objid, cur_objndx_spndx); if (fobj->objndx_spndx == cur_objndx_spndx) { pgndx = fobj->objndx_pgndx; } else { ret = spiffs_objlu_find_id_and_span(fs, fobj->objid | SPIFFS_OBJID_NDXFLAG, cur_objndx_spndx, 0, &pgndx); if (ret < 0) { ferr("ERROR: spiffs_objlu_find_id_and_span() " "failed: %d\n", ret); return ret; } } finfo("objid=%04x found object index at " "page=%04x [fobj size=%" PRIuOFF "]\n", fobj->objid, pgndx, fobj->size); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } ret = spiffs_validate_objndx(&objhdr->phdr, fobj->objid, cur_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } cur_objndx_pgndx = pgndx; } fobj->objndx_pgndx = cur_objndx_pgndx; fobj->objndx_spndx = cur_objndx_spndx; fobj->offset = offset + nwritten; fobj->size = offset + nwritten; } prev_objndx_spndx = cur_objndx_spndx; } /* Write data */ to_write = MIN(len - nwritten, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); if (page_offs == 0) { /* ATt beginning of a page, allocate and write a new page of data */ phdr.objid = fobj->objid & ~SPIFFS_OBJID_NDXFLAG; phdr.spndx = data_spndx; phdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL); /* Finalize immediately */ ret = spiffs_page_allocate_data(fs, fobj->objid & ~SPIFFS_OBJID_NDXFLAG, &phdr, &data[nwritten], to_write, page_offs, 1, &data_page); finfo("objid=%04x store new data page, %04x:%04x " "offset=%" PRId32 ", len=%zu nwritten=%zu\n", fobj->objid, data_page, data_spndx, page_offs, to_write, nwritten); } else { /* Append to existing page, fill out free data in existing page */ if (cur_objndx_spndx == 0) { /* Get data page from object index header page */ data_page = ((FAR int16_t *)((FAR uint8_t *)objhdr + sizeof(struct spiffs_pgobj_ndxheader_s)))[data_spndx]; } else { /* Get data page from object index page */ data_page = ((FAR int16_t *)((FAR uint8_t *)objndx + sizeof(struct spiffs_page_objndx_s))) [SPIFFS_OBJNDX_ENTRY(fs, data_spndx)]; } ret = spiffs_page_data_check(fs, fobj, data_page, data_spndx); if (ret < 0) { ferr("ERROR: spiffs_page_data_check() failed: %d\n", ret); return ret; } ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, data_page) + sizeof(struct spiffs_page_header_s) + page_offs, to_write, &data[nwritten]); finfo("objid=%04x store to existing data page, " "%04x:%04x offset=%" PRId32 ", " "len=%zu, nwritten=%zu\n", fobj->objid, data_page, data_spndx, page_offs, to_write, nwritten); } if (ret < 0) { break; } /* Update memory representation of object index page with new data * page */ if (cur_objndx_spndx == 0) { /* Update object index header page */ ((FAR int16_t *)((FAR uint8_t *) objhdr + sizeof(struct spiffs_pgobj_ndxheader_s)))[data_spndx] = data_page; finfo("objid=%04x wrote page %04x to objhdr entry=%04x in mem\n", fobj->objid, data_page, data_spndx); objhdr->size = offset + nwritten; } else { /* Update object index page */ ((FAR int16_t *)((FAR uint8_t *) objndx + sizeof(struct spiffs_page_objndx_s))) [SPIFFS_OBJNDX_ENTRY(fs, data_spndx)] = data_page; finfo("objid=%04x wrote page=%04x to objndx entry %04x in mem\n", fobj->objid, data_page, (int16_t)SPIFFS_OBJNDX_ENTRY(fs, data_spndx)); } /* Update internals */ page_offs = 0; data_spndx++; nwritten += to_write; } fobj->size = offset + nwritten; fobj->offset = offset + nwritten; fobj->objndx_pgndx = cur_objndx_pgndx; fobj->objndx_spndx = cur_objndx_spndx; /* Finalize updated object indices */ ret2 = OK; if (cur_objndx_spndx != 0) { /* Wrote beyond object index header page. Write last modified object * index page, unless object header index page */ finfo("objid=%04x store objndx page, %04x:%04x, nwritten=%zu\n", fobj->objid, cur_objndx_pgndx, cur_objndx_spndx, nwritten); ret2 = spiffs_page_index_check(fs, fobj, cur_objndx_pgndx, cur_objndx_spndx); if (ret2 < 0) { ferr("ERROR: spiffs_page_index_check() failed: %d\n", ret2); return ret2; } ret2 = spiffs_cache_write(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, cur_objndx_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret2 < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret2); return ret2; } spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *)fs->work, SPIFFS_EV_NDXUPD, fobj->objid, objndx->phdr.spndx, cur_objndx_pgndx, 0); /* Update size in object header index page */ ret2 = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, fobj->objhdr_pgndx, 0, 0, offset + nwritten, &new_objhdr_page); finfo("objid=%04x store new size II %" PRIdOFF " in objhdr, %04x:%04x, nwritten=%zu, ret=%d\n", fobj->objid, offset + nwritten, new_objhdr_page, 0, nwritten, ret2); if (ret2 < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", ret2); return ret2; } } else { /* Wrote within object index header page */ if (offset == 0) { /* Wrote to empty object - simply update size and write whole * page */ objhdr->size = offset + nwritten; finfo("objid=%04x store fresh objhdr page, " "%04x:%04x, nwritten=%zu\n", fobj->objid, cur_objndx_pgndx, cur_objndx_spndx, nwritten); ret2 = spiffs_page_index_check(fs, fobj, cur_objndx_pgndx, cur_objndx_spndx); if (ret2 < 0) { ferr("ERROR: spiffs_page_index_check() failed: %d\n", ret2); return ret2; } ret2 = spiffs_cache_write(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, cur_objndx_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret2 < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret2); return ret2; } spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *)fs->work, SPIFFS_EV_NDXUPD_HDR, fobj->objid, objhdr->phdr.spndx, cur_objndx_pgndx, objhdr->size); } else { /* Modifying object index header page, update size and make new * copy. */ ret2 = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, fobj->objhdr_pgndx, fs->work, 0, offset + nwritten, &new_objhdr_page); finfo("objid=%04x store modified objhdr page, %04x:%04x, " "nwritten=%zu\n", fobj->objid, new_objhdr_page, 0, nwritten); if (ret2 < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", ret2); return ret2; } } } return nwritten; } /**************************************************************************** * Name: spiffs_fobj_modify * * Description: * Modify object. Keep current object index (header) page in fs->work * buffer * ****************************************************************************/ ssize_t spiffs_fobj_modify(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj, off_t offset, FAR uint8_t *data, size_t len) { struct spiffs_page_header_s phdr; FAR struct spiffs_pgobj_ndxheader_s *objhdr; FAR struct spiffs_page_objndx_s *objndx; size_t nwritten = 0; uint32_t page_offs; int16_t cur_objndx_spndx; int16_t prev_objndx_spndx; int16_t cur_objndx_pgndx; int16_t new_objhdr_pgndx; int16_t data_spndx; int16_t data_pgndx; int ret = OK; int ret2; ret = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); if (ret < 0) { ferr("ERROR: spiffs_gc_check() failed: %d\n", ret); return ret; } objhdr = (FAR struct spiffs_pgobj_ndxheader_s *)fs->work; objndx = (FAR struct spiffs_page_objndx_s *)fs->work; cur_objndx_spndx = 0; prev_objndx_spndx = (int16_t) - 1; cur_objndx_pgndx = fobj->objhdr_pgndx; data_spndx = offset / SPIFFS_DATA_PAGE_SIZE(fs); page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); /* Write all data */ while (ret >= 0 && nwritten < len) { size_t to_write; int16_t orig_data_pgndx; /* Calculate object index page span index */ cur_objndx_spndx = SPIFFS_OBJNDX_ENTRY_SPNDX(fs, data_spndx); /* Handle storing and loading of object indices */ if (cur_objndx_spndx != prev_objndx_spndx) { /* New object index page. Within this clause we return directly * if something fails, object index mess-up */ if (nwritten > 0) { /* Store previous object index (header) page, unless first * pass. */ if (prev_objndx_spndx == 0) { /* Store previous object index header page */ ret = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, fobj->objhdr_pgndx, fs->work, 0, 0, &new_objhdr_pgndx); finfo("Store modified objhdr page, " "%04x:%04x, nwritten=%zu\n", new_objhdr_pgndx, 0, nwritten); if (ret < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", ret); return ret; } } else { /* Store new version of previous object index page */ int16_t new_objndx_pgndx; ret = spiffs_page_index_check(fs, fobj, cur_objndx_pgndx, prev_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_page_index_check() failed: %d\n", ret); return ret; } ret = spiffs_page_move(fs, fobj->objid, (FAR uint8_t *)objndx, fobj->objid, 0, cur_objndx_pgndx, &new_objndx_pgndx); finfo("Store previous modified objndx page, %04x:%04x, " "nwritten=%zu\n", new_objndx_pgndx, objndx->phdr.spndx, nwritten); if (ret < 0) { ferr("ERROR: spiffs_page_move() failed: %d\n", ret); return ret; } spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *) objndx, SPIFFS_EV_NDXUPD, fobj->objid, objndx->phdr.spndx, new_objndx_pgndx, 0); } } /* Load next object index page */ if (cur_objndx_spndx == 0) { /* Load object index header page, must exist */ finfo("Load objndxhdr page %04x:%04x\n", cur_objndx_pgndx, cur_objndx_spndx); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, cur_objndx_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } ret = spiffs_validate_objndx(&objhdr->phdr, fobj->objid, cur_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } } else { int16_t pgndx; /* Load existing object index page on first pass */ finfo("Find objndx spndx=%04x\n", cur_objndx_spndx); if (fobj->objndx_spndx == cur_objndx_spndx) { pgndx = fobj->objndx_pgndx; } else { ret = spiffs_objlu_find_id_and_span(fs, fobj->objid | SPIFFS_OBJID_NDXFLAG, cur_objndx_spndx, 0, &pgndx); if (ret < 0) { ferr("ERROR: spiffs_objlu_find_id_and_span() failed: " "%d\n", ret); return ret; } } finfo("Found object index at page=%04x\n", pgndx); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } ret = spiffs_validate_objndx(&objhdr->phdr, fobj->objid, cur_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } cur_objndx_pgndx = pgndx; } fobj->objndx_pgndx = cur_objndx_pgndx; fobj->objndx_spndx = cur_objndx_spndx; fobj->offset = offset + nwritten; prev_objndx_spndx = cur_objndx_spndx; } /* Write partial data */ to_write = MIN(len - nwritten, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); if (cur_objndx_spndx == 0) { /* Get data page from object index header page */ orig_data_pgndx = ((FAR int16_t *)((FAR uint8_t *)objhdr + sizeof(struct spiffs_pgobj_ndxheader_s)))[data_spndx]; } else { /* Get data page from object index page */ orig_data_pgndx = ((FAR int16_t *)((FAR uint8_t *)objndx + sizeof(struct spiffs_page_objndx_s))) [SPIFFS_OBJNDX_ENTRY(fs, data_spndx)]; } phdr.objid = fobj->objid & ~SPIFFS_OBJID_NDXFLAG; phdr.spndx = data_spndx; phdr.flags = 0xff; if (page_offs == 0 && to_write == SPIFFS_DATA_PAGE_SIZE(fs)) { /* A full page, allocate and write a new page of data */ ret = spiffs_page_allocate_data(fs, fobj->objid & ~SPIFFS_OBJID_NDXFLAG, &phdr, &data[nwritten], to_write, page_offs, 1, &data_pgndx); finfo("Store new data page, %04x:%04x offset=%" PRId32 ", " "len=%zu, nwritten=%zu\n", data_pgndx, data_spndx, page_offs, to_write, nwritten); } else { /* Write to existing page, allocate new and copy unmodified data */ ret = spiffs_page_data_check(fs, fobj, orig_data_pgndx, data_spndx); if (ret < 0) { ferr("ERROR: spiffs_page_data_check() failed: %d\n", ret); return ret; } ret = spiffs_page_allocate_data(fs, fobj->objid & ~SPIFFS_OBJID_NDXFLAG, &phdr, 0, 0, 0, 0, &data_pgndx); if (ret < 0) { ferr("ERROR: spiffs_page_allocate_data() failed: %d\n", ret); break; } /* Copy unmodified data */ if (page_offs > 0) { /* Before modification */ ret = spiffs_phys_cpy(fs, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, data_pgndx) + sizeof(struct spiffs_page_header_s), SPIFFS_PAGE_TO_PADDR(fs, orig_data_pgndx) + sizeof(struct spiffs_page_header_s), page_offs); if (ret < 0) { ferr("ERROR: spiffs_phys_cpy() failed: %d\n", ret); break; } } if (page_offs + to_write < SPIFFS_DATA_PAGE_SIZE(fs)) { /* After modification */ ret = spiffs_phys_cpy(fs, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, data_pgndx) + sizeof(struct spiffs_page_header_s) + page_offs + to_write, SPIFFS_PAGE_TO_PADDR(fs, orig_data_pgndx) + sizeof(struct spiffs_page_header_s) + page_offs + to_write, SPIFFS_DATA_PAGE_SIZE(fs) - (page_offs + to_write)); if (ret < 0) { ferr("ERROR: spiffs_phys_cpy() failed: %d\n", ret); break; } } ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, data_pgndx) + sizeof(struct spiffs_page_header_s) + page_offs, to_write, &data[nwritten]); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); break; } phdr.flags &= ~SPIFFS_PH_FLAG_FINAL; ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, data_pgndx) + offsetof(struct spiffs_page_header_s, flags), sizeof(uint8_t), (FAR uint8_t *)&phdr.flags); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); break; } finfo("Store to existing data page, src=%04x, dest=%04x:%04x " "offset=%" PRId32 ", len=%zu, nwritten=%zu\n", orig_data_pgndx, data_pgndx, data_spndx, page_offs, to_write, nwritten); } /* Delete original data page */ ret = spiffs_page_delete(fs, orig_data_pgndx); if (ret < 0) { ferr("ERROR: spiffs_page_delete() failed: %d\n", ret); break; } /* Update memory representation of object index page with new data * page */ if (cur_objndx_spndx == 0) { /* Update object index header page */ ((FAR int16_t *)((FAR uint8_t *)objhdr + sizeof(struct spiffs_pgobj_ndxheader_s)))[data_spndx] = data_pgndx; finfo("Wrote page %04x to objhdr entry=%04x in mem\n", data_pgndx, data_spndx); } else { /* Update object index page */ ((FAR int16_t *)((FAR uint8_t *)objndx + sizeof(struct spiffs_page_objndx_s))) [SPIFFS_OBJNDX_ENTRY(fs, data_spndx)] = data_pgndx; finfo("Wrote page %04x to objndx entry %04x in mem\n", data_pgndx, (int16_t)SPIFFS_OBJNDX_ENTRY(fs, data_spndx)); } /* Update internals */ page_offs = 0; data_spndx++; nwritten += to_write; } fobj->offset = offset + nwritten; fobj->objndx_pgndx = cur_objndx_pgndx; fobj->objndx_spndx = cur_objndx_spndx; /* Finalize updated object indices */ ret2 = OK; if (cur_objndx_spndx != 0) { int16_t new_objndx_pgndx; /* Wrote beyond object index header page. Write last modified object * index page. Move and update page */ ret2 = spiffs_page_index_check(fs, fobj, cur_objndx_pgndx, cur_objndx_spndx); if (ret2 < 0) { ferr("ERROR: spiffs_page_index_check() failed: %d\n", ret2); return ret2; } ret2 = spiffs_page_move(fs, fobj->objid, (uint8_t *) objndx, fobj->objid, 0, cur_objndx_pgndx, &new_objndx_pgndx); finfo("Store modified objndx page, %04x:%04x, nwritten=%zu\n", new_objndx_pgndx, cur_objndx_spndx, nwritten); fobj->objndx_pgndx = new_objndx_pgndx; fobj->objndx_spndx = cur_objndx_spndx; if (ret2 < 0) { ferr("ERROR: spiffs_page_move() failed: %d\n", ret2); return ret2; } spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *)objndx, SPIFFS_EV_NDXUPD, fobj->objid, objndx->phdr.spndx, new_objndx_pgndx, 0); } else { /* Wrote within object index header page */ ret2 = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, fobj->objhdr_pgndx, fs->work, 0, 0, &new_objhdr_pgndx); finfo("Store modified objhdr page, %04x:%04x, nwritten=%zu\n", new_objhdr_pgndx, 0, nwritten); if (ret2 < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", ret2); return ret2; } } return nwritten; } /**************************************************************************** * Name: spiffs_find_objhdr_pgndx * * Description: * Finds object index header page by name * ****************************************************************************/ int spiffs_find_objhdr_pgndx(FAR struct spiffs_s *fs, const uint8_t name[CONFIG_SPIFFS_NAME_MAX], FAR int16_t *pgndx) { int16_t blkndx; int entry; int ret; ret = spiffs_foreach_objlu(fs, fs->lu_blkndx, fs->lu_entry, 0, 0, spiffs_find_objhdr_pgndx_callback, name, 0, &blkndx, &entry); if (ret == SPIFFS_VIS_END) { ret = -ENOENT; } else if (ret < 0) { ferr("ERROR: spiffs_foreach_objlu() failed: %d\n", ret); return ret; } if (pgndx != NULL) { *pgndx = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, entry); } fs->lu_blkndx = blkndx; fs->lu_entry = entry; return ret; } /**************************************************************************** * Name: spiffs_fobj_truncate * * Description: * Truncates object to new size. If new size is NULL, object may be removed * totally * ****************************************************************************/ int spiffs_fobj_truncate(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj, off_t new_size, bool remove_full) { FAR struct spiffs_pgobj_ndxheader_s *objhdr; FAR struct spiffs_page_objndx_s *objndx; uint32_t cur_size; int16_t objndx_pgndx; int16_t data_spndx; int16_t cur_objndx_spndx; int16_t prev_objndx_spndx; int16_t data_pgndx; int16_t new_objhdr_pgndx; int ret = OK; /* If the file has zero (or undefined) length and we were not asked to * remove the file, then there is nothing to do. */ if ((fobj->size == SPIFFS_UNDEFINED_LEN || fobj->size == 0) && !remove_full) { /* Do nothing */ return ret; } /* Need 2 pages if not removing: object index page + possibly chopped data * page */ if (!remove_full) { ret = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs) * 2); if (ret < 0) { ferr("ERROR: spiffs_gc_check() failed: %d\n", ret); return ret; } } objhdr = (FAR struct spiffs_pgobj_ndxheader_s *)fs->work; objndx = (FAR struct spiffs_page_objndx_s *)fs->work; objndx_pgndx = fobj->objhdr_pgndx; cur_objndx_spndx = 0; prev_objndx_spndx = (int16_t)-1; data_spndx = 0; if (fobj->size > 0) { data_spndx = (fobj->size - 1) / SPIFFS_DATA_PAGE_SIZE(fs); } cur_size = 0; if (fobj->size != (uint32_t)SPIFFS_UNDEFINED_LEN) { cur_size = fobj->size; } /* Before truncating, check if object is to be fully removed and mark * this */ if (remove_full && new_size == 0) { uint8_t flags = ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_NDXDELE); ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, fobj->objhdr_pgndx) + offsetof(struct spiffs_page_header_s, flags), sizeof(uint8_t), (FAR uint8_t *)&flags); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); return ret; } } /* Delete from end of object until desired len is reached */ while (cur_size > new_size) { cur_objndx_spndx = SPIFFS_OBJNDX_ENTRY_SPNDX(fs, data_spndx); /* Put object index for current data span index in work buffer */ if (prev_objndx_spndx != cur_objndx_spndx) { if (prev_objndx_spndx != (int16_t)- 1) { /* Remove previous object index page */ finfo("Delete objndx page %04x:%04x\n", objndx_pgndx, prev_objndx_spndx); ret = spiffs_page_index_check(fs, fobj, objndx_pgndx, prev_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_page_index_check() failed: %d\n", ret); return ret; } ret = spiffs_page_delete(fs, objndx_pgndx); if (ret < 0) { ferr("ERROR: spiffs_page_delete() failed: %d\n", ret); return ret; } spiffs_fobj_event(fs, NULL, SPIFFS_EV_NDXDEL, fobj->objid, objndx->phdr.spndx, objndx_pgndx, 0); if (prev_objndx_spndx > 0) { /* Update object index header page, unless we totally want * to remove the file. * * If fully removing, we're not keeping consistency as * good as when storing the header between chunks, * would we be aborted. But when removing full files, a * crammed system may otherwise report ERR_FULL a la * windows. We cannot have that. Hence, take the risk - * if aborted, a file check would free the lost pages and * mend things as the file is marked as fully deleted in * the beginning. */ if (!remove_full) { finfo("Update objndx hdr page %04x:%04x to" "size=%" PRIu32 "\n", fobj->objhdr_pgndx, prev_objndx_spndx, cur_size); ret = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, fobj->objhdr_pgndx, 0, 0, cur_size, &new_objhdr_pgndx); if (ret < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: " "%d\n", ret); return ret; } } fobj->size = cur_size; } } /* Load current object index (header) page */ if (cur_objndx_spndx == 0) { objndx_pgndx = fobj->objhdr_pgndx; } else { ret = spiffs_objlu_find_id_and_span(fs, fobj->objid | SPIFFS_OBJID_NDXFLAG, cur_objndx_spndx, 0, &objndx_pgndx); if (ret < 0) { ferr("ERROR: spiffs_objlu_find_id_and_span() failed: %d\n", ret); return ret; } } finfo("Load objndx page %04x:%04x for data spndx=%04x\n", objndx_pgndx, cur_objndx_spndx, data_spndx); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, objndx_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } ret = spiffs_validate_objndx(&objhdr->phdr, fobj->objid, cur_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } fobj->objndx_pgndx = objndx_pgndx; fobj->objndx_spndx = cur_objndx_spndx; fobj->offset = cur_size; prev_objndx_spndx = cur_objndx_spndx; } if (cur_objndx_spndx == 0) { /* Get data page from object index header page */ data_pgndx = ((FAR int16_t *)((FAR uint8_t *) objhdr + sizeof(struct spiffs_pgobj_ndxheader_s)))[data_spndx]; ((FAR int16_t *)((FAR uint8_t *)objhdr + sizeof(struct spiffs_pgobj_ndxheader_s)))[data_spndx] = SPIFFS_OBJID_FREE; } else { /* Get data page from object index page */ data_pgndx = ((FAR int16_t *)((FAR uint8_t *)objndx + sizeof(struct spiffs_page_objndx_s))) [SPIFFS_OBJNDX_ENTRY(fs, data_spndx)]; ((FAR int16_t *)((FAR uint8_t *)objndx + sizeof(struct spiffs_page_objndx_s))) [SPIFFS_OBJNDX_ENTRY(fs, data_spndx)] = SPIFFS_OBJID_FREE; } finfo("Got data pgndx %04x\n", data_pgndx); if (new_size == 0 || remove_full || cur_size - new_size >= SPIFFS_DATA_PAGE_SIZE(fs)) { /* Delete full data page */ ret = spiffs_page_data_check(fs, fobj, data_pgndx, data_spndx); if (ret != SPIFFS_ERR_DELETED && ret < 0 && ret != SPIFFS_ERR_INDEX_REF_FREE) { ferr("ERROR: Failed to validate data pgndx=%d\n", ret); break; } if (ret >= 0) { ret = spiffs_page_delete(fs, data_pgndx); if (ret < 0) { ferr("ERROR: Failed to delete data pgndx=%d\n", ret); break; } } else if (ret == SPIFFS_ERR_DELETED || ret == SPIFFS_ERR_INDEX_REF_FREE) { ret = OK; } /* 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); } fobj->size = cur_size; fobj->offset = cur_size; finfo("Delete data page %04x for data spndx=%04x," "cur_size=%" PRIu32 "\n", data_pgndx, data_spndx, cur_size); } else { struct spiffs_page_header_s phdr; int16_t new_data_pgndx; uint32_t bytes_to_remove; /* Delete last page, partially */ bytes_to_remove = SPIFFS_DATA_PAGE_SIZE(fs) - (new_size % SPIFFS_DATA_PAGE_SIZE(fs)); finfo("Delete %" PRIu32 " bytes from data page=%04x for " "data spndx=%04x, cur_size=%" PRIu32 "\n", bytes_to_remove, data_pgndx, data_spndx, cur_size); ret = spiffs_page_data_check(fs, fobj, data_pgndx, data_spndx); if (ret < 0) { ferr("ERROR: spiffs_page_data_check() failed: %d\n", ret); break; } phdr.objid = fobj->objid & ~SPIFFS_OBJID_NDXFLAG; phdr.spndx = data_spndx; phdr.flags = 0xff; /* Allocate new page and copy unmodified data */ ret = spiffs_page_allocate_data(fs, fobj->objid & ~SPIFFS_OBJID_NDXFLAG, &phdr, 0, 0, 0, 0, &new_data_pgndx); if (ret < 0) { ferr("ERROR: spiffs_page_allocate_data() failed: %d\n", ret); break; } ret = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_data_pgndx) + sizeof(struct spiffs_page_header_s), SPIFFS_PAGE_TO_PADDR(fs, data_pgndx) + sizeof(struct spiffs_page_header_s), SPIFFS_DATA_PAGE_SIZE(fs) - bytes_to_remove); if (ret < 0) { ferr("ERROR: spiffs_phys_cpy() failed: %d\n", ret); break; } /* Delete original data page */ ret = spiffs_page_delete(fs, data_pgndx); if (ret < 0) { ferr("ERROR: spiffs_page_delete() failed: %d\n", ret); break; } phdr.flags &= ~SPIFFS_PH_FLAG_FINAL; ret = spiffs_cache_write(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, new_data_pgndx) + offsetof(struct spiffs_page_header_s, flags), sizeof(uint8_t), (FAR uint8_t *)&phdr.flags); if (ret < 0) { ferr("ERROR: spiffs_cache_write() failed: %d\n", ret); break; } /* Update memory representation of object index page with new data * page */ if (cur_objndx_spndx == 0) { /* Update object index header page */ ((FAR int16_t *)((FAR uint8_t *) objhdr + sizeof(struct spiffs_pgobj_ndxheader_s)))[data_spndx] = new_data_pgndx; finfo("Wrote page=%04x to objhdr entry %04x in mem\n", new_data_pgndx, (int16_t)SPIFFS_OBJNDX_ENTRY(fs, data_spndx)); } else { /* Update object index page */ ((FAR int16_t *)((FAR uint8_t *)objndx + sizeof(struct spiffs_page_objndx_s))) [SPIFFS_OBJNDX_ENTRY(fs, data_spndx)] = new_data_pgndx; finfo("Wrote page %04x to objndx entry=%04x in mem\n", new_data_pgndx, (int16_t)SPIFFS_OBJNDX_ENTRY(fs, data_spndx)); } cur_size = new_size; fobj->size = new_size; fobj->offset = cur_size; break; } data_spndx--; } /* Update object indices */ if (cur_objndx_spndx == 0) { /* Update object index header page */ if (cur_size == 0) { if (remove_full) { /* Femove object altogether */ finfo("Femove object index header page=%04x\n", objndx_pgndx); ret = spiffs_page_index_check(fs, fobj, objndx_pgndx, 0); if (ret < 0) { ferr("ERROR: spiffs_page_index_check() failed: %d\n", ret); return ret; } ret = spiffs_page_delete(fs, objndx_pgndx); if (ret < 0) { ferr("ERROR: spiffs_page_delete() failed: %d\n", ret); return ret; } spiffs_fobj_event(fs, NULL, SPIFFS_EV_NDXDEL, fobj->objid, 0, objndx_pgndx, 0); } else { /* Make uninitialized object */ finfo("Reset objhdr page=%04x\n", objndx_pgndx); memset(fs->work + sizeof(struct spiffs_pgobj_ndxheader_s), 0xff, SPIFFS_GEO_PAGE_SIZE(fs) - sizeof(struct spiffs_pgobj_ndxheader_s)); ret = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, objndx_pgndx, fs->work, 0, SPIFFS_UNDEFINED_LEN, &new_objhdr_pgndx); { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", ret); return ret; } } } else { /* Update object index header page */ finfo("Update object index header page with indices and size\n"); ret = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, objndx_pgndx, fs->work, 0, cur_size, &new_objhdr_pgndx); if (ret < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", ret); return ret; } } } else { int16_t new_objndx_pgndx; /* Update both current object index page and object index header page */ ret = spiffs_page_index_check(fs, fobj, objndx_pgndx, cur_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_page_index_check() failed: %d\n", ret); return ret; } /* Move and update object index page */ ret = spiffs_page_move(fs, fobj->objid, (FAR uint8_t *)objhdr, fobj->objid, 0, objndx_pgndx, &new_objndx_pgndx); if (ret < 0) { ferr("ERROR: spiffs_page_move() failed: %d\n", ret); return ret; } spiffs_fobj_event(fs, (FAR struct spiffs_page_objndx_s *)objhdr, SPIFFS_EV_NDXUPD, fobj->objid, objndx->phdr.spndx, new_objndx_pgndx, 0); finfo("Store modified objndx page, %04x:%04x\n", new_objndx_pgndx, cur_objndx_spndx); fobj->objndx_pgndx = new_objndx_pgndx; fobj->objndx_spndx = cur_objndx_spndx; fobj->offset = cur_size; /* Update object index header page with new size */ ret = spiffs_fobj_update_ndxhdr(fs, fobj, fobj->objid, fobj->objhdr_pgndx, 0, 0, cur_size, &new_objhdr_pgndx); if (ret < 0) { ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", ret); return ret; } } fobj->size = cur_size; return ret; } /**************************************************************************** * Name: spiffs_object_read * * Description: * ****************************************************************************/ ssize_t spiffs_object_read(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj, off_t offset, size_t len, FAR uint8_t *dest) { FAR struct spiffs_pgobj_ndxheader_s *objhdr; FAR struct spiffs_page_objndx_s *objndx ; uint32_t cur_offset; int16_t objndx_pgndx; int16_t data_pgndx; int16_t data_spndx; int16_t cur_objndx_spndx; int16_t prev_objndx_spndx; int ret = OK; objhdr = (FAR struct spiffs_pgobj_ndxheader_s *)fs->work; objndx = (FAR struct spiffs_page_objndx_s *)fs->work; data_spndx = offset / SPIFFS_DATA_PAGE_SIZE(fs); cur_offset = offset; prev_objndx_spndx = (int16_t)-1; while (cur_offset < offset + len) { uint32_t len_to_read; cur_objndx_spndx = SPIFFS_OBJNDX_ENTRY_SPNDX(fs, data_spndx); if (prev_objndx_spndx != cur_objndx_spndx) { /* Load current object index (header) page */ if (cur_objndx_spndx == 0) { objndx_pgndx = fobj->objhdr_pgndx; } else { finfo("Find objndx %04x:%04x\n", fobj->objid, cur_objndx_spndx); if (fobj->objndx_spndx == cur_objndx_spndx) { objndx_pgndx = fobj->objndx_pgndx; } else { ret = spiffs_objlu_find_id_and_span(fs, fobj->objid | SPIFFS_OBJID_NDXFLAG, cur_objndx_spndx, 0, &objndx_pgndx); if (ret < 0) { ferr("ERROR: spiffs_objlu_find_id_and_span() failed: " "%d\n", ret); return ret; } } } finfo("Load objndx page %d:%d for data spndx=%d\n", objndx_pgndx, cur_objndx_spndx, data_spndx); ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJNDX | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, objndx_pgndx), SPIFFS_GEO_PAGE_SIZE(fs), fs->work); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } ret = spiffs_validate_objndx(&objndx->phdr, fobj->objid, cur_objndx_spndx); if (ret < 0) { ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); return ret; } fobj->offset = cur_offset; fobj->objndx_pgndx = objndx_pgndx; fobj->objndx_spndx = cur_objndx_spndx; prev_objndx_spndx = cur_objndx_spndx; } if (cur_objndx_spndx == 0) { /* Get data page from object index header page */ data_pgndx = ((FAR int16_t *)((FAR uint8_t *)objhdr + sizeof(struct spiffs_pgobj_ndxheader_s)))[data_spndx]; } else { /* Get data page from object index page */ data_pgndx = ((FAR int16_t *)((FAR uint8_t *) objndx + sizeof(struct spiffs_page_objndx_s))) [SPIFFS_OBJNDX_ENTRY(fs, data_spndx)]; } /* All remaining data */ 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, fobj->size); finfo("Read offset=%" PRIu32 " rd=%" PRIu32 " data spndx=%d is " "data_pgndx=%d addr=%" PRIu32 "\n", cur_offset, len_to_read, data_spndx, data_pgndx, (uint32_t)(SPIFFS_PAGE_TO_PADDR(fs, data_pgndx) + sizeof(struct spiffs_page_header_s) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)))); if (len_to_read <= 0) { len = cur_offset - offset; break; } ret = spiffs_page_data_check(fs, fobj, data_pgndx, data_spndx); if (ret < 0) { ferr("ERROR: spiffs_page_data_check() failed: %d\n", ret); return ret; } ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, fobj->objid, SPIFFS_PAGE_TO_PADDR(fs, data_pgndx) + sizeof(struct spiffs_page_header_s) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)), len_to_read, dest); if (ret < 0) { ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); return ret; } dest += len_to_read; cur_offset += len_to_read; fobj->offset = cur_offset; data_spndx++; } return len; } /**************************************************************************** * Name: spiffs_objlu_find_free_objid * * Description: * Scans through 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 bitmask is searched for a free objid * ****************************************************************************/ int spiffs_objlu_find_free_objid(FAR struct spiffs_s *fs, int16_t *objid, FAR const uint8_t *conflicting_name) { uint32_t max_objects; struct spiffs_free_objid_s state; int16_t free_objid; int ret = OK; max_objects = (SPIFFS_GEO_BLOCK_COUNT(fs) * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) / 2; free_objid = SPIFFS_OBJID_FREE; state.min_objid = 1; state.max_objid = max_objects + 1; if ((state.max_objid & SPIFFS_OBJID_NDXFLAG) != 0) { state.max_objid = ((int16_t) - 1) & ~SPIFFS_OBJID_NDXFLAG; } state.compaction = 0; state.conflicting_name = conflicting_name; while (ret >= 0 && free_objid == SPIFFS_OBJID_FREE) { if (state.max_objid - state.min_objid <= (int16_t)SPIFFS_GEO_PAGE_SIZE(fs) * 8) { uint32_t i; uint32_t j; /* Possible to represent in bitmap */ finfo("BITM min=%04x max=%04x\n", state.min_objid, state.max_objid); memset(fs->work, 0, SPIFFS_GEO_PAGE_SIZE(fs)); ret = spiffs_foreach_objlu(fs, 0, 0, 0, 0, spiffs_objlu_find_free_objid_bitmap_callback, conflicting_name, &state.min_objid, 0, 0); if (ret == SPIFFS_VIS_END) { ret = OK; } else if (ret < 0) { ferr("ERROR: spiffs_foreach_objlu() failed: %d\n", ret); return ret; } /* Traverse bitmask until found free objid */ for (i = 0; i < SPIFFS_GEO_PAGE_SIZE(fs); i++) { uint8_t mask = fs->work[i]; if (mask == 0xff) { continue; } for (j = 0; j < 8; j++) { if ((mask & (1 << j)) == 0) { *objid = (i << 3) + j + state.min_objid; return OK; } } } return -ENOSPC; } else { /* Not possible to represent all ids in range in a bitmap, compact * and count */ if (state.compaction != 0) { uint32_t i; uint32_t min_i; uint8_t *map; uint8_t min_count; /* Select element in compacted table, decrease range and * recompact */ min_i = 0; map = (uint8_t *)fs->work; min_count = 0xff; for (i = 0; i < SPIFFS_GEO_PAGE_SIZE(fs) / sizeof(uint8_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! */ fwarn("WARNING: Compacted table is full\n"); return -ENOSPC; } finfo("COMP select index=%" PRIu32 " min_count=%d min=%04x " "max=%04x compact:%" PRIu32 "\n", min_i, min_count, state.min_objid, state.max_objid, state.compaction); if (min_count == 0) { /* No objid in this range, skip compacting and use * directly */ *objid = min_i * state.compaction + state.min_objid; return OK; } else { finfo("COMP SEL chunk=%" PRIu32 " min=%04x " "-> %04" PRIx32 "\n", state.compaction, state.min_objid, state.min_objid + min_i * state.compaction); state.min_objid += min_i * state.compaction; state.max_objid = state.min_objid + state.compaction; /* Decrease compaction */ } if ((state.max_objid - state.min_objid <= (int16_t)SPIFFS_GEO_PAGE_SIZE(fs) * 8)) { /* No need for compacting, use bitmap */ continue; } } /* In a work memory of bytes, we may fit in * IDs. NOTE: Currently page size is equivalent to * block size. * * TODO: What if compaction is > 255 - then we cannot fit it in a * byte */ state.compaction = (state.max_objid - state.min_objid) / ((SPIFFS_GEO_PAGE_SIZE(fs) / sizeof(uint8_t))); finfo("COMP min=%04x max=%04x compact=%" PRIu32 "\n", state.min_objid, state.max_objid, state.compaction); memset(fs->work, 0, SPIFFS_GEO_PAGE_SIZE(fs)); ret = spiffs_foreach_objlu(fs, 0, 0, 0, 0, spiffs_objlu_find_free_objid_compact_callback, &state, 0, 0, 0); if (ret == SPIFFS_VIS_END) { ret = OK; } else if (ret < 0) { ferr("ERROR: spiffs_foreach_objlu() failed: %d\n", ret); return ret; } state.conflicting_name = 0; /* Searched for conflicting name once, * no need to do it again */ } } return ret; }