u32_t tfile_get_size(tfile_size s) { switch (s) { case EMPTY: return 0; case SMALL: // half a data page return SPIFFS_DATA_PAGE_SIZE(FS)/2; case MEDIUM: // one block return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS)); case LARGE: // third of fs return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS)) * (FS)->block_count/3; } return 0; }
uint32_t tfile_get_size(tfile_size s) { switch (s) { case EMPTY: return 0; case SMALL: return SPIFFS_DATA_PAGE_SIZE(FS)/2; case MEDIUM: return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS)); case LARGE: return (FS)->cfg.phys_size/3; } return 0; }
} TEST_END #if !SPIFFS_READ_ONLY TEST(truncate_48) { fs_reset(); u32_t len = SPIFFS_DATA_PAGE_SIZE(FS)/2; s32_t res = test_create_and_write_file("small", len, len); TEST_CHECK_GE(res, 0); spiffs_file fd = SPIFFS_open(FS, "small", SPIFFS_RDWR, 0); TEST_CHECK_GE(fd, 0); spiffs_fd *desc; #if SPIFFS_FILEHDL_OFFSET res = spiffs_fd_get(FS, fd - TEST_SPIFFS_FILEHDL_OFFSET, &desc); #else res = spiffs_fd_get(FS, fd, &desc); #endif TEST_CHECK_GE(res, 0); TEST_CHECK_EQ(desc->size, len); u32_t new_len = len/2; res = spiffs_object_truncate(desc, new_len, 0); TEST_CHECK_GE(res, 0); TEST_CHECK_EQ(desc->size, new_len); res = SPIFFS_close(FS, fd); TEST_CHECK_GE(res, 0); spiffs_stat s; res = SPIFFS_stat(FS, "small", &s); TEST_CHECK_GE(res, 0); TEST_CHECK_EQ(s.size, new_len); res = SPIFFS_remove(FS, "small"); TEST_CHECK_GE(res, 0); fd = SPIFFS_open(FS, "small", SPIFFS_RDWR, 0); TEST_CHECK_LT(fd, 0); TEST_CHECK_EQ(SPIFFS_errno(FS), SPIFFS_ERR_NOT_FOUND); return TEST_RES_OK; } TEST_END
s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) { SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); spiffs_fd *fd; s32_t res; fh = SPIFFS_FH_UNOFFS(fs, fh); res = spiffs_fd_get(fs, fh, &fd); SPIFFS_API_CHECK_RES(fs, res); #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 > (s32_t)fd->size) && (SPIFFS_UNDEFINED_LEN != 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 offs; }
s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used) { s32_t res = SPIFFS_OK; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); u32_t pages_per_block = SPIFFS_PAGES_PER_BLOCK(fs); u32_t blocks = fs->block_count; u32_t obj_lu_pages = SPIFFS_OBJ_LOOKUP_PAGES(fs); u32_t data_page_size = SPIFFS_DATA_PAGE_SIZE(fs); u32_t total_data_pages = (blocks - 2) * (pages_per_block - obj_lu_pages) + 1; // -2 for spare blocks, +1 for emergency page if (total) { *total = total_data_pages * data_page_size; } if (used) { *used = fs->stats_p_allocated * data_page_size; } SPIFFS_UNLOCK(fs); return res; }
int run_file_config(int cfg_count, tfile_conf* cfgs, int max_runs, int max_concurrent_files, int dbg) { int res; tfile *tfiles = malloc(sizeof(tfile) * max_concurrent_files); memset(tfiles, 0, sizeof(tfile) * max_concurrent_files); int run = 0; int cur_config_ix = 0; char name[32]; while (run < max_runs) { if (dbg) printf(" run %i/%i\n", run, max_runs); int i; for (i = 0; i < max_concurrent_files; i++) { sprintf(name, "file%i_%i", (1+run), i); tfile *tf = &tfiles[i]; if (tf->state == 0 && cur_config_ix < cfg_count) { // create a new file strcpy(tf->name, name); tf->state = 1; tf->cfg = cfgs[cur_config_ix]; int size = tfile_get_size(tf->cfg.tsize); if (dbg) printf(" create new %s with cfg %i/%i, size %i\n", name, (1+cur_config_ix), cfg_count, size); if (tf->cfg.tsize == EMPTY) { res = SPIFFS_creat(FS, name, 0); CHECK_RES(res); int pfd = open(make_test_fname(name), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); close(pfd); int extra_flags = tf->cfg.ttype == APPENDED ? SPIFFS_APPEND : 0; spiffs_file fd = SPIFFS_open(FS, name, extra_flags | SPIFFS_RDWR, 0); CHECK(fd > 0); tf->fd = fd; } else { int extra_flags = tf->cfg.ttype == APPENDED ? SPIFFS_APPEND : 0; spiffs_file fd = SPIFFS_open(FS, name, extra_flags | SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0); CHECK(fd > 0); extra_flags = tf->cfg.ttype == APPENDED ? O_APPEND : 0; int pfd = open(make_test_fname(name), extra_flags | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); tf->fd = fd; uint8_t *buf = malloc(size); memrand(buf, size); res = SPIFFS_write(FS, fd, buf, size); CHECK_RES(res); write(pfd, buf, size); close(pfd); free(buf); res = read_and_verify(name); CHECK_RES(res); } cur_config_ix++; } else if (tf->state > 0) { // hande file lifecycle switch (tf->cfg.ttype) { case UNTAMPERED: { break; } case APPENDED: { if (dbg) printf(" appending %s\n", tf->name); int size = SPIFFS_DATA_PAGE_SIZE(FS)*3; uint8_t *buf = malloc(size); memrand(buf, size); res = SPIFFS_write(FS, tf->fd, buf, size); CHECK_RES(res); int pfd = open(make_test_fname(tf->name), O_APPEND | O_RDWR); write(pfd, buf, size); close(pfd); free(buf); res = read_and_verify(tf->name); CHECK_RES(res); break; } case MODIFIED: { if (dbg) printf(" modify %s\n", tf->name); spiffs_stat stat; res = SPIFFS_fstat(FS, tf->fd, &stat); CHECK_RES(res); int size = stat.size / tf->cfg.tlife + SPIFFS_DATA_PAGE_SIZE(FS)/3; int offs = (stat.size / tf->cfg.tlife) * tf->state; res = SPIFFS_lseek(FS, tf->fd, offs, SPIFFS_SEEK_SET); CHECK_RES(res); uint8_t *buf = malloc(size); memrand(buf, size); res = SPIFFS_write(FS, tf->fd, buf, size); CHECK_RES(res); int pfd = open(make_test_fname(tf->name), O_RDWR); lseek(pfd, offs, SEEK_SET); write(pfd, buf, size); close(pfd); free(buf); res = read_and_verify(tf->name); CHECK_RES(res); break; } case REWRITTEN: { if (tf->fd > 0) { SPIFFS_close(FS, tf->fd); } if (dbg) printf(" rewriting %s\n", tf->name); spiffs_file fd = SPIFFS_open(FS, tf->name, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0); CHECK(fd > 0); int pfd = open(make_test_fname(tf->name), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); tf->fd = fd; int size = tfile_get_size(tf->cfg.tsize); uint8_t *buf = malloc(size); memrand(buf, size); res = SPIFFS_write(FS, fd, buf, size); CHECK_RES(res); write(pfd, buf, size); close(pfd); free(buf); res = read_and_verify(tf->name); CHECK_RES(res); break; } } tf->state++; if (tf->state > tf->cfg.tlife) { // file outlived its time, kill it if (tf->fd > 0) { SPIFFS_close(FS, tf->fd); } if (dbg) printf(" removing %s\n", tf->name); res = read_and_verify(tf->name); CHECK_RES(res); res = SPIFFS_remove(FS, tf->name); CHECK_RES(res); remove(make_test_fname(tf->name)); memset(tf, 0, sizeof(tf)); } } } run++; } free(tfiles); return 0; }
// Checks if garbage 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; s32_t free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count-2) - fs->stats_p_allocated - fs->stats_p_deleted; int tries = 0; if (fs->free_blocks > 3 && (s32_t)len < free_pages * (s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { return SPIFFS_OK; } u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); // if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) { // SPIFFS_GC_DBG("gc: full freeblk:%i needed:%i free:%i dele:%i\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); // return SPIFFS_ERR_FULL; // } if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) { SPIFFS_GC_DBG("gc_check: full freeblk:%i needed:%i free:%i dele:%i\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); return SPIFFS_ERR_FULL; } do { SPIFFS_GC_DBG("\ngc_check #%i: run gc free_blocks:%i pfree:%i pallo:%i pdele:%i [%i] len:%i of %i\n", 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; s32_t prev_free_pages = free_pages; // if the fs is crammed, ignore block age when selecting candidate - kind of a bad state res = spiffs_gc_find_candidate(fs, &cands, &count, free_pages <= 0); SPIFFS_CHECK_RES(res); if (count == 0) { SPIFFS_GC_DBG("gc_check: no candidates, return\n"); return (s32_t)needed_pages < free_pages ? SPIFFS_OK : SPIFFS_ERR_FULL; } #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 - 2) - fs->stats_p_allocated - fs->stats_p_deleted; if (prev_free_pages <= 0 && prev_free_pages == free_pages) { // abort early to reduce wear, at least tried once SPIFFS_GC_DBG("gc_check: early abort, no result on gc when fs crammed\n"); break; } } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 || (s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs))); free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) - fs->stats_p_allocated - fs->stats_p_deleted; if ((s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { res = SPIFFS_ERR_FULL; } SPIFFS_GC_DBG("gc_check: 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; }
// 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; s32_t free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count-2) - fs->stats_p_allocated - fs->stats_p_deleted; int tries = 0; if (fs->free_blocks > 3 && (s32_t)len < free_pages * (s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { return SPIFFS_OK; } u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) { return SPIFFS_ERR_FULL; } //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 - 2) - fs->stats_p_allocated - fs->stats_p_deleted; } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 || (s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs))); free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) - fs->stats_p_allocated - fs->stats_p_deleted; if ((s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { res = SPIFFS_ERR_FULL; } SPIFFS_GC_DBG("gc_check: 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; }