feat(spiffs): Bring from esp-idf

Commit ID: 13018449
This commit is contained in:
dongheng
2019-03-15 14:38:36 +08:00
parent 3827caebc4
commit 9b2d10bdce
52 changed files with 11321 additions and 923 deletions

@ -1,7 +1,15 @@
set(COMPONENT_ADD_INCLUDEDIRS include include/spiffs) set(COMPONENT_ADD_INCLUDEDIRS "include")
set(COMPONENT_SRCDIRS library) set(COMPONENT_PRIV_INCLUDEDIRS "." "spiffs/src")
set(COMPONENT_SRCS "esp_spiffs.c"
"spiffs_api.c"
"spiffs/src/spiffs_cache.c"
"spiffs/src/spiffs_check.c"
"spiffs/src/spiffs_gc.c"
"spiffs/src/spiffs_hydrogen.c"
"spiffs/src/spiffs_nucleus.c")
set(COMPONENT_REQUIRES freertos) set(COMPONENT_REQUIRES spi_flash)
set(COMPONENT_PRIV_REQUIRES esp8266) set(COMPONENT_PRIV_REQUIRES bootloader_support)
register_component() register_component()

161
components/spiffs/Kconfig Normal file

@ -0,0 +1,161 @@
menu "SPIFFS Configuration"
config SPIFFS_MAX_PARTITIONS
int "Maximum Number of Partitions"
default 3
range 1 10
help
Define maximum number of partitions that can be mounted.
menu "SPIFFS Cache Configuration"
config SPIFFS_CACHE
bool "Enable SPIFFS Cache"
default "y"
help
Enables/disable memory read caching of nucleus file system
operations.
config SPIFFS_CACHE_WR
bool "Enable SPIFFS Write Caching"
default "y"
depends on SPIFFS_CACHE
help
Enables memory write caching for file descriptors in hydrogen.
config SPIFFS_CACHE_STATS
bool "Enable SPIFFS Cache Statistics"
default "n"
depends on SPIFFS_CACHE
help
Enable/disable statistics on caching. Debug/test purpose only.
endmenu
config SPIFFS_PAGE_CHECK
bool "Enable SPIFFS Page Check"
default "y"
help
Always check header of each accessed page to ensure consistent state.
If enabled it will increase number of reads from flash, especially
if cache is disabled.
config SPIFFS_GC_MAX_RUNS
int "Set Maximum GC Runs"
default 10
range 1 255
help
Define maximum number of GC runs to perform to reach desired free pages.
config SPIFFS_GC_STATS
bool "Enable SPIFFS GC Statistics"
default "n"
help
Enable/disable statistics on gc. Debug/test purpose only.
config SPIFFS_PAGE_SIZE
int "SPIFFS logical page size"
default 256
range 256 1024
help
Logical page size of SPIFFS partition, in bytes. Must be multiple
of flash page size (which is usually 256 bytes).
Larger page sizes reduce overhead when storing large files, and
improve filesystem performance when reading large files.
Smaller page sizes reduce overhead when storing small (< page size)
files.
config SPIFFS_OBJ_NAME_LEN
int "Set SPIFFS Maximum Name Length"
default 32
range 1 256
help
Object name maximum length. Note that this length include the
zero-termination character, meaning maximum string of characters
can at most be SPIFFS_OBJ_NAME_LEN - 1.
SPIFFS_OBJ_NAME_LEN + SPIFFS_META_LENGTH should not exceed
SPIFFS_PAGE_SIZE - 64.
config SPIFFS_USE_MAGIC
bool "Enable SPIFFS Filesystem Magic"
default "y"
help
Enable this to have an identifiable spiffs filesystem.
This will look for a magic in all sectors to determine if this
is a valid spiffs system or not at mount time.
config SPIFFS_USE_MAGIC_LENGTH
bool "Enable SPIFFS Filesystem Length Magic"
default "y"
depends on SPIFFS_USE_MAGIC
help
If this option is enabled, the magic will also be dependent
on the length of the filesystem. For example, a filesystem
configured and formatted for 4 megabytes will not be accepted
for mounting with a configuration defining the filesystem as 2 megabytes.
config SPIFFS_META_LENGTH
int "Size of per-file metadata field"
default 4
help
This option sets the number of extra bytes stored in the file header.
These bytes can be used in an application-specific manner.
Set this to at least 4 bytes to enable support for saving file
modification time.
SPIFFS_OBJ_NAME_LEN + SPIFFS_META_LENGTH should not exceed
SPIFFS_PAGE_SIZE - 64.
config SPIFFS_USE_MTIME
bool "Save file modification time"
default "y"
depends on SPIFFS_META_LENGTH >= 4
help
If enabled, then the first 4 bytes of per-file metadata will be used
to store file modification time (mtime), accessible through
stat/fstat functions.
Modification time is updated when the file is opened.
menu "Debug Configuration"
config SPIFFS_DBG
bool "Enable general SPIFFS debug"
default "n"
help
Enabling this option will print general debug mesages to the console.
config SPIFFS_API_DBG
bool "Enable SPIFFS API debug"
default "n"
help
Enabling this option will print API debug mesages to the console.
config SPIFFS_GC_DBG
bool "Enable SPIFFS Garbage Cleaner debug"
default "n"
help
Enabling this option will print GC debug mesages to the console.
config SPIFFS_CACHE_DBG
bool "Enable SPIFFS Cache debug"
default "n"
depends on SPIFFS_CACHE
help
Enabling this option will print cache debug mesages to the console.
config SPIFFS_CHECK_DBG
bool "Enable SPIFFS Filesystem Check debug"
default "n"
help
Enabling this option will print Filesystem Check debug mesages
to the console.
config SPIFFS_TEST_VISUALISATION
bool "Enable SPIFFS Filesystem Visualization"
default "n"
help
Enable this option to enable SPIFFS_vis function in the API.
endmenu
endmenu

@ -1,6 +1,5 @@
# COMPONENT_ADD_INCLUDEDIRS := include
# Component Makefile COMPONENT_PRIV_INCLUDEDIRS := . spiffs/src
# COMPONENT_SRCDIRS := . spiffs/src
COMPONENT_ADD_INCLUDEDIRS += include/spiffs
COMPONENT_SRCDIRS := library COMPONENT_SUBMODULES := spiffs

@ -0,0 +1,746 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_spiffs.h"
#include "spiffs.h"
#include "spiffs_nucleus.h"
#include "esp_log.h"
#include "esp_partition.h"
#include "esp_spi_flash.h"
#include "esp_image_format.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <unistd.h>
#include <dirent.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
#include "esp_vfs.h"
#include "esp_err.h"
#include "rom/spi_flash.h"
#include "spiffs_api.h"
static const char* TAG = "SPIFFS";
#ifdef CONFIG_SPIFFS_USE_MTIME
_Static_assert(CONFIG_SPIFFS_META_LENGTH >= sizeof(time_t),
"SPIFFS_META_LENGTH size should be >= sizeof(time_t)");
#endif //CONFIG_SPIFFS_USE_MTIME
/**
* @brief SPIFFS DIR structure
*/
typedef struct {
DIR dir; /*!< VFS DIR struct */
spiffs_DIR d; /*!< SPIFFS DIR struct */
struct dirent e; /*!< Last open dirent */
long offset; /*!< Offset of the current dirent */
char path[SPIFFS_OBJ_NAME_LEN]; /*!< Requested directory name */
} vfs_spiffs_dir_t;
static int vfs_spiffs_open(void* ctx, const char * path, int flags, int mode);
static ssize_t vfs_spiffs_write(void* ctx, int fd, const void * data, size_t size);
static ssize_t vfs_spiffs_read(void* ctx, int fd, void * dst, size_t size);
static int vfs_spiffs_close(void* ctx, int fd);
static off_t vfs_spiffs_lseek(void* ctx, int fd, off_t offset, int mode);
static int vfs_spiffs_fstat(void* ctx, int fd, struct stat * st);
static int vfs_spiffs_stat(void* ctx, const char * path, struct stat * st);
static int vfs_spiffs_unlink(void* ctx, const char *path);
static int vfs_spiffs_link(void* ctx, const char* n1, const char* n2);
static int vfs_spiffs_rename(void* ctx, const char *src, const char *dst);
static DIR* vfs_spiffs_opendir(void* ctx, const char* name);
static int vfs_spiffs_closedir(void* ctx, DIR* pdir);
static struct dirent* vfs_spiffs_readdir(void* ctx, DIR* pdir);
static int vfs_spiffs_readdir_r(void* ctx, DIR* pdir,
struct dirent* entry, struct dirent** out_dirent);
static long vfs_spiffs_telldir(void* ctx, DIR* pdir);
static void vfs_spiffs_seekdir(void* ctx, DIR* pdir, long offset);
static int vfs_spiffs_mkdir(void* ctx, const char* name, mode_t mode);
static int vfs_spiffs_rmdir(void* ctx, const char* name);
static void vfs_spiffs_update_mtime(spiffs *fs, spiffs_file f);
static time_t vfs_spiffs_get_mtime(const spiffs_stat* s);
static esp_spiffs_t * _efs[CONFIG_SPIFFS_MAX_PARTITIONS];
static void esp_spiffs_free(esp_spiffs_t ** efs)
{
esp_spiffs_t * e = *efs;
if (*efs == NULL) {
return;
}
*efs = NULL;
if (e->fs) {
SPIFFS_unmount(e->fs);
free(e->fs);
}
vSemaphoreDelete(e->lock);
free(e->fds);
free(e->cache);
free(e->work);
free(e);
}
static esp_err_t esp_spiffs_by_label(const char* label, int * index){
int i;
esp_spiffs_t * p;
for (i = 0; i < CONFIG_SPIFFS_MAX_PARTITIONS; i++) {
p = _efs[i];
if (p) {
if (!label && !p->by_label) {
*index = i;
return ESP_OK;
}
if (label && p->by_label && strncmp(label, p->partition->label, 17) == 0) {
*index = i;
return ESP_OK;
}
}
}
return ESP_ERR_NOT_FOUND;
}
static esp_err_t esp_spiffs_get_empty(int * index){
int i;
for (i = 0; i < CONFIG_SPIFFS_MAX_PARTITIONS; i++) {
if (_efs[i] == NULL) {
*index = i;
return ESP_OK;
}
}
return ESP_ERR_NOT_FOUND;
}
static esp_err_t esp_spiffs_init(const esp_vfs_spiffs_conf_t* conf)
{
int index;
//find if such partition is already mounted
if (esp_spiffs_by_label(conf->partition_label, &index) == ESP_OK) {
return ESP_ERR_INVALID_STATE;
}
if (esp_spiffs_get_empty(&index) != ESP_OK) {
ESP_LOGE(TAG, "max mounted partitions reached");
return ESP_ERR_INVALID_STATE;
}
uint32_t flash_page_size = g_rom_flashchip.page_size;
uint32_t log_page_size = CONFIG_SPIFFS_PAGE_SIZE;
if (log_page_size % flash_page_size != 0) {
ESP_LOGE(TAG, "SPIFFS_PAGE_SIZE is not multiple of flash chip page size (%d)",
flash_page_size);
return ESP_ERR_INVALID_ARG;
}
esp_partition_subtype_t subtype = conf->partition_label ?
ESP_PARTITION_SUBTYPE_ANY : ESP_PARTITION_SUBTYPE_DATA_SPIFFS;
const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
subtype, conf->partition_label);
if (!partition) {
ESP_LOGE(TAG, "spiffs partition could not be found");
return ESP_ERR_NOT_FOUND;
}
if (partition->encrypted) {
ESP_LOGE(TAG, "spiffs can not run on encrypted partition");
return ESP_ERR_INVALID_STATE;
}
esp_spiffs_t * efs = malloc(sizeof(esp_spiffs_t));
if (efs == NULL) {
ESP_LOGE(TAG, "esp_spiffs could not be malloced");
return ESP_ERR_NO_MEM;
}
memset(efs, 0, sizeof(esp_spiffs_t));
efs->cfg.hal_erase_f = spiffs_api_erase;
efs->cfg.hal_read_f = spiffs_api_read;
efs->cfg.hal_write_f = spiffs_api_write;
efs->cfg.log_block_size = g_rom_flashchip.sector_size;
efs->cfg.log_page_size = log_page_size;
efs->cfg.phys_addr = 0;
efs->cfg.phys_erase_block = g_rom_flashchip.sector_size;
efs->cfg.phys_size = partition->size;
efs->by_label = conf->partition_label != NULL;
efs->lock = xSemaphoreCreateMutex();
if (efs->lock == NULL) {
ESP_LOGE(TAG, "mutex lock could not be created");
esp_spiffs_free(&efs);
return ESP_ERR_NO_MEM;
}
efs->fds_sz = conf->max_files * sizeof(spiffs_fd);
efs->fds = malloc(efs->fds_sz);
if (efs->fds == NULL) {
ESP_LOGE(TAG, "fd buffer could not be malloced");
esp_spiffs_free(&efs);
return ESP_ERR_NO_MEM;
}
memset(efs->fds, 0, efs->fds_sz);
#if SPIFFS_CACHE
efs->cache_sz = sizeof(spiffs_cache) + conf->max_files * (sizeof(spiffs_cache_page)
+ efs->cfg.log_page_size);
efs->cache = malloc(efs->cache_sz);
if (efs->cache == NULL) {
ESP_LOGE(TAG, "cache buffer could not be malloced");
esp_spiffs_free(&efs);
return ESP_ERR_NO_MEM;
}
memset(efs->cache, 0, efs->cache_sz);
#endif
const uint32_t work_sz = efs->cfg.log_page_size * 2;
efs->work = malloc(work_sz);
if (efs->work == NULL) {
ESP_LOGE(TAG, "work buffer could not be malloced");
esp_spiffs_free(&efs);
return ESP_ERR_NO_MEM;
}
memset(efs->work, 0, work_sz);
efs->fs = malloc(sizeof(spiffs));
if (efs->fs == NULL) {
ESP_LOGE(TAG, "spiffs could not be malloced");
esp_spiffs_free(&efs);
return ESP_ERR_NO_MEM;
}
memset(efs->fs, 0, sizeof(spiffs));
efs->fs->user_data = (void *)efs;
efs->partition = partition;
s32_t res = SPIFFS_mount(efs->fs, &efs->cfg, efs->work, efs->fds, efs->fds_sz,
efs->cache, efs->cache_sz, spiffs_api_check);
if (conf->format_if_mount_failed && res != SPIFFS_OK) {
ESP_LOGW(TAG, "mount failed, %i. formatting...", SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
res = SPIFFS_format(efs->fs);
if (res != SPIFFS_OK) {
ESP_LOGE(TAG, "format failed, %i", SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
esp_spiffs_free(&efs);
return ESP_FAIL;
}
res = SPIFFS_mount(efs->fs, &efs->cfg, efs->work, efs->fds, efs->fds_sz,
efs->cache, efs->cache_sz, spiffs_api_check);
}
if (res != SPIFFS_OK) {
ESP_LOGE(TAG, "mount failed, %i", SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
esp_spiffs_free(&efs);
return ESP_FAIL;
}
_efs[index] = efs;
return ESP_OK;
}
bool esp_spiffs_mounted(const char* partition_label)
{
int index;
if (esp_spiffs_by_label(partition_label, &index) != ESP_OK) {
return false;
}
return (SPIFFS_mounted(_efs[index]->fs));
}
esp_err_t esp_spiffs_info(const char* partition_label, size_t *total_bytes, size_t *used_bytes)
{
int index;
if (esp_spiffs_by_label(partition_label, &index) != ESP_OK) {
return ESP_ERR_INVALID_STATE;
}
SPIFFS_info(_efs[index]->fs, total_bytes, used_bytes);
return ESP_OK;
}
esp_err_t esp_spiffs_format(const char* partition_label)
{
bool partition_was_mounted = false;
int index;
/* If the partition is not mounted, need to create SPIFFS structures
* and mount the partition, unmount, format, delete SPIFFS structures.
* See SPIFFS wiki for the reason why.
*/
esp_err_t err = esp_spiffs_by_label(partition_label, &index);
if (err != ESP_OK) {
esp_vfs_spiffs_conf_t conf = {
.format_if_mount_failed = true,
.partition_label = partition_label,
.max_files = 1
};
err = esp_spiffs_init(&conf);
if (err != ESP_OK) {
return err;
}
err = esp_spiffs_by_label(partition_label, &index);
assert(err == ESP_OK && "failed to get index of the partition just mounted");
} else if (SPIFFS_mounted(_efs[index]->fs)) {
partition_was_mounted = true;
}
SPIFFS_unmount(_efs[index]->fs);
s32_t res = SPIFFS_format(_efs[index]->fs);
if (res != SPIFFS_OK) {
ESP_LOGE(TAG, "format failed, %i", SPIFFS_errno(_efs[index]->fs));
SPIFFS_clearerr(_efs[index]->fs);
/* If the partition was previously mounted, but format failed, don't
* try to mount the partition back (it will probably fail). On the
* other hand, if it was not mounted, need to clean up.
*/
if (!partition_was_mounted) {
esp_spiffs_free(&_efs[index]);
}
return ESP_FAIL;
}
if (partition_was_mounted) {
res = SPIFFS_mount(_efs[index]->fs, &_efs[index]->cfg, _efs[index]->work,
_efs[index]->fds, _efs[index]->fds_sz, _efs[index]->cache,
_efs[index]->cache_sz, spiffs_api_check);
if (res != SPIFFS_OK) {
ESP_LOGE(TAG, "mount failed, %i", SPIFFS_errno(_efs[index]->fs));
SPIFFS_clearerr(_efs[index]->fs);
return ESP_FAIL;
}
} else {
esp_spiffs_free(&_efs[index]);
}
return ESP_OK;
}
esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf)
{
assert(conf->base_path);
const esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
.write_p = &vfs_spiffs_write,
.lseek_p = &vfs_spiffs_lseek,
.read_p = &vfs_spiffs_read,
.open_p = &vfs_spiffs_open,
.close_p = &vfs_spiffs_close,
.fstat_p = &vfs_spiffs_fstat,
.stat_p = &vfs_spiffs_stat,
.link_p = &vfs_spiffs_link,
.unlink_p = &vfs_spiffs_unlink,
.rename_p = &vfs_spiffs_rename,
.opendir_p = &vfs_spiffs_opendir,
.closedir_p = &vfs_spiffs_closedir,
.readdir_p = &vfs_spiffs_readdir,
.readdir_r_p = &vfs_spiffs_readdir_r,
.seekdir_p = &vfs_spiffs_seekdir,
.telldir_p = &vfs_spiffs_telldir,
.mkdir_p = &vfs_spiffs_mkdir,
.rmdir_p = &vfs_spiffs_rmdir
};
esp_err_t err = esp_spiffs_init(conf);
if (err != ESP_OK) {
return err;
}
int index;
if (esp_spiffs_by_label(conf->partition_label, &index) != ESP_OK) {
return ESP_ERR_INVALID_STATE;
}
strlcat(_efs[index]->base_path, conf->base_path, ESP_VFS_PATH_MAX + 1);
err = esp_vfs_register(conf->base_path, &vfs, _efs[index]);
if (err != ESP_OK) {
esp_spiffs_free(&_efs[index]);
return err;
}
return ESP_OK;
}
esp_err_t esp_vfs_spiffs_unregister(const char* partition_label)
{
int index;
if (esp_spiffs_by_label(partition_label, &index) != ESP_OK) {
return ESP_ERR_INVALID_STATE;
}
esp_err_t err = esp_vfs_unregister(_efs[index]->base_path);
if (err != ESP_OK) {
return err;
}
esp_spiffs_free(&_efs[index]);
return ESP_OK;
}
static int spiffs_res_to_errno(s32_t fr)
{
switch(fr) {
case SPIFFS_OK :
return 0;
case SPIFFS_ERR_NOT_MOUNTED :
return ENODEV;
case SPIFFS_ERR_NOT_A_FS :
return ENODEV;
case SPIFFS_ERR_FULL :
return ENOSPC;
case SPIFFS_ERR_BAD_DESCRIPTOR :
return EBADF;
case SPIFFS_ERR_MOUNTED :
return EEXIST;
case SPIFFS_ERR_FILE_EXISTS :
return EEXIST;
case SPIFFS_ERR_NOT_FOUND :
return ENOENT;
case SPIFFS_ERR_NOT_A_FILE :
return ENOENT;
case SPIFFS_ERR_DELETED :
return ENOENT;
case SPIFFS_ERR_FILE_DELETED :
return ENOENT;
case SPIFFS_ERR_NAME_TOO_LONG :
return ENAMETOOLONG;
case SPIFFS_ERR_RO_NOT_IMPL :
return EROFS;
case SPIFFS_ERR_RO_ABORTED_OPERATION :
return EROFS;
default :
return EIO;
}
return ENOTSUP;
}
static int spiffs_mode_conv(int m)
{
int res = 0;
int acc_mode = m & O_ACCMODE;
if (acc_mode == O_RDONLY) {
res |= SPIFFS_O_RDONLY;
} else if (acc_mode == O_WRONLY) {
res |= SPIFFS_O_WRONLY;
} else if (acc_mode == O_RDWR) {
res |= SPIFFS_O_RDWR;
}
if ((m & O_CREAT) && (m & O_EXCL)) {
res |= SPIFFS_O_CREAT | SPIFFS_O_EXCL;
} else if ((m & O_CREAT) && (m & O_TRUNC)) {
res |= SPIFFS_O_CREAT | SPIFFS_O_TRUNC;
}
if (m & O_APPEND) {
res |= SPIFFS_O_CREAT | SPIFFS_O_APPEND;
}
return res;
}
static int vfs_spiffs_open(void* ctx, const char * path, int flags, int mode)
{
assert(path);
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
int spiffs_flags = spiffs_mode_conv(flags);
int fd = SPIFFS_open(efs->fs, path, spiffs_flags, mode);
if (fd < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
if (!(spiffs_flags & SPIFFS_RDONLY)) {
vfs_spiffs_update_mtime(efs->fs, fd);
}
return fd;
}
static ssize_t vfs_spiffs_write(void* ctx, int fd, const void * data, size_t size)
{
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
ssize_t res = SPIFFS_write(efs->fs, fd, (void *)data, size);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
return res;
}
static ssize_t vfs_spiffs_read(void* ctx, int fd, void * dst, size_t size)
{
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
ssize_t res = SPIFFS_read(efs->fs, fd, dst, size);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
return res;
}
static int vfs_spiffs_close(void* ctx, int fd)
{
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
int res = SPIFFS_close(efs->fs, fd);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
return res;
}
static off_t vfs_spiffs_lseek(void* ctx, int fd, off_t offset, int mode)
{
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
off_t res = SPIFFS_lseek(efs->fs, fd, offset, mode);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
return res;
}
static int vfs_spiffs_fstat(void* ctx, int fd, struct stat * st)
{
assert(st);
spiffs_stat s;
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
off_t res = SPIFFS_fstat(efs->fs, fd, &s);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
st->st_size = s.size;
st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFREG;
st->st_mtime = vfs_spiffs_get_mtime(&s);
st->st_atime = 0;
st->st_ctime = 0;
return res;
}
static int vfs_spiffs_stat(void* ctx, const char * path, struct stat * st)
{
assert(path);
assert(st);
spiffs_stat s;
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
off_t res = SPIFFS_stat(efs->fs, path, &s);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
st->st_size = s.size;
st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO;
st->st_mode |= (s.type == SPIFFS_TYPE_DIR)?S_IFDIR:S_IFREG;
st->st_mtime = vfs_spiffs_get_mtime(&s);
st->st_atime = 0;
st->st_ctime = 0;
return res;
}
static int vfs_spiffs_rename(void* ctx, const char *src, const char *dst)
{
assert(src);
assert(dst);
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
int res = SPIFFS_rename(efs->fs, src, dst);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
return res;
}
static int vfs_spiffs_unlink(void* ctx, const char *path)
{
assert(path);
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
int res = SPIFFS_remove(efs->fs, path);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
return res;
}
static DIR* vfs_spiffs_opendir(void* ctx, const char* name)
{
assert(name);
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
vfs_spiffs_dir_t * dir = calloc(1, sizeof(vfs_spiffs_dir_t));
if (!dir) {
errno = ENOMEM;
return NULL;
}
if (!SPIFFS_opendir(efs->fs, name, &dir->d)) {
free(dir);
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return NULL;
}
dir->offset = 0;
strlcpy(dir->path, name, SPIFFS_OBJ_NAME_LEN);
return (DIR*) dir;
}
static int vfs_spiffs_closedir(void* ctx, DIR* pdir)
{
assert(pdir);
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir;
int res = SPIFFS_closedir(&dir->d);
free(dir);
if (res < 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return -1;
}
return res;
}
static struct dirent* vfs_spiffs_readdir(void* ctx, DIR* pdir)
{
assert(pdir);
vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir;
struct dirent* out_dirent;
int err = vfs_spiffs_readdir_r(ctx, pdir, &dir->e, &out_dirent);
if (err != 0) {
errno = err;
return NULL;
}
return out_dirent;
}
static int vfs_spiffs_readdir_r(void* ctx, DIR* pdir, struct dirent* entry,
struct dirent** out_dirent)
{
assert(pdir);
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir;
struct spiffs_dirent out;
size_t plen;
char * item_name;
do {
if (SPIFFS_readdir(&dir->d, &out) == 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
if (!errno) {
*out_dirent = NULL;
}
return errno;
}
item_name = (char *)out.name;
plen = strlen(dir->path);
} while ((plen > 1) && (strncasecmp(dir->path, (const char*)out.name, plen) || out.name[plen] != '/' || !out.name[plen + 1]));
if (plen > 1) {
item_name += plen + 1;
} else if (item_name[0] == '/') {
item_name++;
}
entry->d_ino = 0;
entry->d_type = out.type;
snprintf(entry->d_name, SPIFFS_OBJ_NAME_LEN, "%s", item_name);
dir->offset++;
*out_dirent = entry;
return 0;
}
static long vfs_spiffs_telldir(void* ctx, DIR* pdir)
{
assert(pdir);
vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir;
return dir->offset;
}
static void vfs_spiffs_seekdir(void* ctx, DIR* pdir, long offset)
{
assert(pdir);
esp_spiffs_t * efs = (esp_spiffs_t *)ctx;
vfs_spiffs_dir_t * dir = (vfs_spiffs_dir_t *)pdir;
struct spiffs_dirent tmp;
if (offset < dir->offset) {
//rewind dir
SPIFFS_closedir(&dir->d);
if (!SPIFFS_opendir(efs->fs, NULL, &dir->d)) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return;
}
dir->offset = 0;
}
while (dir->offset < offset) {
if (SPIFFS_readdir(&dir->d, &tmp) == 0) {
errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
SPIFFS_clearerr(efs->fs);
return;
}
size_t plen = strlen(dir->path);
if (plen > 1) {
if (strncasecmp(dir->path, (const char *)tmp.name, plen) || tmp.name[plen] != '/' || !tmp.name[plen+1]) {
continue;
}
}
dir->offset++;
}
}
static int vfs_spiffs_mkdir(void* ctx, const char* name, mode_t mode)
{
errno = ENOTSUP;
return -1;
}
static int vfs_spiffs_rmdir(void* ctx, const char* name)
{
errno = ENOTSUP;
return -1;
}
static int vfs_spiffs_link(void* ctx, const char* n1, const char* n2)
{
errno = ENOTSUP;
return -1;
}
static void vfs_spiffs_update_mtime(spiffs *fs, spiffs_file fd)
{
#ifdef CONFIG_SPIFFS_USE_MTIME
time_t t = time(NULL);
spiffs_stat s;
int ret = SPIFFS_OK;
if (CONFIG_SPIFFS_META_LENGTH > sizeof(t)) {
ret = SPIFFS_fstat(fs, fd, &s);
}
if (ret == SPIFFS_OK) {
memcpy(s.meta, &t, sizeof(t));
ret = SPIFFS_fupdate_meta(fs, fd, s.meta);
}
if (ret != SPIFFS_OK) {
ESP_LOGW(TAG, "Failed to update mtime (%d)", ret);
}
#endif //CONFIG_SPIFFS_USE_MTIME
}
static time_t vfs_spiffs_get_mtime(const spiffs_stat* s)
{
time_t t = 0;
#ifdef CONFIG_SPIFFS_USE_MTIME
memcpy(&t, s->meta, sizeof(t));
#endif
return t;
}

@ -0,0 +1,102 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _ESP_SPIFFS_H_
#define _ESP_SPIFFS_H_
#include <stdbool.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Configuration structure for esp_vfs_spiffs_register
*/
typedef struct {
const char* base_path; /*!< File path prefix associated with the filesystem. */
const char* partition_label; /*!< Optional, label of SPIFFS partition to use. If set to NULL, first partition with subtype=spiffs will be used. */
size_t max_files; /*!< Maximum files that could be open at the same time. */
bool format_if_mount_failed; /*!< If true, it will format the file system if it fails to mount. */
} esp_vfs_spiffs_conf_t;
/**
* Register and mount SPIFFS to VFS with given path prefix.
*
* @param conf Pointer to esp_vfs_spiffs_conf_t configuration structure
*
* @return
* - ESP_OK if success
* - ESP_ERR_NO_MEM if objects could not be allocated
* - ESP_ERR_INVALID_STATE if already mounted or partition is encrypted
* - ESP_ERR_NOT_FOUND if partition for SPIFFS was not found
* - ESP_FAIL if mount or format fails
*/
esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf);
/**
* Unregister and unmount SPIFFS from VFS
*
* @param partition_label Optional, label of the partition to unregister.
* If not specified, first partition with subtype=spiffs is used.
*
* @return
* - ESP_OK if successful
* - ESP_ERR_INVALID_STATE already unregistered
*/
esp_err_t esp_vfs_spiffs_unregister(const char* partition_label);
/**
* Check if SPIFFS is mounted
*
* @param partition_label Optional, label of the partition to check.
* If not specified, first partition with subtype=spiffs is used.
*
* @return
* - true if mounted
* - false if not mounted
*/
bool esp_spiffs_mounted(const char* partition_label);
/**
* Format the SPIFFS partition
*
* @param partition_label Optional, label of the partition to format.
* If not specified, first partition with subtype=spiffs is used.
* @return
* - ESP_OK if successful
* - ESP_FAIL on error
*/
esp_err_t esp_spiffs_format(const char* partition_label);
/**
* Get information for SPIFFS
*
* @param partition_label Optional, label of the partition to get info for.
* If not specified, first partition with subtype=spiffs is used.
* @param[out] total_bytes Size of the file system
* @param[out] used_bytes Current used bytes in the file system
*
* @return
* - ESP_OK if success
* - ESP_ERR_INVALID_STATE if not mounted
*/
esp_err_t esp_spiffs_info(const char* partition_label, size_t *total_bytes, size_t *used_bytes);
#ifdef __cplusplus
}
#endif
#endif /* _ESP_SPIFFS_H_ */

@ -1,84 +0,0 @@
/*
* ESPRSSIF MIT License
*
* Copyright (c) 2015 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#ifndef __ESP_SPIFFS_H__
#define __ESP_SPIFFS_H__
#include "spiffs/spiffs.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \defgroup Spiffs_APIs Spiffs APIs
* @brief Spiffs APIs
*
* More details about spiffs on https://github.com/pellepl/spiffs
*
*/
/** @addtogroup Spiffs_APIs
* @{
*/
struct esp_spiffs_config {
u32_t phys_size; /**< physical size of the SPI Flash */
u32_t phys_addr; /**< physical offset in spi flash used for spiffs, must be on block boundary */
u32_t phys_erase_block; /**< physical size when erasing a block */
u32_t log_block_size; /**< logical size of a block, must be on physical block size boundary and must never be less than a physical block */
u32_t log_page_size; /**< logical size of a page, at least log_block_size/8 */
u32_t fd_buf_size; /**< file descriptor memory area size */
u32_t cache_buf_size; /**< cache buffer size */
};
/**
* @brief Initialize spiffs
*
* @param struct esp_spiffs_config *config : ESP8266 spiffs configuration
*
* @return 0 : succeed (Equals SPIFFS_OK)
* @return otherwise : fail (-1 or SPIFFS_ERR_*)
*/
s32_t esp_spiffs_init(struct esp_spiffs_config *config);
/**
* @brief Deinitialize spiffs
*
* @param uint8 format : 0, only deinit; otherwise, deinit spiffs and format.
*
* @return null
*/
void esp_spiffs_deinit(u8_t format);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* __ESP_SPIFFS_H__ */

@ -0,0 +1,316 @@
/*
* spiffs_config.h
*
* Created on: Jul 3, 2013
* Author: petera
*/
#ifndef SPIFFS_CONFIG_H_
#define SPIFFS_CONFIG_H_
// ----------- 8< ------------
// Following includes are for the linux test build of spiffs
// These may/should/must be removed/altered/replaced in your target
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
#include <sdkconfig.h>
#include <esp_log.h>
// compile time switches
#define SPIFFS_TAG "SPIFFS"
// Set generic spiffs debug output call.
#if CONFIG_SPIFFS_DBG
#define SPIFFS_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
#else
#define SPIFFS_DBG(...)
#endif
#if CONFIG_SPIFFS_API_DBG
#define SPIFFS_API_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
#else
#define SPIFFS_API_DBG(...)
#endif
#if CONFIG_SPIFFS_DBG
#define SPIFFS_GC_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
#else
#define SPIFFS_GC_DBG(...)
#endif
#if CONFIG_SPIFFS_CACHE_DBG
#define SPIFFS_CACHE_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
#else
#define SPIFFS_CACHE_DBG(...)
#endif
#if CONFIG_SPIFFS_CHECK_DBG
#define SPIFFS_CHECK_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
#else
#define SPIFFS_CHECK_DBG(...)
#endif
// needed types
typedef signed int s32_t;
typedef unsigned int u32_t;
typedef signed short s16_t;
typedef unsigned short u16_t;
typedef signed char s8_t;
typedef unsigned char u8_t;
struct spiffs_t;
extern void spiffs_api_lock(struct spiffs_t *fs);
extern void spiffs_api_unlock(struct spiffs_t *fs);
// Defines spiffs debug print formatters
// some general signed number
#define _SPIPRIi "%d"
// address
#define _SPIPRIad "%08x"
// block
#define _SPIPRIbl "%04x"
// page
#define _SPIPRIpg "%04x"
// span index
#define _SPIPRIsp "%04x"
// file descriptor
#define _SPIPRIfd "%d"
// file object id
#define _SPIPRIid "%04x"
// file flags
#define _SPIPRIfl "%02x"
// Enable/disable API functions to determine exact number of bytes
// for filedescriptor and cache buffers. Once decided for a configuration,
// this can be disabled to reduce flash.
#define SPIFFS_BUFFER_HELP 0
// Enables/disable memory read caching of nucleus file system operations.
// If enabled, memory area must be provided for cache in SPIFFS_mount.
#ifdef CONFIG_SPIFFS_CACHE
#define SPIFFS_CACHE (1)
#else
#define SPIFFS_CACHE (0)
#endif
#if SPIFFS_CACHE
// Enables memory write caching for file descriptors in hydrogen
#ifdef CONFIG_SPIFFS_CACHE_WR
#define SPIFFS_CACHE_WR (1)
#else
#define SPIFFS_CACHE_WR (0)
#endif
// Enable/disable statistics on caching. Debug/test purpose only.
#ifdef CONFIG_SPIFFS_CACHE_STATS
#define SPIFFS_CACHE_STATS (1)
#else
#define SPIFFS_CACHE_STATS (0)
#endif
#endif
// Always check header of each accessed page to ensure consistent state.
// If enabled it will increase number of reads, will increase flash.
#ifdef CONFIG_SPIFFS_PAGE_CHECK
#define SPIFFS_PAGE_CHECK (1)
#else
#define SPIFFS_PAGE_CHECK (0)
#endif
// Define maximum number of gc runs to perform to reach desired free pages.
#define SPIFFS_GC_MAX_RUNS CONFIG_SPIFFS_GC_MAX_RUNS
// Enable/disable statistics on gc. Debug/test purpose only.
#ifdef CONFIG_SPIFFS_GC_STATS
#define SPIFFS_GC_STATS (1)
#else
#define SPIFFS_GC_STATS (0)
#endif
// Garbage collecting examines all pages in a block which and sums up
// to a block score. Deleted pages normally gives positive score and
// used pages normally gives a negative score (as these must be moved).
// To have a fair wear-leveling, the erase age is also included in score,
// whose factor normally is the most positive.
// The larger the score, the more likely it is that the block will
// picked for garbage collection.
// Garbage collecting heuristics - weight used for deleted pages.
#define SPIFFS_GC_HEUR_W_DELET (5)
// Garbage collecting heuristics - weight used for used pages.
#define SPIFFS_GC_HEUR_W_USED (-1)
// Garbage collecting heuristics - weight used for time between
// last erased and erase of this block.
#define SPIFFS_GC_HEUR_W_ERASE_AGE (50)
// Object name maximum length. Note that this length include the
// zero-termination character, meaning maximum string of characters
// can at most be SPIFFS_OBJ_NAME_LEN - 1.
#define SPIFFS_OBJ_NAME_LEN (CONFIG_SPIFFS_OBJ_NAME_LEN)
// Maximum length of the metadata associated with an object.
// Setting to non-zero value enables metadata-related API but also
// changes the on-disk format, so the change is not backward-compatible.
//
// Do note: the meta length must never exceed
// logical_page_size - (SPIFFS_OBJ_NAME_LEN + SPIFFS_PAGE_EXTRA_SIZE)
//
// This is derived from following:
// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) +
// spiffs_object_ix_header fields + at least some LUT entries)
#define SPIFFS_OBJ_META_LEN (CONFIG_SPIFFS_META_LENGTH)
#define SPIFFS_PAGE_EXTRA_SIZE (64)
_Static_assert(SPIFFS_OBJ_META_LEN + SPIFFS_OBJ_NAME_LEN + SPIFFS_PAGE_EXTRA_SIZE
<= CONFIG_SPIFFS_PAGE_SIZE, "SPIFFS_OBJ_META_LEN or SPIFFS_OBJ_NAME_LEN too long");
// Size of buffer allocated on stack used when copying data.
// Lower value generates more read/writes. No meaning having it bigger
// than logical page size.
#define SPIFFS_COPY_BUFFER_STACK (256)
// Enable this to have an identifiable spiffs filesystem. This will look for
// a magic in all sectors to determine if this is a valid spiffs system or
// not on mount point. If not, SPIFFS_format must be called prior to mounting
// again.
#ifdef CONFIG_SPIFFS_USE_MAGIC
#define SPIFFS_USE_MAGIC (1)
#else
#define SPIFFS_USE_MAGIC (0)
#endif
#if SPIFFS_USE_MAGIC
// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is
// enabled, the magic will also be dependent on the length of the filesystem.
// For example, a filesystem configured and formatted for 4 megabytes will not
// be accepted for mounting with a configuration defining the filesystem as 2
// megabytes.
#ifdef CONFIG_SPIFFS_USE_MAGIC_LENGTH
#define SPIFFS_USE_MAGIC_LENGTH (1)
#else
#define SPIFFS_USE_MAGIC_LENGTH (0)
#endif
#endif
// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level
// These should be defined on a multithreaded system
// define this to enter a mutex if you're running on a multithreaded system
#define SPIFFS_LOCK(fs) spiffs_api_lock(fs)
// define this to exit a mutex if you're running on a multithreaded system
#define SPIFFS_UNLOCK(fs) spiffs_api_unlock(fs)
// Enable if only one spiffs instance with constant configuration will exist
// on the target. This will reduce calculations, flash and memory accesses.
// Parts of configuration must be defined below instead of at time of mount.
#define SPIFFS_SINGLETON 0
// Enable this if your target needs aligned data for index tables
#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 0
// Enable this if you want the HAL callbacks to be called with the spiffs struct
#define SPIFFS_HAL_CALLBACK_EXTRA 1
// Enable this if you want to add an integer offset to all file handles
// (spiffs_file). This is useful if running multiple instances of spiffs on
// same target, in order to recognise to what spiffs instance a file handle
// belongs.
// NB: This adds config field fh_ix_offset in the configuration struct when
// mounting, which must be defined.
#define SPIFFS_FILEHDL_OFFSET 0
// Enable this to compile a read only version of spiffs.
// This will reduce binary size of spiffs. All code comprising modification
// of the file system will not be compiled. Some config will be ignored.
// HAL functions for erasing and writing to spi-flash may be null. Cache
// can be disabled for even further binary size reduction (and ram savings).
// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL.
// If the file system cannot be mounted due to aborted erase operation and
// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be
// returned.
// Might be useful for e.g. bootloaders and such.
#define SPIFFS_READ_ONLY 0
// Enable this to add a temporal file cache using the fd buffer.
// The effects of the cache is that SPIFFS_open will find the file faster in
// certain cases. It will make it a lot easier for spiffs to find files
// opened frequently, reducing number of readings from the spi flash for
// finding those files.
// This will grow each fd by 6 bytes. If your files are opened in patterns
// with a degree of temporal locality, the system is optimized.
// Examples can be letting spiffs serve web content, where one file is the css.
// The css is accessed for each html file that is opened, meaning it is
// accessed almost every second time a file is opened. Another example could be
// a log file that is often opened, written, and closed.
// The size of the cache is number of given file descriptors, as it piggybacks
// on the fd update mechanism. The cache lives in the closed file descriptors.
// When closed, the fd know the whereabouts of the file. Instead of forgetting
// this, the temporal cache will keep handling updates to that file even if the
// fd is closed. If the file is opened again, the location of the file is found
// directly. If all available descriptors become opened, all cache memory is
// lost.
#define SPIFFS_TEMPORAL_FD_CACHE 1
// Temporal file cache hit score. Each time a file is opened, all cached files
// will lose one point. If the opened file is found in cache, that entry will
// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this
// value for the specific access patterns of the application. However, it must
// be between 1 (no gain for hitting a cached entry often) and 255.
#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 4
// Enable to be able to map object indices to memory.
// This allows for faster and more deterministic reading if cases of reading
// large files and when changing file offset by seeking around a lot.
// When mapping a file's index, the file system will be scanned for index pages
// and the info will be put in memory provided by user. When reading, the
// memory map can be looked up instead of searching for index pages on the
// medium. This way, user can trade memory against performance.
// Whole, parts of, or future parts not being written yet can be mapped. The
// memory array will be owned by spiffs and updated accordingly during garbage
// collecting or when modifying the indices. The latter is invoked by when the
// file is modified in some way. The index buffer is tied to the file
// descriptor.
#define SPIFFS_IX_MAP 1
// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function
// in the api. This function will visualize all filesystem using given printf
// function.
#ifdef CONFIG_SPIFFS_TEST_VISUALISATION
#define SPIFFS_TEST_VISUALISATION 1
#else
#define SPIFFS_TEST_VISUALISATION 0
#endif
#if SPIFFS_TEST_VISUALISATION
#ifndef spiffs_printf
#define spiffs_printf(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
#endif
// spiffs_printf argument for a free page
#define SPIFFS_TEST_VIS_FREE_STR "_"
// spiffs_printf argument for a deleted page
#define SPIFFS_TEST_VIS_DELE_STR "/"
// spiffs_printf argument for an index page for given object id
#define SPIFFS_TEST_VIS_INDX_STR(id) "i"
// spiffs_printf argument for a data page for given object id
#define SPIFFS_TEST_VIS_DATA_STR(id) "d"
#endif
// Types depending on configuration such as the amount of flash bytes
// given to spiffs file system in total (spiffs_file_system_size),
// the logical block size (log_block_size), and the logical page size
// (log_page_size)
// Block index type. Make sure the size of this type can hold
// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size
typedef u16_t spiffs_block_ix;
// Page index type. Make sure the size of this type can hold
// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size
typedef u16_t spiffs_page_ix;
// Object id type - most significant bit is reserved for index flag. Make sure the
// size of this type can hold the highest object id on a full system,
// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2
typedef u16_t spiffs_obj_id;
// Object span index type. Make sure the size of this type can
// hold the largest possible span index on the system -
// i.e. (spiffs_file_system_size / log_page_size) - 1
typedef u16_t spiffs_span_ix;
#endif /* SPIFFS_CONFIG_H_ */

@ -1,311 +0,0 @@
#include <fcntl.h>
#include <stdio.h>
#include "esp_spiffs.h"
#include "spi_flash.h"
#define NUM_SYS_FD 3
static spiffs fs;
static u8_t *spiffs_work_buf;
static u8_t *spiffs_fd_buf;
static u8_t *spiffs_cache_buf;
#define FLASH_UNIT_SIZE 4
static s32_t esp_spiffs_readwrite(u32_t addr, u32_t size, u8_t *p, int write)
{
/*
* With proper configurarion spiffs never reads or writes more than
* LOG_PAGE_SIZE
*/
if (size > fs.cfg.log_page_size) {
printf("Invalid size provided to read/write (%d)\n\r", (int) size);
return SPIFFS_ERR_NOT_CONFIGURED;
}
char tmp_buf[fs.cfg.log_page_size + FLASH_UNIT_SIZE * 2];
u32_t aligned_addr = addr & (-FLASH_UNIT_SIZE);
u32_t aligned_size =
((size + (FLASH_UNIT_SIZE - 1)) & -FLASH_UNIT_SIZE) + FLASH_UNIT_SIZE;
int res = spi_flash_read(aligned_addr, (u32_t *) tmp_buf, aligned_size);
if (res != 0) {
printf("spi_flash_read failed: %d (%d, %d)\n\r", res, (int) aligned_addr,
(int) aligned_size);
return res;
}
if (!write) {
memcpy(p, tmp_buf + (addr - aligned_addr), size);
return SPIFFS_OK;
}
memcpy(tmp_buf + (addr - aligned_addr), p, size);
res = spi_flash_write(aligned_addr, (u32_t *) tmp_buf, aligned_size);
if (res != 0) {
// printf("spi_flash_write failed: %d (%d, %d)\n\r", res,
// (int) aligned_addr, (int) aligned_size);
return res;
}
return SPIFFS_OK;
}
static s32_t esp_spiffs_read(u32_t addr, u32_t size, u8_t *dst)
{
return esp_spiffs_readwrite(addr, size, dst, 0);
}
static s32_t esp_spiffs_write(u32_t addr, u32_t size, u8_t *src)
{
return esp_spiffs_readwrite(addr, size, src, 1);
}
static s32_t esp_spiffs_erase(u32_t addr, u32_t size)
{
/*
* With proper configurarion spiffs always
* provides here sector address & sector size
*/
if (size != fs.cfg.phys_erase_block || addr % fs.cfg.phys_erase_block != 0) {
printf("Invalid size provided to esp_spiffs_erase (%d, %d)\n\r",
(int) addr, (int) size);
return SPIFFS_ERR_NOT_CONFIGURED;
}
return spi_flash_erase_sector(addr / fs.cfg.phys_erase_block);
}
s32_t esp_spiffs_init(struct esp_spiffs_config *config)
{
if (SPIFFS_mounted(&fs)) {
return SPIFFS_ERR_MOUNTED;
}
spiffs_config cfg;
s32_t ret;
cfg.phys_size = config->phys_size;
cfg.phys_addr = config->phys_addr;
cfg.phys_erase_block = config->phys_erase_block;
cfg.log_block_size = config->log_block_size;
cfg.log_page_size = config->log_page_size;
cfg.hal_read_f = esp_spiffs_read;
cfg.hal_write_f = esp_spiffs_write;
cfg.hal_erase_f = esp_spiffs_erase;
if (spiffs_work_buf != NULL) {
free(spiffs_work_buf);
spiffs_work_buf = NULL;
}
spiffs_work_buf = malloc(config->log_page_size * 2);
if (spiffs_work_buf == NULL) {
return -1;
}
if (spiffs_fd_buf != NULL) {
free(spiffs_fd_buf);
spiffs_fd_buf = NULL;
}
spiffs_fd_buf = malloc(config->fd_buf_size);
if (spiffs_fd_buf == NULL) {
free(spiffs_work_buf);
return -1;
}
if (spiffs_cache_buf != NULL) {
free(spiffs_cache_buf);
spiffs_cache_buf = NULL;
}
spiffs_cache_buf = malloc(config->cache_buf_size);
if (spiffs_cache_buf == NULL) {
free(spiffs_work_buf);
free(spiffs_fd_buf);
return -1;
}
ret = SPIFFS_mount(&fs, &cfg, spiffs_work_buf,
spiffs_fd_buf, config->fd_buf_size,
spiffs_cache_buf, config->cache_buf_size,
0);
if (ret == -1) {
free(spiffs_work_buf);
free(spiffs_fd_buf);
free(spiffs_cache_buf);
}
ret = SPIFFS_errno(&fs);
return ret;
}
void esp_spiffs_deinit(u8_t format)
{
if (SPIFFS_mounted(&fs)) {
SPIFFS_unmount(&fs);
free(spiffs_work_buf);
free(spiffs_fd_buf);
free(spiffs_cache_buf);
}
if (format) {
SPIFFS_format(&fs);
}
}
int _spiffs_open_r(struct _reent *r, const char *filename, int flags, int mode)
{
spiffs_mode sm = 0;
int res;
int rw = (flags & 3);
if (rw == O_RDONLY || rw == O_RDWR) {
sm |= SPIFFS_RDONLY;
}
if (rw == O_WRONLY || rw == O_RDWR) {
sm |= SPIFFS_WRONLY;
}
if (flags & O_CREAT) {
sm |= SPIFFS_CREAT;
}
if (flags & O_TRUNC) {
sm |= SPIFFS_TRUNC;
}
if (flags & O_APPEND) {
sm |= SPIFFS_APPEND;
}
/* Supported in newer versions of SPIFFS. */
/* if (flags && O_EXCL) sm |= SPIFFS_EXCL; */
/* if (flags && O_DIRECT) sm |= SPIFFS_DIRECT; */
res = SPIFFS_open(&fs, (char *) filename, sm, 0);
if (res >= 0) {
res += NUM_SYS_FD;
}
else {
res = SPIFFS_errno(&fs);
}
return res;
}
_ssize_t _spiffs_read_r(struct _reent *r, int fd, void *buf, size_t len)
{
ssize_t res;
if (fd < NUM_SYS_FD) {
res = -1;
} else {
res = SPIFFS_read(&fs, fd - NUM_SYS_FD, buf, len);
if(res < 0) {
res = SPIFFS_errno(&fs);
}
}
return res;
}
_ssize_t _spiffs_write_r(struct _reent *r, int fd, void *buf, size_t len)
{
if (fd < NUM_SYS_FD) {
return -1;
}
int res = SPIFFS_write(&fs, fd - NUM_SYS_FD, (char *) buf, len);
if(res < 0){
return SPIFFS_errno(&fs);
}
return res;
}
_off_t _spiffs_lseek_r(struct _reent *r, int fd, _off_t where, int whence)
{
ssize_t res;
if (fd < NUM_SYS_FD) {
res = -1;
} else {
res = SPIFFS_lseek(&fs, fd - NUM_SYS_FD, where, whence);
if(res < 0) {
res = SPIFFS_errno(&fs);
}
}
return res;
}
int _spiffs_close_r(struct _reent *r, int fd)
{
if (fd < NUM_SYS_FD) {
return -1;
}
SPIFFS_close(&fs, fd - NUM_SYS_FD);
return 0;
}
int _spiffs_rename_r(struct _reent *r, const char *from, const char *to)
{
int res = SPIFFS_rename(&fs, (char *) from, (char *) to);
if(res < 0) {
res = SPIFFS_errno(&fs);
}
return res;
}
int _spiffs_unlink_r(struct _reent *r, const char *filename)
{
int res = SPIFFS_remove(&fs, (char *) filename);
if(res < 0) {
res = SPIFFS_errno(&fs);
}
return res;
}
int _spiffs_fstat_r(struct _reent *r, int fd, struct stat *s)
{
int res;
spiffs_stat ss;
memset(s, 0, sizeof(*s));
if (fd < NUM_SYS_FD) {
s->st_ino = fd;
s->st_rdev = fd;
s->st_mode = S_IFCHR | 0666;
return 0;
}
res = SPIFFS_fstat(&fs, fd - NUM_SYS_FD, &ss);
if (res < 0) {
return SPIFFS_errno(&fs);
}
s->st_ino = ss.obj_id;
s->st_mode = 0666;
s->st_nlink = 1;
s->st_size = ss.size;
return 0;
}

@ -0,0 +1,8 @@
language: c
compiler:
- gcc
before_script:
script: make all && make clean && make test && make build-all && make clean test FLAGS=-DSPIFFS_OBJ_META_LEN=8

@ -0,0 +1,47 @@
# Fuzzing SPIFFS
The SPIFFS test suite includes a test program designed for fuzzing with
[AFL](http://lcamtuf.coredump.cx/afl/). This automatically exercises the
SPIFFS API and verifies that the file system does not crash or interact incorrectly
with the flash chip.
There are two steps to fuzzing. The first is to build the test suite with
the AFL version of gcc. The CC variable should point to your copy of afl-gcc.
```
make clean test CC=/usr/local/bin/afl-gcc
```
There is a new test `afl_test` that reads from stdin a list of commands
and arguments. These are interpreted and executed on the API. The `afltests`
directory contains a number of test cases that can be fed to the `afl_test` test.
The second is to run this test suite under afl as follows (where findings is
the output directory):
```
afl-fuzz -i afltests -o findings ./build/linux_spiffs_test -f afl_test
```
This run will take hours (or days) and will (hopefully) not find any crashes.
If a crash (or hang) is found, then the input file that caused the crash is
saved. This allows the specific test case to be debugged.
## Reducing the size of the file
AFL comes with `afl-tmin` which can reduce the size of the test input file to
make it easier to debug.
```
afl-tmin -i findings/crashes/<somefile> -o smalltest -- build/linux_spiffs_test -f afl_test
```
This will write a short version of the testcase file to `smalltest`. This can then be
fed into the test program for debugging:
```
build/linux_spiffs_test -f afl_test < smalltest
```
This should still crash, but allows it to be run under a debugger.

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-2017 Peter Andersson (pelleplutt1976<at>gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,212 @@
# SPIFFS (SPI Flash File System)
**V0.3.7**
[![Build Status](https://travis-ci.org/pellepl/spiffs.svg?branch=master)](https://travis-ci.org/pellepl/spiffs)
Copyright (c) 2013-2017 Peter Andersson (pelleplutt1976 at gmail.com)
For legal stuff, see [LICENSE](https://github.com/pellepl/spiffs/blob/master/LICENSE). Basically, you may do whatever you want with the source. Use, modify, sell, print it out, roll it and smoke it - as long as I won't be held responsible.
Love to hear feedback though!
## INTRODUCTION
Spiffs is a file system intended for SPI NOR flash devices on embedded targets.
Spiffs is designed with following characteristics in mind:
- Small (embedded) targets, sparse RAM without heap
- Only big areas of data (blocks) can be erased
- An erase will reset all bits in block to ones
- Writing pulls one to zeroes
- Zeroes can only be pulled to ones by erase
- Wear leveling
## BUILDING
`mkdir build; make`
Otherwise, configure the `builddir` variable towards the top of `makefile` as something opposed to the default `build`. Sanity check on the host via `make test` and refer to `.travis.yml` for the official in-depth testing procedure. See the wiki for [integrating](https://github.com/pellepl/spiffs/wiki/Integrate-spiffs) spiffs into projects and [spiffsimg](https://github.com/nodemcu/nodemcu-firmware/tree/master/tools/spiffsimg) from [nodemcu](https://github.com/nodemcu) is a good example on the subject.
## FEATURES
What spiffs does:
- Specifically designed for low ram usage
- Uses statically sized ram buffers, independent of number of files
- Posix-like api: open, close, read, write, seek, stat, etc
- It can run on any NOR flash, not only SPI flash - theoretically also on embedded flash of a microprocessor
- Multiple spiffs configurations can run on same target - and even on same SPI flash device
- Implements static wear leveling
- Built in file system consistency checks
- Highly configurable
What spiffs does not:
- Presently, spiffs does not support directories. It produces a flat structure. Creating a file with path *tmp/myfile.txt* will create a file called *tmp/myfile.txt* instead of a *myfile.txt* under directory *tmp*.
- It is not a realtime stack. One write operation might last much longer than another.
- Poor scalability. Spiffs is intended for small memory devices - the normal sizes for SPI flashes. Going beyond ~128Mbyte is probably a bad idea. This is a side effect of the design goal to use as little ram as possible.
- Presently, it does not detect or handle bad blocks.
- One configuration, one binary. There's no generic spiffs binary that handles all types of configurations.
## MORE INFO
See the [wiki](https://github.com/pellepl/spiffs/wiki) for [configuring](https://github.com/pellepl/spiffs/wiki/Configure-spiffs), [integrating](https://github.com/pellepl/spiffs/wiki/Integrate-spiffs), [using](https://github.com/pellepl/spiffs/wiki/Using-spiffs), and [optimizing](https://github.com/pellepl/spiffs/wiki/Performance-and-Optimizing) spiffs.
For design, see [docs/TECH_SPEC](https://github.com/pellepl/spiffs/blob/master/docs/TECH_SPEC).
For a generic spi flash driver, see [this](https://github.com/pellepl/spiflash_driver).
## HISTORY
### 0.3.7
- fixed prevent seeking to negative offsets #158
- fixed file descriptor offsets not updated for multiple fds on same file #157
- fixed cache page not closed for removed files #156
- fixed a lseek bug when seeking exactly to end of a fully indexed first level LUT #148
- fixed wear leveling issue #145
- fixed attempt to write out of bounds in flash #130,
- set file offset when seeking over end #121 (thanks @sensslen)
- fixed seeking in virgin files #120 (thanks @sensslen)
- Optional file metadata #128 (thanks @cesanta)
- AFL testing framework #100 #143 (thanks @pjsg)
- Testframe updates
New API functions:
- `SPIFFS_update_meta, SPIFFS_fupdate_meta` - updates metadata for a file
New config defines:
- `SPIFFS_OBJ_META_LEN` - enable possibility to add extra metadata to files
### 0.3.6
- Fix range bug in index memory mapping #98
- Add index memory mapping #97
- Optimize SPIFFS_read for large files #96
- Add temporal cache for opening files #95
- More robust gc #93 (thanks @dismirlian)
- Fixed a double write of same data in certain cache situations
- Fixed an open bug in READ_ONLY builds
- File not visible in SPIFFS_readdir #90 (thanks @benpicco-tmp)
- Cache load code cleanup #92 (thanks @niclash)
- Fixed lock/unlock asymmetry #88 #87 (thanks @JackJefferson, @dpruessner)
- Testframe updates
New API functions:
- `SPIFFS_ix_map` - map index meta data to memory for a file
- `SPIFFS_ix_unmap` - unmaps index meta data for a file
- `SPIFFS_ix_remap` - changes file offset for index metadata map
- `SPIFFS_bytes_to_ix_map_entries` - utility, get length of needed vector for given amount of bytes
- `SPIFFS_ix_map_entries_to_bytes` - utility, get number of bytes a vector can represent given length
New config defines:
- `SPIFFS_IX_MAP` - enable possibility to map index meta data to memory for reading faster
- `SPIFFS_TEMPORAL_FD_CACHE` - enable temporal cache for opening files faster
- `SPIFFS_TEMPORAL_CACHE_HIT_SCORE` - for tuning the temporal cache
### 0.3.5
- Fixed a bug in fs check
- API returns actual error codes #84) (thanks @Nails)
- Fix compiler warnings for non-gcc #83 #81 (thanks @Nails)
- Unable to recover from full fs #82 (thanks @rojer)
- Define SPIFFS_O_* flags #80
- Problem with long filenames #79 (thanks @psjg)
- Duplicate file name bug fix #74 (thanks @igrr)
- SPIFFS_eof and SPIFFS_tell return wrong value #72 (thanks @ArtemPisarenko)
- Bunch of testframe updates #77 #78 #86 (thanks @dpreussner, @psjg a.o)
### 0.3.4
- Added user callback file func.
- Fixed a stat bug with obj id.
- SPIFFS_probe_fs added
- Add possibility to compile a read-only version of spiffs
- Make magic dependent on fs length, if needed (see #59 & #66) (thanks @hreintke)
- Exposed SPIFFS_open_by_page_function
- Zero-size file cannot be seek #57 (thanks @lishen2)
- Add tell and eof functions #54 (thanks @raburton)
- Make api string params const #53 (thanks @raburton)
- Preserve user_data during mount() #51 (thanks @rojer)
New API functions:
- `SPIFFS_set_file_callback_func` - register a callback informing about file events
- `SPIFFS_probe_fs` - probe a spi flash trying to figure out size of fs
- `SPIFFS_open_by_page` - open a file by page index
- `SPIFFS_eof` - checks if end of file is reached
- `SPIFFS_tell` - returns current file offset
New config defines:
- `SPIFFS_READ_ONLY`
- `SPIFFS_USE_MAGIC_LENGTH`
### 0.3.3
**Might not be compatible with 0.3.2 structures. See issue #40**
- Possibility to add integer offset to file handles
- Truncate function presumes too few free pages #49
- Bug in truncate function #48 (thanks @PawelDefee)
- Update spiffs_gc.c - remove unnecessary parameter (thanks @PawelDefee)
- Update INTEGRATION docs (thanks @PawelDefee)
- Fix pointer truncation in 64-bit platforms (thanks @igrr)
- Zero-sized files cannot be read #44 (thanks @rojer)
- (More) correct calculation of max_id in obj_lu_find #42 #41 (thanks @lishen2)
- Check correct error code in obj_lu_find_free #41 (thanks @lishen2)
- Moar comments for SPIFFS_lseek (thanks @igrr)
- Fixed padding in spiffs_page_object_ix #40 (thanks @jmattsson @lishen2)
- Fixed gc_quick test (thanks @jmattsson)
- Add SPIFFS_EXCL flag #36
- SPIFFS_close may fail silently if cache is enabled #37
- User data in callbacks #34
- Ignoring SINGLETON build in cache setup (thanks Luca)
- Compilation error fixed #32 (thanks @chotasanjiv)
- Align cand_scores (thanks @hefloryd)
- Fix build warnings when SPIFFS_CACHE is 0 (thanks @ajaybhargav)
New config defines:
- `SPIFFS_FILEHDL_OFFSET`
### 0.3.2
- Limit cache size if too much cache is given (thanks pgeiem)
- New feature - Controlled erase. #23
- SPIFFS_rename leaks file descriptors #28 (thanks benpicco)
- moved dbg print defines in test framework to params_test.h
- lseek should return the resulting offset (thanks hefloryd)
- fixed type on dbg ifdefs
- silence warning about signed/unsigned comparison when spiffs_obj_id is 32 bit (thanks benpicco)
- Possible error in test_spiffs.c #21 (thanks yihcdaso-yeskela)
- Cache might writethrough too often #16
- even moar testrunner updates
- Test framework update and some added tests
- Some thoughts for next gen
- Test sigsevs when having too many sectors #13 (thanks alonewolfx2)
- GC might be suboptimal #11
- Fix eternal readdir when objheader at last block, last entry
New API functions:
- `SPIFFS_gc_quick` - call a nonintrusive gc
- `SPIFFS_gc` - call a full-scale intrusive gc
### 0.3.1
- Removed two return warnings, was too triggerhappy on release
### 0.3.0
- Added existing namecheck when creating files
- Lots of static analysis bugs #6
- Added rename func
- Fix SPIFFS_read length when reading beyond file size
- Added reading beyond file length testcase
- Made build a bit more configurable
- Changed name in spiffs from "errno" to "err_code" due to conflicts compiling in mingw
- Improved GC checks, fixed an append bug, more robust truncate for very special case
- GC checks preempts GC, truncate even less picky
- Struct alignment needed for some targets, define in spiffs config #10
- Spiffs filesystem magic, definable in config
New config defines:
- `SPIFFS_USE_MAGIC` - enable or disable magic check upon mount
- `SPIFFS_ALIGNED_OBJECT_INDEX_TABLES` - alignment for certain targets
New API functions:
- `SPIFFS_rename` - rename files
- `SPIFFS_clearerr` - clears last errno
- `SPIFFS_info` - returns info on used and total bytes in fs
- `SPIFFS_format` - formats the filesystem
- `SPIFFS_mounted` - checks if filesystem is mounted

@ -0,0 +1,15 @@
<EFBFBD>5S-C4
d5rh
OlWkR#C4
d5rh
O4W4R4O4W4<EFBFBD>C4#d5rh
O4d5rh
OlWkRh
O4Y5rh
OlWkR4C44R45<EFBFBD><EFBFBD>
O4W4<EFBFBD>4C4C4
O4O4W4R4O4W4<EFBFBD>C4#d5rh
O4d5rh
W4R45r<EFBFBD>
O4W4<EFBFBD>4#d5rh
rz

Binary file not shown.

@ -0,0 +1,18 @@
b55
O4W4R4C4D4
b45
d5rh
O4W4R4f4C4
baaU
d5rh
OaWaRafaCa
cd5rh
OaWaRafaCa
O4S4W4R4C4
d5rh
O4W4S4R4C4
d5rh
O4W4R4S4C4
d5rh
O4W4R4C4
d5rh

@ -0,0 +1,15 @@
b55
O4
W?W?W?W?W?f4
W<W=W>W:W;f4
C4
b45
d5rh
O4W?R4f4C4
baa
d5rh
OaWaRafaCa
d5rh
OaWaRafaCa
O4W?R4C4
d5rh

@ -0,0 +1,239 @@
* USING SPIFFS
TODO
* SPIFFS DESIGN
Spiffs is inspired by YAFFS. However, YAFFS is designed for NAND flashes, and
for bigger targets with much more ram. Nevertheless, many wise thoughts have
been borrowed from YAFFS when writing spiffs. Kudos!
The main complication writing spiffs was that it cannot be assumed the target
has a heap. Spiffs must go along only with the work ram buffer given to it.
This forces extra implementation on many areas of spiffs.
** SPI flash devices using NOR technology
Below is a small description of how SPI flashes work internally. This is to
give an understanding of the design choices made in spiffs.
SPI flash devices are physically divided in blocks. On some SPI flash devices,
blocks are further divided into sectors. Datasheets sometimes name blocks as
sectors and vice versa.
Common memory capacaties for SPI flashes are 512kB up to 8MB of data, where
blocks may be 64kB. Sectors can be e.g. 4kB, if supported. Many SPI flashes
have uniform block sizes, whereas others have non-uniform - the latter meaning
that e.g. the first 16 blocks are 4kB big, and the rest are 64kB.
The entire memory is linear and can be read and written in random access.
Erasing can only be done block- or sectorwise; or by mass erase.
SPI flashes can normally be erased from 100.000 up to 1.000.000 cycles before
they fail.
A clean SPI flash from factory have all bits in entire memory set to one. A
mass erase will reset the device to this state. Block or sector erasing will
put the all bits in the area given by the sector or block to ones. Writing to a
NOR flash pulls ones to zeroes. Writing 0xFF to an address is simply a no-op.
Writing 0b10101010 to a flash address holding 0b00001111 will yield 0b00001010.
This way of "write by nand" is used considerably in spiffs.
Common characteristics of NOR flashes are quick reads, but slow writes.
And finally, unlike NAND flashes, NOR flashes seem to not need any error
correction. They always write correctly I gather.
** Spiffs logical structure
Some terminology before proceeding. Physical blocks/sectors means sizes stated
in the datasheet. Logical blocks and pages is something the integrator choose.
** Blocks and pages
Spiffs is allocated to a part or all of the memory of the SPI flash device.
This area is divided into logical blocks, which in turn are divided into
logical pages. The boundary of a logical block must coincide with one or more
physical blocks. The sizes for logical blocks and logical pages always remain
the same, they are uniform.
Example: non-uniform flash mapped to spiffs with 128kB logical blocks
PHYSICAL FLASH BLOCKS SPIFFS LOGICAL BLOCKS: 128kB
+-----------------------+ - - - +-----------------------+
| Block 1 : 16kB | | Block 1 : 128kB |
+-----------------------+ | |
| Block 2 : 16kB | | |
+-----------------------+ | |
| Block 3 : 16kB | | |
+-----------------------+ | |
| Block 4 : 16kB | | |
+-----------------------+ | |
| Block 5 : 64kB | | |
+-----------------------+ - - - +-----------------------+
| Block 6 : 64kB | | Block 2 : 128kB |
+-----------------------+ | |
| Block 7 : 64kB | | |
+-----------------------+ - - - +-----------------------+
| Block 8 : 64kB | | Block 3 : 128kB |
+-----------------------+ | |
| Block 9 : 64kB | | |
+-----------------------+ - - - +-----------------------+
| ... | | ... |
A logical block is divided further into a number of logical pages. A page
defines the smallest data holding element known to spiffs. Hence, if a file
is created being one byte big, it will occupy one page for index and one page
for data - it will occupy 2 x size of a logical page on flash.
So it seems it is good to select a small page size.
Each page has a metadata header being normally 5 to 9 bytes. This said, a very
small page size will make metadata occupy a lot of the memory on the flash. A
page size of 64 bytes will waste 8-14% on metadata, while 256 bytes 2-4%.
So it seems it is good to select a big page size.
Also, spiffs uses a ram buffer being two times the page size. This ram buffer
is used for loading and manipulating pages, but it is also used for algorithms
to find free file ids, scanning the file system, etc. Having too small a page
size means less work buffer for spiffs, ending up in more reads operations and
eventually gives a slower file system.
Choosing the page size for the system involves many factors:
- How big is the logical block size
- What is the normal size of most files
- How much ram can be spent
- How much data (vs metadata) must be crammed into the file system
- How fast must spiffs be
- Other things impossible to find out
So, chosing the Optimal Page Size (tm) seems tricky, to say the least. Don't
fret - there is no optimal page size. This varies from how the target will use
spiffs. Use the golden rule:
~~~ Logical Page Size = Logical Block Size / 256 ~~~
This is a good starting point. The final page size can then be derived through
heuristical experimenting for us non-analytical minds.
** Objects, indices and look-ups
A file, or an object as called in spiffs, is identified by an object id.
Another YAFFS rip-off. This object id is a part of the page header. So, all
pages know to which object/file they belong - not counting the free pages.
An object is made up of two types of pages: object index pages and data pages.
Data pages contain the data written by user. Index pages contain metadata about
the object, more specifically what data pages are part of the object.
The page header also includes something called a span index. Let's say a file
is written covering three data pages. The first data page will then have span
index 0, the second span index 1, and the last data page will have span index
2. Simple as that.
Finally, each page header contain flags, telling if the page is used,
deleted, finalized, holds index or data, and more.
Object indices also have span indices, where an object index with span index 0
is referred to as the object index header. This page does not only contain
references to data pages, but also extra info such as object name, object size
in bytes, flags for file or directory, etc.
If one were to create a file covering three data pages, named e.g.
"spandex-joke.txt", given object id 12, it could look like this:
PAGE 0 <things to be unveiled soon>
PAGE 1 page header: [obj_id:12 span_ix:0 flags:USED|DATA]
<first data page of joke>
PAGE 2 page header: [obj_id:12 span_ix:1 flags:USED|DATA]
<second data page of joke>
PAGE 3 page header: [obj_id:545 span_ix:13 flags:USED|DATA]
<some data belonging to object 545, probably not very amusing>
PAGE 4 page header: [obj_id:12 span_ix:2 flags:USED|DATA]
<third data page of joke>
PAGE 5 page header: [obj_id:12 span_ix:0 flags:USED|INDEX]
obj ix header: [name:spandex-joke.txt size:600 bytes flags:FILE]
obj ix: [1 2 4]
Looking in detail at page 5, the object index header page, the object index
array refers to each data page in order, as mentioned before. The index of the
object index array correlates with the data page span index.
entry ix: 0 1 2
obj ix: [1 2 4]
| | |
PAGE 1, DATA, SPAN_IX 0 --------/ | |
PAGE 2, DATA, SPAN_IX 1 --------/ |
PAGE 4, DATA, SPAN_IX 2 --------/
Things to be unveiled in page 0 - well.. Spiffs is designed for systems low on
ram. We cannot keep a dynamic list on the whereabouts of each object index
header so we can find a file fast. There might not even be a heap! But, we do
not want to scan all page headers on the flash to find the object index header.
The first page(s) of each block contains the so called object look-up. These
are not normal pages, they do not have a header. Instead, they are arrays
pointing out what object-id the rest of all pages in the block belongs to.
By this look-up, only the first page(s) in each block must to scanned to find
the actual page which contains the object index header of the desired object.
The object lookup is redundant metadata. The assumption is that it presents
less overhead reading a full page of data to memory from each block and search
that, instead of reading a small amount of data from each page (i.e. the page
header) in all blocks. Each read operation from SPI flash normally contains
extra data as the read command itself and the flash address. Also, depending on
the underlying implementation, other criterions may need to be passed for each
read transaction, like mutexes and such.
The veiled example unveiled would look like this, with some extra pages:
PAGE 0 [ 12 12 545 12 12 34 34 4 0 0 0 0 ...]
PAGE 1 page header: [obj_id:12 span_ix:0 flags:USED|DATA] ...
PAGE 2 page header: [obj_id:12 span_ix:1 flags:USED|DATA] ...
PAGE 3 page header: [obj_id:545 span_ix:13 flags:USED|DATA] ...
PAGE 4 page header: [obj_id:12 span_ix:2 flags:USED|DATA] ...
PAGE 5 page header: [obj_id:12 span_ix:0 flags:USED|INDEX] ...
PAGE 6 page header: [obj_id:34 span_ix:0 flags:USED|DATA] ...
PAGE 7 page header: [obj_id:34 span_ix:1 flags:USED|DATA] ...
PAGE 8 page header: [obj_id:4 span_ix:1 flags:USED|INDEX] ...
PAGE 9 page header: [obj_id:23 span_ix:0 flags:DELETED|INDEX] ...
PAGE 10 page header: [obj_id:23 span_ix:0 flags:DELETED|DATA] ...
PAGE 11 page header: [obj_id:23 span_ix:1 flags:DELETED|DATA] ...
PAGE 12 page header: [obj_id:23 span_ix:2 flags:DELETED|DATA] ...
...
Ok, so why are page 9 to 12 marked as 0 when they belong to object id 23? These
pages are deleted, so this is marked both in page header flags and in the look
up. This is an example where spiffs uses NOR flashes "nand-way" of writing.
As a matter of fact, there are two object id's which are special:
obj id 0 (all bits zeroes) - indicates a deleted page in object look up
obj id 0xff.. (all bits ones) - indicates a free page in object look up
Actually, the object id's have another quirk: if the most significant bit is
set, this indicates an object index page. If the most significant bit is zero,
this indicates a data page. So to be fully correct, page 0 in above example
would look like this:
PAGE 0 [ 12 12 545 12 *12 34 34 *4 0 0 0 0 ...]
where the asterisk means the msb of the object id is set.
This is another way to speed up the searches when looking for object indices.
By looking on the object id's msb in the object lookup, it is also possible
to find out whether the page is an object index page or a data page.

@ -0,0 +1,15 @@
* When mending lost pages, also see if they fit into length specified in object index header
SPIFFS2 thoughts
* Instead of exact object id:s in the object lookup tables, use a hash of span index and object id.
Eg. object id xor:ed with bit-reversed span index.
This should decrease number of actual pages that needs to be visited when looking thru the obj lut.
* Logical number of each block. When moving stuff in a garbage collected page, the free
page is assigned the same number as the garbage collected. Thus, object index pages do not have to
be rewritten.
* Steal one page, use as a bit parity page. When starting an fs modification operation, write one bit
as zero. When ending, write another bit as zero. On mount, if number of zeroes in page is uneven, a
check is automatically run.

@ -0,0 +1,12 @@
ifndef spiffs
$(warn defaulting path to generic spiffs module, spiffs variable not set)
spiffs = ../generic/spiffs
endif
FLAGS += -DCONFIG_BUILD_SPIFFS
INC += -I${spiffs}/src
CPATH += ${spiffs}/src
CFILES += spiffs_nucleus.c
CFILES += spiffs_gc.c
CFILES += spiffs_hydrogen.c
CFILES += spiffs_cache.c
CFILES += spiffs_check.c

@ -0,0 +1,163 @@
BINARY = linux_spiffs_test
############
#
# Paths
#
############
sourcedir = src
builddir = build
#############
#
# Build tools
#
#############
CC ?= gcc
LD ?= ld
GDB ?= gdb
OBJCOPY ?= objcopy
OBJDUMP ?= objdump
MKDIR ?= mkdir -p
###############
#
# Files and libs
#
###############
NO_TEST ?= 0
CFLAGS = $(FLAGS)
ifeq (1, $(strip $(NO_TEST)))
CFILES_TEST = main.c
CFLAGS += -DNO_TEST -Werror
else
CFILES_TEST = main.c \
test_spiffs.c \
test_dev.c \
test_check.c \
test_hydrogen.c \
test_bugreports.c \
testsuites.c \
testrunner.c
CFLAGS += -D_SPIFFS_TEST
endif
include files.mk
INCLUDE_DIRECTIVES = -I./${sourcedir} -I./${sourcedir}/default -I./${sourcedir}/test
COMPILEROPTIONS = $(INCLUDE_DIRECTIVES)
COMPILEROPTIONS_APP = $(INCLUDE_DIRECTIVES) \
-Wall -Wno-format-y2k -W -Wstrict-prototypes -Wmissing-prototypes \
-Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch \
-Wshadow -Wcast-align -Wchar-subscripts -Winline -Wnested-externs\
-Wredundant-decls
############
#
# Tasks
#
############
vpath %.c ${sourcedir} ${sourcedir}/default ${sourcedir}/test
OBJFILES = $(CFILES:%.c=${builddir}/%.o)
OBJFILES_TEST = $(CFILES_TEST:%.c=${builddir}/%.o)
DEPFILES = $(CFILES:%.c=${builddir}/%.d) $(CFILES_TEST:%.c=${builddir}/%.d)
ALLOBJFILES += $(OBJFILES) $(OBJFILES_TEST)
DEPENDENCIES = $(DEPFILES)
# link object files, create binary
$(BINARY): $(ALLOBJFILES)
@echo "... linking"
@${CC} $(LINKEROPTIONS) -o ${builddir}/$(BINARY) $(ALLOBJFILES) $(LIBS)
ifeq (1, $(strip $(NO_TEST)))
@echo "size: `du -b ${builddir}/${BINARY} | sed 's/\([0-9]*\).*/\1/g '` bytes"
endif
-include $(DEPENDENCIES)
# compile c files
$(OBJFILES) : ${builddir}/%.o:%.c
@echo "... compile $@"
@${CC} $(COMPILEROPTIONS_APP) $(CFLAGS) -g -c -o $@ $<
$(OBJFILES_TEST) : ${builddir}/%.o:%.c
@echo "... compile $@"
@${CC} ${COMPILEROPTIONS} $(CFLAGS) -g -c -o $@ $<
# make dependencies
# @echo "... depend $@";
$(DEPFILES) : ${builddir}/%.d:%.c
@rm -f $@; \
${CC} $(COMPILEROPTIONS) -M $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*, ${builddir}/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
all: mkdirs $(BINARY)
mkdirs:
-@${MKDIR} ${builddir}
-@${MKDIR} test_data
FILTER ?=
test: $(BINARY)
ifdef $(FILTER)
./build/$(BINARY)
else
./build/$(BINARY) -f $(FILTER)
endif
test_failed: $(BINARY)
./build/$(BINARY) _tests_fail
clean:
@echo ... removing build files in ${builddir}
@rm -f ${builddir}/*.o
@rm -f ${builddir}/*.d
@rm -f ${builddir}/*.elf
ONOFF = 1 0
OFFON = 0 1
build-all:
@for rdonly in $(ONOFF); do \
for singleton in $(ONOFF); do \
for hal_cb_xtra in $(OFFON); do \
for cache in $(OFFON); do \
for magic in $(OFFON); do \
for temporal_cache in $(OFFON); do \
for ix_map in $(OFFON); do \
echo; \
echo ============================================================; \
echo SPIFFS_READ_ONLY=$$rdonly; \
echo SPIFFS_SINGLETON=$$singleton; \
echo SPIFFS_HAL_CALLBACK_EXTRA=$$hal_cb_xtra; \
echo SPIFFS_CACHE, SPIFFS_CACHE_WR=$$cache; \
echo SPIFFS_USE_MAGIC, SPIFFS_USE_MAGIC_LENGTH=$$magic; \
echo SPIFFS_TEMPORAL_FD_CACHE=$$temporal_cache; \
echo SPIFFS_IX_MAP=$$ix_map; \
$(MAKE) clean && $(MAKE) FLAGS="\
-DSPIFFS_HAL_CALLBACK_EXTRA=$$hal_cb_xtra \
-DSPIFFS_SINGLETON=$$singleton \
-DSPIFFS_CACHE=$$cache \
-DSPIFFS_CACHE_WR=$$cache \
-DSPIFFS_READ_ONLY=$$rdonly \
-DSPIFFS_USE_MAGIC=$$magic \
-DSPIFFS_USE_MAGIC_LENGTH=$$magic \
-DSPIFFS_TEMPORAL_FD_CACHE=$$temporal_cache \
-DSPIFFS_IX_MAP=$$ix_map \
" NO_TEST=1; \
done || exit 1; \
done \
done \
done \
done \
done \
done

@ -11,42 +11,76 @@
// ----------- 8< ------------ // ----------- 8< ------------
// Following includes are for the linux test build of spiffs // Following includes are for the linux test build of spiffs
// These may/should/must be removed/altered/replaced in your target // These may/should/must be removed/altered/replaced in your target
#include "params_test.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <unistd.h>
#ifdef _SPIFFS_TEST
#include "freertos/FreeRTOS.h" #include "testrunner.h"
#include "freertos/task.h" #endif
// ----------- >8 ------------ // ----------- >8 ------------
typedef uint8_t u8_t;
typedef int8_t s8_t;
typedef uint16_t u16_t;
typedef int16_t s16_t;
typedef uint32_t u32_t;
typedef int32_t s32_t;
// compile time switches // compile time switches
// Set generic spiffs debug output call. // Set generic spiffs debug output call.
#ifndef SPIFFS_DBG #ifndef SPIFFS_DBG
#define SPIFFS_DBG(...) //printf(__VA_ARGS__) #define SPIFFS_DBG(_f, ...) //printf(_f, ## __VA_ARGS__)
#endif #endif
// Set spiffs debug output call for garbage collecting. // Set spiffs debug output call for garbage collecting.
#ifndef SPIFFS_GC_DBG #ifndef SPIFFS_GC_DBG
#define SPIFFS_GC_DBG(...) //printf(__VA_ARGS__) #define SPIFFS_GC_DBG(_f, ...) //printf(_f, ## __VA_ARGS__)
#endif #endif
// Set spiffs debug output call for caching. // Set spiffs debug output call for caching.
#ifndef SPIFFS_CACHE_DBG #ifndef SPIFFS_CACHE_DBG
#define SPIFFS_CACHE_DBG(...) //printf(__VA_ARGS__) #define SPIFFS_CACHE_DBG(_f, ...) //printf(_f, ## __VA_ARGS__)
#endif #endif
// Set spiffs debug output call for system consistency checks. // Set spiffs debug output call for system consistency checks.
#ifndef SPIFFS_CHECK_DBG #ifndef SPIFFS_CHECK_DBG
#define SPIFFS_CHECK_DBG(...) //printf(__VA_ARGS__) #define SPIFFS_CHECK_DBG(_f, ...) //printf(_f, ## __VA_ARGS__)
#endif #endif
// Set spiffs debug output call for all api invocations.
#ifndef SPIFFS_API_DBG
#define SPIFFS_API_DBG(_f, ...) //printf(_f, ## __VA_ARGS__)
#endif
// Defines spiffs debug print formatters
// some general signed number
#ifndef _SPIPRIi
#define _SPIPRIi "%d"
#endif
// address
#ifndef _SPIPRIad
#define _SPIPRIad "%08x"
#endif
// block
#ifndef _SPIPRIbl
#define _SPIPRIbl "%04x"
#endif
// page
#ifndef _SPIPRIpg
#define _SPIPRIpg "%04x"
#endif
// span index
#ifndef _SPIPRIsp
#define _SPIPRIsp "%04x"
#endif
// file descriptor
#ifndef _SPIPRIfd
#define _SPIPRIfd "%d"
#endif
// file object id
#ifndef _SPIPRIid
#define _SPIPRIid "%04x"
#endif
// file flags
#ifndef _SPIPRIfl
#define _SPIPRIfl "%02x"
#endif
// Enable/disable API functions to determine exact number of bytes // Enable/disable API functions to determine exact number of bytes
// for filedescriptor and cache buffers. Once decided for a configuration, // for filedescriptor and cache buffers. Once decided for a configuration,
@ -60,7 +94,6 @@ typedef int32_t s32_t;
#ifndef SPIFFS_CACHE #ifndef SPIFFS_CACHE
#define SPIFFS_CACHE 1 #define SPIFFS_CACHE 1
#endif #endif
#if SPIFFS_CACHE #if SPIFFS_CACHE
// Enables memory write caching for file descriptors in hydrogen // Enables memory write caching for file descriptors in hydrogen
#ifndef SPIFFS_CACHE_WR #ifndef SPIFFS_CACHE_WR
@ -111,11 +144,27 @@ typedef int32_t s32_t;
#define SPIFFS_GC_HEUR_W_ERASE_AGE (50) #define SPIFFS_GC_HEUR_W_ERASE_AGE (50)
#endif #endif
// Object name maximum length. // Object name maximum length. Note that this length include the
// zero-termination character, meaning maximum string of characters
// can at most be SPIFFS_OBJ_NAME_LEN - 1.
#ifndef SPIFFS_OBJ_NAME_LEN #ifndef SPIFFS_OBJ_NAME_LEN
#define SPIFFS_OBJ_NAME_LEN (32) #define SPIFFS_OBJ_NAME_LEN (32)
#endif #endif
// Maximum length of the metadata associated with an object.
// Setting to non-zero value enables metadata-related API but also
// changes the on-disk format, so the change is not backward-compatible.
//
// Do note: the meta length must never exceed
// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64)
//
// This is derived from following:
// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) +
// spiffs_object_ix_header fields + at least some LUT entries)
#ifndef SPIFFS_OBJ_META_LEN
#define SPIFFS_OBJ_META_LEN (0)
#endif
// Size of buffer allocated on stack used when copying data. // Size of buffer allocated on stack used when copying data.
// Lower value generates more read/writes. No meaning having it bigger // Lower value generates more read/writes. No meaning having it bigger
// than logical page size. // than logical page size.
@ -131,6 +180,17 @@ typedef int32_t s32_t;
#define SPIFFS_USE_MAGIC (0) #define SPIFFS_USE_MAGIC (0)
#endif #endif
#if SPIFFS_USE_MAGIC
// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is
// enabled, the magic will also be dependent on the length of the filesystem.
// For example, a filesystem configured and formatted for 4 megabytes will not
// be accepted for mounting with a configuration defining the filesystem as 2
// megabytes.
#ifndef SPIFFS_USE_MAGIC_LENGTH
#define SPIFFS_USE_MAGIC_LENGTH (0)
#endif
#endif
// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level // SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level
// These should be defined on a multithreaded system // These should be defined on a multithreaded system
@ -143,7 +203,6 @@ typedef int32_t s32_t;
#define SPIFFS_UNLOCK(fs) #define SPIFFS_UNLOCK(fs)
#endif #endif
// Enable if only one spiffs instance with constant configuration will exist // Enable if only one spiffs instance with constant configuration will exist
// on the target. This will reduce calculations, flash and memory accesses. // on the target. This will reduce calculations, flash and memory accesses.
// Parts of configuration must be defined below instead of at time of mount. // Parts of configuration must be defined below instead of at time of mount.
@ -173,14 +232,90 @@ typedef int32_t s32_t;
// Enable this if your target needs aligned data for index tables // Enable this if your target needs aligned data for index tables
#ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES #ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES
#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 1 #define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 0
#endif
// Enable this if you want the HAL callbacks to be called with the spiffs struct
#ifndef SPIFFS_HAL_CALLBACK_EXTRA
#define SPIFFS_HAL_CALLBACK_EXTRA 0
#endif
// Enable this if you want to add an integer offset to all file handles
// (spiffs_file). This is useful if running multiple instances of spiffs on
// same target, in order to recognise to what spiffs instance a file handle
// belongs.
// NB: This adds config field fh_ix_offset in the configuration struct when
// mounting, which must be defined.
#ifndef SPIFFS_FILEHDL_OFFSET
#define SPIFFS_FILEHDL_OFFSET 0
#endif
// Enable this to compile a read only version of spiffs.
// This will reduce binary size of spiffs. All code comprising modification
// of the file system will not be compiled. Some config will be ignored.
// HAL functions for erasing and writing to spi-flash may be null. Cache
// can be disabled for even further binary size reduction (and ram savings).
// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL.
// If the file system cannot be mounted due to aborted erase operation and
// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be
// returned.
// Might be useful for e.g. bootloaders and such.
#ifndef SPIFFS_READ_ONLY
#define SPIFFS_READ_ONLY 0
#endif
// Enable this to add a temporal file cache using the fd buffer.
// The effects of the cache is that SPIFFS_open will find the file faster in
// certain cases. It will make it a lot easier for spiffs to find files
// opened frequently, reducing number of readings from the spi flash for
// finding those files.
// This will grow each fd by 6 bytes. If your files are opened in patterns
// with a degree of temporal locality, the system is optimized.
// Examples can be letting spiffs serve web content, where one file is the css.
// The css is accessed for each html file that is opened, meaning it is
// accessed almost every second time a file is opened. Another example could be
// a log file that is often opened, written, and closed.
// The size of the cache is number of given file descriptors, as it piggybacks
// on the fd update mechanism. The cache lives in the closed file descriptors.
// When closed, the fd know the whereabouts of the file. Instead of forgetting
// this, the temporal cache will keep handling updates to that file even if the
// fd is closed. If the file is opened again, the location of the file is found
// directly. If all available descriptors become opened, all cache memory is
// lost.
#ifndef SPIFFS_TEMPORAL_FD_CACHE
#define SPIFFS_TEMPORAL_FD_CACHE 1
#endif
// Temporal file cache hit score. Each time a file is opened, all cached files
// will lose one point. If the opened file is found in cache, that entry will
// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this
// value for the specific access patterns of the application. However, it must
// be between 1 (no gain for hitting a cached entry often) and 255.
#ifndef SPIFFS_TEMPORAL_CACHE_HIT_SCORE
#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 4
#endif
// Enable to be able to map object indices to memory.
// This allows for faster and more deterministic reading if cases of reading
// large files and when changing file offset by seeking around a lot.
// When mapping a file's index, the file system will be scanned for index pages
// and the info will be put in memory provided by user. When reading, the
// memory map can be looked up instead of searching for index pages on the
// medium. This way, user can trade memory against performance.
// Whole, parts of, or future parts not being written yet can be mapped. The
// memory array will be owned by spiffs and updated accordingly during garbage
// collecting or when modifying the indices. The latter is invoked by when the
// file is modified in some way. The index buffer is tied to the file
// descriptor.
#ifndef SPIFFS_IX_MAP
#define SPIFFS_IX_MAP 1
#endif #endif
// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function // Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function
// in the api. This function will visualize all filesystem using given printf // in the api. This function will visualize all filesystem using given printf
// function. // function.
#ifndef SPIFFS_TEST_VISUALISATION #ifndef SPIFFS_TEST_VISUALISATION
#define SPIFFS_TEST_VISUALISATION 0 #define SPIFFS_TEST_VISUALISATION 1
#endif #endif
#if SPIFFS_TEST_VISUALISATION #if SPIFFS_TEST_VISUALISATION
#ifndef spiffs_printf #ifndef spiffs_printf

@ -5,8 +5,6 @@
* Author: petera * Author: petera
*/ */
#ifndef SPIFFS_H_ #ifndef SPIFFS_H_
#define SPIFFS_H_ #define SPIFFS_H_
#if defined(__cplusplus) #if defined(__cplusplus)
@ -49,6 +47,22 @@ extern "C" {
#define SPIFFS_ERR_NO_DELETED_BLOCKS -10029 #define SPIFFS_ERR_NO_DELETED_BLOCKS -10029
#define SPIFFS_ERR_FILE_EXISTS -10030
#define SPIFFS_ERR_NOT_A_FILE -10031
#define SPIFFS_ERR_RO_NOT_IMPL -10032
#define SPIFFS_ERR_RO_ABORTED_OPERATION -10033
#define SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS -10034
#define SPIFFS_ERR_PROBE_NOT_A_FS -10035
#define SPIFFS_ERR_NAME_TOO_LONG -10036
#define SPIFFS_ERR_IX_MAP_UNMAPPED -10037
#define SPIFFS_ERR_IX_MAP_MAPPED -10038
#define SPIFFS_ERR_IX_MAP_BAD_RANGE -10039
#define SPIFFS_ERR_SEEK_BOUNDS -10040
#define SPIFFS_ERR_INTERNAL -10050 #define SPIFFS_ERR_INTERNAL -10050
#define SPIFFS_ERR_TEST -10100 #define SPIFFS_ERR_TEST -10100
@ -63,12 +77,26 @@ typedef u16_t spiffs_mode;
// object type // object type
typedef u8_t spiffs_obj_type; typedef u8_t spiffs_obj_type;
struct spiffs_t;
#if SPIFFS_HAL_CALLBACK_EXTRA
/* spi read call function type */
typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst);
/* spi write call function type */
typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src);
/* spi erase call function type */
typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size);
#else // SPIFFS_HAL_CALLBACK_EXTRA
/* spi read call function type */ /* spi read call function type */
typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst); typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst);
/* spi write call function type */ /* spi write call function type */
typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src); typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src);
/* spi erase call function type */ /* spi erase call function type */
typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size); typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size);
#endif // SPIFFS_HAL_CALLBACK_EXTRA
/* file system check callback report operation */ /* file system check callback report operation */
typedef enum { typedef enum {
@ -85,16 +113,34 @@ typedef enum {
SPIFFS_CHECK_FIX_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP,
SPIFFS_CHECK_DELETE_ORPHANED_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX,
SPIFFS_CHECK_DELETE_PAGE, SPIFFS_CHECK_DELETE_PAGE,
SPIFFS_CHECK_DELETE_BAD_FILE, SPIFFS_CHECK_DELETE_BAD_FILE
} spiffs_check_report; } spiffs_check_report;
/* file system check callback function */ /* file system check callback function */
#if SPIFFS_HAL_CALLBACK_EXTRA
typedef void (*spiffs_check_callback)(struct spiffs_t *fs, spiffs_check_type type, spiffs_check_report report,
u32_t arg1, u32_t arg2);
#else // SPIFFS_HAL_CALLBACK_EXTRA
typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_report report, typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_report report,
u32_t arg1, u32_t arg2); u32_t arg1, u32_t arg2);
#endif // SPIFFS_HAL_CALLBACK_EXTRA
/* file system listener callback operation */
typedef enum {
/* the file has been created */
SPIFFS_CB_CREATED = 0,
/* the file has been updated or moved to another page */
SPIFFS_CB_UPDATED,
/* the file has been deleted */
SPIFFS_CB_DELETED
} spiffs_fileop_type;
/* file system listener callback function */
typedef void (*spiffs_file_callback)(struct spiffs_t *fs, spiffs_fileop_type op, spiffs_obj_id obj_id, spiffs_page_ix pix);
#ifndef SPIFFS_DBG #ifndef SPIFFS_DBG
#define SPIFFS_DBG(...) \ #define SPIFFS_DBG(...) \
print(__VA_ARGS__) printf(__VA_ARGS__)
#endif #endif
#ifndef SPIFFS_GC_DBG #ifndef SPIFFS_GC_DBG
#define SPIFFS_GC_DBG(...) printf(__VA_ARGS__) #define SPIFFS_GC_DBG(...) printf(__VA_ARGS__)
@ -108,18 +154,28 @@ typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_repor
/* Any write to the filehandle is appended to end of the file */ /* Any write to the filehandle is appended to end of the file */
#define SPIFFS_APPEND (1<<0) #define SPIFFS_APPEND (1<<0)
#define SPIFFS_O_APPEND SPIFFS_APPEND
/* If the opened file exists, it will be truncated to zero length before opened */ /* If the opened file exists, it will be truncated to zero length before opened */
#define SPIFFS_TRUNC (1<<1) #define SPIFFS_TRUNC (1<<1)
#define SPIFFS_O_TRUNC SPIFFS_TRUNC
/* If the opened file does not exist, it will be created before opened */ /* If the opened file does not exist, it will be created before opened */
#define SPIFFS_CREAT (1<<2) #define SPIFFS_CREAT (1<<2)
#define SPIFFS_O_CREAT SPIFFS_CREAT
/* The opened file may only be read */ /* The opened file may only be read */
#define SPIFFS_RDONLY (1<<3) #define SPIFFS_RDONLY (1<<3)
/* The opened file may only be writted */ #define SPIFFS_O_RDONLY SPIFFS_RDONLY
/* The opened file may only be written */
#define SPIFFS_WRONLY (1<<4) #define SPIFFS_WRONLY (1<<4)
/* The opened file may be both read and writted */ #define SPIFFS_O_WRONLY SPIFFS_WRONLY
/* The opened file may be both read and written */
#define SPIFFS_RDWR (SPIFFS_RDONLY | SPIFFS_WRONLY) #define SPIFFS_RDWR (SPIFFS_RDONLY | SPIFFS_WRONLY)
/* Any writes to the filehandle will never be cached */ #define SPIFFS_O_RDWR SPIFFS_RDWR
/* Any writes to the filehandle will never be cached but flushed directly */
#define SPIFFS_DIRECT (1<<5) #define SPIFFS_DIRECT (1<<5)
#define SPIFFS_O_DIRECT SPIFFS_DIRECT
/* If SPIFFS_O_CREAT and SPIFFS_O_EXCL are set, SPIFFS_open() shall fail if the file exists */
#define SPIFFS_EXCL (1<<6)
#define SPIFFS_O_EXCL SPIFFS_EXCL
#define SPIFFS_SEEK_SET (0) #define SPIFFS_SEEK_SET (0)
#define SPIFFS_SEEK_CUR (1) #define SPIFFS_SEEK_CUR (1)
@ -164,10 +220,15 @@ typedef struct {
// logical size of a page, must be at least // logical size of a page, must be at least
// log_block_size / 8 // log_block_size / 8
u32_t log_page_size; u32_t log_page_size;
#endif
#if SPIFFS_FILEHDL_OFFSET
// an integer offset added to each file handle
u16_t fh_ix_offset;
#endif #endif
} spiffs_config; } spiffs_config;
typedef struct { typedef struct spiffs_t {
// file system configuration // file system configuration
spiffs_config cfg; spiffs_config cfg;
// number of logical blocks // number of logical blocks
@ -222,9 +283,12 @@ typedef struct {
// check callback function // check callback function
spiffs_check_callback check_cb_f; spiffs_check_callback check_cb_f;
// file callback function
spiffs_file_callback file_cb_f;
// mounted flag // mounted flag
u8_t mounted; u8_t mounted;
// user data
void *user_data;
// config magic // config magic
u32_t config_magic; u32_t config_magic;
} spiffs; } spiffs;
@ -234,7 +298,11 @@ typedef struct {
spiffs_obj_id obj_id; spiffs_obj_id obj_id;
u32_t size; u32_t size;
spiffs_obj_type type; spiffs_obj_type type;
spiffs_page_ix pix;
u8_t name[SPIFFS_OBJ_NAME_LEN]; u8_t name[SPIFFS_OBJ_NAME_LEN];
#if SPIFFS_OBJ_META_LEN
u8_t meta[SPIFFS_OBJ_META_LEN];
#endif
} spiffs_stat; } spiffs_stat;
struct spiffs_dirent { struct spiffs_dirent {
@ -243,6 +311,9 @@ struct spiffs_dirent {
spiffs_obj_type type; spiffs_obj_type type;
u32_t size; u32_t size;
spiffs_page_ix pix; spiffs_page_ix pix;
#if SPIFFS_OBJ_META_LEN
u8_t meta[SPIFFS_OBJ_META_LEN];
#endif
}; };
typedef struct { typedef struct {
@ -251,8 +322,57 @@ typedef struct {
int entry; int entry;
} spiffs_DIR; } spiffs_DIR;
#if SPIFFS_IX_MAP
typedef struct {
// buffer with looked up data pixes
spiffs_page_ix *map_buf;
// precise file byte offset
u32_t offset;
// start data span index of lookup buffer
spiffs_span_ix start_spix;
// end data span index of lookup buffer
spiffs_span_ix end_spix;
} spiffs_ix_map;
#endif
// functions // functions
#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
/**
* Special function. This takes a spiffs config struct and returns the number
* of blocks this file system was formatted with. This function relies on
* that following info is set correctly in given config struct:
*
* phys_addr, log_page_size, and log_block_size.
*
* Also, hal_read_f must be set in the config struct.
*
* One must be sure of the correct page size and that the physical address is
* correct in the probed file system when calling this function. It is not
* checked if the phys_addr actually points to the start of the file system,
* so one might get a false positive if entering a phys_addr somewhere in the
* middle of the file system at block boundary. In addition, it is not checked
* if the page size is actually correct. If it is not, weird file system sizes
* will be returned.
*
* If this function detects a file system it returns the assumed file system
* size, which can be used to set the phys_size.
*
* Otherwise, it returns an error indicating why it is not regarded as a file
* system.
*
* Note: this function is not protected with SPIFFS_LOCK and SPIFFS_UNLOCK
* macros. It returns the error code directly, instead of as read by
* SPIFFS_errno.
*
* @param config essential parts of the physical and logical
* configuration of the file system.
*/
s32_t SPIFFS_probe_fs(spiffs_config *config);
#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
/** /**
* Initializes the file system dynamic parameters and mounts the filesystem. * Initializes the file system dynamic parameters and mounts the filesystem.
* If SPIFFS_USE_MAGIC is enabled the mounting may fail with SPIFFS_ERR_NOT_A_FS * If SPIFFS_USE_MAGIC is enabled the mounting may fail with SPIFFS_ERR_NOT_A_FS
@ -286,19 +406,18 @@ void SPIFFS_unmount(spiffs *fs);
* @param path the path of the new file * @param path the path of the new file
* @param mode ignored, for posix compliance * @param mode ignored, for posix compliance
*/ */
s32_t SPIFFS_creat(spiffs *fs, char *path, spiffs_mode mode); s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode);
/** /**
* Opens/creates a file. * Opens/creates a file.
* @param fs the file system struct * @param fs the file system struct
* @param path the path of the new file * @param path the path of the new file
* @param flags the flags for the open command, can be combinations of * @param flags the flags for the open command, can be combinations of
* SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, * SPIFFS_O_APPEND, SPIFFS_O_TRUNC, SPIFFS_O_CREAT, SPIFFS_O_RDONLY,
* SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT * SPIFFS_O_WRONLY, SPIFFS_O_RDWR, SPIFFS_O_DIRECT, SPIFFS_O_EXCL
* @param mode ignored, for posix compliance * @param mode ignored, for posix compliance
*/ */
spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode mode); spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode);
/** /**
* Opens a file by given dir entry. * Opens a file by given dir entry.
@ -306,7 +425,7 @@ spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode
* a normal SPIFFS_open would need to traverse the filesystem again to find * a normal SPIFFS_open would need to traverse the filesystem again to find
* the file, whilst SPIFFS_open_by_dirent already knows where the file resides. * the file, whilst SPIFFS_open_by_dirent already knows where the file resides.
* @param fs the file system struct * @param fs the file system struct
* @param path the dir entry to the file * @param e the dir entry to the file
* @param flags the flags for the open command, can be combinations of * @param flags the flags for the open command, can be combinations of
* SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY,
* SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT.
@ -315,6 +434,22 @@ spiffs_file SPIFFS_open(spiffs *fs, char *path, spiffs_flags flags, spiffs_mode
*/ */
spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode); spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode);
/**
* Opens a file by given page index.
* Optimization purposes, opens a file by directly pointing to the page
* index in the spi flash.
* If the page index does not point to a file header SPIFFS_ERR_NOT_A_FILE
* is returned.
* @param fs the file system struct
* @param page_ix the page index
* @param flags the flags for the open command, can be combinations of
* SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY,
* SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT.
* SPIFFS_CREAT will have no effect in this case.
* @param mode ignored, for posix compliance
*/
spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode);
/** /**
* Reads from given filehandle. * Reads from given filehandle.
* @param fs the file system struct * @param fs the file system struct
@ -336,13 +471,14 @@ s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len);
s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len); s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len);
/** /**
* Moves the read/write file offset * Moves the read/write file offset. Resulting offset is returned or negative if error.
* lseek(fs, fd, 0, SPIFFS_SEEK_CUR) will thus return current offset.
* @param fs the file system struct * @param fs the file system struct
* @param fh the filehandle * @param fh the filehandle
* @param offs how much/where to move the offset * @param offs how much/where to move the offset
* @param whence if SPIFFS_SEEK_SET, the file offset shall be set to offset bytes * @param whence if SPIFFS_SEEK_SET, the file offset shall be set to offset bytes
* if SPIFFS_SEEK_CUR, the file offset shall be set to its current location plus offset * if SPIFFS_SEEK_CUR, the file offset shall be set to its current location plus offset
* if SPIFFS_SEEK_END, the file offset shall be set to the size of the file plus offset * if SPIFFS_SEEK_END, the file offset shall be set to the size of the file plus offse, which should be negative
*/ */
s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence); s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence);
@ -351,7 +487,7 @@ s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence);
* @param fs the file system struct * @param fs the file system struct
* @param path the path of the file to remove * @param path the path of the file to remove
*/ */
s32_t SPIFFS_remove(spiffs *fs, char *path); s32_t SPIFFS_remove(spiffs *fs, const char *path);
/** /**
* Removes a file by filehandle * Removes a file by filehandle
@ -366,7 +502,7 @@ s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh);
* @param path the path of the file to stat * @param path the path of the file to stat
* @param s the stat struct to populate * @param s the stat struct to populate
*/ */
s32_t SPIFFS_stat(spiffs *fs, char *path, spiffs_stat *s); s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s);
/** /**
* Gets file status by filehandle * Gets file status by filehandle
@ -388,7 +524,7 @@ s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh);
* @param fs the file system struct * @param fs the file system struct
* @param fh the filehandle of the file to close * @param fh the filehandle of the file to close
*/ */
void SPIFFS_close(spiffs *fs, spiffs_file fh); s32_t SPIFFS_close(spiffs *fs, spiffs_file fh);
/** /**
* Renames a file * Renames a file
@ -396,7 +532,25 @@ void SPIFFS_close(spiffs *fs, spiffs_file fh);
* @param old path of file to rename * @param old path of file to rename
* @param newPath new path of file * @param newPath new path of file
*/ */
s32_t SPIFFS_rename(spiffs *fs, char *old, char *newPath); s32_t SPIFFS_rename(spiffs *fs, const char *old, const char *newPath);
#if SPIFFS_OBJ_META_LEN
/**
* Updates file's metadata
* @param fs the file system struct
* @param path path to the file
* @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long.
*/
s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta);
/**
* Updates file's metadata
* @param fs the file system struct
* @param fh file handle of the file
* @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long.
*/
s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta);
#endif
/** /**
* Returns last error of last file operation. * Returns last error of last file operation.
@ -419,7 +573,7 @@ void SPIFFS_clearerr(spiffs *fs);
* @param name the name of the directory * @param name the name of the directory
* @param d pointer the directory stream to be populated * @param d pointer the directory stream to be populated
*/ */
spiffs_DIR *SPIFFS_opendir(spiffs *fs, char *name, spiffs_DIR *d); spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d);
/** /**
* Closes a directory stream * Closes a directory stream
@ -441,13 +595,6 @@ struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e);
*/ */
s32_t SPIFFS_check(spiffs *fs); s32_t SPIFFS_check(spiffs *fs);
/**
* Searches for a block with only deleted entries. If found, it is erased.
* @param fs the file system struct
*/
s32_t SPIFFS_erase_deleted_block(spiffs *fs);
/** /**
* Returns number of total bytes available and number of used bytes. * Returns number of total bytes available and number of used bytes.
* This is an estimation, and depends on if there a many files with little * This is an estimation, and depends on if there a many files with little
@ -527,6 +674,115 @@ s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages);
*/ */
s32_t SPIFFS_gc(spiffs *fs, u32_t size); s32_t SPIFFS_gc(spiffs *fs, u32_t size);
/**
* Check if EOF reached.
* @param fs the file system struct
* @param fh the filehandle of the file to check
*/
s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh);
/**
* Get position in file.
* @param fs the file system struct
* @param fh the filehandle of the file to check
*/
s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh);
/**
* Registers a callback function that keeps track on operations on file
* headers. Do note, that this callback is called from within internal spiffs
* mechanisms. Any operations on the actual file system being callbacked from
* in this callback will mess things up for sure - do not do this.
* This can be used to track where files are and move around during garbage
* collection, which in turn can be used to build location tables in ram.
* Used in conjuction with SPIFFS_open_by_page this may improve performance
* when opening a lot of files.
* Must be invoked after mount.
*
* @param fs the file system struct
* @param cb_func the callback on file operations
*/
s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func);
#if SPIFFS_IX_MAP
/**
* Maps the first level index lookup to a given memory map.
* This will make reading big files faster, as the memory map will be used for
* looking up data pages instead of searching for the indices on the physical
* medium. When mapping, all affected indicies are found and the information is
* copied to the array.
* Whole file or only parts of it may be mapped. The index map will cover file
* contents from argument offset until and including arguments (offset+len).
* It is valid to map a longer range than the current file size. The map will
* then be populated when the file grows.
* On garbage collections and file data page movements, the map array will be
* automatically updated. Do not tamper with the map array, as this contains
* the references to the data pages. Modifying it from outside will corrupt any
* future readings using this file descriptor.
* The map will no longer be used when the file descriptor closed or the file
* is unmapped.
* This can be useful to get faster and more deterministic timing when reading
* large files, or when seeking and reading a lot within a file.
* @param fs the file system struct
* @param fh the file handle of the file to map
* @param map a spiffs_ix_map struct, describing the index map
* @param offset absolute file offset where to start the index map
* @param len length of the mapping in actual file bytes
* @param map_buf the array buffer for the look up data - number of required
* elements in the array can be derived from function
* SPIFFS_bytes_to_ix_map_entries given the length
*/
s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map,
u32_t offset, u32_t len, spiffs_page_ix *map_buf);
/**
* Unmaps the index lookup from this filehandle. All future readings will
* proceed as normal, requiring reading of the first level indices from
* physical media.
* The map and map buffer given in function SPIFFS_ix_map will no longer be
* referenced by spiffs.
* It is not strictly necessary to unmap a file before closing it, as closing
* a file will automatically unmap it.
* @param fs the file system struct
* @param fh the file handle of the file to unmap
*/
s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh);
/**
* Moves the offset for the index map given in function SPIFFS_ix_map. Parts or
* all of the map buffer will repopulated.
* @param fs the file system struct
* @param fh the mapped file handle of the file to remap
* @param offset new absolute file offset where to start the index map
*/
s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offs);
/**
* Utility function to get number of spiffs_page_ix entries a map buffer must
* contain on order to map given amount of file data in bytes.
* See function SPIFFS_ix_map and SPIFFS_ix_map_entries_to_bytes.
* @param fs the file system struct
* @param bytes number of file data bytes to map
* @return needed number of elements in a spiffs_page_ix array needed to
* map given amount of bytes in a file
*/
s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes);
/**
* Utility function to amount of file data bytes that can be mapped when
* mapping a file with buffer having given number of spiffs_page_ix entries.
* See function SPIFFS_ix_map and SPIFFS_bytes_to_ix_map_entries.
* @param fs the file system struct
* @param map_page_ix_entries number of entries in a spiffs_page_ix array
* @return amount of file data in bytes that can be mapped given a map
* buffer having given amount of spiffs_page_ix entries
*/
s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries);
#endif // SPIFFS_IX_MAP
#if SPIFFS_TEST_VISUALISATION #if SPIFFS_TEST_VISUALISATION
/** /**
* Prints out a visualization of the filesystem. * Prints out a visualization of the filesystem.

@ -20,12 +20,12 @@ static spiffs_cache_page *spiffs_cache_page_get(spiffs *fs, spiffs_page_ix pix)
if ((cache->cpage_use_map & (1<<i)) && if ((cache->cpage_use_map & (1<<i)) &&
(cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 &&
cp->pix == pix ) { cp->pix == pix ) {
SPIFFS_CACHE_DBG("CACHE_GET: have cache page %i for %04x\n", i, pix); //SPIFFS_CACHE_DBG("CACHE_GET: have cache page "_SPIPRIi" for "_SPIPRIpg"\n", i, pix);
cp->last_access = cache->last_access; cp->last_access = cache->last_access;
return cp; return cp;
} }
} }
//SPIFFS_CACHE_DBG("CACHE_GET: no cache for %04x\n", pix); //SPIFFS_CACHE_DBG("CACHE_GET: no cache for "_SPIPRIpg"\n", pix);
return 0; return 0;
} }
@ -39,17 +39,20 @@ static s32_t spiffs_cache_page_free(spiffs *fs, int ix, u8_t write_back) {
(cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 &&
(cp->flags & SPIFFS_CACHE_FLAG_DIRTY)) { (cp->flags & SPIFFS_CACHE_FLAG_DIRTY)) {
u8_t *mem = spiffs_get_cache_page(fs, cache, ix); u8_t *mem = spiffs_get_cache_page(fs, cache, ix);
res = fs->cfg.hal_write_f(SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem); SPIFFS_CACHE_DBG("CACHE_FREE: write cache page "_SPIPRIi" pix "_SPIPRIpg"\n", ix, cp->pix);
res = SPIFFS_HAL_WRITE(fs, SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem);
} }
cp->flags = 0; #if SPIFFS_CACHE_WR
cache->cpage_use_map &= ~(1 << ix);
if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) { if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) {
SPIFFS_CACHE_DBG("CACHE_FREE: free cache page %i objid %04x\n", ix, cp->obj_id); SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" objid "_SPIPRIid"\n", ix, cp->obj_id);
} else { } else
SPIFFS_CACHE_DBG("CACHE_FREE: free cache page %i pix %04x\n", ix, cp->pix); #endif
{
SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" pix "_SPIPRIpg"\n", ix, cp->pix);
} }
cache->cpage_use_map &= ~(1 << ix);
cp->flags = 0;
} }
return res; return res;
@ -98,7 +101,7 @@ static spiffs_cache_page *spiffs_cache_page_allocate(spiffs *fs) {
spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
cache->cpage_use_map |= (1<<i); cache->cpage_use_map |= (1<<i);
cp->last_access = cache->last_access; cp->last_access = cache->last_access;
SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page %i\n", i); //SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi"\n", i);
return cp; return cp;
} }
} }
@ -130,38 +133,50 @@ s32_t spiffs_phys_rd(
spiffs_cache_page *cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr)); spiffs_cache_page *cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr));
cache->last_access++; cache->last_access++;
if (cp) { if (cp) {
// we've already got one, you see
#if SPIFFS_CACHE_STATS #if SPIFFS_CACHE_STATS
fs->cache_hits++; fs->cache_hits++;
#endif #endif
cp->last_access = cache->last_access; cp->last_access = cache->last_access;
u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix);
_SPIFFS_MEMCPY(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len);
} else { } else {
if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) { if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) {
// for second layer lookup functions, we do not cache in order to prevent shredding // for second layer lookup functions, we do not cache in order to prevent shredding
return fs->cfg.hal_read_f( return SPIFFS_HAL_READ(fs, addr, len, dst);
addr ,
len,
dst);
} }
#if SPIFFS_CACHE_STATS #if SPIFFS_CACHE_STATS
fs->cache_misses++; fs->cache_misses++;
#endif #endif
// this operation will always free one cache page (unless all already free),
// the result code stems from the write operation of the possibly freed cache page
res = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); res = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0);
cp = spiffs_cache_page_allocate(fs); cp = spiffs_cache_page_allocate(fs);
if (cp) { if (cp) {
cp->flags = SPIFFS_CACHE_FLAG_WRTHRU; cp->flags = SPIFFS_CACHE_FLAG_WRTHRU;
cp->pix = SPIFFS_PADDR_TO_PAGE(fs, addr); cp->pix = SPIFFS_PADDR_TO_PAGE(fs, addr);
} SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi" for pix "_SPIPRIpg "\n", cp->ix, cp->pix);
s32_t res2 = fs->cfg.hal_read_f( s32_t res2 = SPIFFS_HAL_READ(fs,
addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr), addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr),
SPIFFS_CFG_LOG_PAGE_SZ(fs), SPIFFS_CFG_LOG_PAGE_SZ(fs),
spiffs_get_cache_page(fs, cache, cp->ix)); spiffs_get_cache_page(fs, cache, cp->ix));
if (res2 != SPIFFS_OK) { if (res2 != SPIFFS_OK) {
res = res2; // honor read failure before possible write failure (bad idea?)
res = res2;
}
u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix);
_SPIFFS_MEMCPY(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len);
} else {
// this will never happen, last resort for sake of symmetry
s32_t res2 = SPIFFS_HAL_READ(fs, addr, len, dst);
if (res2 != SPIFFS_OK) {
// honor read failure before possible write failure (bad idea?)
res = res2;
}
} }
} }
u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix);
memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len);
return res; return res;
} }
@ -186,24 +201,24 @@ s32_t spiffs_phys_wr(
(op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) { (op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) {
// page is being deleted, wipe from cache - unless it is a lookup page // page is being deleted, wipe from cache - unless it is a lookup page
spiffs_cache_page_free(fs, cp->ix, 0); spiffs_cache_page_free(fs, cp->ix, 0);
return fs->cfg.hal_write_f(addr, len, src); return SPIFFS_HAL_WRITE(fs, addr, len, src);
} }
u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix);
memcpy(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len); _SPIFFS_MEMCPY(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len);
cache->last_access++; cache->last_access++;
cp->last_access = cache->last_access; cp->last_access = cache->last_access;
if (cp->flags & SPIFFS_CACHE_FLAG_WRTHRU) { if (cp->flags & SPIFFS_CACHE_FLAG_WRTHRU) {
// page is being updated, no write-cache, just pass thru // page is being updated, no write-cache, just pass thru
return fs->cfg.hal_write_f(addr, len, src); return SPIFFS_HAL_WRITE(fs, addr, len, src);
} else { } else {
return SPIFFS_OK; return SPIFFS_OK;
} }
} else { } else {
// no cache page, no write cache - just write thru // no cache page, no write cache - just write thru
return fs->cfg.hal_write_f(addr, len, src); return SPIFFS_HAL_WRITE(fs, addr, len, src);
} }
} }
@ -245,6 +260,7 @@ spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd) {
cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR; cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR;
cp->obj_id = fd->obj_id; cp->obj_id = fd->obj_id;
fd->cache_page = cp; fd->cache_page = cp;
SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi" for fd "_SPIPRIfd ":"_SPIPRIid "\n", cp->ix, fd->file_nbr, fd->obj_id);
return cp; return cp;
} }
@ -288,7 +304,7 @@ void spiffs_cache_init(spiffs *fs) {
cache.cpage_use_map = 0xffffffff; cache.cpage_use_map = 0xffffffff;
cache.cpage_use_mask = cache_mask; cache.cpage_use_mask = cache_mask;
memcpy(fs->cache, &cache, sizeof(spiffs_cache)); _SPIFFS_MEMCPY(fs->cache, &cache, sizeof(spiffs_cache));
spiffs_cache *c = spiffs_get_cache(fs); spiffs_cache *c = spiffs_get_cache(fs);

@ -19,9 +19,24 @@
* Author: petera * Author: petera
*/ */
#include "spiffs.h" #include "spiffs.h"
#include "spiffs_nucleus.h" #include "spiffs_nucleus.h"
#if !SPIFFS_READ_ONLY
#if SPIFFS_HAL_CALLBACK_EXTRA
#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \
do { \
if ((_fs)->check_cb_f) (_fs)->check_cb_f((_fs), (_type), (_rep), (_arg1), (_arg2)); \
} while (0)
#else
#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \
do { \
if ((_fs)->check_cb_f) (_fs)->check_cb_f((_type), (_rep), (_arg1), (_arg2)); \
} while (0)
#endif
//--------------------------------------- //---------------------------------------
// Look up consistency // Look up consistency
@ -93,6 +108,7 @@ static s32_t spiffs_rewrite_index(spiffs *fs, spiffs_obj_id obj_id, spiffs_span_
} else { } else {
// calc entry in index // calc entry in index
entry = SPIFFS_OBJ_IX_ENTRY(fs, data_spix); entry = SPIFFS_OBJ_IX_ENTRY(fs, data_spix);
} }
// load index // load index
res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
@ -166,7 +182,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
if (((lu_obj_id == SPIFFS_OBJ_ID_DELETED) && (p_hdr->flags & SPIFFS_PH_FLAG_DELET)) || if (((lu_obj_id == SPIFFS_OBJ_ID_DELETED) && (p_hdr->flags & SPIFFS_PH_FLAG_DELET)) ||
((lu_obj_id == SPIFFS_OBJ_ID_FREE) && (p_hdr->flags & SPIFFS_PH_FLAG_USED) == 0)) { ((lu_obj_id == SPIFFS_OBJ_ID_FREE) && (p_hdr->flags & SPIFFS_PH_FLAG_USED) == 0)) {
// look up entry deleted / free but used in page header // look up entry deleted / free but used in page header
SPIFFS_CHECK_DBG("LU: pix %04x deleted/free in lu but not on page\n", cur_pix); SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" deleted/free in lu but not on page\n", cur_pix);
*reload_lu = 1; *reload_lu = 1;
delete_page = 1; delete_page = 1;
if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) { if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) {
@ -183,20 +199,20 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
// copy page to new place and re-write the object index to new place // copy page to new place and re-write the object index to new place
spiffs_page_ix new_pix; spiffs_page_ix new_pix;
res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix);
SPIFFS_CHECK_DBG("LU: FIXUP: data page not found elsewhere, rewriting %04x to new page %04x\n", cur_pix, new_pix); SPIFFS_CHECK_DBG("LU: FIXUP: data page not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
*reload_lu = 1; *reload_lu = 1;
SPIFFS_CHECK_DBG("LU: FIXUP: %04x rewritten to %04x, affected objix_pix %04x\n", cur_pix, new_pix, objix_pix); SPIFFS_CHECK_DBG("LU: FIXUP: "_SPIPRIpg" rewritten to "_SPIPRIpg", affected objix_pix "_SPIPRIpg"\n", cur_pix, new_pix, objix_pix);
res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix);
if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
// index bad also, cannot mend this file // index bad also, cannot mend this file
SPIFFS_CHECK_DBG("LU: FIXUP: index bad %i, cannot mend!\n", res); SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res);
res = spiffs_page_delete(fs, new_pix); res = spiffs_page_delete(fs, new_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0);
} else { } else {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, p_hdr->obj_id, p_hdr->span_ix); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, p_hdr->obj_id, p_hdr->span_ix);
} }
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
} }
@ -213,10 +229,10 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
// got a data page also, assume lu corruption only, rewrite to new page // got a data page also, assume lu corruption only, rewrite to new page
spiffs_page_ix new_pix; spiffs_page_ix new_pix;
res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix);
SPIFFS_CHECK_DBG("LU: FIXUP: ix page with data not found elsewhere, rewriting %04x to new page %04x\n", cur_pix, new_pix); SPIFFS_CHECK_DBG("LU: FIXUP: ix page with data not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
*reload_lu = 1; *reload_lu = 1;
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix);
} }
} else { } else {
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
@ -226,7 +242,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
if (lu_obj_id != SPIFFS_OBJ_ID_FREE && lu_obj_id != SPIFFS_OBJ_ID_DELETED) { if (lu_obj_id != SPIFFS_OBJ_ID_FREE && lu_obj_id != SPIFFS_OBJ_ID_DELETED) {
// look up entry used // look up entry used
if ((p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG) != (lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG)) { if ((p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG) != (lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG)) {
SPIFFS_CHECK_DBG("LU: pix %04x differ in obj_id lu:%04x ph:%04x\n", cur_pix, lu_obj_id, p_hdr->obj_id); SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" differ in obj_id lu:"_SPIPRIid" ph:"_SPIPRIid"\n", cur_pix, lu_obj_id, p_hdr->obj_id);
delete_page = 1; delete_page = 1;
if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0 || if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0 ||
(p_hdr->flags & SPIFFS_PH_FLAG_FINAL) || (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) ||
@ -249,12 +265,12 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix);
if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
// index bad also, cannot mend this file // index bad also, cannot mend this file
SPIFFS_CHECK_DBG("LU: FIXUP: index bad %i, cannot mend!\n", res); SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res);
res = spiffs_page_delete(fs, new_pix); res = spiffs_page_delete(fs, new_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id);
*reload_lu = 1; *reload_lu = 1;
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0);
} }
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
} }
@ -305,8 +321,8 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
// rewrite as obj_id_ph // rewrite as obj_id_ph
new_ph.obj_id = p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG; new_ph.obj_id = p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG;
res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix);
SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page %04x as %04x to pix %04x\n", cur_pix, new_ph.obj_id, new_pix); SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid" to pix "_SPIPRIpg"\n", cur_pix, new_ph.obj_id, new_pix);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
*reload_lu = 1; *reload_lu = 1;
} else if ((objix_pix_ph && data_pix_ph && data_pix_lu && objix_pix_lu == 0) || } else if ((objix_pix_ph && data_pix_ph && data_pix_lu && objix_pix_lu == 0) ||
@ -314,8 +330,8 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
// got a data page for look up obj id // got a data page for look up obj id
// rewrite as obj_id_lu // rewrite as obj_id_lu
new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG; new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG;
SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page %04x as %04x\n", cur_pix, new_ph.obj_id); SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid"\n", cur_pix, new_ph.obj_id);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix);
res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
*reload_lu = 1; *reload_lu = 1;
@ -328,7 +344,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
} }
} else if (((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX)) || } else if (((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX)) ||
((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0 && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) == 0)) { ((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0 && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) == 0)) {
SPIFFS_CHECK_DBG("LU: %04x lu/page index marking differ\n", cur_pix); SPIFFS_CHECK_DBG("LU: "_SPIPRIpg" lu/page index marking differ\n", cur_pix);
spiffs_page_ix data_pix, objix_pix_d; spiffs_page_ix data_pix, objix_pix_d;
// see if other data page exists for given obj id and span index // see if other data page exists for given obj id and span index
res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &data_pix); res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &data_pix);
@ -353,7 +369,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
// if only data page exists, make this page index // if only data page exists, make this page index
if (data_pix && objix_pix_d == 0) { if (data_pix && objix_pix_d == 0) {
SPIFFS_CHECK_DBG("LU: FIXUP: other data page exists, make this index\n"); SPIFFS_CHECK_DBG("LU: FIXUP: other data page exists, make this index\n");
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, lu_obj_id, p_hdr->span_ix); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, lu_obj_id, p_hdr->span_ix);
spiffs_page_header new_ph; spiffs_page_header new_ph;
spiffs_page_ix new_pix; spiffs_page_ix new_pix;
new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX);
@ -369,7 +385,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
// if only index exists, make data page // if only index exists, make data page
if (data_pix == 0 && objix_pix_d) { if (data_pix == 0 && objix_pix_d) {
SPIFFS_CHECK_DBG("LU: FIXUP: other index page exists, make this data\n"); SPIFFS_CHECK_DBG("LU: FIXUP: other index page exists, make this data\n");
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, lu_obj_id, p_hdr->span_ix); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, lu_obj_id, p_hdr->span_ix);
spiffs_page_header new_ph; spiffs_page_header new_ph;
spiffs_page_ix new_pix; spiffs_page_ix new_pix;
new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL); new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL);
@ -386,10 +402,10 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
} }
} }
else if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0) { else if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0) {
SPIFFS_CHECK_DBG("LU: pix %04x busy in lu but deleted on page\n", cur_pix); SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy in lu but deleted on page\n", cur_pix);
delete_page = 1; delete_page = 1;
} else if ((p_hdr->flags & SPIFFS_PH_FLAG_FINAL)) { } else if ((p_hdr->flags & SPIFFS_PH_FLAG_FINAL)) {
SPIFFS_CHECK_DBG("LU: pix %04x busy but not final\n", cur_pix); SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy but not final\n", cur_pix);
// page can be removed if not referenced by object index // page can be removed if not referenced by object index
*reload_lu = 1; *reload_lu = 1;
res = spiffs_object_get_data_page_index_reference(fs, lu_obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); res = spiffs_object_get_data_page_index_reference(fs, lu_obj_id, p_hdr->span_ix, &ref_pix, &objix_pix);
@ -406,7 +422,7 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
// page referenced by object index but not final // page referenced by object index but not final
// just finalize // just finalize
SPIFFS_CHECK_DBG("LU: FIXUP: unfinalized page is referred, finalizing\n"); SPIFFS_CHECK_DBG("LU: FIXUP: unfinalized page is referred, finalizing\n");
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix);
u8_t flags = 0xff & ~SPIFFS_PH_FLAG_FINAL; u8_t flags = 0xff & ~SPIFFS_PH_FLAG_FINAL;
res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + offsetof(spiffs_page_header, flags), 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + offsetof(spiffs_page_header, flags),
@ -417,8 +433,8 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
} }
if (delete_page) { if (delete_page) {
SPIFFS_CHECK_DBG("LU: FIXUP: deleting page %04x\n", cur_pix); SPIFFS_CHECK_DBG("LU: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0);
res = spiffs_page_delete(fs, cur_pix); res = spiffs_page_delete(fs, cur_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
} }
@ -427,14 +443,14 @@ static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, s
} }
static s32_t spiffs_lookup_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, int cur_entry, static s32_t spiffs_lookup_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, int cur_entry,
u32_t user_data, void *user_p) { const void *user_const_p, void *user_var_p) {
(void)user_data; (void)user_const_p;
(void)user_p; (void)user_var_p;
s32_t res = SPIFFS_OK; s32_t res = SPIFFS_OK;
spiffs_page_header p_hdr; spiffs_page_header p_hdr;
spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry); spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS,
(cur_block * 256)/fs->block_count, 0); (cur_block * 256)/fs->block_count, 0);
// load header // load header
@ -460,7 +476,7 @@ s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t check_all_objects) {
(void)check_all_objects; (void)check_all_objects;
s32_t res = SPIFFS_OK; s32_t res = SPIFFS_OK;
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 0, 0); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 0, 0);
res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_lookup_check_v, 0, 0, 0, 0); res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_lookup_check_v, 0, 0, 0, 0);
@ -469,10 +485,10 @@ s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t check_all_objects) {
} }
if (res != SPIFFS_OK) { if (res != SPIFFS_OK) {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_ERROR, res, 0); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_ERROR, res, 0);
} }
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0); CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0);
return res; return res;
} }
@ -506,14 +522,17 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
spiffs_block_ix cur_block = 0; spiffs_block_ix cur_block = 0;
// build consistency bitmap for id range traversing all blocks // build consistency bitmap for id range traversing all blocks
while (!restart && cur_block < fs->block_count) { while (!restart && cur_block < fs->block_count) {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS,
(pix_offset*256)/(SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) + (pix_offset*256)/(SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) +
((((cur_block * pages_per_scan * 256)/ (SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count))) / fs->block_count), ((((cur_block * pages_per_scan * 256)/ (SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count))) / fs->block_count),
0); 0);
// traverse each page except for lookup pages // traverse each page except for lookup pages
spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_PAGES(fs) + SPIFFS_PAGES_PER_BLOCK(fs) * cur_block; spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_PAGES(fs) + SPIFFS_PAGES_PER_BLOCK(fs) * cur_block;
while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block+1)) { while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block+1)) {
//if ((cur_pix & 0xff) == 0)
// SPIFFS_CHECK_DBG("PA: processing pix "_SPIPRIpg", block "_SPIPRIbl" of pix "_SPIPRIpg", block "_SPIPRIbl"\n",
// cur_pix, cur_block, SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count, fs->block_count);
// read header // read header
spiffs_page_header p_hdr; spiffs_page_header p_hdr;
res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
@ -570,7 +589,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
|| (rpix_within_range && SPIFFS_IS_LOOKUP_PAGE(fs, rpix))) { || (rpix_within_range && SPIFFS_IS_LOOKUP_PAGE(fs, rpix))) {
// bad reference // bad reference
SPIFFS_CHECK_DBG("PA: pix %04x bad pix / LU referenced from page %04x\n", SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg"x bad pix / LU referenced from page "_SPIPRIpg"\n",
rpix, cur_pix); rpix, cur_pix);
// check for data page elsewhere // check for data page elsewhere
spiffs_page_ix data_pix; spiffs_page_ix data_pix;
@ -589,20 +608,20 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
new_ph.span_ix = data_spix_offset + i; new_ph.span_ix = data_spix_offset + i;
res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &data_pix); res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &data_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
SPIFFS_CHECK_DBG("PA: FIXUP: found no existing data page, created new @ %04x\n", data_pix); SPIFFS_CHECK_DBG("PA: FIXUP: found no existing data page, created new @ "_SPIPRIpg"\n", data_pix);
} }
// remap index // remap index
SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix %04x\n", cur_pix); SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix "_SPIPRIpg"\n", cur_pix);
res = spiffs_rewrite_index(fs, objix_p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, res = spiffs_rewrite_index(fs, objix_p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG,
data_spix_offset + i, data_pix, cur_pix); data_spix_offset + i, data_pix, cur_pix);
if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
// index bad also, cannot mend this file // index bad also, cannot mend this file
SPIFFS_CHECK_DBG("PA: FIXUP: index bad %i, cannot mend - delete object\n", res); SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend - delete object\n", res);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, objix_p_hdr->obj_id, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, objix_p_hdr->obj_id, 0);
// delete file // delete file
res = spiffs_page_delete(fs, cur_pix); res = spiffs_page_delete(fs, cur_pix);
} else { } else {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, objix_p_hdr->obj_id, objix_p_hdr->span_ix); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, objix_p_hdr->obj_id, objix_p_hdr->span_ix);
} }
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
restart = 1; restart = 1;
@ -621,7 +640,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
rp_hdr.span_ix != data_spix_offset + i || rp_hdr.span_ix != data_spix_offset + i ||
(rp_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) != (rp_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) !=
(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX)) { (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX)) {
SPIFFS_CHECK_DBG("PA: pix %04x has inconsistent page header ix id/span:%04x/%04x, ref id/span:%04x/%04x flags:%02x\n", SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" has inconsistent page header ix id/span:"_SPIPRIid"/"_SPIPRIsp", ref id/span:"_SPIPRIid"/"_SPIPRIsp" flags:"_SPIPRIfl"\n",
rpix, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, data_spix_offset + i, rpix, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, data_spix_offset + i,
rp_hdr.obj_id, rp_hdr.span_ix, rp_hdr.flags); rp_hdr.obj_id, rp_hdr.span_ix, rp_hdr.flags);
// try finding correct page // try finding correct page
@ -635,23 +654,23 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
if (data_pix == 0) { if (data_pix == 0) {
// not found, this index is badly borked // not found, this index is badly borked
SPIFFS_CHECK_DBG("PA: FIXUP: index bad, delete object id %04x\n", p_hdr.obj_id); SPIFFS_CHECK_DBG("PA: FIXUP: index bad, delete object id "_SPIPRIid"\n", p_hdr.obj_id);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0);
res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
break; break;
} else { } else {
// found it, so rewrite index // found it, so rewrite index
SPIFFS_CHECK_DBG("PA: FIXUP: found correct data pix %04x, rewrite ix pix %04x id %04x\n", SPIFFS_CHECK_DBG("PA: FIXUP: found correct data pix "_SPIPRIpg", rewrite ix pix "_SPIPRIpg" id "_SPIPRIid"\n",
data_pix, cur_pix, p_hdr.obj_id); data_pix, cur_pix, p_hdr.obj_id);
res = spiffs_rewrite_index(fs, p_hdr.obj_id, data_spix_offset + i, data_pix, cur_pix); res = spiffs_rewrite_index(fs, p_hdr.obj_id, data_spix_offset + i, data_pix, cur_pix);
if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
// index bad also, cannot mend this file // index bad also, cannot mend this file
SPIFFS_CHECK_DBG("PA: FIXUP: index bad %i, cannot mend!\n", res); SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0);
res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
} else { } else {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix);
} }
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
restart = 1; restart = 1;
@ -662,14 +681,14 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
const u32_t rpix_byte_ix = (rpix - pix_offset) / (8/bits); const u32_t rpix_byte_ix = (rpix - pix_offset) / (8/bits);
const u8_t rpix_bit_ix = (rpix & ((8/bits)-1)) * bits; const u8_t rpix_bit_ix = (rpix & ((8/bits)-1)) * bits;
if (fs->work[rpix_byte_ix] & (1<<(rpix_bit_ix + 1))) { if (fs->work[rpix_byte_ix] & (1<<(rpix_bit_ix + 1))) {
SPIFFS_CHECK_DBG("PA: pix %04x multiple referenced from page %04x\n", SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" multiple referenced from page "_SPIPRIpg"\n",
rpix, cur_pix); rpix, cur_pix);
// Here, we should have fixed all broken references - getting this means there // Here, we should have fixed all broken references - getting this means there
// must be multiple files with same object id. Only solution is to delete // must be multiple files with same object id. Only solution is to delete
// the object which is referring to this page // the object which is referring to this page
SPIFFS_CHECK_DBG("PA: FIXUP: removing object %04x and page %04x\n", SPIFFS_CHECK_DBG("PA: FIXUP: removing object "_SPIPRIid" and page "_SPIPRIpg"\n",
p_hdr.obj_id, cur_pix); p_hdr.obj_id, cur_pix);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0);
res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
// extra precaution, delete this page also // extra precaution, delete this page also
@ -706,7 +725,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
if (bitmask == 0x1) { if (bitmask == 0x1) {
// 001 // 001
SPIFFS_CHECK_DBG("PA: pix %04x USED, UNREFERENCED, not index\n", cur_pix); SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, UNREFERENCED, not index\n", cur_pix);
u8_t rewrite_ix_to_this = 0; u8_t rewrite_ix_to_this = 0;
u8_t delete_page = 0; u8_t delete_page = 0;
@ -722,7 +741,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
if (((rpix == (spiffs_page_ix)-1 || rpix > SPIFFS_MAX_PAGES(fs)) || (SPIFFS_IS_LOOKUP_PAGE(fs, rpix)))) { if (((rpix == (spiffs_page_ix)-1 || rpix > SPIFFS_MAX_PAGES(fs)) || (SPIFFS_IS_LOOKUP_PAGE(fs, rpix)))) {
// pointing to a bad page altogether, rewrite index to this // pointing to a bad page altogether, rewrite index to this
rewrite_ix_to_this = 1; rewrite_ix_to_this = 1;
SPIFFS_CHECK_DBG("PA: corresponding ref is bad: %04x, rewrite to this %04x\n", rpix, cur_pix); SPIFFS_CHECK_DBG("PA: corresponding ref is bad: "_SPIPRIpg", rewrite to this "_SPIPRIpg"\n", rpix, cur_pix);
} else { } else {
// pointing to something else, check what // pointing to something else, check what
spiffs_page_header rp_hdr; spiffs_page_header rp_hdr;
@ -733,12 +752,12 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
((rp_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL)) == ((rp_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL)) ==
(SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET))) { (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET))) {
// pointing to something else valid, just delete this page then // pointing to something else valid, just delete this page then
SPIFFS_CHECK_DBG("PA: corresponding ref is good but different: %04x, delete this %04x\n", rpix, cur_pix); SPIFFS_CHECK_DBG("PA: corresponding ref is good but different: "_SPIPRIpg", delete this "_SPIPRIpg"\n", rpix, cur_pix);
delete_page = 1; delete_page = 1;
} else { } else {
// pointing to something weird, update index to point to this page instead // pointing to something weird, update index to point to this page instead
if (rpix != cur_pix) { if (rpix != cur_pix) {
SPIFFS_CHECK_DBG("PA: corresponding ref is weird: %04x %s%s%s%s, rewrite this %04x\n", rpix, SPIFFS_CHECK_DBG("PA: corresponding ref is weird: "_SPIPRIpg" %s%s%s%s, rewrite this "_SPIPRIpg"\n", rpix,
(rp_hdr.flags & SPIFFS_PH_FLAG_INDEX) ? "" : "INDEX ", (rp_hdr.flags & SPIFFS_PH_FLAG_INDEX) ? "" : "INDEX ",
(rp_hdr.flags & SPIFFS_PH_FLAG_DELET) ? "" : "DELETED ", (rp_hdr.flags & SPIFFS_PH_FLAG_DELET) ? "" : "DELETED ",
(rp_hdr.flags & SPIFFS_PH_FLAG_USED) ? "NOTUSED " : "", (rp_hdr.flags & SPIFFS_PH_FLAG_USED) ? "NOTUSED " : "",
@ -751,32 +770,32 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
} }
} }
} else if (res == SPIFFS_ERR_NOT_FOUND) { } else if (res == SPIFFS_ERR_NOT_FOUND) {
SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete %04x\n", cur_pix); SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete "_SPIPRIpg"\n", cur_pix);
delete_page = 1; delete_page = 1;
res = SPIFFS_OK; res = SPIFFS_OK;
} }
if (rewrite_ix_to_this) { if (rewrite_ix_to_this) {
// if pointing to invalid page, redirect index to this page // if pointing to invalid page, redirect index to this page
SPIFFS_CHECK_DBG("PA: FIXUP: rewrite index id %04x data spix %04x to point to this pix: %04x\n", SPIFFS_CHECK_DBG("PA: FIXUP: rewrite index id "_SPIPRIid" data spix "_SPIPRIsp" to point to this pix: "_SPIPRIpg"\n",
p_hdr.obj_id, p_hdr.span_ix, cur_pix); p_hdr.obj_id, p_hdr.span_ix, cur_pix);
res = spiffs_rewrite_index(fs, p_hdr.obj_id, p_hdr.span_ix, cur_pix, objix_pix); res = spiffs_rewrite_index(fs, p_hdr.obj_id, p_hdr.span_ix, cur_pix, objix_pix);
if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
// index bad also, cannot mend this file // index bad also, cannot mend this file
SPIFFS_CHECK_DBG("PA: FIXUP: index bad %i, cannot mend!\n", res); SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0);
res = spiffs_page_delete(fs, cur_pix); res = spiffs_page_delete(fs, cur_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
} else { } else {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix);
} }
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
restart = 1; restart = 1;
continue; continue;
} else if (delete_page) { } else if (delete_page) {
SPIFFS_CHECK_DBG("PA: FIXUP: deleting page %04x\n", cur_pix); SPIFFS_CHECK_DBG("PA: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0);
res = spiffs_page_delete(fs, cur_pix); res = spiffs_page_delete(fs, cur_pix);
} }
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
@ -784,7 +803,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
if (bitmask == 0x2) { if (bitmask == 0x2) {
// 010 // 010
SPIFFS_CHECK_DBG("PA: pix %04x FREE, REFERENCED, not index\n", cur_pix); SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, not index\n", cur_pix);
// no op, this should be taken care of when checking valid references // no op, this should be taken care of when checking valid references
} }
@ -794,7 +813,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
if (bitmask == 0x4) { if (bitmask == 0x4) {
// 100 // 100
SPIFFS_CHECK_DBG("PA: pix %04x FREE, unreferenced, INDEX\n", cur_pix); SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, unreferenced, INDEX\n", cur_pix);
// this should never happen, major fubar // this should never happen, major fubar
} }
@ -804,20 +823,22 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
if (bitmask == 0x6) { if (bitmask == 0x6) {
// 110 // 110
SPIFFS_CHECK_DBG("PA: pix %04x FREE, REFERENCED, INDEX\n", cur_pix); SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, INDEX\n", cur_pix);
// no op, this should be taken care of when checking valid references // no op, this should be taken care of when checking valid references
} }
if (bitmask == 0x7) { if (bitmask == 0x7) {
// 111 // 111
SPIFFS_CHECK_DBG("PA: pix %04x USED, REFERENCED, INDEX\n", cur_pix); SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, REFERENCED, INDEX\n", cur_pix);
// no op, this should be taken care of when checking valid references // no op, this should be taken care of when checking valid references
} }
} }
} }
} }
SPIFFS_CHECK_DBG("PA: processed "_SPIPRIpg", restart "_SPIPRIi"\n", pix_offset, restart);
// next page range // next page range
if (!restart) { if (!restart) {
pix_offset += pages_per_scan; pix_offset += pages_per_scan;
@ -828,12 +849,12 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
// Checks consistency amongst all pages and fixes irregularities // Checks consistency amongst all pages and fixes irregularities
s32_t spiffs_page_consistency_check(spiffs *fs) { s32_t spiffs_page_consistency_check(spiffs *fs) {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 0, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 0, 0);
s32_t res = spiffs_page_consistency_check_i(fs); s32_t res = spiffs_page_consistency_check_i(fs);
if (res != SPIFFS_OK) { if (res != SPIFFS_OK) {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_ERROR, res, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_ERROR, res, 0);
} }
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0); CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0);
return res; return res;
} }
@ -855,14 +876,14 @@ static int spiffs_object_index_search(spiffs *fs, spiffs_obj_id obj_id) {
} }
static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block,
int cur_entry, u32_t user_data, void *user_p) { int cur_entry, const void *user_const_p, void *user_var_p) {
(void)user_data; (void)user_const_p;
s32_t res_c = SPIFFS_VIS_COUNTINUE; s32_t res_c = SPIFFS_VIS_COUNTINUE;
s32_t res = SPIFFS_OK; s32_t res = SPIFFS_OK;
u32_t *log_ix = (u32_t *)user_p; u32_t *log_ix = (u32_t*)user_var_p;
spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work; spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work;
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS,
(cur_block * 256)/fs->block_count, 0); (cur_block * 256)/fs->block_count, 0);
if (obj_id != SPIFFS_OBJ_ID_FREE && obj_id != SPIFFS_OBJ_ID_DELETED && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { if (obj_id != SPIFFS_OBJ_ID_FREE && obj_id != SPIFFS_OBJ_ID_DELETED && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) {
@ -877,9 +898,9 @@ static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id o
if (p_hdr.span_ix == 0 && if (p_hdr.span_ix == 0 &&
(p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) == (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) ==
(SPIFFS_PH_FLAG_DELET)) { (SPIFFS_PH_FLAG_DELET)) {
SPIFFS_CHECK_DBG("IX: pix %04x, obj id:%04x spix:%04x header not fully deleted - deleting\n", SPIFFS_CHECK_DBG("IX: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" header not fully deleted - deleting\n",
cur_pix, obj_id, p_hdr.span_ix); cur_pix, obj_id, p_hdr.span_ix);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_PAGE, cur_pix, obj_id); CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_PAGE, cur_pix, obj_id);
res = spiffs_page_delete(fs, cur_pix); res = spiffs_page_delete(fs, cur_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
return res_c; return res_c;
@ -933,9 +954,9 @@ static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id o
} }
if (delete) { if (delete) {
SPIFFS_CHECK_DBG("IX: FIXUP: pix %04x, obj id:%04x spix:%04x is orphan index - deleting\n", SPIFFS_CHECK_DBG("IX: FIXUP: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" is orphan index - deleting\n",
cur_pix, obj_id, p_hdr.span_ix); cur_pix, obj_id, p_hdr.span_ix);
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, cur_pix, obj_id); CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, cur_pix, obj_id);
res = spiffs_page_delete(fs, cur_pix); res = spiffs_page_delete(fs, cur_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
} }
@ -958,16 +979,17 @@ s32_t spiffs_object_index_consistency_check(spiffs *fs) {
// a reachable/unreachable object id. // a reachable/unreachable object id.
memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs));
u32_t obj_id_log_ix = 0; u32_t obj_id_log_ix = 0;
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 0, 0); CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 0, 0);
res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_object_index_consistency_check_v, 0, &obj_id_log_ix, res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_object_index_consistency_check_v, 0, &obj_id_log_ix,
0, 0); 0, 0);
if (res == SPIFFS_VIS_END) { if (res == SPIFFS_VIS_END) {
res = SPIFFS_OK; res = SPIFFS_OK;
} }
if (res != SPIFFS_OK) { if (res != SPIFFS_OK) {
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_ERROR, res, 0); CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_ERROR, res, 0);
} }
if (fs->check_cb_f) fs->check_cb_f(SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0); CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0);
return res; return res;
} }
#endif // !SPIFFS_READ_ONLY

@ -1,6 +1,8 @@
#include "spiffs.h" #include "spiffs.h"
#include "spiffs_nucleus.h" #include "spiffs_nucleus.h"
#if !SPIFFS_READ_ONLY
// Erases a logical block and updates the erase counter. // Erases a logical block and updates the erase counter.
// If cache is enabled, all pages that might be cached in this block // If cache is enabled, all pages that might be cached in this block
// is dropped. // is dropped.
@ -9,7 +11,7 @@ static s32_t spiffs_gc_erase_block(
spiffs_block_ix bix) { spiffs_block_ix bix) {
s32_t res; s32_t res;
SPIFFS_GC_DBG("gc: erase block %i\n", bix); SPIFFS_GC_DBG("gc: erase block "_SPIPRIbl"\n", bix);
res = spiffs_erase_block(fs, bix); res = spiffs_erase_block(fs, bix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
@ -36,7 +38,7 @@ s32_t spiffs_gc_quick(
int cur_entry = 0; int cur_entry = 0;
spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
SPIFFS_GC_DBG("gc_quick: running\n", cur_block); SPIFFS_GC_DBG("gc_quick: running\n");
#if SPIFFS_GC_STATS #if SPIFFS_GC_STATS
fs->stats_gc_runs++; fs->stats_gc_runs++;
#endif #endif
@ -120,19 +122,19 @@ s32_t spiffs_gc_check(
u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); 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) { // if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) {
// SPIFFS_GC_DBG("gc: full freeblk:%i needed:%i free:%i dele:%i\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); // SPIFFS_GC_DBG("gc: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted);
// return SPIFFS_ERR_FULL; // return SPIFFS_ERR_FULL;
// } // }
if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) { if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) {
SPIFFS_GC_DBG("gc_check: full freeblk:%i needed:%i free:%i dele:%i\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); SPIFFS_GC_DBG("gc_check: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted);
return SPIFFS_ERR_FULL; return SPIFFS_ERR_FULL;
} }
do { do {
SPIFFS_GC_DBG("\ngc_check #%i: run gc free_blocks:%i pfree:%i pallo:%i pdele:%i [%i] len:%i of %i\n", SPIFFS_GC_DBG("\ngc_check #"_SPIPRIi": run gc free_blocks:"_SPIPRIi" pfree:"_SPIPRIi" pallo:"_SPIPRIi" pdele:"_SPIPRIi" ["_SPIPRIi"] len:"_SPIPRIi" of "_SPIPRIi"\n",
tries, tries,
fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted), fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted),
len, free_pages*SPIFFS_DATA_PAGE_SIZE(fs)); len, (u32_t)(free_pages*SPIFFS_DATA_PAGE_SIZE(fs)));
spiffs_block_ix *cands; spiffs_block_ix *cands;
int count; int count;
@ -150,13 +152,13 @@ s32_t spiffs_gc_check(
#endif #endif
cand = cands[0]; cand = cands[0];
fs->cleaning = 1; fs->cleaning = 1;
//printf("gcing: cleaning block %i\n", cand); //SPIFFS_GC_DBG("gcing: cleaning block "_SPIPRIi"\n", cand);
res = spiffs_gc_clean(fs, cand); res = spiffs_gc_clean(fs, cand);
fs->cleaning = 0; fs->cleaning = 0;
if (res < 0) { if (res < 0) {
SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res);
} else { } else {
SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res);
} }
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
@ -186,7 +188,7 @@ s32_t spiffs_gc_check(
res = SPIFFS_ERR_FULL; res = SPIFFS_ERR_FULL;
} }
SPIFFS_GC_DBG("gc_check: finished, %i dirty, blocks %i free, %i pages free, %i tries, res %i\n", SPIFFS_GC_DBG("gc_check: finished, "_SPIPRIi" dirty, blocks "_SPIPRIi" free, "_SPIPRIi" pages free, "_SPIPRIi" tries, res "_SPIPRIi"\n",
fs->stats_p_allocated + fs->stats_p_deleted, fs->stats_p_allocated + fs->stats_p_deleted,
fs->free_blocks, free_pages, tries, res); fs->free_blocks, free_pages, tries, res);
@ -224,7 +226,7 @@ s32_t spiffs_gc_erase_page_stats(
} // per entry } // per entry
obj_lookup_page++; obj_lookup_page++;
} // per object lookup page } // per object lookup page
SPIFFS_GC_DBG("gc_check: wipe pallo:%i pdele:%i\n", allo, dele); SPIFFS_GC_DBG("gc_check: wipe pallo:"_SPIPRIi" pdele:"_SPIPRIi"\n", allo, dele);
fs->stats_p_allocated -= allo; fs->stats_p_allocated -= allo;
fs->stats_p_deleted -= dele; fs->stats_p_deleted -= dele;
return res; return res;
@ -249,10 +251,12 @@ s32_t spiffs_gc_find_candidate(
memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs));
// divide up work area into block indices and scores // divide up work area into block indices and scores
// todo alignment?
spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work; spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work;
s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix)); s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix));
// align cand_scores on s32_t boundary
cand_scores = (s32_t*)(((intptr_t)cand_scores + sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1));
*block_candidates = cand_blocks; *block_candidates = cand_blocks;
int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
@ -290,7 +294,7 @@ s32_t spiffs_gc_find_candidate(
// calculate score and insert into candidate table // calculate score and insert into candidate table
// stoneage sort, but probably not so many blocks // stoneage sort, but probably not so many blocks
if (res == SPIFFS_OK && deleted_pages_in_block > 0) { if (res == SPIFFS_OK /*&& deleted_pages_in_block > 0*/) {
// read erase count // read erase count
spiffs_obj_id erase_count; spiffs_obj_id erase_count;
res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0,
@ -310,7 +314,7 @@ s32_t spiffs_gc_find_candidate(
used_pages_in_block * SPIFFS_GC_HEUR_W_USED + used_pages_in_block * SPIFFS_GC_HEUR_W_USED +
erase_age * (fs_crammed ? 0 : SPIFFS_GC_HEUR_W_ERASE_AGE); erase_age * (fs_crammed ? 0 : SPIFFS_GC_HEUR_W_ERASE_AGE);
int cand_ix = 0; int cand_ix = 0;
SPIFFS_GC_DBG("gc_check: bix:%i del:%i use:%i score:%i\n", cur_block, deleted_pages_in_block, used_pages_in_block, score); SPIFFS_GC_DBG("gc_check: bix:"_SPIPRIbl" del:"_SPIPRIi" use:"_SPIPRIi" score:"_SPIPRIi"\n", cur_block, deleted_pages_in_block, used_pages_in_block, score);
while (cand_ix < max_candidates) { while (cand_ix < max_candidates) {
if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) { if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) {
cand_blocks[cand_ix] = cur_block; cand_blocks[cand_ix] = cur_block;
@ -352,6 +356,7 @@ typedef struct {
spiffs_obj_id cur_obj_id; spiffs_obj_id cur_obj_id;
spiffs_span_ix cur_objix_spix; spiffs_span_ix cur_objix_spix;
spiffs_page_ix cur_objix_pix; spiffs_page_ix cur_objix_pix;
spiffs_page_ix cur_data_pix;
int stored_scan_entry_index; int stored_scan_entry_index;
u8_t obj_id_found; u8_t obj_id_found;
} spiffs_gc; } spiffs_gc;
@ -371,15 +376,16 @@ typedef struct {
// //
s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
s32_t res = SPIFFS_OK; s32_t res = SPIFFS_OK;
int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); const int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
// this is the global localizer being pushed and popped
int cur_entry = 0; int cur_entry = 0;
spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
spiffs_gc gc; spiffs_gc gc; // our stack frame/state
spiffs_page_ix cur_pix = 0; spiffs_page_ix cur_pix = 0;
spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work;
spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work;
SPIFFS_GC_DBG("gc_clean: cleaning block %i\n", bix); SPIFFS_GC_DBG("gc_clean: cleaning block "_SPIPRIbl"\n", bix);
memset(&gc, 0, sizeof(spiffs_gc)); memset(&gc, 0, sizeof(spiffs_gc));
gc.state = FIND_OBJ_DATA; gc.state = FIND_OBJ_DATA;
@ -388,12 +394,12 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
// move free cursor to next block, cannot use free pages from the block we want to clean // move free cursor to next block, cannot use free pages from the block we want to clean
fs->free_cursor_block_ix = (bix+1)%fs->block_count; fs->free_cursor_block_ix = (bix+1)%fs->block_count;
fs->free_cursor_obj_lu_entry = 0; fs->free_cursor_obj_lu_entry = 0;
SPIFFS_GC_DBG("gc_clean: move free cursor to block %i\n", fs->free_cursor_block_ix); SPIFFS_GC_DBG("gc_clean: move free cursor to block "_SPIPRIbl"\n", fs->free_cursor_block_ix);
} }
while (res == SPIFFS_OK && gc.state != FINISHED) { while (res == SPIFFS_OK && gc.state != FINISHED) {
SPIFFS_GC_DBG("gc_clean: state = %i entry:%i\n", gc.state, cur_entry); SPIFFS_GC_DBG("gc_clean: state = "_SPIPRIi" entry:"_SPIPRIi"\n", gc.state, cur_entry);
gc.obj_id_found = 0; gc.obj_id_found = 0; // reset (to no found data page)
// scan through lookup pages // scan through lookup pages
int obj_lookup_page = cur_entry / entries_per_page; int obj_lookup_page = cur_entry / entries_per_page;
@ -404,7 +410,7 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page),
SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
// check each entry // check each object lookup entry
while (scan && res == SPIFFS_OK && while (scan && res == SPIFFS_OK &&
cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) {
spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
@ -413,21 +419,26 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
// act upon object id depending on gc state // act upon object id depending on gc state
switch (gc.state) { switch (gc.state) {
case FIND_OBJ_DATA: case FIND_OBJ_DATA:
// find a data page
if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE &&
((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) { ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) {
SPIFFS_GC_DBG("gc_clean: FIND_DATA state:%i - found obj id %04x\n", gc.state, obj_id); // found a data page, stop scanning and handle in switch case below
SPIFFS_GC_DBG("gc_clean: FIND_DATA state:"_SPIPRIi" - found obj id "_SPIPRIid"\n", gc.state, obj_id);
gc.obj_id_found = 1; gc.obj_id_found = 1;
gc.cur_obj_id = obj_id; gc.cur_obj_id = obj_id;
gc.cur_data_pix = cur_pix;
scan = 0; scan = 0;
} }
break; break;
case MOVE_OBJ_DATA: case MOVE_OBJ_DATA:
// evacuate found data pages for corresponding object index we have in memory,
// update memory representation
if (obj_id == gc.cur_obj_id) { if (obj_id == gc.cur_obj_id) {
spiffs_page_header p_hdr; spiffs_page_header p_hdr;
res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page %04x:%04x @ %04x\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page "_SPIPRIid":"_SPIPRIsp" @ "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix);
if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) { if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) {
SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n"); SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n");
} else { } else {
@ -435,7 +446,7 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) {
// move page // move page
res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix); res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix);
SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix %04x:%04x page %04x to %04x\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
// move wipes obj_lu, reload it // move wipes obj_lu, reload it
res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
@ -443,8 +454,10 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
} else { } else {
// page is deleted but not deleted in lookup, scrap it // page is deleted but not deleted in lookup, scrap it -
SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix %04x:%04x page %04x\n", obj_id, p_hdr.span_ix, cur_pix); // might seem unnecessary as we will erase this block, but
// we might get aborted
SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix);
res = spiffs_page_delete(fs, cur_pix); res = spiffs_page_delete(fs, cur_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
new_data_pix = SPIFFS_OBJ_ID_FREE; new_data_pix = SPIFFS_OBJ_ID_FREE;
@ -453,16 +466,17 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
if (gc.cur_objix_spix == 0) { if (gc.cur_objix_spix == 0) {
// update object index header page // update object index header page
((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix; ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix;
SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page %04x to objix_hdr entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix));
} else { } else {
// update object index page // update object index page
((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix; ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix;
SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page %04x to objix entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix));
} }
} }
} }
break; break;
case MOVE_OBJ_IX: case MOVE_OBJ_IX:
// find and evacuate object index pages
if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE &&
(obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) {
// found an index object id // found an index object id
@ -475,20 +489,24 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) {
// move page // move page
res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix); res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix);
SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix %04x:%04x page %04x to %04x\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix, new_pix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, obj_id, p_hdr.span_ix, new_pix, 0); spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&p_hdr,
SPIFFS_EV_IX_MOV, obj_id, p_hdr.span_ix, new_pix, 0);
// move wipes obj_lu, reload it // move wipes obj_lu, reload it
res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page),
SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
} else { } else {
// page is deleted but not deleted in lookup, scrap it // page is deleted but not deleted in lookup, scrap it -
SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix %04x:%04x page %04x\n", obj_id, p_hdr.span_ix, cur_pix); // might seem unnecessary as we will erase this block, but
// we might get aborted
SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix);
res = spiffs_page_delete(fs, cur_pix); res = spiffs_page_delete(fs, cur_pix);
if (res == SPIFFS_OK) { if (res == SPIFFS_OK) {
spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0,
SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0);
} }
} }
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
@ -497,72 +515,92 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
default: default:
scan = 0; scan = 0;
break; break;
} } // switch gc state
cur_entry++; cur_entry++;
} // per entry } // per entry
obj_lookup_page++; obj_lookup_page++; // no need to check scan variable here, obj_lookup_page is set in start of loop
} // per object lookup page } // per object lookup page
if (res != SPIFFS_OK) break; if (res != SPIFFS_OK) break;
// state finalization and switch // state finalization and switch
switch (gc.state) { switch (gc.state) {
case FIND_OBJ_DATA: case FIND_OBJ_DATA:
if (gc.obj_id_found) { if (gc.obj_id_found) {
// handle found data page -
// find out corresponding obj ix page and load it to memory // find out corresponding obj ix page and load it to memory
spiffs_page_header p_hdr; spiffs_page_header p_hdr;
spiffs_page_ix objix_pix; spiffs_page_ix objix_pix;
gc.stored_scan_entry_index = cur_entry; gc.stored_scan_entry_index = cur_entry; // push cursor
cur_entry = 0; cur_entry = 0; // restart scan from start
gc.state = MOVE_OBJ_DATA; gc.state = MOVE_OBJ_DATA;
res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix); gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix);
SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:%04x\n", gc.cur_objix_spix); SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:"_SPIPRIsp"\n", gc.cur_objix_spix);
res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix); res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix);
if (res == SPIFFS_ERR_NOT_FOUND) {
// on borked systems we might get an ERR_NOT_FOUND here -
// this is handled by simply deleting the page as it is not referenced
// from anywhere
SPIFFS_GC_DBG("gc_clean: FIND_OBJ_DATA objix not found! Wipe page "_SPIPRIpg"\n", gc.cur_data_pix);
res = spiffs_page_delete(fs, gc.cur_data_pix);
SPIFFS_CHECK_RES(res);
// then we restore states and continue scanning for data pages
cur_entry = gc.stored_scan_entry_index; // pop cursor
gc.state = FIND_OBJ_DATA;
break; // done
}
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page %04x\n", objix_pix); SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page "_SPIPRIpg"\n", objix_pix);
res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
// cannot allow a gc if the presumed index in fact is no index, a
// check must run or lot of data may be lost
SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix); SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix);
gc.cur_objix_pix = objix_pix; gc.cur_objix_pix = objix_pix;
} else { } else {
// no more data pages found, passed thru all block, start evacuating object indices
gc.state = MOVE_OBJ_IX; gc.state = MOVE_OBJ_IX;
cur_entry = 0; // restart entry scan index cur_entry = 0; // restart entry scan index
} }
break; break;
case MOVE_OBJ_DATA: { case MOVE_OBJ_DATA: {
// store modified objix (hdr) page // store modified objix (hdr) page residing in memory now that all
// data pages belonging to this object index and residing in the block
// we want to evacuate
spiffs_page_ix new_objix_pix; spiffs_page_ix new_objix_pix;
gc.state = FIND_OBJ_DATA; gc.state = FIND_OBJ_DATA;
cur_entry = gc.stored_scan_entry_index; cur_entry = gc.stored_scan_entry_index; // pop cursor
if (gc.cur_objix_spix == 0) { if (gc.cur_objix_spix == 0) {
// store object index header page // store object index header page
res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, &new_objix_pix); res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, 0, &new_objix_pix);
SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, %04x:%04x\n", new_objix_pix, 0); SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, 0);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
} else { } else {
// store object index page // store object index page
res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix); res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix);
SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, %04x:%04x\n", new_objix_pix, objix->p_hdr.span_ix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, objix->p_hdr.span_ix);
SPIFFS_CHECK_RES(res); SPIFFS_CHECK_RES(res);
spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work,
SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0);
} }
} }
break; break;
case MOVE_OBJ_IX: case MOVE_OBJ_IX:
// scanned thru all block, no more object indices found - our work here is done
gc.state = FINISHED; gc.state = FINISHED;
break; break;
default: default:
cur_entry = 0; cur_entry = 0;
break; break;
} } // switch gc.state
SPIFFS_GC_DBG("gc_clean: state-> %i\n", gc.state); SPIFFS_GC_DBG("gc_clean: state-> "_SPIPRIi"\n", gc.state);
} // while state != FINISHED } // while state != FINISHED
return res; return res;
} }
#endif // !SPIFFS_READ_ONLY

@ -116,13 +116,23 @@
#define SPIFFS_ERR_CHECK_FLAGS_BAD (SPIFFS_ERR_INTERNAL - 3) #define SPIFFS_ERR_CHECK_FLAGS_BAD (SPIFFS_ERR_INTERNAL - 3)
#define _SPIFFS_ERR_CHECK_LAST (SPIFFS_ERR_INTERNAL - 4) #define _SPIFFS_ERR_CHECK_LAST (SPIFFS_ERR_INTERNAL - 4)
// visitor result, continue searching
#define SPIFFS_VIS_COUNTINUE (SPIFFS_ERR_INTERNAL - 20) #define SPIFFS_VIS_COUNTINUE (SPIFFS_ERR_INTERNAL - 20)
// visitor result, continue searching after reloading lu buffer
#define SPIFFS_VIS_COUNTINUE_RELOAD (SPIFFS_ERR_INTERNAL - 21) #define SPIFFS_VIS_COUNTINUE_RELOAD (SPIFFS_ERR_INTERNAL - 21)
// visitor result, stop searching
#define SPIFFS_VIS_END (SPIFFS_ERR_INTERNAL - 22) #define SPIFFS_VIS_END (SPIFFS_ERR_INTERNAL - 22)
#define SPIFFS_EV_IX_UPD 0 // updating an object index contents
#define SPIFFS_EV_IX_NEW 1 #define SPIFFS_EV_IX_UPD (0)
#define SPIFFS_EV_IX_DEL 2 // creating a new object index
#define SPIFFS_EV_IX_NEW (1)
// deleting an object index
#define SPIFFS_EV_IX_DEL (2)
// moving an object index without updating contents
#define SPIFFS_EV_IX_MOV (3)
// updating an object index header data only, not the table itself
#define SPIFFS_EV_IX_UPD_HDR (4)
#define SPIFFS_OBJ_ID_IX_FLAG ((spiffs_obj_id)(1<<(8*sizeof(spiffs_obj_id)-1))) #define SPIFFS_OBJ_ID_IX_FLAG ((spiffs_obj_id)(1<<(8*sizeof(spiffs_obj_id)-1)))
@ -131,7 +141,31 @@
#define SPIFFS_OBJ_ID_DELETED ((spiffs_obj_id)0) #define SPIFFS_OBJ_ID_DELETED ((spiffs_obj_id)0)
#define SPIFFS_OBJ_ID_FREE ((spiffs_obj_id)-1) #define SPIFFS_OBJ_ID_FREE ((spiffs_obj_id)-1)
#define SPIFFS_MAGIC(fs) ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs)))
#if defined(__GNUC__) || defined(__clang__)
/* For GCC and clang */
#define SPIFFS_PACKED __attribute__((packed))
#elif defined(__ICCARM__) || defined(__CC_ARM)
/* For IAR ARM and Keil MDK-ARM compilers */
#define SPIFFS_PACKED
#else
/* Unknown compiler */
#define SPIFFS_PACKED
#endif
#if SPIFFS_USE_MAGIC
#if !SPIFFS_USE_MAGIC_LENGTH
#define SPIFFS_MAGIC(fs, bix) \
((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs)))
#else // SPIFFS_USE_MAGIC_LENGTH
#define SPIFFS_MAGIC(fs, bix) \
((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs) ^ ((fs)->block_count - (bix))))
#endif // SPIFFS_USE_MAGIC_LENGTH
#endif // SPIFFS_USE_MAGIC
#define SPIFFS_CONFIG_MAGIC (0x20090315) #define SPIFFS_CONFIG_MAGIC (0x20090315)
@ -220,6 +254,17 @@
// object index span index number for given data span index or entry // object index span index number for given data span index or entry
#define SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, spix) \ #define SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, spix) \
((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? 0 : (1+((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))/SPIFFS_OBJ_IX_LEN(fs))) ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? 0 : (1+((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))/SPIFFS_OBJ_IX_LEN(fs)))
// get data span index for object index span index
#define SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, spix) \
( (spix) == 0 ? 0 : (SPIFFS_OBJ_HDR_IX_LEN(fs) + (((spix)-1) * SPIFFS_OBJ_IX_LEN(fs))) )
#if SPIFFS_FILEHDL_OFFSET
#define SPIFFS_FH_OFFS(fs, fh) ((fh) != 0 ? ((fh) + (fs)->cfg.fh_ix_offset) : 0)
#define SPIFFS_FH_UNOFFS(fs, fh) ((fh) != 0 ? ((fh) - (fs)->cfg.fh_ix_offset) : 0)
#else
#define SPIFFS_FH_OFFS(fs, fh) (fh)
#define SPIFFS_FH_UNOFFS(fs, fh) (fh)
#endif
#define SPIFFS_OP_T_OBJ_LU (0<<0) #define SPIFFS_OP_T_OBJ_LU (0<<0)
@ -264,26 +309,26 @@
#define SPIFFS_API_CHECK_MOUNT(fs) \ #define SPIFFS_API_CHECK_MOUNT(fs) \
if (!SPIFFS_CHECK_MOUNT((fs))) { \ if (!SPIFFS_CHECK_MOUNT((fs))) { \
(fs)->err_code = SPIFFS_ERR_NOT_MOUNTED; \ (fs)->err_code = SPIFFS_ERR_NOT_MOUNTED; \
return -1; \ return SPIFFS_ERR_NOT_MOUNTED; \
} }
#define SPIFFS_API_CHECK_CFG(fs) \ #define SPIFFS_API_CHECK_CFG(fs) \
if (!SPIFFS_CHECK_CFG((fs))) { \ if (!SPIFFS_CHECK_CFG((fs))) { \
(fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; \ (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; \
return -1; \ return SPIFFS_ERR_NOT_CONFIGURED; \
} }
#define SPIFFS_API_CHECK_RES(fs, res) \ #define SPIFFS_API_CHECK_RES(fs, res) \
if ((res) < SPIFFS_OK) { \ if ((res) < SPIFFS_OK) { \
(fs)->err_code = (res); \ (fs)->err_code = (res); \
return -1; \ return (res); \
} }
#define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \ #define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \
if ((res) < SPIFFS_OK) { \ if ((res) < SPIFFS_OK) { \
(fs)->err_code = (res); \ (fs)->err_code = (res); \
SPIFFS_UNLOCK(fs); \ SPIFFS_UNLOCK(fs); \
return -1; \ return (res); \
} }
#define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \ #define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \
@ -304,13 +349,33 @@
if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH; if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH;
// check id // check id, only visit matching objec ids
#define SPIFFS_VIS_CHECK_ID (1<<0) #define SPIFFS_VIS_CHECK_ID (1<<0)
// report argument object id to visitor - else object lookup id is reported // report argument object id to visitor - else object lookup id is reported
#define SPIFFS_VIS_CHECK_PH (1<<1) #define SPIFFS_VIS_CHECK_PH (1<<1)
// stop searching at end of all look up pages // stop searching at end of all look up pages
#define SPIFFS_VIS_NO_WRAP (1<<2) #define SPIFFS_VIS_NO_WRAP (1<<2)
#if SPIFFS_HAL_CALLBACK_EXTRA
#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \
(_fs)->cfg.hal_write_f((_fs), (_paddr), (_len), (_src))
#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \
(_fs)->cfg.hal_read_f((_fs), (_paddr), (_len), (_dst))
#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \
(_fs)->cfg.hal_erase_f((_fs), (_paddr), (_len))
#else // SPIFFS_HAL_CALLBACK_EXTRA
#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \
(_fs)->cfg.hal_write_f((_paddr), (_len), (_src))
#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \
(_fs)->cfg.hal_read_f((_paddr), (_len), (_dst))
#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \
(_fs)->cfg.hal_erase_f((_paddr), (_len))
#endif // SPIFFS_HAL_CALLBACK_EXTRA
#if SPIFFS_CACHE #if SPIFFS_CACHE
#define SPIFFS_CACHE_FLAG_DIRTY (1<<0) #define SPIFFS_CACHE_FLAG_DIRTY (1<<0)
@ -390,13 +455,23 @@ typedef struct {
spiffs_span_ix cursor_objix_spix; spiffs_span_ix cursor_objix_spix;
// current absolute offset // current absolute offset
u32_t offset; u32_t offset;
// current file descriptor offset // current file descriptor offset (cached)
u32_t fdoffset; u32_t fdoffset;
// fd flags // fd flags
spiffs_flags flags; spiffs_flags flags;
#if SPIFFS_CACHE_WR #if SPIFFS_CACHE_WR
spiffs_cache_page *cache_page; spiffs_cache_page *cache_page;
#endif #endif
#if SPIFFS_TEMPORAL_FD_CACHE
// djb2 hash of filename
u32_t name_hash;
// hit score (score == 0 indicates never used fd)
u16_t score;
#endif
#if SPIFFS_IX_MAP
// spiffs index map, if 0 it means unmapped
spiffs_ix_map *ix_map;
#endif
} spiffs_fd; } spiffs_fd;
@ -405,7 +480,7 @@ typedef struct {
// page header, part of each page except object lookup pages // page header, part of each page except object lookup pages
// NB: this is always aligned when the data page is an object index, // NB: this is always aligned when the data page is an object index,
// as in this case struct spiffs_page_object_ix is used // as in this case struct spiffs_page_object_ix is used
typedef struct __attribute(( packed )) { typedef struct SPIFFS_PACKED {
// object id // object id
spiffs_obj_id obj_id; spiffs_obj_id obj_id;
// object span index // object span index
@ -415,7 +490,7 @@ typedef struct __attribute(( packed )) {
} spiffs_page_header; } spiffs_page_header;
// object index header page header // object index header page header
typedef struct __attribute(( packed )) typedef struct SPIFFS_PACKED
#if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES #if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES
__attribute(( aligned(sizeof(spiffs_page_ix)) )) __attribute(( aligned(sizeof(spiffs_page_ix)) ))
#endif #endif
@ -423,24 +498,28 @@ typedef struct __attribute(( packed ))
// common page header // common page header
spiffs_page_header p_hdr; spiffs_page_header p_hdr;
// alignment // alignment
u8_t _align[4 - (sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3)]; u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))];
// size of object // size of object
u32_t size; u32_t size;
// type of object // type of object
spiffs_obj_type type; spiffs_obj_type type;
// name of object // name of object
u8_t name[SPIFFS_OBJ_NAME_LEN]; u8_t name[SPIFFS_OBJ_NAME_LEN];
#if SPIFFS_OBJ_META_LEN
// metadata. not interpreted by SPIFFS in any way.
u8_t meta[SPIFFS_OBJ_META_LEN];
#endif
} spiffs_page_object_ix_header; } spiffs_page_object_ix_header;
// object index page header // object index page header
typedef struct __attribute(( packed )) { typedef struct SPIFFS_PACKED {
spiffs_page_header p_hdr; spiffs_page_header p_hdr;
u8_t _align[4 - (sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3)]; u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))];
} spiffs_page_object_ix; } spiffs_page_object_ix;
// callback func for object lookup visitor // callback func for object lookup visitor
typedef s32_t (*spiffs_visitor_f)(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, typedef s32_t (*spiffs_visitor_f)(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry,
u32_t user_data, void *user_p); const void *user_const_p, void *user_var_p);
#if SPIFFS_CACHE #if SPIFFS_CACHE
@ -501,8 +580,8 @@ s32_t spiffs_obj_lu_find_entry_visitor(
u8_t flags, u8_t flags,
spiffs_obj_id obj_id, spiffs_obj_id obj_id,
spiffs_visitor_f v, spiffs_visitor_f v,
u32_t user_data, const void *user_const_p,
void *user_p, void *user_var_p,
spiffs_block_ix *block_ix, spiffs_block_ix *block_ix,
int *lu_entry); int *lu_entry);
@ -510,6 +589,11 @@ s32_t spiffs_erase_block(
spiffs *fs, spiffs *fs,
spiffs_block_ix bix); spiffs_block_ix bix);
#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH
s32_t spiffs_probe(
spiffs_config *cfg);
#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH
// --------------- // ---------------
s32_t spiffs_obj_lu_scan( s32_t spiffs_obj_lu_scan(
@ -518,7 +602,7 @@ s32_t spiffs_obj_lu_scan(
s32_t spiffs_obj_lu_find_free_obj_id( s32_t spiffs_obj_lu_find_free_obj_id(
spiffs *fs, spiffs *fs,
spiffs_obj_id *obj_id, spiffs_obj_id *obj_id,
u8_t *conflicting_name); const u8_t *conflicting_name);
s32_t spiffs_obj_lu_find_free( s32_t spiffs_obj_lu_find_free(
spiffs *fs, spiffs *fs,
@ -579,7 +663,8 @@ s32_t spiffs_page_delete(
s32_t spiffs_object_create( s32_t spiffs_object_create(
spiffs *fs, spiffs *fs,
spiffs_obj_id obj_id, spiffs_obj_id obj_id,
u8_t name[SPIFFS_OBJ_NAME_LEN], const u8_t name[],
const u8_t meta[],
spiffs_obj_type type, spiffs_obj_type type,
spiffs_page_ix *objix_hdr_pix); spiffs_page_ix *objix_hdr_pix);
@ -589,13 +674,24 @@ s32_t spiffs_object_update_index_hdr(
spiffs_obj_id obj_id, spiffs_obj_id obj_id,
spiffs_page_ix objix_hdr_pix, spiffs_page_ix objix_hdr_pix,
u8_t *new_objix_hdr_data, u8_t *new_objix_hdr_data,
u8_t name[SPIFFS_OBJ_NAME_LEN], const u8_t name[],
const u8_t meta[],
u32_t size, u32_t size,
spiffs_page_ix *new_pix); spiffs_page_ix *new_pix);
void spiffs_cb_object_event( #if SPIFFS_IX_MAP
s32_t spiffs_populate_ix_map(
spiffs *fs, spiffs *fs,
spiffs_fd *fd, spiffs_fd *fd,
u32_t vec_entry_start,
u32_t vec_entry_end);
#endif
void spiffs_cb_object_event(
spiffs *fs,
spiffs_page_object_ix *objix,
int ev, int ev,
spiffs_obj_id obj_id, spiffs_obj_id obj_id,
spiffs_span_ix spix, spiffs_span_ix spix,
@ -641,7 +737,7 @@ s32_t spiffs_object_truncate(
s32_t spiffs_object_find_object_index_header_by_name( s32_t spiffs_object_find_object_index_header_by_name(
spiffs *fs, spiffs *fs,
u8_t name[SPIFFS_OBJ_NAME_LEN], const u8_t name[SPIFFS_OBJ_NAME_LEN],
spiffs_page_ix *pix); spiffs_page_ix *pix);
// --------------- // ---------------
@ -671,7 +767,8 @@ s32_t spiffs_gc_quick(
s32_t spiffs_fd_find_new( s32_t spiffs_fd_find_new(
spiffs *fs, spiffs *fs,
spiffs_fd **fd); spiffs_fd **fd,
const char *name);
s32_t spiffs_fd_return( s32_t spiffs_fd_return(
spiffs *fs, spiffs *fs,
@ -682,6 +779,13 @@ s32_t spiffs_fd_get(
spiffs_file f, spiffs_file f,
spiffs_fd **fd); spiffs_fd **fd);
#if SPIFFS_TEMPORAL_FD_CACHE
void spiffs_fd_temporal_cache_rehash(
spiffs *fs,
const char *old_path,
const char *new_path);
#endif
#if SPIFFS_CACHE #if SPIFFS_CACHE
void spiffs_cache_init( void spiffs_cache_init(
spiffs *fs); spiffs *fs);
@ -715,4 +819,24 @@ s32_t spiffs_page_consistency_check(
s32_t spiffs_object_index_consistency_check( s32_t spiffs_object_index_consistency_check(
spiffs *fs); spiffs *fs);
// memcpy macro,
// checked in test builds, otherwise plain memcpy (unless already defined)
#ifdef _SPIFFS_TEST
#define _SPIFFS_MEMCPY(__d, __s, __l) do { \
intptr_t __a1 = (intptr_t)((u8_t*)(__s)); \
intptr_t __a2 = (intptr_t)((u8_t*)(__s)+(__l)); \
intptr_t __b1 = (intptr_t)((u8_t*)(__d)); \
intptr_t __b2 = (intptr_t)((u8_t*)(__d)+(__l)); \
if (__a1 <= __b2 && __b1 <= __a2) { \
printf("FATAL OVERLAP: memcpy from %lx..%lx to %lx..%lx\n", __a1, __a2, __b1, __b2); \
ERREXIT(); \
} \
memcpy((__d),(__s),(__l)); \
} while (0)
#else
#ifndef _SPIFFS_MEMCPY
#define _SPIFFS_MEMCPY(__d, __s, __l) do{memcpy((__d),(__s),(__l));}while(0)
#endif
#endif //_SPIFFS_TEST
#endif /* SPIFFS_NUCLEUS_H_ */ #endif /* SPIFFS_NUCLEUS_H_ */

@ -0,0 +1,12 @@
#include <stdlib.h>
#ifndef NO_TEST
#include "testrunner.h"
#endif
int main(int argc, char **args) {
#ifndef NO_TEST
run_tests(argc, args);
#endif
exit(EXIT_SUCCESS);
}

@ -0,0 +1,84 @@
/*
* params_test.h
*
* Created on: May 26, 2013
* Author: petera
*/
#ifndef PARAMS_TEST_H_
#define PARAMS_TEST_H_
//////////////// TEST PARAMS ////////////////
// default test total emulated spi flash size
#define PHYS_FLASH_SIZE (16*1024*1024)
// default test spiffs file system size
#define SPIFFS_FLASH_SIZE (2*1024*1024)
// default test spiffs file system offset in emulated spi flash
#define SPIFFS_PHYS_ADDR (4*1024*1024)
// default test sector size
#define SECTOR_SIZE 65536
// default test logical block size
#define LOG_BLOCK (SECTOR_SIZE*2)
// default test logical page size
#define LOG_PAGE (SECTOR_SIZE/256)
// default test number of filedescs
#define DEFAULT_NUM_FD 16
// default test number of cache pages
#define DEFAULT_NUM_CACHE_PAGES 8
// When testing, test bench create reference files for comparison on
// the actual hard drive. By default, put these on ram drive for speed.
#define TEST_PATH "/dev/shm/spiffs/test-data/"
#define ASSERT(c, m) real_assert((c),(m), __FILE__, __LINE__);
void real_assert(int c, const char *n, const char *file, int l);
/////////// SPIFFS BUILD CONFIG ////////////
// test using filesystem magic
#ifndef SPIFFS_USE_MAGIC
#define SPIFFS_USE_MAGIC 1
#endif
// test using filesystem magic length
#ifndef SPIFFS_USE_MAGIC_LENGTH
#define SPIFFS_USE_MAGIC_LENGTH 1
#endif
// test using extra param in callback
#ifndef SPIFFS_HAL_CALLBACK_EXTRA
#define SPIFFS_HAL_CALLBACK_EXTRA 1
#endif
// test using filehandle offset
#ifndef SPIFFS_FILEHDL_OFFSET
#define SPIFFS_FILEHDL_OFFSET 1
// use this offset
#define TEST_SPIFFS_FILEHDL_OFFSET 0x1000
#endif
#ifdef NO_TEST
#define SPIFFS_LOCK(fs)
#define SPIFFS_UNLOCK(fs)
#else
struct spiffs_t;
extern void test_lock(struct spiffs_t *fs);
extern void test_unlock(struct spiffs_t *fs);
#define SPIFFS_LOCK(fs) test_lock(fs)
#define SPIFFS_UNLOCK(fs) test_unlock(fs)
#endif
// dbg output
#define SPIFFS_DBG(_f, ...) //printf("\x1b[32m" _f "\x1b[0m", ## __VA_ARGS__)
#define SPIFFS_API_DBG(_f, ...) //printf("\n\x1b[1m\x1b[7m" _f "\x1b[0m", ## __VA_ARGS__)
#define SPIFFS_GC_DBG(_f, ...) //printf("\x1b[36m" _f "\x1b[0m", ## __VA_ARGS__)
#define SPIFFS_CACHE_DBG(_f, ...) //printf("\x1b[33m" _f "\x1b[0m", ## __VA_ARGS__)
#define SPIFFS_CHECK_DBG(_f, ...) //printf("\x1b[31m" _f "\x1b[0m", ## __VA_ARGS__)
// needed types
typedef signed int s32_t;
typedef unsigned int u32_t;
typedef signed short s16_t;
typedef unsigned short u16_t;
typedef signed char s8_t;
typedef unsigned char u8_t;
#endif /* PARAMS_TEST_H_ */

File diff suppressed because it is too large Load Diff

@ -0,0 +1,427 @@
/*
* test_dev.c
*
* Created on: Jul 14, 2013
* Author: petera
*/
#include "testrunner.h"
#include "test_spiffs.h"
#include "spiffs_nucleus.h"
#include "spiffs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
SUITE(check_tests)
static void setup() {
_setup();
}
static void teardown() {
_teardown();
}
TEST(evil_write) {
fs_set_validate_flashing(0);
printf("writing corruption to block 1 data range (leaving lu intact)\n");
u32_t data_range = SPIFFS_CFG_LOG_BLOCK_SZ(FS) -
SPIFFS_CFG_LOG_PAGE_SZ(FS) * (SPIFFS_OBJ_LOOKUP_PAGES(FS));
u8_t *corruption = malloc(data_range);
memrand(corruption, data_range);
u32_t addr = 0 * SPIFFS_CFG_LOG_PAGE_SZ(FS) * SPIFFS_OBJ_LOOKUP_PAGES(FS);
area_write(addr, corruption, data_range);
free(corruption);
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
printf("CHECK1-----------------\n");
SPIFFS_check(FS);
printf("CHECK2-----------------\n");
SPIFFS_check(FS);
printf("CHECK3-----------------\n");
SPIFFS_check(FS);
res = test_create_and_write_file("file2", size, size);
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END
TEST(lu_check1) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index 1
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 1, 0, &pix);
TEST_CHECK(res >= 0);
// reset lu entry to being erased, but keep page data
spiffs_obj_id obj_id = SPIFFS_OBJ_ID_DELETED;
spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(FS, pix);
int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(FS, pix);
u32_t addr = SPIFFS_BLOCK_TO_PADDR(FS, bix) + entry*sizeof(spiffs_obj_id);
area_write(addr, (u8_t*)&obj_id, sizeof(spiffs_obj_id));
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
return TEST_RES_OK;
} TEST_END
TEST(page_cons1) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify object index, find object index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
// set object index entry 2 to a bad page
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + sizeof(spiffs_page_object_ix_header) + 0 * sizeof(spiffs_page_ix);
spiffs_page_ix bad_pix_ref = 0x55;
area_write(addr, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
area_write(addr + sizeof(spiffs_page_ix), (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
// delete all cache
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END
TEST(page_cons2) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify object index, find object index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
// find data page span index 0
spiffs_page_ix dpix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &dpix);
TEST_CHECK(res >= 0);
// set object index entry 1+2 to a data page 0
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + sizeof(spiffs_page_object_ix_header) + 1 * sizeof(spiffs_page_ix);
spiffs_page_ix bad_pix_ref = dpix;
area_write(addr, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
area_write(addr+sizeof(spiffs_page_ix), (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
// delete all cache
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END
TEST(page_cons3) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify object index, find object index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
// set object index entry 1+2 lookup page
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + sizeof(spiffs_page_object_ix_header) + 1 * sizeof(spiffs_page_ix);
spiffs_page_ix bad_pix_ref = SPIFFS_PAGES_PER_BLOCK(FS) * (*FS.block_count - 2);
area_write(addr, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
area_write(addr+sizeof(spiffs_page_ix), (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
// delete all cache
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END
TEST(page_cons_final) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify page header, make unfinalized
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 1, 0, &pix);
TEST_CHECK(res >= 0);
// set page span ix 1 as unfinalized
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + offsetof(spiffs_page_header, flags);
u8_t flags;
area_read(addr, (u8_t*)&flags, 1);
flags |= SPIFFS_PH_FLAG_FINAL;
area_write(addr, (u8_t*)&flags, 1);
// delete all cache
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END
TEST(index_cons1) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*SPIFFS_PAGES_PER_BLOCK(FS);
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
printf(" deleting lu entry pix %04x\n", pix);
// reset lu entry to being erased, but keep page data
spiffs_obj_id obj_id = SPIFFS_OBJ_ID_DELETED;
spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(FS, pix);
int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(FS, pix);
u32_t addr = SPIFFS_BLOCK_TO_PADDR(FS, bix) + entry * sizeof(spiffs_obj_id);
area_write(addr, (u8_t*)&obj_id, sizeof(spiffs_obj_id));
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END
TEST(index_cons2) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*SPIFFS_PAGES_PER_BLOCK(FS);
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
printf(" writing lu entry for index page, ix %04x, as data page\n", pix);
spiffs_obj_id obj_id = 0x1234;
spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(FS, pix);
int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(FS, pix);
u32_t addr = SPIFFS_BLOCK_TO_PADDR(FS, bix) + entry * sizeof(spiffs_obj_id);
area_write(addr, (u8_t*)&obj_id, sizeof(spiffs_obj_id));
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END
TEST(index_cons3) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*SPIFFS_PAGES_PER_BLOCK(FS);
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
printf(" setting lu entry pix %04x to another index page\n", pix);
// reset lu entry to being erased, but keep page data
spiffs_obj_id obj_id = 1234 | SPIFFS_OBJ_ID_IX_FLAG;
spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(FS, pix);
int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(FS, pix);
u32_t addr = SPIFFS_BLOCK_TO_PADDR(FS, bix) + entry * sizeof(spiffs_obj_id);
area_write(addr, (u8_t*)&obj_id, sizeof(spiffs_obj_id));
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END
TEST(index_cons4) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*SPIFFS_PAGES_PER_BLOCK(FS);
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index header, flags
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
printf(" cue objix hdr deletion in page %04x\n", pix);
// set flags as deleting ix header
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + offsetof(spiffs_page_header, flags);
u8_t flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_IXDELE);
area_write(addr, (u8_t*)&flags, 1);
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
return TEST_RES_OK;
} TEST_END
SUITE_TESTS(check_tests)
ADD_TEST(evil_write)
ADD_TEST(lu_check1)
ADD_TEST(page_cons1)
ADD_TEST(page_cons2)
ADD_TEST(page_cons3)
ADD_TEST(page_cons_final)
ADD_TEST(index_cons1)
ADD_TEST(index_cons2)
ADD_TEST(index_cons3)
ADD_TEST(index_cons4)
SUITE_END(check_tests)

@ -0,0 +1,122 @@
/*
* test_dev.c
*
* Created on: Jul 14, 2013
* Author: petera
*/
#include "testrunner.h"
#include "test_spiffs.h"
#include "spiffs_nucleus.h"
#include "spiffs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
SUITE(dev_tests)
static void setup() {
_setup();
}
static void teardown() {
_teardown();
}
TEST(interrupted_write) {
char *name = "interrupt";
char *name2 = "interrupt2";
int res;
spiffs_file fd;
const u32_t sz = SPIFFS_CFG_LOG_PAGE_SZ(FS)*8;
u8_t *buf = malloc(sz);
memrand(buf, sz);
printf(" create reference file\n");
fd = SPIFFS_open(FS, name, SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(fd > 0);
clear_flash_ops_log();
res = SPIFFS_write(FS, fd, buf, sz);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
u32_t written = get_flash_ops_log_write_bytes();
printf(" written bytes: %i\n", written);
printf(" create error file\n");
fd = SPIFFS_open(FS, name2, SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(fd > 0);
clear_flash_ops_log();
invoke_error_after_write_bytes(written/2, 0);
res = SPIFFS_write(FS, fd, buf, sz);
SPIFFS_close(FS, fd);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_TEST);
clear_flash_ops_log();
#if SPIFFS_CACHE
// delete all cache
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
printf(" read error file\n");
fd = SPIFFS_open(FS, name2, SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
printf(" file size: %i\n", s.size);
if (s.size > 0) {
u8_t *buf2 = malloc(s.size);
res = SPIFFS_read(FS, fd, buf2, s.size);
TEST_CHECK(res >= 0);
u32_t ix = 0;
for (ix = 0; ix < s.size; ix += 16) {
int i;
printf(" ");
for (i = 0; i < 16; i++) {
printf("%02x", buf[ix+i]);
}
printf(" ");
for (i = 0; i < 16; i++) {
printf("%02x", buf2[ix+i]);
}
printf("\n");
}
free(buf2);
}
SPIFFS_close(FS, fd);
printf(" FS check\n");
SPIFFS_check(FS);
printf(" read error file again\n");
fd = SPIFFS_open(FS, name2, SPIFFS_APPEND | SPIFFS_RDWR, 0);
TEST_CHECK(fd > 0);
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
printf(" file size: %i\n", s.size);
printf(" write file\n");
res = SPIFFS_write(FS, fd, buf, sz);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
free(buf);
return TEST_RES_OK;
} TEST_END
SUITE_TESTS(dev_tests)
ADD_TEST(interrupted_write)
SUITE_END(dev_tests)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,109 @@
/*
* test_spiffs.h
*
* Created on: Jun 19, 2013
* Author: petera
*/
#ifndef TEST_SPIFFS_H_
#define TEST_SPIFFS_H_
#include "spiffs.h"
#define FS &__fs
extern spiffs __fs;
#define CHECK(r) if (!(r)) return -1;
#define CHECK_RES(r) if (r < 0) return -1;
#define FS_PURE_DATA_PAGES(fs) \
(SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_PAGE_SZ(fs)- (fs)->block_count * SPIFFS_OBJ_LOOKUP_PAGES(fs))
#define FS_PURE_DATA_SIZE(fs) \
FS_PURE_DATA_PAGES(fs) * SPIFFS_DATA_PAGE_SIZE(fs)
typedef enum {
EMPTY,
SMALL,
MEDIUM,
LARGE,
} tfile_size;
typedef enum {
UNTAMPERED,
APPENDED,
MODIFIED,
REWRITTEN,
} tfile_type;
typedef enum {
SHORT = 3,
NORMAL = 15,
LONG = 100,
} tfile_life;
typedef struct {
tfile_size tsize;
tfile_type ttype;
tfile_life tlife;
} tfile_conf;
typedef struct {
int state;
spiffs_file fd;
tfile_conf cfg;
char name[32];
} tfile;
void fs_reset();
void fs_reset_specific(u32_t addr_offset, u32_t phys_addr, u32_t phys_size,
u32_t phys_sector_size,
u32_t log_block_size, u32_t log_page_size);
s32_t fs_mount_specific(u32_t phys_addr, u32_t phys_size,
u32_t phys_sector_size,
u32_t log_block_size, u32_t log_page_size);
void fs_mount_dump(char *fname,
u32_t addr_offset, u32_t phys_addr, u32_t phys_size,
u32_t phys_sector_size,
u32_t log_block_size, u32_t log_page_size);
void fs_store_dump(char *fname);
void fs_load_dump(char *fname);
void fs_set_addr_offset(u32_t offset);
int read_and_verify(char *name);
int read_and_verify_fd(spiffs_file fd, char *name);
void dump_page(spiffs *fs, spiffs_page_ix p);
void hexdump(u32_t addr, u32_t len);
char *make_test_fname(const char *name);
void clear_test_path();
void area_write(u32_t addr, u8_t *buf, u32_t size);
void area_set(u32_t addr, u8_t d, u32_t size);
void area_read(u32_t addr, u8_t *buf, u32_t size);
void dump_erase_counts(spiffs *fs);
void dump_flash_access_stats();
void set_flash_ops_log(int enable);
void clear_flash_ops_log();
u32_t get_flash_ops_log_read_bytes();
u32_t get_flash_ops_log_write_bytes();
void invoke_error_after_read_bytes(u32_t b, char once_only);
void invoke_error_after_write_bytes(u32_t b, char once_only);
void fs_set_validate_flashing(int i);
int get_error_count();
int count_taken_fds(spiffs *fs);
void memrand(u8_t *b, int len);
int test_create_file(char *name);
int test_create_and_write_file(char *name, int size, int chunk_size);
u32_t get_spiffs_file_crc_by_fd(spiffs_file fd);
u32_t get_spiffs_file_crc(char *name);
void _setup();
void _setup_test_only();
void _teardown();
u32_t tfile_get_size(tfile_size s);
int run_file_config(int cfg_count, tfile_conf* cfgs, int max_runs, int max_concurrent_files, int dbg);
void test_lock(spiffs *fs);
void test_unlock(spiffs *fs);
#endif /* TEST_SPIFFS_H_ */

@ -0,0 +1,238 @@
/*
* testrunner.c
*
* Created on: Jun 18, 2013
* Author: petera
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include "testrunner.h"
static struct {
test *tests;
test *_last_test;
int test_count;
void (*on_stop)(test *t);
test_res *failed;
test_res *failed_last;
test_res *stopped;
test_res *stopped_last;
FILE *spec;
char incl_filter[256];
char excl_filter[256];
} test_main;
void test_init(void (*on_stop)(test *t)) {
test_main.on_stop = on_stop;
}
static int abort_on_error = 0;
static int error_count = 0;
static char check_spec(char *name) {
if (test_main.spec) {
fseek(test_main.spec, 0, SEEK_SET);
char *line = NULL;
size_t sz;
ssize_t read;
while ((read = getline(&line, &sz, test_main.spec)) != -1) {
if (strncmp(line, name, strlen(line)-1) == 0) {
free(line);
return 1;
}
}
free(line);
return 0;
} else {
return 1;
}
}
static char check_incl_filter(char *name) {
if (strlen(test_main.incl_filter)== 0) return 1;
return strstr(name, test_main.incl_filter) == 0 ? 0 : 2;
}
static char check_excl_filter(char *name) {
if (strlen(test_main.excl_filter)== 0) return 1;
return strstr(name, test_main.excl_filter) == 0 ? 1 : 0;
}
void _add_test(test_f f, char *name, void (*setup)(test *t), void (*teardown)(test *t), int non_default) {
if (f == 0) return;
if (!check_spec(name)) return;
if (check_incl_filter(name) <= non_default) return;
if (!check_excl_filter(name)) return;
DBGT("adding test %s\n", name);
test *t = malloc(sizeof(test));
memset(t, 0, sizeof(test));
t->f = f;
strcpy(t->name, name);
t->setup = setup;
t->teardown = teardown;
if (test_main.tests == 0) {
test_main.tests = t;
} else {
test_main._last_test->_next = t;
}
test_main._last_test = t;
test_main.test_count++;
}
static void add_res(test *t, test_res **head, test_res **last) {
test_res *tr = malloc(sizeof(test_res));
memset(tr,0,sizeof(test_res));
strcpy(tr->name, t->name);
if (*head == 0) {
*head = tr;
} else {
(*last)->_next = tr;
}
*last = tr;
}
static void dump_res(test_res **head) {
test_res *tr = (*head);
while (tr) {
test_res *next_tr = tr->_next;
printf(" %s\n", tr->name);
free(tr);
tr = next_tr;
}
}
int get_error_count(void) {
return error_count;
}
void inc_error_count(void) {
error_count++;
}
int set_abort_on_error(int val) {
int old_val = abort_on_error;
abort_on_error = val;
return old_val;
}
int get_abort_on_error(void) {
return abort_on_error;
}
int run_tests(int argc, char **args) {
memset(&test_main, 0, sizeof(test_main));
int arg;
int incl_filter = 0;
int excl_filter = 0;
for (arg = 1; arg < argc; arg++) {
if (strlen(args[arg]) == 0) continue;
if (0 == strcmp("-f", args[arg])) {
incl_filter = 1;
continue;
}
if (0 == strcmp("-e", args[arg])) {
excl_filter = 1;
continue;
}
if (incl_filter) {
strcpy(test_main.incl_filter, args[arg]);
incl_filter = 0;
} else if (excl_filter) {
strcpy(test_main.excl_filter, args[arg]);
excl_filter = 0;
} else {
printf("running tests from %s\n", args[arg]);
FILE *fd = fopen(args[1], "r");
if (fd == NULL) {
printf("%s not found\n", args[arg]);
return -2;
}
test_main.spec = fd;
}
}
DBGT("adding suites...\n");
add_suites();
DBGT("%i tests added\n", test_main.test_count);
if (test_main.spec) {
fclose(test_main.spec);
}
if (test_main.test_count == 0) {
printf("No tests to run\n");
return 0;
}
int fd_success = open("_tests_ok", O_APPEND | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
int fd_bad = open("_tests_fail", O_APPEND | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
DBGT("running tests...\n");
int ok = 0;
int failed = 0;
int stopped = 0;
test *cur_t = test_main.tests;
int i = 1;
while (cur_t) {
cur_t->setup(cur_t);
test *next_test = cur_t->_next;
DBGT("TEST %i/%i : running test %s\n", i, test_main.test_count, cur_t->name);
i++;
int start_error_count = get_error_count();
int res = cur_t->f(cur_t);
if (res == TEST_RES_OK && get_error_count() != start_error_count) {
res = TEST_RES_FAIL;
}
cur_t->test_result = res;
int fd = res == TEST_RES_OK ? fd_success : fd_bad;
write(fd, cur_t->name, strlen(cur_t->name));
write(fd, "\n", 1);
switch (res) {
case TEST_RES_OK:
ok++;
printf(" .. ok\n");
break;
case TEST_RES_FAIL:
failed++;
printf(" .. FAILED\n");
if (test_main.on_stop) test_main.on_stop(cur_t);
add_res(cur_t, &test_main.failed, &test_main.failed_last);
break;
case TEST_RES_ASSERT:
stopped++;
printf(" .. ABORTED\n");
if (test_main.on_stop) test_main.on_stop(cur_t);
add_res(cur_t, &test_main.stopped, &test_main.stopped_last);
break;
}
cur_t->teardown(cur_t);
free(cur_t);
cur_t = next_test;
}
close(fd_success);
close(fd_bad);
DBGT("ran %i tests\n", test_main.test_count);
printf("Test report, %i tests\n", test_main.test_count);
printf("%i succeeded\n", ok);
printf("%i failed\n", failed);
dump_res(&test_main.failed);
printf("%i stopped\n", stopped);
dump_res(&test_main.stopped);
if (ok < test_main.test_count) {
printf("\nFAILED\n");
return -1;
} else {
printf("\nALL TESTS OK\n");
return 0;
}
}

@ -0,0 +1,165 @@
/*
* testrunner.h
*
* Created on: Jun 19, 2013
* Author: petera
*/
/*
file mysuite.c:
SUITE(mysuite)
static void setup(test *t) {}
static void teardown(test *t) {}
TEST(mytest) {
printf("mytest runs now..\n");
return 0;
} TEST_END
SUITE_TESTS(mysuite)
ADD_TEST(mytest)
SUITE_END(mysuite)
file mysuite2.c:
SUITE(mysuite2)
static void setup(test *t) {}
static void teardown(test *t) {}
TEST(mytest2a) {
printf("mytest2a runs now..\n");
return 0;
} TEST_END
TEST(mytest2b) {
printf("mytest2b runs now..\n");
return 0;
} TEST_END
SUITE_TESTS(mysuite2)
ADD_TEST(mytest2a)
ADD_TEST(mytest2b)
SUITE_END(mysuite2)
some other file.c:
void add_suites() {
ADD_SUITE(mysuite);
ADD_SUITE(mysuite2);
}
*/
#ifndef TESTRUNNER_H_
#define TESTRUNNER_H_
#define TEST_RES_OK 0
#define TEST_RES_FAIL -1
#define TEST_RES_ASSERT -2
#define ERREXIT() if (get_abort_on_error()) abort(); else inc_error_count()
struct test_s;
typedef int (*test_f)(struct test_s *t);
typedef struct test_s {
test_f f;
char name[256];
void *data;
void (*setup)(struct test_s *t);
void (*teardown)(struct test_s *t);
struct test_s *_next;
unsigned char test_result;
} test;
typedef struct test_res_s {
char name[256];
struct test_res_s *_next;
} test_res;
#define TEST_CHECK(x) if (!(x)) { \
printf(" TEST FAIL %s:%d\n", __FILE__, __LINE__); \
goto __fail_stop; \
}
#define TEST_CHECK_EQ(x, y) if ((x) != (y)) { \
printf(" TEST FAIL %s:%d, %d != %d\n", __FILE__, __LINE__, (int)(x), (int)(y)); \
goto __fail_stop; \
}
#define TEST_CHECK_NEQ(x, y) if ((x) == (y)) { \
printf(" TEST FAIL %s:%d, %d == %d\n", __FILE__, __LINE__, (int)(x), (int)(y)); \
goto __fail_stop; \
}
#define TEST_CHECK_GT(x, y) if ((x) <= (y)) { \
printf(" TEST FAIL %s:%d, %d <= %d\n", __FILE__, __LINE__, (int)(x), (int)(y)); \
goto __fail_stop; \
}
#define TEST_CHECK_LT(x, y) if ((x) >= (y)) { \
printf(" TEST FAIL %s:%d, %d >= %d\n", __FILE__, __LINE__, (int)(x), (int)(y)); \
goto __fail_stop; \
}
#define TEST_CHECK_GE(x, y) if ((x) < (y)) { \
printf(" TEST FAIL %s:%d, %d < %d\n", __FILE__, __LINE__, (int)(x), (int)(y)); \
goto __fail_stop; \
}
#define TEST_CHECK_LE(x, y) if ((x) > (y)) { \
printf(" TEST FAIL %s:%d, %d > %d\n", __FILE__, __LINE__, (int)(x), (int)(y)); \
goto __fail_stop; \
}
#define TEST_ASSERT(x) if (!(x)) { \
printf(" TEST ASSERT %s:%d\n", __FILE__, __LINE__); \
goto __fail_assert; \
}
#define DBGT(...) printf(__VA_ARGS__)
#define str(s) #s
#define SUITE(sui)
#define SUITE_TESTS(sui) \
void _add_suite_tests_##sui(void) {
#define SUITE_END(sui) \
}
#define ADD_TEST(tf) \
_add_test(__test_##tf, str(tf), setup, teardown, 0);
#define ADD_TEST_NON_DEFAULT(tf) \
_add_test(__test_##tf, str(tf), setup, teardown, 1);
#define ADD_SUITE(sui) \
extern void _add_suite_tests_##sui(void); \
_add_suite_tests_##sui();
#define TEST(tf) \
static int __test_##tf(struct test_s *t) { do
#define TEST_END \
while(0); \
__fail_stop: return TEST_RES_FAIL; \
__fail_assert: return TEST_RES_ASSERT; \
}
int set_abort_on_error(int val);
int get_abort_on_error(void);
int get_error_count(void);
void inc_error_count(void);
void add_suites(void);
void test_init(void (*on_stop)(test *t));
// returns 0 if all tests ok, -1 if any test failed, -2 on badness
int run_tests(int argc, char **args);
void _add_suite(const char *suite_name);
void _add_test(test_f f, char *name, void (*setup)(test *t), void (*teardown)(test *t), int non_default);
#endif /* TESTRUNNER_H_ */

@ -0,0 +1,15 @@
/*
* testsuites.c
*
* Created on: Jun 19, 2013
* Author: petera
*/
#include "testrunner.h"
void add_suites(void) {
//ADD_SUITE(dev_tests);
ADD_SUITE(check_tests);
ADD_SUITE(hydrogen_tests);
ADD_SUITE(bug_tests);
}

@ -0,0 +1,93 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "esp_partition.h"
#include "esp_spiffs.h"
#include "esp_vfs.h"
#include "spiffs_api.h"
static const char* TAG = "SPIFFS";
void spiffs_api_lock(spiffs *fs)
{
(void) xSemaphoreTake(((esp_spiffs_t *)(fs->user_data))->lock, portMAX_DELAY);
}
void spiffs_api_unlock(spiffs *fs)
{
xSemaphoreGive(((esp_spiffs_t *)(fs->user_data))->lock);
}
s32_t spiffs_api_read(spiffs *fs, uint32_t addr, uint32_t size, uint8_t *dst)
{
esp_err_t err = esp_partition_read(((esp_spiffs_t *)(fs->user_data))->partition,
addr, dst, size);
if (err) {
ESP_LOGE(TAG, "failed to read addr %08x, size %08x, err %d", addr, size, err);
return -1;
}
return 0;
}
s32_t spiffs_api_write(spiffs *fs, uint32_t addr, uint32_t size, uint8_t *src)
{
esp_err_t err = esp_partition_write(((esp_spiffs_t *)(fs->user_data))->partition,
addr, src, size);
if (err) {
ESP_LOGE(TAG, "failed to write addr %08x, size %08x, err %d", addr, size, err);
return -1;
}
return 0;
}
s32_t spiffs_api_erase(spiffs *fs, uint32_t addr, uint32_t size)
{
esp_err_t err = esp_partition_erase_range(((esp_spiffs_t *)(fs->user_data))->partition,
addr, size);
if (err) {
ESP_LOGE(TAG, "failed to erase addr %08x, size %08x, err %d", addr, size, err);
return -1;
}
return 0;
}
void spiffs_api_check(spiffs *fs, spiffs_check_type type,
spiffs_check_report report, uint32_t arg1, uint32_t arg2)
{
static const char * spiffs_check_type_str[3] = {
"LOOKUP",
"INDEX",
"PAGE"
};
static const char * spiffs_check_report_str[7] = {
"PROGRESS",
"ERROR",
"FIX INDEX",
"FIX LOOKUP",
"DELETE ORPHANED INDEX",
"DELETE PAGE",
"DELETE BAD FILE"
};
if (report != SPIFFS_CHECK_PROGRESS) {
ESP_LOGE(TAG, "CHECK: type:%s, report:%s, %x:%x", spiffs_check_type_str[type],
spiffs_check_report_str[report], arg1, arg2);
} else {
ESP_LOGV(TAG, "CHECK PROGRESS: report:%s, %x:%x",
spiffs_check_report_str[report], arg1, arg2);
}
}

@ -0,0 +1,57 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "spiffs.h"
#include "esp_vfs.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief SPIFFS definition structure
*/
typedef struct {
spiffs *fs; /*!< Handle to the underlying SPIFFS */
SemaphoreHandle_t lock; /*!< FS lock */
const esp_partition_t* partition; /*!< The partition on which SPIFFS is located */
char base_path[ESP_VFS_PATH_MAX+1]; /*!< Mount point */
bool by_label; /*!< Partition was mounted by label */
spiffs_config cfg; /*!< SPIFFS Mount configuration */
uint8_t *work; /*!< Work Buffer */
uint8_t *fds; /*!< File Descriptor Buffer */
uint32_t fds_sz; /*!< File Descriptor Buffer Length */
uint8_t *cache; /*!< Cache Buffer */
uint32_t cache_sz; /*!< Cache Buffer Length */
} esp_spiffs_t;
s32_t spiffs_api_read(spiffs *fs, uint32_t addr, uint32_t size, uint8_t *dst);
s32_t spiffs_api_write(spiffs *fs, uint32_t addr, uint32_t size, uint8_t *src);
s32_t spiffs_api_erase(spiffs *fs, uint32_t addr, uint32_t size);
void spiffs_api_check(spiffs *fs, spiffs_check_type type,
spiffs_check_report report, uint32_t arg1, uint32_t arg2);
#ifdef __cplusplus
}
#endif

@ -0,0 +1,6 @@
set(COMPONENT_SRCDIRS ".")
set(COMPONENT_ADD_INCLUDEDIRS ".")
set(COMPONENT_REQUIRES unity spiffs)
register_component()

@ -0,0 +1 @@
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

@ -0,0 +1,652 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/unistd.h>
#include "unity.h"
#include "test_utils.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_vfs.h"
#include "esp_spiffs.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_partition.h"
const char* spiffs_test_hello_str = "Hello, World!\n";
const char* spiffs_test_partition_label = "flash_test";
void test_spiffs_create_file_with_text(const char* name, const char* text)
{
FILE* f = fopen(name, "wb");
TEST_ASSERT_NOT_NULL(f);
TEST_ASSERT_TRUE(fputs(text, f) != EOF);
TEST_ASSERT_EQUAL(0, fclose(f));
}
void test_spiffs_overwrite_append(const char* filename)
{
/* Create new file with 'aaaa' */
test_spiffs_create_file_with_text(filename, "aaaa");
/* Append 'bbbb' to file */
FILE *f_a = fopen(filename, "a");
TEST_ASSERT_NOT_NULL(f_a);
TEST_ASSERT_NOT_EQUAL(EOF, fputs("bbbb", f_a));
TEST_ASSERT_EQUAL(0, fclose(f_a));
/* Read back 8 bytes from file, verify it's 'aaaabbbb' */
char buf[10] = { 0 };
FILE *f_r = fopen(filename, "r");
TEST_ASSERT_NOT_NULL(f_r);
TEST_ASSERT_EQUAL(8, fread(buf, 1, 8, f_r));
TEST_ASSERT_EQUAL_STRING_LEN("aaaabbbb", buf, 8);
/* Be sure we're at end of file */
TEST_ASSERT_EQUAL(0, fread(buf, 1, 8, f_r));
TEST_ASSERT_EQUAL(0, fclose(f_r));
/* Overwrite file with 'cccc' */
test_spiffs_create_file_with_text(filename, "cccc");
/* Verify file now only contains 'cccc' */
f_r = fopen(filename, "r");
TEST_ASSERT_NOT_NULL(f_r);
bzero(buf, sizeof(buf));
TEST_ASSERT_EQUAL(4, fread(buf, 1, 8, f_r)); // trying to read 8 bytes, only expecting 4
TEST_ASSERT_EQUAL_STRING_LEN("cccc", buf, 4);
TEST_ASSERT_EQUAL(0, fclose(f_r));
}
void test_spiffs_read_file(const char* filename)
{
FILE* f = fopen(filename, "r");
TEST_ASSERT_NOT_NULL(f);
char buf[32] = { 0 };
int cb = fread(buf, 1, sizeof(buf), f);
TEST_ASSERT_EQUAL(strlen(spiffs_test_hello_str), cb);
TEST_ASSERT_EQUAL(0, strcmp(spiffs_test_hello_str, buf));
TEST_ASSERT_EQUAL(0, fclose(f));
}
void test_spiffs_open_max_files(const char* filename_prefix, size_t files_count)
{
FILE** files = calloc(files_count, sizeof(FILE*));
for (size_t i = 0; i < files_count; ++i) {
char name[32];
snprintf(name, sizeof(name), "%s_%d.txt", filename_prefix, i);
files[i] = fopen(name, "w");
TEST_ASSERT_NOT_NULL(files[i]);
}
/* close everything and clean up */
for (size_t i = 0; i < files_count; ++i) {
fclose(files[i]);
}
free(files);
}
void test_spiffs_lseek(const char* filename)
{
FILE* f = fopen(filename, "wb+");
TEST_ASSERT_NOT_NULL(f);
TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n"));
TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR));
TEST_ASSERT_EQUAL('9', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET));
TEST_ASSERT_EQUAL('3', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END));
TEST_ASSERT_EQUAL('8', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END));
TEST_ASSERT_EQUAL(11, ftell(f));
TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n"));
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END));
TEST_ASSERT_EQUAL(15, ftell(f));
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET));
char buf[20];
TEST_ASSERT_EQUAL(15, fread(buf, 1, sizeof(buf), f));
const char ref_buf[] = "0123456789\nabc\n";
TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1);
TEST_ASSERT_EQUAL(0, fclose(f));
}
void test_spiffs_stat(const char* filename)
{
test_spiffs_create_file_with_text(filename, "foo\n");
struct stat st;
TEST_ASSERT_EQUAL(0, stat(filename, &st));
TEST_ASSERT(st.st_mode & S_IFREG);
TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
}
void test_spiffs_unlink(const char* filename)
{
test_spiffs_create_file_with_text(filename, "unlink\n");
TEST_ASSERT_EQUAL(0, unlink(filename));
TEST_ASSERT_NULL(fopen(filename, "r"));
}
void test_spiffs_rename(const char* filename_prefix)
{
char name_dst[64];
char name_src[64];
snprintf(name_dst, sizeof(name_dst), "%s_dst.txt", filename_prefix);
snprintf(name_src, sizeof(name_src), "%s_src.txt", filename_prefix);
unlink(name_dst);
unlink(name_src);
FILE* f = fopen(name_src, "w+");
TEST_ASSERT_NOT_NULL(f);
const char* str = "0123456789";
for (int i = 0; i < 400; ++i) {
TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f));
}
TEST_ASSERT_EQUAL(0, fclose(f));
TEST_ASSERT_EQUAL(0, rename(name_src, name_dst));
TEST_ASSERT_NULL(fopen(name_src, "r"));
FILE* fdst = fopen(name_dst, "r");
TEST_ASSERT_NOT_NULL(fdst);
TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END));
TEST_ASSERT_EQUAL(4000, ftell(fdst));
TEST_ASSERT_EQUAL(0, fclose(fdst));
}
void test_spiffs_can_opendir(const char* path)
{
char name_dir_file[64];
const char * file_name = "test_opd.txt";
snprintf(name_dir_file, sizeof(name_dir_file), "%s/%s", path, file_name);
unlink(name_dir_file);
test_spiffs_create_file_with_text(name_dir_file, "test_opendir\n");
DIR* dir = opendir(path);
TEST_ASSERT_NOT_NULL(dir);
bool found = false;
while (true) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
if (strcasecmp(de->d_name, file_name) == 0) {
found = true;
break;
}
}
TEST_ASSERT_TRUE(found);
TEST_ASSERT_EQUAL(0, closedir(dir));
unlink(name_dir_file);
}
void test_spiffs_opendir_readdir_rewinddir(const char* dir_prefix)
{
char name_dir_inner_file[64];
char name_dir_inner[64];
char name_dir_file3[64];
char name_dir_file2[64];
char name_dir_file1[64];
snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/inner/3.txt", dir_prefix);
snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/inner", dir_prefix);
snprintf(name_dir_file3, sizeof(name_dir_file2), "%s/boo.bin", dir_prefix);
snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/2.txt", dir_prefix);
snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/1.txt", dir_prefix);
unlink(name_dir_inner_file);
rmdir(name_dir_inner);
unlink(name_dir_file1);
unlink(name_dir_file2);
unlink(name_dir_file3);
rmdir(dir_prefix);
test_spiffs_create_file_with_text(name_dir_file1, "1\n");
test_spiffs_create_file_with_text(name_dir_file2, "2\n");
test_spiffs_create_file_with_text(name_dir_file3, "\01\02\03");
test_spiffs_create_file_with_text(name_dir_inner_file, "3\n");
DIR* dir = opendir(dir_prefix);
TEST_ASSERT_NOT_NULL(dir);
int count = 0;
const char* names[4];
while(count < 4) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
printf("found '%s'\n", de->d_name);
if (strcasecmp(de->d_name, "1.txt") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "1.txt";
++count;
} else if (strcasecmp(de->d_name, "2.txt") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "2.txt";
++count;
} else if (strcasecmp(de->d_name, "inner/3.txt") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "inner/3.txt";
++count;
} else if (strcasecmp(de->d_name, "boo.bin") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "boo.bin";
++count;
} else {
TEST_FAIL_MESSAGE("unexpected directory entry");
}
}
TEST_ASSERT_EQUAL(count, 4);
rewinddir(dir);
struct dirent* de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0]));
seekdir(dir, 3);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3]));
seekdir(dir, 1);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1]));
seekdir(dir, 2);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2]));
TEST_ASSERT_EQUAL(0, closedir(dir));
}
void test_spiffs_readdir_many_files(const char* dir_prefix)
{
const int n_files = 40;
const int n_folders = 4;
unsigned char file_count[n_files * n_folders];
memset(file_count, 0, sizeof(file_count)/sizeof(file_count[0]));
char file_name[ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN];
/* clean stale files before the test */
DIR* dir = opendir(dir_prefix);
if (dir) {
while (true) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
int len = snprintf(file_name, sizeof(file_name), "%s/%s", dir_prefix, de->d_name);
assert(len < sizeof(file_name));
unlink(file_name);
}
}
/* create files */
for (int d = 0; d < n_folders; ++d) {
printf("filling directory %d\n", d);
for (int f = 0; f < n_files; ++f) {
snprintf(file_name, sizeof(file_name), "%s/%d/%d.txt", dir_prefix, d, f);
test_spiffs_create_file_with_text(file_name, file_name);
}
}
/* list files */
for (int d = 0; d < n_folders; ++d) {
printf("listing files in directory %d\n", d);
snprintf(file_name, sizeof(file_name), "%s/%d", dir_prefix, d);
dir = opendir(file_name);
TEST_ASSERT_NOT_NULL(dir);
while (true) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
int file_id;
TEST_ASSERT_EQUAL(1, sscanf(de->d_name, "%d.txt", &file_id));
file_count[file_id + d * n_files]++;
}
closedir(dir);
}
/* check that all created files have been seen */
for (int d = 0; d < n_folders; ++d) {
printf("checking that all files have been found in directory %d\n", d);
for (int f = 0; f < n_files; ++f) {
TEST_ASSERT_EQUAL(1, file_count[f + d * n_files]);
}
}
}
typedef struct {
const char* filename;
bool write;
size_t word_count;
int seed;
SemaphoreHandle_t done;
int result;
} read_write_test_arg_t;
#define READ_WRITE_TEST_ARG_INIT(name, seed_) \
{ \
.filename = name, \
.seed = seed_, \
.word_count = 4096, \
.write = true, \
.done = xSemaphoreCreateBinary() \
}
static void read_write_task(void* param)
{
read_write_test_arg_t* args = (read_write_test_arg_t*) param;
FILE* f = fopen(args->filename, args->write ? "wb" : "rb");
if (f == NULL) {
args->result = ESP_ERR_NOT_FOUND;
goto done;
}
srand(args->seed);
for (size_t i = 0; i < args->word_count; ++i) {
uint32_t val = rand();
if (args->write) {
int cnt = fwrite(&val, sizeof(val), 1, f);
if (cnt != 1) {
ets_printf("E(w): i=%d, cnt=%d val=%d\n\n", i, cnt, val);
args->result = ESP_FAIL;
goto close;
}
} else {
uint32_t rval;
int cnt = fread(&rval, sizeof(rval), 1, f);
if (cnt != 1) {
ets_printf("E(r): i=%d, cnt=%d rval=%d\n\n", i, cnt, rval);
args->result = ESP_FAIL;
goto close;
}
}
}
args->result = ESP_OK;
close:
fclose(f);
done:
xSemaphoreGive(args->done);
vTaskDelay(1);
vTaskDelete(NULL);
}
void test_spiffs_concurrent(const char* filename_prefix)
{
char names[4][64];
for (size_t i = 0; i < 4; ++i) {
snprintf(names[i], sizeof(names[i]), "%s%d", filename_prefix, i + 1);
unlink(names[i]);
}
read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT(names[0], 1);
read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT(names[1], 2);
printf("writing f1 and f2\n");
const int cpuid_0 = 0;
const int cpuid_1 = portNUM_PROCESSORS - 1;
xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, cpuid_0);
xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, cpuid_1);
xSemaphoreTake(args1.done, portMAX_DELAY);
printf("f1 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
xSemaphoreTake(args2.done, portMAX_DELAY);
printf("f2 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
args1.write = false;
args2.write = false;
read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT(names[2], 3);
read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT(names[3], 4);
printf("reading f1 and f2, writing f3 and f4\n");
xTaskCreatePinnedToCore(&read_write_task, "rw3", 2048, &args3, 3, NULL, cpuid_1);
xTaskCreatePinnedToCore(&read_write_task, "rw4", 2048, &args4, 3, NULL, cpuid_0);
xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, cpuid_0);
xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, cpuid_1);
xSemaphoreTake(args1.done, portMAX_DELAY);
printf("f1 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
xSemaphoreTake(args2.done, portMAX_DELAY);
printf("f2 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
xSemaphoreTake(args3.done, portMAX_DELAY);
printf("f3 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args3.result);
xSemaphoreTake(args4.done, portMAX_DELAY);
printf("f4 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args4.result);
vSemaphoreDelete(args1.done);
vSemaphoreDelete(args2.done);
vSemaphoreDelete(args3.done);
vSemaphoreDelete(args4.done);
}
static void test_setup()
{
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = spiffs_test_partition_label,
.max_files = 5,
.format_if_mount_failed = true
};
TEST_ESP_OK(esp_vfs_spiffs_register(&conf));
}
static void test_teardown()
{
TEST_ESP_OK(esp_vfs_spiffs_unregister(spiffs_test_partition_label));
}
TEST_CASE("can initialize SPIFFS in erased partition", "[spiffs]")
{
const esp_partition_t* part = get_test_data_partition();
TEST_ASSERT_NOT_NULL(part);
TEST_ESP_OK(esp_partition_erase_range(part, 0, part->size));
test_setup();
size_t total = 0, used = 0;
TEST_ESP_OK(esp_spiffs_info(spiffs_test_partition_label, &total, &used));
printf("total: %d, used: %d\n", total, used);
TEST_ASSERT_EQUAL(0, used);
test_teardown();
}
TEST_CASE("can format mounted partition", "[spiffs]")
{
// Mount SPIFFS, create file, format, check that the file does not exist.
const esp_partition_t* part = get_test_data_partition();
TEST_ASSERT_NOT_NULL(part);
test_setup();
const char* filename = "/spiffs/hello.txt";
test_spiffs_create_file_with_text(filename, spiffs_test_hello_str);
esp_spiffs_format(part->label);
FILE* f = fopen(filename, "r");
TEST_ASSERT_NULL(f);
test_teardown();
}
TEST_CASE("can format unmounted partition", "[spiffs]")
{
// Mount SPIFFS, create file, unmount. Format. Mount again, check that
// the file does not exist.
const esp_partition_t* part = get_test_data_partition();
TEST_ASSERT_NOT_NULL(part);
test_setup();
const char* filename = "/spiffs/hello.txt";
test_spiffs_create_file_with_text(filename, spiffs_test_hello_str);
test_teardown();
esp_spiffs_format(part->label);
// Don't use test_setup here, need to mount without formatting
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = spiffs_test_partition_label,
.max_files = 5,
.format_if_mount_failed = false
};
TEST_ESP_OK(esp_vfs_spiffs_register(&conf));
FILE* f = fopen(filename, "r");
TEST_ASSERT_NULL(f);
test_teardown();
}
TEST_CASE("can create and write file", "[spiffs]")
{
test_setup();
test_spiffs_create_file_with_text("/spiffs/hello.txt", spiffs_test_hello_str);
test_teardown();
}
TEST_CASE("can read file", "[spiffs]")
{
test_setup();
test_spiffs_create_file_with_text("/spiffs/hello.txt", spiffs_test_hello_str);
test_spiffs_read_file("/spiffs/hello.txt");
test_teardown();
}
TEST_CASE("can open maximum number of files", "[spiffs]")
{
size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr */
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = spiffs_test_partition_label,
.format_if_mount_failed = true,
.max_files = max_files
};
TEST_ESP_OK(esp_vfs_spiffs_register(&conf));
test_spiffs_open_max_files("/spiffs/f", max_files);
TEST_ESP_OK(esp_vfs_spiffs_unregister(spiffs_test_partition_label));
}
TEST_CASE("overwrite and append file", "[spiffs]")
{
test_setup();
test_spiffs_overwrite_append("/spiffs/hello.txt");
test_teardown();
}
TEST_CASE("can lseek", "[spiffs]")
{
test_setup();
test_spiffs_lseek("/spiffs/seek.txt");
test_teardown();
}
TEST_CASE("stat returns correct values", "[spiffs]")
{
test_setup();
test_spiffs_stat("/spiffs/stat.txt");
test_teardown();
}
TEST_CASE("unlink removes a file", "[spiffs]")
{
test_setup();
test_spiffs_unlink("/spiffs/unlink.txt");
test_teardown();
}
TEST_CASE("rename moves a file", "[spiffs]")
{
test_setup();
test_spiffs_rename("/spiffs/move");
test_teardown();
}
TEST_CASE("can opendir root directory of FS", "[spiffs]")
{
test_setup();
test_spiffs_can_opendir("/spiffs");
test_teardown();
}
TEST_CASE("opendir, readdir, rewinddir, seekdir work as expected", "[spiffs]")
{
test_setup();
test_spiffs_opendir_readdir_rewinddir("/spiffs/dir");
test_teardown();
}
TEST_CASE("readdir with large number of files", "[spiffs][timeout=30]")
{
test_setup();
test_spiffs_readdir_many_files("/spiffs/dir2");
test_teardown();
}
TEST_CASE("multiple tasks can use same volume", "[spiffs]")
{
test_setup();
test_spiffs_concurrent("/spiffs/f");
test_teardown();
}
#ifdef CONFIG_SPIFFS_USE_MTIME
TEST_CASE("mtime is updated when file is opened", "[spiffs]")
{
/* Open a file, check that mtime is set correctly */
const char* filename = "/spiffs/time";
test_setup();
time_t t_before_create = time(NULL);
test_spiffs_create_file_with_text(filename, "\n");
time_t t_after_create = time(NULL);
struct stat st;
TEST_ASSERT_EQUAL(0, stat(filename, &st));
printf("mtime=%d\n", (int) st.st_mtime);
TEST_ASSERT(st.st_mtime >= t_before_create
&& st.st_mtime <= t_after_create);
/* Wait a bit, open again, check that mtime is updated */
vTaskDelay(2000 / portTICK_PERIOD_MS);
time_t t_before_open = time(NULL);
FILE *f = fopen(filename, "a");
time_t t_after_open = time(NULL);
TEST_ASSERT_EQUAL(0, fstat(fileno(f), &st));
printf("mtime=%d\n", (int) st.st_mtime);
TEST_ASSERT(st.st_mtime >= t_before_open
&& st.st_mtime <= t_after_open);
fclose(f);
/* Wait a bit, open for reading, check that mtime is not updated */
vTaskDelay(2000 / portTICK_PERIOD_MS);
time_t t_before_open_ro = time(NULL);
f = fopen(filename, "r");
TEST_ASSERT_EQUAL(0, fstat(fileno(f), &st));
printf("mtime=%d\n", (int) st.st_mtime);
TEST_ASSERT(t_before_open_ro > t_after_open
&& st.st_mtime >= t_before_open
&& st.st_mtime <= t_after_open);
fclose(f);
test_teardown();
}
#endif // CONFIG_SPIFFS_USE_MTIME

@ -0,0 +1,98 @@
ifndef COMPONENT
COMPONENT := spiffs
endif
COMPONENT_LIB := lib$(COMPONENT).a
TEST_PROGRAM := test_$(COMPONENT)
STUBS_LIB_DIR := ../../../components/spi_flash/sim/stubs
STUBS_LIB_BUILD_DIR := $(STUBS_LIB_DIR)/build
STUBS_LIB := libstubs.a
SPI_FLASH_SIM_DIR := ../../../components/spi_flash/sim
SPI_FLASH_SIM_BUILD_DIR := $(SPI_FLASH_SIM_DIR)/build
SPI_FLASH_SIM_LIB := libspi_flash.a
include Makefile.files
all: test
ifndef SDKCONFIG
SDKCONFIG_DIR := $(dir $(realpath sdkconfig/sdkconfig.h))
SDKCONFIG := $(SDKCONFIG_DIR)sdkconfig.h
else
SDKCONFIG_DIR := $(dir $(realpath $(SDKCONFIG)))
endif
INCLUDE_FLAGS := $(addprefix -I, $(INCLUDE_DIRS) $(SDKCONFIG_DIR) ../../../tools/catch)
CPPFLAGS += $(INCLUDE_FLAGS) -g -m32
CXXFLAGS += $(INCLUDE_FLAGS) -std=c++11 -g -m32
# Build libraries that this component is dependent on
$(STUBS_LIB_BUILD_DIR)/$(STUBS_LIB): force
$(MAKE) -C $(STUBS_LIB_DIR) lib SDKCONFIG=$(SDKCONFIG)
$(SPI_FLASH_SIM_BUILD_DIR)/$(SPI_FLASH_SIM_LIB): force
$(MAKE) -C $(SPI_FLASH_SIM_DIR) lib SDKCONFIG=$(SDKCONFIG)
# Create target for building this component as a library
CFILES := $(filter %.c, $(SOURCE_FILES))
CPPFILES := $(filter %.cpp, $(SOURCE_FILES))
CTARGET = ${2}/$(patsubst %.c,%.o,$(notdir ${1}))
CPPTARGET = ${2}/$(patsubst %.cpp,%.o,$(notdir ${1}))
ifndef BUILD_DIR
BUILD_DIR := build
endif
OBJ_FILES := $(addprefix $(BUILD_DIR)/, $(filter %.o, $(notdir $(SOURCE_FILES:.cpp=.o) $(SOURCE_FILES:.c=.o))))
define COMPILE_C
$(call CTARGET, ${1}, $(BUILD_DIR)) : ${1} $(SDKCONFIG)
mkdir -p $(BUILD_DIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $(call CTARGET, ${1}, $(BUILD_DIR)) ${1}
endef
define COMPILE_CPP
$(call CPPTARGET, ${1}, $(BUILD_DIR)) : ${1} $(SDKCONFIG)
mkdir -p $(BUILD_DIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $(call CPPTARGET, ${1}, $(BUILD_DIR)) ${1}
endef
$(BUILD_DIR)/$(COMPONENT_LIB): $(OBJ_FILES) $(SDKCONFIG)
mkdir -p $(BUILD_DIR)
$(AR) rcs $@ $^
clean:
$(MAKE) -C $(STUBS_LIB_DIR) clean
$(MAKE) -C $(SPI_FLASH_SIM_DIR) clean
rm -f $(OBJ_FILES) $(TEST_OBJ_FILES) $(TEST_PROGRAM) $(COMPONENT_LIB) partition_table.bin
lib: $(BUILD_DIR)/$(COMPONENT_LIB)
$(foreach cfile, $(CFILES), $(eval $(call COMPILE_C, $(cfile))))
$(foreach cxxfile, $(CPPFILES), $(eval $(call COMPILE_CPP, $(cxxfile))))
# Create target for building this component as a test
TEST_SOURCE_FILES = \
test_spiffs.cpp \
main.cpp \
test_utils.c
TEST_OBJ_FILES = $(filter %.o, $(TEST_SOURCE_FILES:.cpp=.o) $(TEST_SOURCE_FILES:.c=.o))
$(TEST_PROGRAM): lib $(TEST_OBJ_FILES) $(SPI_FLASH_SIM_BUILD_DIR)/$(SPI_FLASH_SIM_LIB) $(STUBS_LIB_BUILD_DIR)/$(STUBS_LIB) partition_table.bin $(SDKCONFIG)
g++ $(LDFLAGS) $(CXXFLAGS) -o $@ $(TEST_OBJ_FILES) -L$(BUILD_DIR) -l:$(COMPONENT_LIB) -L$(SPI_FLASH_SIM_BUILD_DIR) -l:$(SPI_FLASH_SIM_LIB) -L$(STUBS_LIB_BUILD_DIR) -l:$(STUBS_LIB)
test: $(TEST_PROGRAM)
./$(TEST_PROGRAM)
# Create other necessary targets
partition_table.bin: partition_table.csv
python ../../../components/partition_table/gen_esp32part.py --verify $< $@
force:
.PHONY: all lib test clean force

@ -0,0 +1,33 @@
SOURCE_FILES := \
../spiffs_api.c \
$(addprefix ../spiffs/src/, \
spiffs_cache.c \
spiffs_check.c \
spiffs_gc.c \
spiffs_hydrogen.c \
spiffs_nucleus.c \
)
INCLUDE_DIRS := \
. \
.. \
../spiffs/src \
../include \
$(addprefix ../../spi_flash/sim/stubs/, \
app_update/include \
driver/include \
esp32/include \
freertos/include \
log/include \
newlib/include \
sdmmc/include \
vfs/include \
) \
$(addprefix ../../../components/, \
soc/esp32/include \
esp32/include \
bootloader_support/include \
app_update/include \
spi_flash/include \
wear_levelling/include \
)

@ -0,0 +1,17 @@
include $(COMPONENT_PATH)/Makefile.files
COMPONENT_OWNBUILDTARGET := 1
COMPONENT_OWNCLEANTARGET := 1
COMPONENT_ADD_INCLUDEDIRS := $(INCLUDE_DIRS)
.PHONY: build
build: $(SDKCONFIG_HEADER)
$(MAKE) -C $(COMPONENT_PATH) lib SDKCONFIG=$(SDKCONFIG_HEADER) BUILD_DIR=$(COMPONENT_BUILD_DIR) COMPONENT=$(COMPONENT_NAME)
CLEAN_FILES := component_project_vars.mk
.PHONY: clean
clean:
$(summary) RM $(CLEAN_FILES)
rm -f $(CLEAN_FILES)
$(MAKE) -C $(COMPONENT_PATH) clean SDKCONFIG=$(SDKCONFIG_HEADER) BUILD_DIR=$(COMPONENT_BUILD_DIR) COMPONENT=$(COMPONENT_NAME)

@ -0,0 +1,3 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, spiffs, , 2M,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, spiffs, , 2M,

@ -0,0 +1,19 @@
#pragma once
#define CONFIG_SPIFFS_USE_MAGIC_LENGTH 1
#define CONFIG_SPIFFS_MAX_PARTITIONS 3
#define CONFIG_SPIFFS_OBJ_NAME_LEN 32
#define CONFIG_SPIFFS_PAGE_SIZE 256
#define CONFIG_SPIFFS_GC_MAX_RUNS 10
#define CONFIG_SPIFFS_CACHE_WR 1
#define CONFIG_SPIFFS_CACHE 1
#define CONFIG_SPIFFS_META_LENGTH 4
#define CONFIG_SPIFFS_USE_MAGIC 1
#define CONFIG_SPIFFS_PAGE_CHECK 1
#define CONFIG_SPIFFS_USE_MTIME 1
#define CONFIG_WL_SECTOR_SIZE 4096
#define CONFIG_LOG_DEFAULT_LEVEL 3
#define CONFIG_PARTITION_TABLE_OFFSET 0x8000
#define CONFIG_ESPTOOLPY_FLASHSIZE "8MB"

@ -0,0 +1,107 @@
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "esp_partition.h"
#include "spiffs.h"
#include "spiffs_nucleus.h"
#include "spiffs_api.h"
#include "catch.hpp"
extern "C" void init_spi_flash(const char* chip_size, size_t block_size, size_t sector_size, size_t page_size, const char* partition_bin);
TEST_CASE("format disk, open file, write and read file", "[spiffs]")
{
init_spi_flash(CONFIG_ESPTOOLPY_FLASHSIZE, CONFIG_WL_SECTOR_SIZE * 16, CONFIG_WL_SECTOR_SIZE, CONFIG_WL_SECTOR_SIZE, "partition_table.bin");
spiffs fs;
spiffs_config cfg;
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "storage");
// Configure objects needed by SPIFFS
esp_spiffs_t esp_user_data;
esp_user_data.partition = partition;
fs.user_data = (void*)&esp_user_data;
cfg.hal_erase_f = spiffs_api_erase;
cfg.hal_read_f = spiffs_api_read;
cfg.hal_write_f = spiffs_api_write;
cfg.log_block_size = CONFIG_WL_SECTOR_SIZE;
cfg.log_page_size = CONFIG_SPIFFS_PAGE_SIZE;
cfg.phys_addr = 0;
cfg.phys_erase_block = CONFIG_WL_SECTOR_SIZE;
cfg.phys_size = partition->size;
uint32_t max_files = 5;
uint32_t fds_sz = max_files * sizeof(spiffs_fd);
uint32_t work_sz = cfg.log_page_size * 2;
uint32_t cache_sz = sizeof(spiffs_cache) + max_files * (sizeof(spiffs_cache_page)
+ cfg.log_page_size);
uint8_t *work = (uint8_t*) malloc(work_sz);
uint8_t *fds = (uint8_t*) malloc(fds_sz);
uint8_t *cache = (uint8_t*) malloc(cache_sz);
s32_t spiffs_res;
// Special mounting procedure: mount, format, mount as per
// https://github.com/pellepl/spiffs/wiki/Using-spiffs
spiffs_res = SPIFFS_mount(&fs, &cfg, work, fds, fds_sz,
cache, cache_sz, spiffs_api_check);
REQUIRE(spiffs_res == SPIFFS_ERR_NOT_A_FS);
spiffs_res = SPIFFS_format(&fs);
REQUIRE(spiffs_res >= SPIFFS_OK);
spiffs_res = SPIFFS_mount(&fs, &cfg, work, fds, fds_sz,
cache, cache_sz, spiffs_api_check);
REQUIRE(spiffs_res >= SPIFFS_OK);
// Open test file
spiffs_res = SPIFFS_open(&fs, "test.txt", SPIFFS_O_CREAT | SPIFFS_O_RDWR, 0);
REQUIRE(spiffs_res >= SPIFFS_OK);
// Generate data
spiffs_file file = spiffs_res;
uint32_t data_size = 100000;
char *data = (char*) malloc(data_size);
char *read = (char*) malloc(data_size);
for(uint32_t i = 0; i < data_size; i += sizeof(i))
{
*((uint32_t*)(data + i)) = i;
}
s32_t bw;
// Write data to file
spiffs_res = SPIFFS_write(&fs, file, (void*)data, data_size);
REQUIRE(spiffs_res >= SPIFFS_OK);
REQUIRE(spiffs_res == data_size);
// Set the file object pointer to the beginning
spiffs_res = SPIFFS_lseek(&fs, file, 0, SPIFFS_SEEK_SET);
REQUIRE(spiffs_res >= SPIFFS_OK);
// Read the file
spiffs_res = SPIFFS_read(&fs, file, (void*)read, data_size);
REQUIRE(spiffs_res >= SPIFFS_OK);
REQUIRE(spiffs_res == data_size);
// Close the test file
spiffs_res = SPIFFS_close(&fs, file);
REQUIRE(spiffs_res >= SPIFFS_OK);
REQUIRE(memcmp(data, read, data_size) == 0);
// Unmount
SPIFFS_unmount(&fs);
free(read);
free(data);
}

@ -0,0 +1,7 @@
#include "esp_spi_flash.h"
#include "esp_partition.h"
void init_spi_flash(const char* chip_size, size_t block_size, size_t sector_size, size_t page_size, const char* partition_bin)
{
spi_flash_init(chip_size, block_size, sector_size, page_size, partition_bin);
}