]> granicus.if.org Git - esp-idf/commitdiff
Add SPIFFS Component to IDF
authorme-no-dev <hristo@espressif.com>
Thu, 7 Sep 2017 14:37:59 +0000 (17:37 +0300)
committerme-no-dev <hristo@espressif.com>
Mon, 11 Sep 2017 16:56:40 +0000 (19:56 +0300)
21 files changed:
.gitmodules
components/fatfs/src/vfs_fat_spiflash.c
components/spiffs/Kconfig [new file with mode: 0644]
components/spiffs/component.mk [new file with mode: 0644]
components/spiffs/esp_spiffs.c [new file with mode: 0644]
components/spiffs/include/esp_spiffs.h [new file with mode: 0644]
components/spiffs/include/spiffs_config.h [new file with mode: 0755]
components/spiffs/spiffs [new submodule]
components/spiffs/test/component.mk [new file with mode: 0644]
components/spiffs/test/test_spiffs.c [new file with mode: 0644]
docs/Doxyfile
docs/api-reference/storage/index.rst
docs/api-reference/storage/spiffs.rst [new file with mode: 0644]
examples/storage/spiffs/Makefile [new file with mode: 0644]
examples/storage/spiffs/README.md [new file with mode: 0644]
examples/storage/spiffs/main/component.mk [new file with mode: 0644]
examples/storage/spiffs/main/spiffs_example_main.c [new file with mode: 0644]
examples/storage/spiffs/partitions_example.csv [new file with mode: 0644]
examples/storage/spiffs/sdkconfig.defaults [new file with mode: 0644]
tools/ci/mirror-list.txt
tools/unit-test-app/partition_table_unit_test_app.csv

index c54435224f6bb54fce57e90834bea1ebbdac0337..e118821b73c51d2c51d68a9abd720c2ccea37389 100644 (file)
@@ -29,3 +29,7 @@
 [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
index b9f80354a14fcad755de8f93240ccba525005914..2e90468d2b0dcac62c81fe73ed0f0be5abcedd6c 100644 (file)
@@ -32,7 +32,10 @@ esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
     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;
diff --git a/components/spiffs/Kconfig b/components/spiffs/Kconfig
new file mode 100644 (file)
index 0000000..d82ceec
--- /dev/null
@@ -0,0 +1,139 @@
+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
diff --git a/components/spiffs/component.mk b/components/spiffs/component.mk
new file mode 100644 (file)
index 0000000..624b219
--- /dev/null
@@ -0,0 +1,3 @@
+COMPONENT_ADD_INCLUDEDIRS := include
+COMPONENT_PRIV_INCLUDEDIRS := spiffs/src
+COMPONENT_SRCDIRS := . spiffs/src
diff --git a/components/spiffs/esp_spiffs.c b/components/spiffs/esp_spiffs.c
new file mode 100644 (file)
index 0000000..f731f5b
--- /dev/null
@@ -0,0 +1,766 @@
+// 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;
+}
diff --git a/components/spiffs/include/esp_spiffs.h b/components/spiffs/include/esp_spiffs.h
new file mode 100644 (file)
index 0000000..9a1f12c
--- /dev/null
@@ -0,0 +1,94 @@
+// 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_ */
diff --git a/components/spiffs/include/spiffs_config.h b/components/spiffs/include/spiffs_config.h
new file mode 100755 (executable)
index 0000000..e0c9d7f
--- /dev/null
@@ -0,0 +1,313 @@
+/*
+ * 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_ */
diff --git a/components/spiffs/spiffs b/components/spiffs/spiffs
new file mode 160000 (submodule)
index 0000000..794f047
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 794f0478d2aa9c978c3844da6e97f14239a1e061
diff --git a/components/spiffs/test/component.mk b/components/spiffs/test/component.mk
new file mode 100644 (file)
index 0000000..ce464a2
--- /dev/null
@@ -0,0 +1 @@
+COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
diff --git a/components/spiffs/test/test_spiffs.c b/components/spiffs/test/test_spiffs.c
new file mode 100644 (file)
index 0000000..d60e4a5
--- /dev/null
@@ -0,0 +1,506 @@
+// 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();
+}
index fbc15cd2679e02046be5632839b2f64374573781..628db925acff9e267c01fddc10ecabbdae5f2890 100644 (file)
@@ -91,6 +91,8 @@ INPUT = \
     ../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 \
index 9c4394bd5a25534cadb471b7f63b17d4a4184c57..9c9215b09f7c221cbc6ec2d457a46b49e88217f4 100644 (file)
@@ -10,6 +10,7 @@ Storage API
    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.
diff --git a/docs/api-reference/storage/spiffs.rst b/docs/api-reference/storage/spiffs.rst
new file mode 100644 (file)
index 0000000..d463024
--- /dev/null
@@ -0,0 +1,53 @@
+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
diff --git a/examples/storage/spiffs/Makefile b/examples/storage/spiffs/Makefile
new file mode 100644 (file)
index 0000000..4523423
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# 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
+
diff --git a/examples/storage/spiffs/README.md b/examples/storage/spiffs/README.md
new file mode 100644 (file)
index 0000000..8c048fa
--- /dev/null
@@ -0,0 +1,27 @@
+# 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
+```
+
diff --git a/examples/storage/spiffs/main/component.mk b/examples/storage/spiffs/main/component.mk
new file mode 100644 (file)
index 0000000..a98f634
--- /dev/null
@@ -0,0 +1,4 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
diff --git a/examples/storage/spiffs/main/spiffs_example_main.c b/examples/storage/spiffs/main/spiffs_example_main.c
new file mode 100644 (file)
index 0000000..d2ead73
--- /dev/null
@@ -0,0 +1,99 @@
+/* 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");
+}
diff --git a/examples/storage/spiffs/partitions_example.csv b/examples/storage/spiffs/partitions_example.csv
new file mode 100644 (file)
index 0000000..6d9ee2e
--- /dev/null
@@ -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,  ,        0xF0000, 
diff --git a/examples/storage/spiffs/sdkconfig.defaults b/examples/storage/spiffs/sdkconfig.defaults
new file mode 100644 (file)
index 0000000..f30f322
--- /dev/null
@@ -0,0 +1,5 @@
+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
index 549359f60e175bd2d5365d994f450aa3d14ff9bc..d3972aa5d5a4ac3e6e3882e2e55ec844da6542f3 100644 (file)
@@ -6,5 +6,6 @@ components/esptool_py/esptool                       @GENERAL_MIRROR_SERVER@/idf/
 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
index ea43d0791cbd9873f69a7395088548d8e105e9c6..46173719b5826c8dfd84ae31b359928d2710895d 100644 (file)
@@ -10,7 +10,5 @@ factory,    0,    0,       0x10000, 0x140000
 # (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