[submodule "components/libsodium/libsodium"]
path = components/libsodium/libsodium
url = https://github.com/jedisct1/libsodium.git
+
+[submodule "components/spiffs/spiffs"]
+ path = components/spiffs/spiffs
+ url = https://github.com/pellepl/spiffs.git
const size_t workbuf_size = 4096;
void *workbuf = NULL;
- esp_partition_t *data_partition = (esp_partition_t *)esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label);
+ esp_partition_subtype_t subtype = partition_label ?
+ ESP_PARTITION_SUBTYPE_ANY : ESP_PARTITION_SUBTYPE_DATA_FAT;
+ const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
+ subtype, partition_label);
if (data_partition == NULL) {
ESP_LOGE(TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
return ESP_ERR_NOT_FOUND;
--- /dev/null
+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, will increase flash.
+
+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_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.
+
+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 on mount point.
+
+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.
+
+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
--- /dev/null
+COMPONENT_ADD_INCLUDEDIRS := include
+COMPONENT_PRIV_INCLUDEDIRS := spiffs/src
+COMPONENT_SRCDIRS := . spiffs/src
--- /dev/null
+// 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"
+
+static const char * TAG = "SPIFFS";
+
+/**
+ * @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;
+
+/**
+ * @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 esp_spiffs_t * _efs[CONFIG_SPIFFS_MAX_PARTITIONS];
+
+void spiffs_api_lock(spiffs *fs)
+{
+ xSemaphoreTake(((esp_spiffs_t *)(fs->user_data))->lock, portMAX_DELAY);
+}
+
+void spiffs_api_unlock(spiffs *fs)
+{
+ xSemaphoreGive(((esp_spiffs_t *)(fs->user_data))->lock);
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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);
+ }
+}
+
+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;
+ }
+
+ 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 = g_rom_flashchip.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 mount_on_success = false;
+ int index;
+ 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);
+ if (err != ESP_OK) {
+ return err;
+ }
+ esp_spiffs_free(&_efs[index]);
+ return ESP_OK;
+ } else if (SPIFFS_mounted(_efs[index]->fs)) {
+ SPIFFS_unmount(_efs[index]->fs);
+ mount_on_success = true;
+ }
+ 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);
+ return ESP_FAIL;
+ }
+
+ if (mount_on_success) {
+ 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;
+ }
+ }
+ 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;
+ } else if (m & O_APPEND) {
+ res |= 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 fd = SPIFFS_open(efs->fs, path, spiffs_mode_conv(flags), mode);
+ if (fd < 0) {
+ errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs));
+ SPIFFS_clearerr(efs->fs);
+ return -1;
+ }
+ 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;
+ 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;
+ 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;
+ 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;
+ }
+ const char * item_name = (const char *)out.name;
+ size_t plen = strlen(dir->path);
+ if (plen > 1) {
+ if (strncasecmp(dir->path, (const char *)out.name, plen) || out.name[plen] != '/' || !out.name[plen+1]) {
+ return vfs_spiffs_readdir_r(ctx, pdir, entry, out_dirent);
+ }
+ 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;
+}
--- /dev/null
+// 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"
+
+/**
+ * @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);
+
+#endif /* _ESP_SPIFFS_H_ */
--- /dev/null
+/*
+ * 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 CONGIG_SPIFFS_DBG
+#define SPIFFS_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
+#else
+#define SPIFFS_DBG(...)
+#endif
+#if CONGIG_SPIFFS_API_DBG
+#define SPIFFS_API_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
+#else
+#define SPIFFS_API_DBG(...)
+#endif
+#if CONGIG_SPIFFS_DBG
+#define SPIFFS_GC_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
+#else
+#define SPIFFS_GC_DBG(...)
+#endif
+#if CONGIG_SPIFFS_CACHE_DBG
+#define SPIFFS_CACHE_DBG(...) ESP_LOGD(SPIFFS_TAG, __VA_ARGS__)
+#else
+#define SPIFFS_CACHE_DBG(...)
+#endif
+#if CONGIG_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 + 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)
+#define SPIFFS_OBJ_META_LEN (0)
+
+// 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_ */
--- /dev/null
+Subproject commit 794f0478d2aa9c978c3844da6e97f14239a1e061
--- /dev/null
+COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
--- /dev/null
+// 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);
+ 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));
+}
+
+
+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");
+
+ xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0);
+ xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 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, 1);
+ xTaskCreatePinnedToCore(&read_write_task, "rw4", 2048, &args4, 3, NULL, 0);
+ xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0);
+ xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 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 format 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 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("multiple tasks can use same volume", "[spiffs]")
+{
+ test_setup();
+ test_spiffs_concurrent("/spiffs/f");
+ test_teardown();
+}
../components/spi_flash/include/esp_spi_flash.h \
../components/spi_flash/include/esp_partition.h \
../components/bootloader_support/include/esp_flash_encrypt.h \
+ ## SPIFFS
+ ../components/spiffs/include/esp_spiffs.h \
## SD/MMC Card Host
## NOTE: for three lines below header_file.inc is not used
../components/sdmmc/include/sdmmc_cmd.h \
Virtual Filesystem <vfs>
FAT Filesystem <fatfs>
Wear Levelling <wear-levelling>
+ SPIFFS Filesystem <spiffs>
Example code for this API section is provided in :example:`storage` directory of ESP-IDF examples.
--- /dev/null
+SPIFFS Filesystem\r
+=================\r
+\r
+Overview\r
+--------\r
+\r
+SPIFFS is a file system intended for SPI NOR flash devices on embedded targets.\r
+It supports wear leveling, file system consistency checks and more.\r
+\r
+Notes\r
+-----\r
+\r
+ - Presently, spiffs does not support directories. It produces a flat structure. If SPIFFS is mounted under ``/spiffs`` creating a file with path ``/spiffs/tmp/myfile.txt`` will create a file called ``/tmp/myfile.txt`` in SPIFFS, instead of ``myfile.txt`` under directory ``/spiffs/tmp``. \r
+ - It is not a realtime stack. One write operation might last much longer than another.\r
+ - Presently, it does not detect or handle bad blocks.\r
+\r
+Tools\r
+-----\r
+\r
+Host-Side tools for creating SPIFS partition images exist and one such tool is `mkspiffs <https://github.com/igrr/mkspiffs>`_.\r
+You can use it to create image from a given folder and then flash that image with ``esptool.py``\r
+\r
+To do that you need to obtain some parameters:\r
+\r
+- Block Size: 4096 (standard for SPI Flash)\r
+- Page Size: 256 (standard for SPI Flash)\r
+- Image Size: Size of the partition in bytes (can be obtained from partition table)\r
+- Partition Offset: Starting address of the partition (can be obtained from partition table)\r
+\r
+To pack a folder into 1 Megabyte image::\r
+\r
+ mkspiffs -c [src_folder] -b 4096 -p 256 -s 0x100000 spiffs.bin\r
+\r
+To flash the image to ESP32 at offset 0x110000::\r
+\r
+ python esptool.py --chip esp32 --port [port] --baud [baud] write_flash -z 0x110000 spiffs.bin\r
+\r
+See also\r
+--------\r
+\r
+- :doc:`Partition Table documentation <../../api-guides/partition-tables>`\r
+\r
+Application Example\r
+-------------------\r
+\r
+An example for using SPIFFS is provided in :example:`storage/spiffs` directory. This example initializes and mounts SPIFFS partition, and writes and reads data from it using POSIX and C library APIs. See README.md file in the example directory for more information.\r
+\r
+High level API Reference\r
+------------------------\r
+\r
+* :component_file:`spiffs/include/esp_spiffs.h`\r
+\r
+.. include:: /_build/inc/esp_spiffs.inc\r
--- /dev/null
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := spiffs
+
+include $(IDF_PATH)/make/project.mk
+
--- /dev/null
+# SPIFFS example
+
+This example demonstrates how to use SPIFFS with ESP32. Example does the following steps:
+
+1. Use an "all-in-one" `esp_vfs_spiffs_register` function to:
+ - initialize SPIFFS,
+ - mount SPIFFS filesystem using SPIFFS library (and format, if the filesystem can not be mounted),
+ - register SPIFFS filesystem in VFS, enabling C standard library and POSIX functions to be used.
+2. Create a file using `fopen` and write to it using `fprintf`.
+3. Rename the file. Before renaming, check if destination file already exists using `stat` function, and remove it using `unlink` function.
+4. Open renamed file for reading, read back the line, and print it to the terminal.
+
+## Example output
+
+Here is an example console output. In this case `format_if_mount_failed` parameter was set to `true` in the source code. SPIFFS was unformatted, so the initial mount has failed. SPIFFS was then formatted, and mounted again.
+
+```
+I (195) example: Initializing SPIFFS
+E (195) SPIFFS: mount failed, -10025. formatting...
+I (4525) example: Opening file
+I (4635) example: File written
+I (4685) example: Renaming file
+I (4735) example: Reading file
+I (4735) example: Read from file: 'Hello World!'
+I (4735) example: SPIFFS unmounted
+```
+
--- /dev/null
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
--- /dev/null
+/* SPIFFS filesystem example.
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/unistd.h>
+#include <sys/stat.h>
+#include "esp_err.h"
+#include "esp_log.h"
+#include "esp_spiffs.h"
+
+static const char *TAG = "example";
+
+void app_main(void)
+{
+ ESP_LOGI(TAG, "Initializing SPIFFS");
+
+ esp_vfs_spiffs_conf_t conf = {
+ .base_path = "/spiffs",
+ .partition_label = NULL,
+ .max_files = 5,
+ .format_if_mount_failed = true
+ };
+
+ // Use settings defined above to initialize and mount SPIFFS filesystem.
+ // Note: esp_vfs_spiffs_register is an all-in-one convenience function.
+ esp_err_t ret = esp_vfs_spiffs_register(&conf);
+
+ if (ret != ESP_OK) {
+ if (ret == ESP_FAIL) {
+ ESP_LOGE(TAG, "Failed to mount or format filesystem");
+ } else if (ret == ESP_ERR_NOT_FOUND) {
+ ESP_LOGE(TAG, "Failed to find SPIFFS partition");
+ } else {
+ ESP_LOGE(TAG, "Failed to initialize SPIFFS (%d)", ret);
+ }
+ return;
+ }
+
+ size_t total = 0, used = 0;
+ ret = esp_spiffs_info(NULL, &total, &used);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to get SPIFFS partition information");
+ } else {
+ ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
+ }
+
+ // Use POSIX and C standard library functions to work with files.
+ // First create a file.
+ ESP_LOGI(TAG, "Opening file");
+ FILE* f = fopen("/spiffs/hello.txt", "w");
+ if (f == NULL) {
+ ESP_LOGE(TAG, "Failed to open file for writing");
+ return;
+ }
+ fprintf(f, "Hello World!\n");
+ fclose(f);
+ ESP_LOGI(TAG, "File written");
+
+ // Check if destination file exists before renaming
+ struct stat st;
+ if (stat("/spiffs/foo.txt", &st) == 0) {
+ // Delete it if it exists
+ unlink("/spiffs/foo.txt");
+ }
+
+ // Rename original file
+ ESP_LOGI(TAG, "Renaming file");
+ if (rename("/spiffs/hello.txt", "/spiffs/foo.txt") != 0) {
+ ESP_LOGE(TAG, "Rename failed");
+ return;
+ }
+
+ // Open renamed file for reading
+ ESP_LOGI(TAG, "Reading file");
+ f = fopen("/spiffs/foo.txt", "r");
+ if (f == NULL) {
+ ESP_LOGE(TAG, "Failed to open file for reading");
+ return;
+ }
+ char line[64];
+ fgets(line, sizeof(line), f);
+ fclose(f);
+ // strip newline
+ char* pos = strchr(line, '\n');
+ if (pos) {
+ *pos = '\0';
+ }
+ ESP_LOGI(TAG, "Read from file: '%s'", line);
+
+ // All done, unmount partition and disable SPIFFS
+ esp_vfs_spiffs_unregister(NULL);
+ ESP_LOGI(TAG, "SPIFFS unmounted");
+}
--- /dev/null
+# 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, , 0xF0000,
--- /dev/null
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
+CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000
+CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
+CONFIG_APP_OFFSET=0x10000
components/libsodium/libsodium @GENERAL_MIRROR_SERVER@/idf/libsodium.git ALLOW_TO_SYNC_FROM_PUBLIC
components/micro-ecc/micro-ecc @GENERAL_MIRROR_SERVER@/idf/micro-ecc.git ALLOW_TO_SYNC_FROM_PUBLIC
components/nghttp/nghttp2 @GENERAL_MIRROR_SERVER@/idf/nghttp2.git ALLOW_TO_SYNC_FROM_PUBLIC
+components/spiffs/spiffs @GENERAL_MIRROR_SERVER@/idf/spiffs.git ALLOW_TO_SYNC_FROM_PUBLIC
third-party/mruby @GENERAL_MIRROR_SERVER@/idf/mruby.git ALLOW_TO_SYNC_FROM_PUBLIC
third-party/neverbleed @GENERAL_MIRROR_SERVER@/idf/neverbleed.git ALLOW_TO_SYNC_FROM_PUBLIC
# (done this way so tests can run in 2MB of flash.)
ota_0, 0, ota_0, , 64K
ota_1, 0, ota_1, , 64K
-# flash_test partition used for SPI flash tests and WL FAT partition
-# 528K is the minimal size needed to create a FAT partition
-# (128 sectors for FAT + 4 sectors for WL)
+# flash_test partition used for SPI flash tests, WL FAT tests, and SPIFFS tests
flash_test, data, fat, , 528K