aboutsummaryrefslogtreecommitdiff
path: root/libiot
diff options
context:
space:
mode:
authorbhgv <bhgv.empire@gmail.com>2020-05-10 02:59:23 +0300
committerbhgv <bhgv.empire@gmail.com>2020-05-10 02:59:23 +0300
commit31b4edc67b75658ce5e2d41f2fc87331f4b26d49 (patch)
treea7b6ea659fe62e0a7239f29170024f524595fb4d /libiot
parentc76314f0f38f4ed028610a6db4452879a556b35f (diff)
a try to add support of FreeRTOS riscV-64 (k210 cpu). first step
Diffstat (limited to 'libiot')
-rw-r--r--libiot/freertos/adds.c317
-rw-r--r--libiot/freertos/adds.h198
-rw-r--r--libiot/freertos/mkfile3
-rw-r--r--libiot/hal/mkfile3
-rw-r--r--libiot/hal/w25qxx.c288
-rw-r--r--libiot/hal/w25qxx.h88
-rw-r--r--libiot/include/esp_err.h148
-rw-r--r--libiot/lfs/lfs.c2592
-rw-r--r--libiot/lfs/lfs.h495
-rw-r--r--libiot/lfs/lfs_util.c31
-rw-r--r--libiot/lfs/lfs_util.h186
-rw-r--r--libiot/linenoise/linenoise.c637
-rw-r--r--libiot/linenoise/linenoise.h56
-rw-r--r--libiot/mkfile26
-rw-r--r--libiot/pthread/_pthread.c743
-rw-r--r--libiot/pthread/_pthread.h241
-rw-r--r--libiot/pthread/attr.c244
-rw-r--r--libiot/pthread/cond.c204
-rw-r--r--libiot/pthread/create.c55
-rw-r--r--libiot/pthread/join.c70
-rw-r--r--libiot/pthread/key.c163
-rw-r--r--libiot/pthread/kill.c60
-rw-r--r--libiot/pthread/mkfile10
-rw-r--r--libiot/pthread/mutex.c241
-rw-r--r--libiot/pthread/once.c67
-rw-r--r--libiot/pthread/self.c53
-rw-r--r--libiot/pthread/test/pthread-cleanup.c200
-rw-r--r--libiot/pthread/test/pthread-cond.c107
-rw-r--r--libiot/pthread/test/pthread-join.c85
-rw-r--r--libiot/pthread/test/pthread-once.c68
-rw-r--r--libiot/ramfs/ramfs.c1088
-rw-r--r--libiot/ramfs/ramfs.h274
-rw-r--r--libiot/spiffs/k210_spiffs.c190
-rw-r--r--libiot/spiffs/k210_spiffs.h59
-rw-r--r--libiot/spiffs/mkfile9
-rw-r--r--libiot/spiffs/spiffs.h816
-rw-r--r--libiot/spiffs/spiffs_cache.c314
-rw-r--r--libiot/spiffs/spiffs_check.c995
-rw-r--r--libiot/spiffs/spiffs_config.h360
-rw-r--r--libiot/spiffs/spiffs_gc.c606
-rw-r--r--libiot/spiffs/spiffs_hydrogen.c1405
-rw-r--r--libiot/spiffs/spiffs_nucleus.c2327
-rw-r--r--libiot/spiffs/spiffs_nucleus.h797
-rw-r--r--libiot/sys/console.c211
-rw-r--r--libiot/sys/console.h61
-rw-r--r--libiot/sys/history.c199
-rw-r--r--libiot/sys/history.h57
-rw-r--r--libiot/sys/list.c429
-rw-r--r--libiot/sys/list.h85
-rw-r--r--libiot/sys/mkfile5
-rw-r--r--libiot/sys/mutex.c155
-rw-r--r--libiot/sys/mutex.h69
-rw-r--r--libiot/sys/panic.c52
-rw-r--r--libiot/sys/panic.h51
-rw-r--r--libiot/vfs/fat.c453
-rw-r--r--libiot/vfs/include/esp_vfs.h424
-rw-r--r--libiot/vfs/include/sys/dirent.h56
-rw-r--r--libiot/vfs/lfs.c949
-rw-r--r--libiot/vfs/mkfile7
-rw-r--r--libiot/vfs/ramfs.c634
-rw-r--r--libiot/vfs/spiffs.c1423
-rw-r--r--libiot/vfs/vfs.h128
62 files changed, 22367 insertions, 0 deletions
diff --git a/libiot/freertos/adds.c b/libiot/freertos/adds.c
new file mode 100644
index 0000000..3eede33
--- /dev/null
+++ b/libiot/freertos/adds.c
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, FreeRTOS additions
+ *
+ */
+
+//#include "luartos.h"
+
+//#include "esp_attr.h"
+
+//#include "lua.h"
+#include <FreeRTOS.h>
+#include <task.h>
+#include "adds.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <stdlib.h>
+
+#include <malloc.h>
+
+#define THREAD_LOCAL_STORAGE_POINTER_ID 0
+
+// Reference to lua_thread, which is created in app_main
+extern pthread_t lua_thread;
+
+// Global state
+static lua_State *gL = NULL;
+
+static int compare(const void *a, const void *b) {
+ if (((task_info_t *)a)->task_type < ((task_info_t *)b)->task_type) {
+ return 1;
+ } else if (((task_info_t*)a)->task_type > ((task_info_t *)b)->task_type) {
+ return -1;
+ } else {
+ if (((task_info_t *)a)->core < ((task_info_t *)b)->core) {
+ return -1;
+ } else if (((task_info_t *)a)->core > ((task_info_t *)b)->core) {
+ return 1;
+ } else {
+ return strcmp(((task_info_t *)a)->name, ((task_info_t *)b)->name);
+ }
+ }
+}
+
+void uxSetThreadId(UBaseType_t id) {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ // Store thread id into Lua RTOS specific TCB parts
+ lua_rtos_tcb->threadid = id;
+ }
+}
+
+UBaseType_t uxGetThreadId() {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+ int threadid = 0;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ // Get current thread od from Lua RTOS specific TCB parts
+ threadid = lua_rtos_tcb->threadid;
+ }
+
+ return threadid;
+}
+
+void uxSetThreadStatus(TaskHandle_t h, pthread_status_t status) {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ // Store thread id into Lua RTOS specific TCB parts
+ lua_rtos_tcb->status = status;
+ }
+}
+
+void uxSetLThread(lthread_t *lthread) {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ lua_rtos_tcb->lthread = lthread;
+ }
+}
+
+void uxSetLuaState(lua_State* L) {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ // Store current lua state into Lua RTOS specific TCB parts
+ if (!lua_rtos_tcb->lthread) {
+ lua_rtos_tcb->lthread = calloc(1, sizeof(lthread_t));
+ if (lua_rtos_tcb->lthread == NULL) {
+ panic();
+ }
+ //assert(lua_rtos_tcb->lthread);
+ }
+ lua_rtos_tcb->lthread->L = L;
+ }
+
+ // If this is the lua thread, store state in global state
+ if ((uxGetThreadId() == lua_thread) && (gL == NULL)) {
+ gL = L;
+ }
+}
+
+lua_State* pvGetLuaState() {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+ lua_State *L = NULL;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(NULL, THREAD_LOCAL_STORAGE_POINTER_ID))) {
+
+ // Get current lua state from Lua RTOS specific TCB parts
+ if (lua_rtos_tcb->lthread) {
+ L = lua_rtos_tcb->lthread->L;
+ }
+ }
+
+ if (L == NULL) {
+ L = gL;
+ }
+
+ return L;
+}
+
+lthread_t *pvGetLThread() {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+ lthread_t *lthread = NULL;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(NULL, THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ lthread = lua_rtos_tcb->lthread;
+ }
+
+ return lthread;
+}
+
+uint32_t uxGetSignaled(TaskHandle_t h) {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+ int signaled = 0;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(h, THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ // Get current signeled mask from Lua RTOS specific TCB parts
+ signaled = lua_rtos_tcb->signaled;
+ }
+
+ return signaled;
+}
+
+void uxSetSignaled(TaskHandle_t h, int s) {
+ lua_rtos_tcb_t *lua_rtos_tcb;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(h, THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ // Store current signaled mask into Lua RTOS specific TCB parts
+ lua_rtos_tcb->signaled = s;
+ }
+}
+
+uint8_t ucGetCoreID(TaskHandle_t h) {
+ tskTCB_t *task = (tskTCB_t *)h;
+
+ return task->xCoreID;
+}
+
+int uxGetStack(TaskHandle_t h) {
+ tskTCB_t *task = (tskTCB_t *)h;
+
+ return task->pxEndOfStack - task->pxStack + 4;
+}
+
+task_info_t *GetTaskInfo() {
+ tskTCB_t *ctask;
+ task_info_t *info;
+ uint8_t task_type;
+ lua_rtos_tcb_t *lua_rtos_tcb;
+ TaskStatus_t *status_array;
+ UBaseType_t task_num = 0;
+ UBaseType_t start_task_num = 0;
+ uint32_t total_runtime = 0;
+
+ //Allocate status_array
+ start_task_num = uxTaskGetNumberOfTasks();
+
+ status_array = (TaskStatus_t *)calloc(start_task_num, sizeof(TaskStatus_t));
+ if (!status_array) {
+ return NULL;
+ }
+
+//#ifndef CONFIG_FREERTOS_USE_TRACE_FACILITY
+//#warning Please enable CONFIG_FREERTOS_USE_TRACE_FACILITY to support thread.list
+// free(status_array);
+// return NULL;
+#ifndef configUSE_TRACE_FACILITY
+#warning Please enable configUSE_TRACE_FACILITY to support thread.list
+ free(status_array);
+ return NULL;
+#else
+ task_num = uxTaskGetSystemState(status_array, (start_task_num), &total_runtime);
+ // For percentage calculations.
+ total_runtime /= 100UL;
+
+ info = (task_info_t *)calloc(task_num + 1, sizeof(task_info_t));
+ if (!info) {
+ free(status_array);
+ return NULL;
+ }
+#endif
+
+ for(int i = 0; i <task_num; i++){
+ if (status_array[i].eCurrentState == eDeleted) {
+ continue;
+ }
+
+ // Get the task TCB
+ ctask = (tskTCB_t *)status_array[i].xHandle;
+
+ // Get the task type
+ // 0: freertos task
+ // 1: pthread task
+ // 2: lua thread task
+ task_type = 0;
+
+ info[i].thid = 0;
+ info[i].status = 0;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(status_array[i].xHandle, THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ // Task has Lua RTOS specific TCB parts
+ if (lua_rtos_tcb->lthread) {
+ // Lua thread
+ task_type = 2;
+ } else {
+ // pthread
+ task_type = 1;
+ }
+
+ info[i].thid = lua_rtos_tcb->threadid;
+ info[i].lthread = lua_rtos_tcb->lthread;
+ info[i].status = lua_rtos_tcb->status;
+ }
+
+ // Populate info item
+ info[i].prio = status_array[i].uxCurrentPriority;
+ info[i].task_type = task_type;
+ info[i].core = ctask->xCoreID;
+
+ // Some system tasks shows 255!!
+ if (info[i].core > 1) {
+ info[i].core = 0;
+ }
+
+ info[i].free_stack = uxTaskGetStackHighWaterMark(status_array[i].xHandle);
+ info[i].stack_size = ctask->pxEndOfStack - ctask->pxStack + 4;
+ memcpy(info[i].name, status_array[i].pcTaskName, configMAX_TASK_NAME_LEN);
+
+ info[i].cpu_usage = 0;
+#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
+ if( total_runtime > 0 ) {
+ // only gives valid values if CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is defined
+ info[i].cpu_usage = status_array[i].ulRunTimeCounter / total_runtime;
+ }
+#endif
+
+ }
+
+ free(status_array);
+
+ qsort (info, task_num, sizeof (task_info_t), compare);
+
+ return info;
+}
diff --git a/libiot/freertos/adds.h b/libiot/freertos/adds.h
new file mode 100644
index 0000000..6134c5d
--- /dev/null
+++ b/libiot/freertos/adds.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, FreeRTOS additions
+ *
+ */
+
+#ifndef _FREERTOS_ADDS_H
+#define _FREERTOS_ADDS_H
+
+//#include "lua.h"
+
+#include <FreeRTOS.h>
+#include <task.h>
+
+#include <stdint.h>
+#include <pthread.h>
+
+typedef void* lua_State;
+
+/* Value that can be assigned to the eNotifyState member of the TCB. */
+typedef enum
+{
+ eNotWaitingNotification = 0,
+ eWaitingNotification,
+ eNotified
+} eNotifyValue_t;
+
+typedef struct
+{
+ volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
+
+ #if ( portUSING_MPU_WRAPPERS == 1 )
+ xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
+ #endif
+
+ ListItem_t xGenericListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
+ ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
+ UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
+ StackType_t *pxStack; /*< Points to the start of the stack. */
+ char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
+ BaseType_t xCoreID; /*< Core this task is pinned to */
+ /* If this moves around (other than pcTaskName size changes), please change the define in xtensa_vectors.S as well. */
+ #if ( portSTACK_GROWTH > 0 || configENABLE_TASK_SNAPSHOT == 1 )
+ StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */
+ #endif
+
+ #if ( portCRITICAL_NESTING_IN_TCB == 1 )
+ UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
+ uint32_t uxOldInterruptState; /*< Interrupt state before the outer taskEnterCritical was called */
+ #endif
+
+ #if ( configUSE_TRACE_FACILITY == 1 )
+ UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
+ UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
+ #endif
+
+ #if ( configUSE_MUTEXES == 1 )
+ UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
+ UBaseType_t uxMutexesHeld;
+ #endif
+
+ #if ( configUSE_APPLICATION_TASK_TAG == 1 )
+ TaskHookFunction_t pxTaskTag;
+ #endif
+
+ #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
+ void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
+ #if ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
+ TlsDeleteCallbackFunction_t pvThreadLocalStoragePointersDelCallback[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
+ #endif
+ #endif
+
+ #if ( configGENERATE_RUN_TIME_STATS == 1 )
+ uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
+ #endif
+
+ #if ( configUSE_NEWLIB_REENTRANT == 1 )
+ /* Allocate a Newlib reent structure that is specific to this task.
+ Note Newlib support has been included by popular demand, but is not
+ used by the FreeRTOS maintainers themselves. FreeRTOS is not
+ responsible for resulting newlib operation. User must be familiar with
+ newlib and must provide system-wide implementations of the necessary
+ stubs. Be warned that (at the time of writing) the current newlib design
+ implements a system-wide malloc() that must be provided with locks. */
+ struct _reent xNewLib_reent;
+ #endif
+
+ #if ( configUSE_TASK_NOTIFICATIONS == 1 )
+ volatile uint32_t ulNotifiedValue;
+ volatile eNotifyValue_t eNotifyState;
+ #endif
+
+ /* See the comments above the definition of
+ tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
+ #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
+ uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
+ #endif
+
+} tskTCB_t;
+
+typedef enum {
+ StatusRunning = 1,
+ StatusSuspended = 2
+} pthread_status_t;
+
+typedef struct lthread {
+ lua_State *PL; // Parent thread
+ lua_State *L; // Thread state
+ int function_ref;
+ int thread_ref;
+ int status;
+} lthread_t;
+
+typedef struct {
+ uint8_t task_type;
+ char name[configMAX_TASK_NAME_LEN];
+ uint8_t core;
+ uint8_t prio;
+ size_t stack_size;
+ size_t free_stack;
+ uint32_t cpu_usage;
+ int thid;
+ lthread_t *lthread;
+ pthread_status_t status;
+} task_info_t;
+
+typedef struct {
+ int32_t threadid;
+ uint32_t signaled;
+ pthread_status_t status;
+ struct lthread *lthread;
+} lua_rtos_tcb_t;
+
+// This macro is not present in all FreeRTOS ports. In Lua RTOS is used in some places
+// in the source code shared by all the supported platforms. ESP32 don't define this macro,
+// so we define it for reuse code beetween platforms.
+/* //{}
+#define portEND_SWITCHING_ISR(xSwitchRequired) \
+if (xSwitchRequired) { \
+ _frxt_setup_switch(); \
+}
+*/
+
+UBaseType_t uxGetTaskId();
+UBaseType_t uxGetThreadId();
+void uxSetThreadStatus(TaskHandle_t h, pthread_status_t status);
+void uxSetThreadId(UBaseType_t id);
+void uxSetLThread(lthread_t *lthread);
+lthread_t *pvGetLThread();
+void uxSetSignaled(TaskHandle_t h, int s);
+uint32_t uxGetSignaled(TaskHandle_t h);
+TaskHandle_t xGetCurrentTask();
+uint8_t ucGetCoreID(TaskHandle_t h);
+int uxGetStack(TaskHandle_t h);
+task_info_t *GetTaskInfo();
+void uxSetLuaState(lua_State* L);
+lua_State* pvGetLuaState();
+
+#endif
diff --git a/libiot/freertos/mkfile b/libiot/freertos/mkfile
new file mode 100644
index 0000000..a56b48c
--- /dev/null
+++ b/libiot/freertos/mkfile
@@ -0,0 +1,3 @@
+OFILES=\
+ $OFILES\
+ freertos/adds.$O\
diff --git a/libiot/hal/mkfile b/libiot/hal/mkfile
new file mode 100644
index 0000000..73b16cb
--- /dev/null
+++ b/libiot/hal/mkfile
@@ -0,0 +1,3 @@
+OFILES=\
+ $OFILES\
+ hal/w25qxx.$O\
diff --git a/libiot/hal/w25qxx.c b/libiot/hal/w25qxx.c
new file mode 100644
index 0000000..a169617
--- /dev/null
+++ b/libiot/hal/w25qxx.c
@@ -0,0 +1,288 @@
+/* Copyright 2018 Canaan Inc.
+ *
+ * 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 <devices.h>
+#include "w25qxx.h"
+
+uintptr_t spi_adapter;
+uintptr_t spi_stand;
+
+static enum w25qxx_status_t w25qxx_receive_data(uint8_t* cmd_buff, uint8_t cmd_len, uint8_t* rx_buff, uint32_t rx_len)
+{
+ spi_dev_transfer_sequential(spi_stand, (uint8_t *)cmd_buff, cmd_len, (uint8_t *)rx_buff, rx_len);
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_receive_data_enhanced(uint32_t* cmd_buff, uint8_t cmd_len, uint8_t* rx_buff, uint32_t rx_len)
+{
+ memcpy(rx_buff, cmd_buff, cmd_len);
+ io_read(spi_adapter, (uint8_t *)rx_buff, rx_len);
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_send_data(uintptr_t file, uint8_t* cmd_buff, uint8_t cmd_len, uint8_t* tx_buff, uint32_t tx_len)
+{
+ configASSERT(cmd_len);
+ uint8_t* tmp_buf = malloc(cmd_len + tx_len);
+ memcpy(tmp_buf, cmd_buff, cmd_len);
+ if (tx_len)
+ memcpy(tmp_buf + cmd_len, tx_buff, tx_len);
+ io_write(file, (uint8_t *)tmp_buf, cmd_len + tx_len);
+ free(tmp_buf);
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_write_enable(void)
+{
+ uint8_t cmd[1] = {WRITE_ENABLE};
+
+ w25qxx_send_data(spi_stand, cmd, 1, 0, 0);
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_read_status_reg1(uint8_t* reg_data)
+{
+ uint8_t cmd[1] = {READ_REG1};
+ uint8_t data[1];
+
+ w25qxx_receive_data(cmd, 1, data, 1);
+ *reg_data = data[0];
+ return W25QXX_OK;
+}
+static enum w25qxx_status_t w25qxx_read_status_reg2(uint8_t* reg_data)
+{
+ uint8_t cmd[1] = {READ_REG2};
+ uint8_t data[1];
+
+ w25qxx_receive_data(cmd, 1, data, 1);
+ *reg_data = data[0];
+ return W25QXX_OK;
+}
+static enum w25qxx_status_t w25qxx_write_status_reg(uint8_t reg1_data, uint8_t reg2_data)
+{
+ uint8_t cmd[3] = {WRITE_REG1, reg1_data, reg2_data};
+
+ w25qxx_write_enable();
+ w25qxx_send_data(spi_stand, cmd, 3, 0, 0);
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_enable_quad_mode(void)
+{
+ uint8_t reg_data;
+
+ w25qxx_read_status_reg2(&reg_data);
+ if (!(reg_data & REG2_QUAL_MASK))
+ {
+ reg_data |= REG2_QUAL_MASK;
+ w25qxx_write_status_reg(0x00, reg_data);
+ }
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_is_busy(void)
+{
+ uint8_t status;
+
+ w25qxx_read_status_reg1(&status);
+ if (status & REG1_BUSY_MASK)
+ return W25QXX_BUSY;
+ return W25QXX_OK;
+}
+
+enum w25qxx_status_t w25qxx_sector_erase(uint32_t addr)
+{
+ uint8_t cmd[4] = {SECTOR_ERASE};
+
+ cmd[1] = (uint8_t)(addr >> 16);
+ cmd[2] = (uint8_t)(addr >> 8);
+ cmd[3] = (uint8_t)(addr);
+ w25qxx_write_enable();
+ w25qxx_send_data(spi_stand, cmd, 4, 0, 0);
+ return W25QXX_OK;
+}
+
+enum w25qxx_status_t w25qxx_read_id(uint8_t *manuf_id, uint8_t *device_id)
+{
+ uint8_t cmd[4] = {READ_ID, 0x00, 0x00, 0x00};
+ uint8_t data[2] = {0};
+
+ w25qxx_receive_data(cmd, 4, data, 2);
+ *manuf_id = data[0];
+ *device_id = data[1];
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_read_data_less_64kb(uint32_t addr, uint8_t* data_buf, uint32_t length)
+{
+ uint32_t cmd[2];
+
+ switch (WORK_TRANS_MODE)
+ {
+ case SPI_FF_DUAL:
+ *(((uint8_t*)cmd) + 0) = FAST_READ_DUAL_OUTPUT;
+ *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0);
+ *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
+ *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16);
+ w25qxx_receive_data_enhanced(cmd, 4, data_buf, length);
+ break;
+ case SPI_FF_QUAD:
+ *(((uint8_t*)cmd) + 0) = FAST_READ_QUAL_OUTPUT;
+ *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0);
+ *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
+ *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16);
+ w25qxx_receive_data_enhanced(cmd, 4, data_buf, length);
+ break;
+ case SPI_FF_STANDARD:
+ default:
+ *(((uint8_t*)cmd) + 0) = READ_DATA;
+ *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 16);
+ *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
+ *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 0);
+ w25qxx_receive_data((uint8_t*)cmd, 4, data_buf, length);
+ break;
+ }
+ return W25QXX_OK;
+}
+
+enum w25qxx_status_t w25qxx_read_data(uint32_t addr, uint8_t* data_buf, uint32_t length)
+{
+ uint32_t len;
+
+ while (length)
+ {
+ len = length >= 0x010000 ? 0x010000 : length;
+ w25qxx_read_data_less_64kb(addr, data_buf, len);
+ addr += len;
+ data_buf += len;
+ length -= len;
+ }
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_page_program(uint32_t addr, uint8_t* data_buf, uint32_t length)
+{
+ uint32_t cmd[2];
+ w25qxx_write_enable();
+ if (WORK_TRANS_MODE == SPI_FF_QUAD)
+ {
+ *(((uint8_t*)cmd) + 0) = QUAD_PAGE_PROGRAM;
+ *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0);
+ *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
+ *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16);
+ w25qxx_send_data(spi_adapter, (uint8_t*)cmd, 4, data_buf, length);
+ }
+ else
+ {
+ *(((uint8_t*)cmd) + 0) = PAGE_PROGRAM;
+ *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 16);
+ *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
+ *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 0);
+ w25qxx_send_data(spi_stand, (uint8_t*)cmd, 4, data_buf, length);
+ }
+ while (w25qxx_is_busy() == W25QXX_BUSY)
+ ;
+ return W25QXX_OK;
+}
+
+static enum w25qxx_status_t w25qxx_sector_program(uint32_t addr, uint8_t* data_buf)
+{
+ uint8_t index;
+
+ for (index = 0; index < w25qxx_FLASH_PAGE_NUM_PER_SECTOR; index++)
+ {
+ w25qxx_page_program(addr, data_buf, w25qxx_FLASH_PAGE_SIZE);
+ addr += w25qxx_FLASH_PAGE_SIZE;
+ data_buf += w25qxx_FLASH_PAGE_SIZE;
+ }
+ return W25QXX_OK;
+}
+
+enum w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t* data_buf, uint32_t length)
+{
+ uint32_t sector_addr, sector_offset, sector_remain, write_len, index;
+ uint8_t swap_buf[w25qxx_FLASH_SECTOR_SIZE];
+ uint8_t *pread, *pwrite;
+
+ while (length)
+ {
+ sector_addr = addr & (~(w25qxx_FLASH_SECTOR_SIZE - 1));
+ sector_offset = addr & (w25qxx_FLASH_SECTOR_SIZE - 1);
+ sector_remain = w25qxx_FLASH_SECTOR_SIZE - sector_offset;
+ write_len = length < sector_remain ? length : sector_remain;
+ w25qxx_read_data(sector_addr, swap_buf, w25qxx_FLASH_SECTOR_SIZE);
+ pread = swap_buf + sector_offset;
+ pwrite = data_buf;
+ for (index = 0; index < write_len; index++)
+ {
+ if ((*pwrite) != ((*pwrite) & (*pread)))
+ {
+ w25qxx_sector_erase(sector_addr);
+ while (w25qxx_is_busy() == W25QXX_BUSY)
+ ;
+ break;
+ }
+ pwrite++;
+ pread++;
+ }
+ if (write_len == w25qxx_FLASH_SECTOR_SIZE)
+ w25qxx_sector_program(sector_addr, data_buf);
+ else
+ {
+ pread = swap_buf + sector_offset;
+ pwrite = data_buf;
+ for (index = 0; index < write_len; index++)
+ *pread++ = *pwrite++;
+ w25qxx_sector_program(sector_addr, swap_buf);
+ }
+ length -= write_len;
+ addr += write_len;
+ data_buf += write_len;
+ }
+ return W25QXX_OK;
+}
+
+enum w25qxx_status_t w25qxx_init(uintptr_t spi_in)
+{
+ uint8_t manuf_id, device_id;
+ spi_stand = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_STANDARD, CHIP_SELECT, FRAME_LENGTH);
+ spi_dev_set_clock_rate(spi_stand, 800000);
+ w25qxx_read_id(&manuf_id, &device_id);
+ if ((manuf_id != 0xEF && manuf_id != 0xC8) || (device_id != 0x17 && device_id != 0x16))
+ {
+ printf("manuf_id:0x%02x, device_id:0x%02x\n", manuf_id, device_id);
+ }
+ printf("manuf_id:0x%02x, device_id:0x%02x\n", manuf_id, device_id);
+ switch (WORK_TRANS_MODE)
+ {
+ case SPI_FF_DUAL:
+ spi_adapter = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_DUAL, CHIP_SELECT, FRAME_LENGTH);
+ spi_dev_config_non_standard(spi_adapter, INSTRUCTION_LENGTH, ADDRESS_LENGTH, WAIT_CYCLE, SPI_AITM_STANDARD);
+ break;
+ case SPI_FF_QUAD:
+ spi_adapter = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_QUAD, CHIP_SELECT, FRAME_LENGTH);
+ spi_dev_config_non_standard(spi_adapter, INSTRUCTION_LENGTH, ADDRESS_LENGTH, WAIT_CYCLE, SPI_AITM_STANDARD);
+ w25qxx_enable_quad_mode();
+ break;
+ case SPI_FF_STANDARD:
+ default:
+ spi_adapter = spi_stand;
+ break;
+ }
+ return W25QXX_OK;
+}
+
diff --git a/libiot/hal/w25qxx.h b/libiot/hal/w25qxx.h
new file mode 100644
index 0000000..5ae32ca
--- /dev/null
+++ b/libiot/hal/w25qxx.h
@@ -0,0 +1,88 @@
+/* Copyright 2018 Canaan Inc.
+ *
+ * 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 _W25QXX_H
+#define _W25QXX_H
+#include <stdint.h>
+
+/* clang-format off */
+#define WORK_TRANS_MODE SPI_FF_STANDARD
+/* #define WORK_TRANS_MODE SPI_FF_DUAL */
+/* #define WORK_TRANS_MODE SPI_FF_QUAD */
+
+#define CHIP_SELECT 1
+#define WAIT_CYCLE 8
+#define FRAME_LENGTH 8
+#define INSTRUCTION_LENGTH 8
+#define ADDRESS_LENGTH 24
+
+#define SPI_SLAVE_SELECT (0x01)
+
+#define w25qxx_FLASH_PAGE_SIZE 256
+#define w25qxx_FLASH_SECTOR_SIZE 4096
+#define w25qxx_FLASH_PAGE_NUM_PER_SECTOR 16
+#define w25qxx_FLASH_CHIP_SIZE (16777216 UL)
+
+#define WRITE_ENABLE 0x06
+#define WRITE_DISABLE 0x04
+#define READ_REG1 0x05
+#define READ_REG2 0x35
+#define READ_REG3 0x15
+#define WRITE_REG1 0x01
+#define WRITE_REG2 0x31
+#define WRITE_REG3 0x11
+#define READ_DATA 0x03
+#define FAST_READ 0x0B
+#define FAST_READ_DUAL_OUTPUT 0x3B
+#define FAST_READ_QUAL_OUTPUT 0x6B
+#define FAST_READ_DUAL_IO 0xBB
+#define FAST_READ_QUAL_IO 0xEB
+#define DUAL_READ_RESET 0xFFFF
+#define QUAL_READ_RESET 0xFF
+#define PAGE_PROGRAM 0x02
+#define QUAD_PAGE_PROGRAM 0x32
+#define SECTOR_ERASE 0x20
+#define BLOCK_32K_ERASE 0x52
+#define BLOCK_64K_ERASE 0xD8
+#define CHIP_ERASE 0x60
+#define READ_ID 0x90
+#define ENABLE_QPI 0x38
+#define EXIT_QPI 0xFF
+#define ENABLE_RESET 0x66
+#define RESET_DEVICE 0x99
+
+#define REG1_BUSY_MASK 0x01
+#define REG2_QUAL_MASK 0x02
+
+#define LETOBE(x) ((x >> 24) | ((x & 0x00FF0000) >> 8) | ((x & 0x0000FF00) << 8) | (x << 24))
+/* clang-format on */
+
+/**
+ * @brief w25qxx operating status enumerate
+ */
+enum w25qxx_status_t
+{
+ W25QXX_OK = 0,
+ W25QXX_BUSY,
+ W25QXX_ERROR,
+};
+
+enum w25qxx_status_t w25qxx_init(uintptr_t spi_in);
+enum w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t* data_buf, uint32_t length);
+enum w25qxx_status_t w25qxx_read_data(uint32_t addr, uint8_t* data_buf, uint32_t length);
+
+enum w25qxx_status_t w25qxx_sector_erase(uint32_t addr);
+
+#endif
+
diff --git a/libiot/include/esp_err.h b/libiot/include/esp_err.h
new file mode 100644
index 0000000..794f32e
--- /dev/null
+++ b/libiot/include/esp_err.h
@@ -0,0 +1,148 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#pragma once
+
+#include <stdint.h>
+#include <stdio.h>
+#include <assert.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int32_t esp_err_t;
+
+/* Definitions for error constants. */
+#define ESP_OK 0 /*!< esp_err_t value indicating success (no error) */
+#define ESP_FAIL -1 /*!< Generic esp_err_t code indicating failure */
+
+#define ESP_ERR_NO_MEM 0x101 /*!< Out of memory */
+#define ESP_ERR_INVALID_ARG 0x102 /*!< Invalid argument */
+#define ESP_ERR_INVALID_STATE 0x103 /*!< Invalid state */
+#define ESP_ERR_INVALID_SIZE 0x104 /*!< Invalid size */
+#define ESP_ERR_NOT_FOUND 0x105 /*!< Requested resource not found */
+#define ESP_ERR_NOT_SUPPORTED 0x106 /*!< Operation or feature not supported */
+#define ESP_ERR_TIMEOUT 0x107 /*!< Operation timed out */
+#define ESP_ERR_INVALID_RESPONSE 0x108 /*!< Received response was invalid */
+#define ESP_ERR_INVALID_CRC 0x109 /*!< CRC or checksum was invalid */
+#define ESP_ERR_INVALID_VERSION 0x10A /*!< Version was invalid */
+#define ESP_ERR_INVALID_MAC 0x10B /*!< MAC address was invalid */
+
+#define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */
+#define ESP_ERR_MESH_BASE 0x4000 /*!< Starting number of MESH error codes */
+
+/**
+ * @brief Returns string for esp_err_t error codes
+ *
+ * This function finds the error code in a pre-generated lookup-table and
+ * returns its string representation.
+ *
+ * The function is generated by the Python script
+ * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t
+ * error is modified, created or removed from the IDF project.
+ *
+ * @param code esp_err_t error code
+ * @return string error message
+ */
+const char *esp_err_to_name(esp_err_t code);
+
+/**
+ * @brief Returns string for esp_err_t and system error codes
+ *
+ * This function finds the error code in a pre-generated lookup-table of
+ * esp_err_t errors and returns its string representation. If the error code
+ * is not found then it is attempted to be found among system errors.
+ *
+ * The function is generated by the Python script
+ * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t
+ * error is modified, created or removed from the IDF project.
+ *
+ * @param code esp_err_t error code
+ * @param[out] buf buffer where the error message should be written
+ * @param buflen Size of buffer buf. At most buflen bytes are written into the buf buffer (including the terminating null byte).
+ * @return buf containing the string error message
+ */
+const char *esp_err_to_name_r(esp_err_t code, char *buf, size_t buflen);
+
+/** @cond */
+void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression) __attribute__((noreturn));
+
+/** @cond */
+void _esp_error_check_failed_without_abort(esp_err_t rc, const char *file, int line, const char *function, const char *expression);
+
+#ifndef __ASSERT_FUNC
+/* This won't happen on IDF, which defines __ASSERT_FUNC in assert.h, but it does happen when building on the host which
+ uses /usr/include/assert.h or equivalent.
+*/
+#ifdef __ASSERT_FUNCTION
+#define __ASSERT_FUNC __ASSERT_FUNCTION /* used in glibc assert.h */
+#else
+#define __ASSERT_FUNC "??"
+#endif
+#endif
+/** @endcond */
+
+/**
+ * Macro which can be used to check the error code,
+ * and terminate the program in case the code is not ESP_OK.
+ * Prints the error code, error location, and the failed statement to serial output.
+ *
+ * Disabled if assertions are disabled.
+ */
+#ifdef NDEBUG
+#define ESP_ERROR_CHECK(x) do { \
+ esp_err_t __err_rc = (x); \
+ (void) sizeof(__err_rc); \
+ } while(0)
+#elif defined(CONFIG_OPTIMIZATION_ASSERTIONS_SILENT)
+#define ESP_ERROR_CHECK(x) do { \
+ esp_err_t __err_rc = (x); \
+ if (__err_rc != ESP_OK) { \
+ abort(); \
+ } \
+ } while(0)
+#else
+#define ESP_ERROR_CHECK(x) do { \
+ esp_err_t __err_rc = (x); \
+ if (__err_rc != ESP_OK) { \
+ _esp_error_check_failed(__err_rc, __FILE__, __LINE__, \
+ __ASSERT_FUNC, #x); \
+ } \
+ } while(0)
+#endif
+
+/**
+ * Macro which can be used to check the error code. Prints the error code, error location, and the failed statement to
+ * serial output.
+ * In comparison with ESP_ERROR_CHECK(), this prints the same error message but isn't terminating the program.
+ */
+#ifdef NDEBUG
+#define ESP_ERROR_CHECK_WITHOUT_ABORT(x) ({ \
+ esp_err_t __err_rc = (x); \
+ __err_rc; \
+ })
+#else
+#define ESP_ERROR_CHECK_WITHOUT_ABORT(x) ({ \
+ esp_err_t __err_rc = (x); \
+ if (__err_rc != ESP_OK) { \
+ _esp_error_check_failed_without_abort(__err_rc, __FILE__, __LINE__, \
+ __ASSERT_FUNC, #x); \
+ } \
+ __err_rc; \
+ })
+#endif //NDEBUG
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libiot/lfs/lfs.c b/libiot/lfs/lfs.c
new file mode 100644
index 0000000..ebbe7c9
--- /dev/null
+++ b/libiot/lfs/lfs.c
@@ -0,0 +1,2592 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include "lfs.h"
+#include "lfs_util.h"
+
+#include <inttypes.h>
+
+
+/// Caching block device operations ///
+static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
+ const lfs_cache_t *pcache, lfs_block_t block,
+ lfs_off_t off, void *buffer, lfs_size_t size) {
+ uint8_t *data = buffer;
+ LFS_ASSERT(block < lfs->cfg->block_count);
+
+ while (size > 0) {
+ if (pcache && block == pcache->block && off >= pcache->off &&
+ off < pcache->off + lfs->cfg->prog_size) {
+ // is already in pcache?
+ lfs_size_t diff = lfs_min(size,
+ lfs->cfg->prog_size - (off-pcache->off));
+ memcpy(data, &pcache->buffer[off-pcache->off], diff);
+
+ data += diff;
+ off += diff;
+ size -= diff;
+ continue;
+ }
+
+ if (block == rcache->block && off >= rcache->off &&
+ off < rcache->off + lfs->cfg->read_size) {
+ // is already in rcache?
+ lfs_size_t diff = lfs_min(size,
+ lfs->cfg->read_size - (off-rcache->off));
+ memcpy(data, &rcache->buffer[off-rcache->off], diff);
+
+ data += diff;
+ off += diff;
+ size -= diff;
+ continue;
+ }
+
+ if (off % lfs->cfg->read_size == 0 && size >= lfs->cfg->read_size) {
+ // bypass cache?
+ lfs_size_t diff = size - (size % lfs->cfg->read_size);
+ int err = lfs->cfg->read(lfs->cfg, block, off, data, diff);
+ if (err) {
+ return err;
+ }
+
+ data += diff;
+ off += diff;
+ size -= diff;
+ continue;
+ }
+
+ // load to cache, first condition can no longer fail
+ rcache->block = block;
+ rcache->off = off - (off % lfs->cfg->read_size);
+ int err = lfs->cfg->read(lfs->cfg, rcache->block,
+ rcache->off, rcache->buffer, lfs->cfg->read_size);
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int lfs_cache_cmp(lfs_t *lfs, lfs_cache_t *rcache,
+ const lfs_cache_t *pcache, lfs_block_t block,
+ lfs_off_t off, const void *buffer, lfs_size_t size) {
+ const uint8_t *data = buffer;
+
+ for (lfs_off_t i = 0; i < size; i++) {
+ uint8_t c;
+ int err = lfs_cache_read(lfs, rcache, pcache,
+ block, off+i, &c, 1);
+ if (err) {
+ return err;
+ }
+
+ if (c != data[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int lfs_cache_crc(lfs_t *lfs, lfs_cache_t *rcache,
+ const lfs_cache_t *pcache, lfs_block_t block,
+ lfs_off_t off, lfs_size_t size, uint32_t *crc) {
+ for (lfs_off_t i = 0; i < size; i++) {
+ uint8_t c;
+ int err = lfs_cache_read(lfs, rcache, pcache,
+ block, off+i, &c, 1);
+ if (err) {
+ return err;
+ }
+
+ lfs_crc(crc, &c, 1);
+ }
+
+ return 0;
+}
+
+static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) {
+ // do not zero, cheaper if cache is readonly or only going to be
+ // written with identical data (during relocates)
+ (void)lfs;
+ rcache->block = 0xffffffff;
+}
+
+static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) {
+ // zero to avoid information leak
+ memset(pcache->buffer, 0xff, lfs->cfg->prog_size);
+ pcache->block = 0xffffffff;
+}
+
+static int lfs_cache_flush(lfs_t *lfs,
+ lfs_cache_t *pcache, lfs_cache_t *rcache) {
+ if (pcache->block != 0xffffffff) {
+ int err = lfs->cfg->prog(lfs->cfg, pcache->block,
+ pcache->off, pcache->buffer, lfs->cfg->prog_size);
+ if (err) {
+ return err;
+ }
+
+ if (rcache) {
+ int res = lfs_cache_cmp(lfs, rcache, NULL, pcache->block,
+ pcache->off, pcache->buffer, lfs->cfg->prog_size);
+ if (res < 0) {
+ return res;
+ }
+
+ if (!res) {
+ return LFS_ERR_CORRUPT;
+ }
+ }
+
+ lfs_cache_zero(lfs, pcache);
+ }
+
+ return 0;
+}
+
+static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
+ lfs_cache_t *rcache, lfs_block_t block,
+ lfs_off_t off, const void *buffer, lfs_size_t size) {
+ const uint8_t *data = buffer;
+ LFS_ASSERT(block < lfs->cfg->block_count);
+
+ while (size > 0) {
+ if (block == pcache->block && off >= pcache->off &&
+ off < pcache->off + lfs->cfg->prog_size) {
+ // is already in pcache?
+ lfs_size_t diff = lfs_min(size,
+ lfs->cfg->prog_size - (off-pcache->off));
+ memcpy(&pcache->buffer[off-pcache->off], data, diff);
+
+ data += diff;
+ off += diff;
+ size -= diff;
+
+ if (off % lfs->cfg->prog_size == 0) {
+ // eagerly flush out pcache if we fill up
+ int err = lfs_cache_flush(lfs, pcache, rcache);
+ if (err) {
+ return err;
+ }
+ }
+
+ continue;
+ }
+
+ // pcache must have been flushed, either by programming and
+ // entire block or manually flushing the pcache
+ LFS_ASSERT(pcache->block == 0xffffffff);
+
+ if (off % lfs->cfg->prog_size == 0 &&
+ size >= lfs->cfg->prog_size) {
+ // bypass pcache?
+ lfs_size_t diff = size - (size % lfs->cfg->prog_size);
+ int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff);
+ if (err) {
+ return err;
+ }
+
+ if (rcache) {
+ int res = lfs_cache_cmp(lfs, rcache, NULL,
+ block, off, data, diff);
+ if (res < 0) {
+ return res;
+ }
+
+ if (!res) {
+ return LFS_ERR_CORRUPT;
+ }
+ }
+
+ data += diff;
+ off += diff;
+ size -= diff;
+ continue;
+ }
+
+ // prepare pcache, first condition can no longer fail
+ pcache->block = block;
+ pcache->off = off - (off % lfs->cfg->prog_size);
+ }
+
+ return 0;
+}
+
+
+/// General lfs block device operations ///
+static int lfs_bd_read(lfs_t *lfs, lfs_block_t block,
+ lfs_off_t off, void *buffer, lfs_size_t size) {
+ // if we ever do more than writes to alternating pairs,
+ // this may need to consider pcache
+ return lfs_cache_read(lfs, &lfs->rcache, NULL,
+ block, off, buffer, size);
+}
+
+static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block,
+ lfs_off_t off, const void *buffer, lfs_size_t size) {
+ return lfs_cache_prog(lfs, &lfs->pcache, NULL,
+ block, off, buffer, size);
+}
+
+static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block,
+ lfs_off_t off, const void *buffer, lfs_size_t size) {
+ return lfs_cache_cmp(lfs, &lfs->rcache, NULL, block, off, buffer, size);
+}
+
+static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block,
+ lfs_off_t off, lfs_size_t size, uint32_t *crc) {
+ return lfs_cache_crc(lfs, &lfs->rcache, NULL, block, off, size, crc);
+}
+
+static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
+ return lfs->cfg->erase(lfs->cfg, block);
+}
+
+static int lfs_bd_sync(lfs_t *lfs) {
+ lfs_cache_drop(lfs, &lfs->rcache);
+
+ int err = lfs_cache_flush(lfs, &lfs->pcache, NULL);
+ if (err) {
+ return err;
+ }
+
+ return lfs->cfg->sync(lfs->cfg);
+}
+
+
+/// Internal operations predeclared here ///
+int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
+static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir);
+static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
+ lfs_dir_t *parent, lfs_entry_t *entry);
+static int lfs_moved(lfs_t *lfs, const void *e);
+static int lfs_relocate(lfs_t *lfs,
+ const lfs_block_t oldpair[2], const lfs_block_t newpair[2]);
+int lfs_deorphan(lfs_t *lfs);
+
+
+/// Block allocator ///
+static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
+ lfs_t *lfs = p;
+
+ lfs_block_t off = ((block - lfs->free.off)
+ + lfs->cfg->block_count) % lfs->cfg->block_count;
+
+ if (off < lfs->free.size) {
+ lfs->free.buffer[off / 32] |= 1U << (off % 32);
+ }
+
+ return 0;
+}
+
+static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
+ while (true) {
+ while (lfs->free.i != lfs->free.size) {
+ lfs_block_t off = lfs->free.i;
+ lfs->free.i += 1;
+ lfs->free.ack -= 1;
+
+ if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
+ // found a free block
+ *block = (lfs->free.off + off) % lfs->cfg->block_count;
+
+ // eagerly find next off so an alloc ack can
+ // discredit old lookahead blocks
+ while (lfs->free.i != lfs->free.size &&
+ (lfs->free.buffer[lfs->free.i / 32]
+ & (1U << (lfs->free.i % 32)))) {
+ lfs->free.i += 1;
+ lfs->free.ack -= 1;
+ }
+
+ return 0;
+ }
+ }
+
+ // check if we have looked at all blocks since last ack
+ if (lfs->free.ack == 0) {
+ LFS_WARN("No more free space %" PRIu32,
+ lfs->free.i + lfs->free.off);
+ return LFS_ERR_NOSPC;
+ }
+
+ lfs->free.off = (lfs->free.off + lfs->free.size)
+ % lfs->cfg->block_count;
+ lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->free.ack);
+ lfs->free.i = 0;
+
+ // find mask of free blocks from tree
+ memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
+ int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs);
+ if (err) {
+ return err;
+ }
+ }
+}
+
+static void lfs_alloc_ack(lfs_t *lfs) {
+ lfs->free.ack = lfs->cfg->block_count;
+}
+
+
+/// Endian swapping functions ///
+static void lfs_dir_fromle32(struct lfs_disk_dir *d) {
+ d->rev = lfs_fromle32(d->rev);
+ d->size = lfs_fromle32(d->size);
+ d->tail[0] = lfs_fromle32(d->tail[0]);
+ d->tail[1] = lfs_fromle32(d->tail[1]);
+}
+
+static void lfs_dir_tole32(struct lfs_disk_dir *d) {
+ d->rev = lfs_tole32(d->rev);
+ d->size = lfs_tole32(d->size);
+ d->tail[0] = lfs_tole32(d->tail[0]);
+ d->tail[1] = lfs_tole32(d->tail[1]);
+}
+
+static void lfs_entry_fromle32(struct lfs_disk_entry *d) {
+ d->u.dir[0] = lfs_fromle32(d->u.dir[0]);
+ d->u.dir[1] = lfs_fromle32(d->u.dir[1]);
+}
+
+static void lfs_entry_tole32(struct lfs_disk_entry *d) {
+ d->u.dir[0] = lfs_tole32(d->u.dir[0]);
+ d->u.dir[1] = lfs_tole32(d->u.dir[1]);
+}
+
+static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) {
+ d->root[0] = lfs_fromle32(d->root[0]);
+ d->root[1] = lfs_fromle32(d->root[1]);
+ d->block_size = lfs_fromle32(d->block_size);
+ d->block_count = lfs_fromle32(d->block_count);
+ d->version = lfs_fromle32(d->version);
+}
+
+static void lfs_superblock_tole32(struct lfs_disk_superblock *d) {
+ d->root[0] = lfs_tole32(d->root[0]);
+ d->root[1] = lfs_tole32(d->root[1]);
+ d->block_size = lfs_tole32(d->block_size);
+ d->block_count = lfs_tole32(d->block_count);
+ d->version = lfs_tole32(d->version);
+}
+
+
+/// Metadata pair and directory operations ///
+static inline void lfs_pairswap(lfs_block_t pair[2]) {
+ lfs_block_t t = pair[0];
+ pair[0] = pair[1];
+ pair[1] = t;
+}
+
+static inline bool lfs_pairisnull(const lfs_block_t pair[2]) {
+ return pair[0] == 0xffffffff || pair[1] == 0xffffffff;
+}
+
+static inline int lfs_paircmp(
+ const lfs_block_t paira[2],
+ const lfs_block_t pairb[2]) {
+ return !(paira[0] == pairb[0] || paira[1] == pairb[1] ||
+ paira[0] == pairb[1] || paira[1] == pairb[0]);
+}
+
+static inline bool lfs_pairsync(
+ const lfs_block_t paira[2],
+ const lfs_block_t pairb[2]) {
+ return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
+ (paira[0] == pairb[1] && paira[1] == pairb[0]);
+}
+
+static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) {
+ return 4 + entry->d.elen + entry->d.alen + entry->d.nlen;
+}
+
+static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
+ // allocate pair of dir blocks
+ for (int i = 0; i < 2; i++) {
+ int err = lfs_alloc(lfs, &dir->pair[i]);
+ if (err) {
+ return err;
+ }
+ }
+
+ // rather than clobbering one of the blocks we just pretend
+ // the revision may be valid
+ int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4);
+ if (err && err != LFS_ERR_CORRUPT) {
+ return err;
+ }
+
+ if (err != LFS_ERR_CORRUPT) {
+ dir->d.rev = lfs_fromle32(dir->d.rev);
+ }
+
+ // set defaults
+ dir->d.rev += 1;
+ dir->d.size = sizeof(dir->d)+4;
+ dir->d.tail[0] = 0xffffffff;
+ dir->d.tail[1] = 0xffffffff;
+ dir->off = sizeof(dir->d);
+
+ // don't write out yet, let caller take care of that
+ return 0;
+}
+
+static int lfs_dir_fetch(lfs_t *lfs,
+ lfs_dir_t *dir, const lfs_block_t pair[2]) {
+ // copy out pair, otherwise may be aliasing dir
+ const lfs_block_t tpair[2] = {pair[0], pair[1]};
+ bool valid = false;
+
+ // check both blocks for the most recent revision
+ for (int i = 0; i < 2; i++) {
+ struct lfs_disk_dir test;
+ int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test));
+ lfs_dir_fromle32(&test);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ continue;
+ }
+ return err;
+ }
+
+ if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) {
+ continue;
+ }
+
+ if ((0x7fffffff & test.size) < sizeof(test)+4 ||
+ (0x7fffffff & test.size) > lfs->cfg->block_size) {
+ continue;
+ }
+
+ uint32_t crc = 0xffffffff;
+ lfs_dir_tole32(&test);
+ lfs_crc(&crc, &test, sizeof(test));
+ lfs_dir_fromle32(&test);
+ err = lfs_bd_crc(lfs, tpair[i], sizeof(test),
+ (0x7fffffff & test.size) - sizeof(test), &crc);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ continue;
+ }
+ return err;
+ }
+
+ if (crc != 0) {
+ continue;
+ }
+
+ valid = true;
+
+ // setup dir in case it's valid
+ dir->pair[0] = tpair[(i+0) % 2];
+ dir->pair[1] = tpair[(i+1) % 2];
+ dir->off = sizeof(dir->d);
+ dir->d = test;
+ }
+
+ if (!valid) {
+ LFS_ERROR("Corrupted dir pair at %" PRIu32 " %" PRIu32 ,
+ tpair[0], tpair[1]);
+ return LFS_ERR_CORRUPT;
+ }
+
+ return 0;
+}
+
+struct lfs_region {
+ lfs_off_t oldoff;
+ lfs_size_t oldlen;
+ const void *newdata;
+ lfs_size_t newlen;
+};
+
+static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
+ const struct lfs_region *regions, int count) {
+ // increment revision count
+ dir->d.rev += 1;
+
+ // keep pairs in order such that pair[0] is most recent
+ lfs_pairswap(dir->pair);
+ for (int i = 0; i < count; i++) {
+ dir->d.size += regions[i].newlen - regions[i].oldlen;
+ }
+
+ const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]};
+ bool relocated = false;
+
+ while (true) {
+ if (true) {
+ int err = lfs_bd_erase(lfs, dir->pair[0]);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ uint32_t crc = 0xffffffff;
+ lfs_dir_tole32(&dir->d);
+ lfs_crc(&crc, &dir->d, sizeof(dir->d));
+ err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d));
+ lfs_dir_fromle32(&dir->d);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ int i = 0;
+ lfs_off_t oldoff = sizeof(dir->d);
+ lfs_off_t newoff = sizeof(dir->d);
+ while (newoff < (0x7fffffff & dir->d.size)-4) {
+ if (i < count && regions[i].oldoff == oldoff) {
+ lfs_crc(&crc, regions[i].newdata, regions[i].newlen);
+ err = lfs_bd_prog(lfs, dir->pair[0],
+ newoff, regions[i].newdata, regions[i].newlen);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ oldoff += regions[i].oldlen;
+ newoff += regions[i].newlen;
+ i += 1;
+ } else {
+ uint8_t data;
+ err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1);
+ if (err) {
+ return err;
+ }
+
+ lfs_crc(&crc, &data, 1);
+ err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ oldoff += 1;
+ newoff += 1;
+ }
+ }
+
+ crc = lfs_tole32(crc);
+ err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4);
+ crc = lfs_fromle32(crc);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ err = lfs_bd_sync(lfs);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ // successful commit, check checksum to make sure
+ uint32_t ncrc = 0xffffffff;
+ err = lfs_bd_crc(lfs, dir->pair[0], 0,
+ (0x7fffffff & dir->d.size)-4, &ncrc);
+ if (err) {
+ return err;
+ }
+
+ if (ncrc != crc) {
+ goto relocate;
+ }
+ }
+
+ break;
+relocate:
+ //commit was corrupted
+ LFS_DEBUG("Bad block at %" PRIu32, dir->pair[0]);
+
+ // drop caches and prepare to relocate block
+ relocated = true;
+ lfs_cache_drop(lfs, &lfs->pcache);
+
+ // can't relocate superblock, filesystem is now frozen
+ if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) {
+ LFS_WARN("Superblock %" PRIu32 " has become unwritable",
+ oldpair[0]);
+ return LFS_ERR_CORRUPT;
+ }
+
+ // relocate half of pair
+ int err = lfs_alloc(lfs, &dir->pair[0]);
+ if (err) {
+ return err;
+ }
+ }
+
+ if (relocated) {
+ // update references if we relocated
+ LFS_DEBUG("Relocating %" PRIu32 " %" PRIu32 " to %" PRIu32 " %" PRIu32,
+ oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]);
+ int err = lfs_relocate(lfs, oldpair, dir->pair);
+ if (err) {
+ return err;
+ }
+ }
+
+ // shift over any directories that are affected
+ for (lfs_dir_t *d = lfs->dirs; d; d = d->next) {
+ if (lfs_paircmp(d->pair, dir->pair) == 0) {
+ d->pair[0] = dir->pair[0];
+ d->pair[1] = dir->pair[1];
+ }
+ }
+
+ return 0;
+}
+
+static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir,
+ lfs_entry_t *entry, const void *data) {
+ lfs_entry_tole32(&entry->d);
+ int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
+ {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)},
+ {entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen}
+ }, data ? 2 : 1);
+ lfs_entry_fromle32(&entry->d);
+ return err;
+}
+
+static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
+ lfs_entry_t *entry, const void *data) {
+ // check if we fit, if top bit is set we do not and move on
+ while (true) {
+ if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) {
+ entry->off = dir->d.size - 4;
+
+ lfs_entry_tole32(&entry->d);
+ int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
+ {entry->off, 0, &entry->d, sizeof(entry->d)},
+ {entry->off, 0, data, entry->d.nlen}
+ }, 2);
+ lfs_entry_fromle32(&entry->d);
+ return err;
+ }
+
+ // we need to allocate a new dir block
+ if (!(0x80000000 & dir->d.size)) {
+ lfs_dir_t olddir = *dir;
+ int err = lfs_dir_alloc(lfs, dir);
+ if (err) {
+ return err;
+ }
+
+ dir->d.tail[0] = olddir.d.tail[0];
+ dir->d.tail[1] = olddir.d.tail[1];
+ entry->off = dir->d.size - 4;
+ lfs_entry_tole32(&entry->d);
+ err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
+ {entry->off, 0, &entry->d, sizeof(entry->d)},
+ {entry->off, 0, data, entry->d.nlen}
+ }, 2);
+ lfs_entry_fromle32(&entry->d);
+ if (err) {
+ return err;
+ }
+
+ olddir.d.size |= 0x80000000;
+ olddir.d.tail[0] = dir->pair[0];
+ olddir.d.tail[1] = dir->pair[1];
+ return lfs_dir_commit(lfs, &olddir, NULL, 0);
+ }
+
+ int err = lfs_dir_fetch(lfs, dir, dir->d.tail);
+ if (err) {
+ return err;
+ }
+ }
+}
+
+static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
+ // check if we should just drop the directory block
+ if ((dir->d.size & 0x7fffffff) == sizeof(dir->d)+4
+ + lfs_entry_size(entry)) {
+ lfs_dir_t pdir;
+ int res = lfs_pred(lfs, dir->pair, &pdir);
+ if (res < 0) {
+ return res;
+ }
+
+ if (pdir.d.size & 0x80000000) {
+ pdir.d.size &= dir->d.size | 0x7fffffff;
+ pdir.d.tail[0] = dir->d.tail[0];
+ pdir.d.tail[1] = dir->d.tail[1];
+ return lfs_dir_commit(lfs, &pdir, NULL, 0);
+ }
+ }
+
+ // shift out the entry
+ int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
+ {entry->off, lfs_entry_size(entry), NULL, 0},
+ }, 1);
+ if (err) {
+ return err;
+ }
+
+ // shift over any files/directories that are affected
+ for (lfs_file_t *f = lfs->files; f; f = f->next) {
+ if (lfs_paircmp(f->pair, dir->pair) == 0) {
+ if (f->poff == entry->off) {
+ f->pair[0] = 0xffffffff;
+ f->pair[1] = 0xffffffff;
+ } else if (f->poff > entry->off) {
+ f->poff -= lfs_entry_size(entry);
+ }
+ }
+ }
+
+ for (lfs_dir_t *d = lfs->dirs; d; d = d->next) {
+ if (lfs_paircmp(d->pair, dir->pair) == 0) {
+ if (d->off > entry->off) {
+ d->off -= lfs_entry_size(entry);
+ d->pos -= lfs_entry_size(entry);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
+ while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) {
+ if (!(0x80000000 & dir->d.size)) {
+ entry->off = dir->off;
+ return LFS_ERR_NOENT;
+ }
+
+ int err = lfs_dir_fetch(lfs, dir, dir->d.tail);
+ if (err) {
+ return err;
+ }
+
+ dir->off = sizeof(dir->d);
+ dir->pos += sizeof(dir->d) + 4;
+ }
+
+ int err = lfs_bd_read(lfs, dir->pair[0], dir->off,
+ &entry->d, sizeof(entry->d));
+ lfs_entry_fromle32(&entry->d);
+ if (err) {
+ return err;
+ }
+
+ entry->off = dir->off;
+ dir->off += lfs_entry_size(entry);
+ dir->pos += lfs_entry_size(entry);
+ return 0;
+}
+
+static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
+ lfs_entry_t *entry, const char **path) {
+ const char *pathname = *path;
+ size_t pathlen;
+ entry->d.type = LFS_TYPE_DIR;
+ entry->d.elen = sizeof(entry->d) - 4;
+ entry->d.alen = 0;
+ entry->d.nlen = 0;
+ entry->d.u.dir[0] = lfs->root[0];
+ entry->d.u.dir[1] = lfs->root[1];
+
+ while (true) {
+nextname:
+ // skip slashes
+ pathname += strspn(pathname, "/");
+ pathlen = strcspn(pathname, "/");
+
+ // skip '.' and root '..'
+ if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) ||
+ (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) {
+ pathname += pathlen;
+ goto nextname;
+ }
+
+ // skip if matched by '..' in name
+ const char *suffix = pathname + pathlen;
+ size_t sufflen;
+ int depth = 1;
+ while (true) {
+ suffix += strspn(suffix, "/");
+ sufflen = strcspn(suffix, "/");
+ if (sufflen == 0) {
+ break;
+ }
+
+ if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
+ depth -= 1;
+ if (depth == 0) {
+ pathname = suffix + sufflen;
+ goto nextname;
+ }
+ } else {
+ depth += 1;
+ }
+
+ suffix += sufflen;
+ }
+
+ // found path
+ if (pathname[0] == '\0') {
+ return 0;
+ }
+
+ // update what we've found
+ *path = pathname;
+
+ // continue on if we hit a directory
+ if (entry->d.type != LFS_TYPE_DIR) {
+ return LFS_ERR_NOTDIR;
+ }
+
+ int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir);
+ if (err) {
+ return err;
+ }
+
+ // find entry matching name
+ while (true) {
+ err = lfs_dir_next(lfs, dir, entry);
+ if (err) {
+ return err;
+ }
+
+ if (((0x7f & entry->d.type) != LFS_TYPE_REG &&
+ (0x7f & entry->d.type) != LFS_TYPE_DIR) ||
+ entry->d.nlen != pathlen) {
+ continue;
+ }
+
+ int res = lfs_bd_cmp(lfs, dir->pair[0],
+ entry->off + 4+entry->d.elen+entry->d.alen,
+ pathname, pathlen);
+ if (res < 0) {
+ return res;
+ }
+
+ // found match
+ if (res) {
+ break;
+ }
+ }
+
+ // check that entry has not been moved
+ if (entry->d.type & 0x80) {
+ int moved = lfs_moved(lfs, &entry->d.u);
+ if (moved < 0 || moved) {
+ return (moved < 0) ? moved : LFS_ERR_NOENT;
+ }
+
+ entry->d.type &= ~0x80;
+ }
+
+ // to next name
+ pathname += pathlen;
+ }
+}
+
+
+/// Top level directory operations ///
+int lfs_mkdir(lfs_t *lfs, const char *path) {
+ // deorphan if we haven't yet, needed at most once after poweron
+ if (!lfs->deorphaned) {
+ int err = lfs_deorphan(lfs);
+ if (err) {
+ return err;
+ }
+ }
+
+ // fetch parent directory
+ lfs_dir_t cwd;
+ lfs_entry_t entry;
+ int err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) {
+ return err ? err : LFS_ERR_EXIST;
+ }
+
+ // build up new directory
+ lfs_alloc_ack(lfs);
+
+ lfs_dir_t dir;
+ err = lfs_dir_alloc(lfs, &dir);
+ if (err) {
+ return err;
+ }
+ dir.d.tail[0] = cwd.d.tail[0];
+ dir.d.tail[1] = cwd.d.tail[1];
+
+ err = lfs_dir_commit(lfs, &dir, NULL, 0);
+ if (err) {
+ return err;
+ }
+
+ entry.d.type = LFS_TYPE_DIR;
+ entry.d.elen = sizeof(entry.d) - 4;
+ entry.d.alen = 0;
+ entry.d.nlen = strlen(path);
+ entry.d.u.dir[0] = dir.pair[0];
+ entry.d.u.dir[1] = dir.pair[1];
+
+ cwd.d.tail[0] = dir.pair[0];
+ cwd.d.tail[1] = dir.pair[1];
+
+ err = lfs_dir_append(lfs, &cwd, &entry, path);
+ if (err) {
+ return err;
+ }
+
+ lfs_alloc_ack(lfs);
+ return 0;
+}
+
+int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) {
+ dir->pair[0] = lfs->root[0];
+ dir->pair[1] = lfs->root[1];
+
+ lfs_entry_t entry;
+ int err = lfs_dir_find(lfs, dir, &entry, &path);
+ if (err) {
+ return err;
+ } else if (entry.d.type != LFS_TYPE_DIR) {
+ return LFS_ERR_NOTDIR;
+ }
+
+ err = lfs_dir_fetch(lfs, dir, entry.d.u.dir);
+ if (err) {
+ return err;
+ }
+
+ // setup head dir
+ // special offset for '.' and '..'
+ dir->head[0] = dir->pair[0];
+ dir->head[1] = dir->pair[1];
+ dir->pos = sizeof(dir->d) - 2;
+ dir->off = sizeof(dir->d);
+
+ // add to list of directories
+ dir->next = lfs->dirs;
+ lfs->dirs = dir;
+
+ return 0;
+}
+
+int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) {
+ // remove from list of directories
+ for (lfs_dir_t **p = &lfs->dirs; *p; p = &(*p)->next) {
+ if (*p == dir) {
+ *p = dir->next;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
+ memset(info, 0, sizeof(*info));
+
+ // special offset for '.' and '..'
+ if (dir->pos == sizeof(dir->d) - 2) {
+ info->type = LFS_TYPE_DIR;
+ strcpy(info->name, ".");
+ dir->pos += 1;
+ return 1;
+ } else if (dir->pos == sizeof(dir->d) - 1) {
+ info->type = LFS_TYPE_DIR;
+ strcpy(info->name, "..");
+ dir->pos += 1;
+ return 1;
+ }
+
+ lfs_entry_t entry;
+ while (true) {
+ int err = lfs_dir_next(lfs, dir, &entry);
+ if (err) {
+ return (err == LFS_ERR_NOENT) ? 0 : err;
+ }
+
+ if ((0x7f & entry.d.type) != LFS_TYPE_REG &&
+ (0x7f & entry.d.type) != LFS_TYPE_DIR) {
+ continue;
+ }
+
+ // check that entry has not been moved
+ if (entry.d.type & 0x80) {
+ int moved = lfs_moved(lfs, &entry.d.u);
+ if (moved < 0) {
+ return moved;
+ }
+
+ if (moved) {
+ continue;
+ }
+
+ entry.d.type &= ~0x80;
+ }
+
+ break;
+ }
+
+ info->type = entry.d.type;
+ if (info->type == LFS_TYPE_REG) {
+ info->size = entry.d.u.file.size;
+ }
+
+ int err = lfs_bd_read(lfs, dir->pair[0],
+ entry.off + 4+entry.d.elen+entry.d.alen,
+ info->name, entry.d.nlen);
+ if (err) {
+ return err;
+ }
+
+ return 1;
+}
+
+int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
+ // simply walk from head dir
+ int err = lfs_dir_rewind(lfs, dir);
+ if (err) {
+ return err;
+ }
+ dir->pos = off;
+
+ while (off > (0x7fffffff & dir->d.size)) {
+ off -= 0x7fffffff & dir->d.size;
+ if (!(0x80000000 & dir->d.size)) {
+ return LFS_ERR_INVAL;
+ }
+
+ err = lfs_dir_fetch(lfs, dir, dir->d.tail);
+ if (err) {
+ return err;
+ }
+ }
+
+ dir->off = off;
+ return 0;
+}
+
+lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) {
+ (void)lfs;
+ return dir->pos;
+}
+
+int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) {
+ // reload the head dir
+ int err = lfs_dir_fetch(lfs, dir, dir->head);
+ if (err) {
+ return err;
+ }
+
+ dir->pair[0] = dir->head[0];
+ dir->pair[1] = dir->head[1];
+ dir->pos = sizeof(dir->d) - 2;
+ dir->off = sizeof(dir->d);
+ return 0;
+}
+
+
+/// File index list operations ///
+static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) {
+ lfs_off_t size = *off;
+ lfs_off_t b = lfs->cfg->block_size - 2*4;
+ lfs_off_t i = size / b;
+ if (i == 0) {
+ return 0;
+ }
+
+ i = (size - 4*(lfs_popc(i-1)+2)) / b;
+ *off = size - b*i - 4*lfs_popc(i);
+ return i;
+}
+
+static int lfs_ctz_find(lfs_t *lfs,
+ lfs_cache_t *rcache, const lfs_cache_t *pcache,
+ lfs_block_t head, lfs_size_t size,
+ lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) {
+ if (size == 0) {
+ *block = 0xffffffff;
+ *off = 0;
+ return 0;
+ }
+
+ lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1});
+ lfs_off_t target = lfs_ctz_index(lfs, &pos);
+
+ while (current > target) {
+ lfs_size_t skip = lfs_min(
+ lfs_npw2(current-target+1) - 1,
+ lfs_ctz(current));
+
+ int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4);
+ head = lfs_fromle32(head);
+ if (err) {
+ return err;
+ }
+
+ LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
+ current -= 1 << skip;
+ }
+
+ *block = head;
+ *off = pos;
+ return 0;
+}
+
+static int lfs_ctz_extend(lfs_t *lfs,
+ lfs_cache_t *rcache, lfs_cache_t *pcache,
+ lfs_block_t head, lfs_size_t size,
+ lfs_block_t *block, lfs_off_t *off) {
+ while (true) {
+ // go ahead and grab a block
+ lfs_block_t nblock;
+ int err = lfs_alloc(lfs, &nblock);
+ if (err) {
+ return err;
+ }
+ LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count);
+
+ if (true) {
+ err = lfs_bd_erase(lfs, nblock);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ if (size == 0) {
+ *block = nblock;
+ *off = 0;
+ return 0;
+ }
+
+ size -= 1;
+ lfs_off_t index = lfs_ctz_index(lfs, &size);
+ size += 1;
+
+ // just copy out the last block if it is incomplete
+ if (size != lfs->cfg->block_size) {
+ for (lfs_off_t i = 0; i < size; i++) {
+ uint8_t data;
+ err = lfs_cache_read(lfs, rcache, NULL,
+ head, i, &data, 1);
+ if (err) {
+ return err;
+ }
+
+ err = lfs_cache_prog(lfs, pcache, rcache,
+ nblock, i, &data, 1);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+ }
+
+ *block = nblock;
+ *off = size;
+ return 0;
+ }
+
+ // append block
+ index += 1;
+ lfs_size_t skips = lfs_ctz(index) + 1;
+
+ for (lfs_off_t i = 0; i < skips; i++) {
+ head = lfs_tole32(head);
+ err = lfs_cache_prog(lfs, pcache, rcache,
+ nblock, 4*i, &head, 4);
+ head = lfs_fromle32(head);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ if (i != skips-1) {
+ err = lfs_cache_read(lfs, rcache, NULL,
+ head, 4*i, &head, 4);
+ head = lfs_fromle32(head);
+ if (err) {
+ return err;
+ }
+ }
+
+ LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
+ }
+
+ *block = nblock;
+ *off = 4*skips;
+ return 0;
+ }
+
+relocate:
+ LFS_DEBUG("Bad block at %" PRIu32, nblock);
+
+ // just clear cache and try a new block
+ lfs_cache_drop(lfs, &lfs->pcache);
+ }
+}
+
+static int lfs_ctz_traverse(lfs_t *lfs,
+ lfs_cache_t *rcache, const lfs_cache_t *pcache,
+ lfs_block_t head, lfs_size_t size,
+ int (*cb)(void*, lfs_block_t), void *data) {
+ if (size == 0) {
+ return 0;
+ }
+
+ lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1});
+
+ while (true) {
+ int err = cb(data, head);
+ if (err) {
+ return err;
+ }
+
+ if (index == 0) {
+ return 0;
+ }
+
+ lfs_block_t heads[2];
+ int count = 2 - (index & 1);
+ err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count*4);
+ heads[0] = lfs_fromle32(heads[0]);
+ heads[1] = lfs_fromle32(heads[1]);
+ if (err) {
+ return err;
+ }
+
+ for (int i = 0; i < count-1; i++) {
+ err = cb(data, heads[i]);
+ if (err) {
+ return err;
+ }
+ }
+
+ head = heads[count-1];
+ index -= count;
+ }
+}
+
+
+/// Top level file operations ///
+int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
+ const char *path, int flags,
+ const struct lfs_file_config *cfg) {
+ // deorphan if we haven't yet, needed at most once after poweron
+ if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) {
+ int err = lfs_deorphan(lfs);
+ if (err) {
+ return err;
+ }
+ }
+
+ // allocate entry for file if it doesn't exist
+ lfs_dir_t cwd;
+ lfs_entry_t entry;
+ int err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) {
+ return err;
+ }
+
+ if (err == LFS_ERR_NOENT) {
+ if (!(flags & LFS_O_CREAT)) {
+ return LFS_ERR_NOENT;
+ }
+
+ // create entry to remember name
+ entry.d.type = LFS_TYPE_REG;
+ entry.d.elen = sizeof(entry.d) - 4;
+ entry.d.alen = 0;
+ entry.d.nlen = strlen(path);
+ entry.d.u.file.head = 0xffffffff;
+ entry.d.u.file.size = 0;
+ err = lfs_dir_append(lfs, &cwd, &entry, path);
+ if (err) {
+ return err;
+ }
+ } else if (entry.d.type == LFS_TYPE_DIR) {
+ return LFS_ERR_ISDIR;
+ } else if (flags & LFS_O_EXCL) {
+ return LFS_ERR_EXIST;
+ }
+
+ // setup file struct
+ file->cfg = cfg;
+ file->pair[0] = cwd.pair[0];
+ file->pair[1] = cwd.pair[1];
+ file->poff = entry.off;
+ file->head = entry.d.u.file.head;
+ file->size = entry.d.u.file.size;
+ file->flags = flags;
+ file->pos = 0;
+
+ if (flags & LFS_O_TRUNC) {
+ if (file->size != 0) {
+ file->flags |= LFS_F_DIRTY;
+ }
+ file->head = 0xffffffff;
+ file->size = 0;
+ }
+
+ // allocate buffer if needed
+ file->cache.block = 0xffffffff;
+ if (file->cfg && file->cfg->buffer) {
+ file->cache.buffer = file->cfg->buffer;
+ } else if (lfs->cfg->file_buffer) {
+ if (lfs->files) {
+ // already in use
+ return LFS_ERR_NOMEM;
+ }
+ file->cache.buffer = lfs->cfg->file_buffer;
+ } else if ((file->flags & 3) == LFS_O_RDONLY) {
+ file->cache.buffer = lfs_malloc(lfs->cfg->read_size);
+ if (!file->cache.buffer) {
+ return LFS_ERR_NOMEM;
+ }
+ } else {
+ file->cache.buffer = lfs_malloc(lfs->cfg->prog_size);
+ if (!file->cache.buffer) {
+ return LFS_ERR_NOMEM;
+ }
+ }
+
+ // zero to avoid information leak
+ lfs_cache_zero(lfs, &file->cache);
+
+ // add to list of files
+ file->next = lfs->files;
+ lfs->files = file;
+
+ return 0;
+}
+
+int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
+ const char *path, int flags) {
+ return lfs_file_opencfg(lfs, file, path, flags, NULL);
+}
+
+int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
+ int err = lfs_file_sync(lfs, file);
+
+ // remove from list of files
+ for (lfs_file_t **p = &lfs->files; *p; p = &(*p)->next) {
+ if (*p == file) {
+ *p = file->next;
+ break;
+ }
+ }
+
+ // clean up memory
+ if (!(file->cfg && file->cfg->buffer) && !lfs->cfg->file_buffer) {
+ lfs_free(file->cache.buffer);
+ }
+
+ return err;
+}
+
+static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) {
+relocate:
+ LFS_DEBUG("Bad block at %" PRIu32, file->block);
+
+ // just relocate what exists into new block
+ lfs_block_t nblock;
+ int err = lfs_alloc(lfs, &nblock);
+ if (err) {
+ return err;
+ }
+
+ err = lfs_bd_erase(lfs, nblock);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ // either read from dirty cache or disk
+ for (lfs_off_t i = 0; i < file->off; i++) {
+ uint8_t data;
+ err = lfs_cache_read(lfs, &lfs->rcache, &file->cache,
+ file->block, i, &data, 1);
+ if (err) {
+ return err;
+ }
+
+ err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache,
+ nblock, i, &data, 1);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+ }
+
+ // copy over new state of file
+ memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size);
+ file->cache.block = lfs->pcache.block;
+ file->cache.off = lfs->pcache.off;
+ lfs_cache_zero(lfs, &lfs->pcache);
+
+ file->block = nblock;
+ return 0;
+}
+
+static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
+ if (file->flags & LFS_F_READING) {
+ // just drop read cache
+ lfs_cache_drop(lfs, &file->cache);
+ file->flags &= ~LFS_F_READING;
+ }
+
+ if (file->flags & LFS_F_WRITING) {
+ lfs_off_t pos = file->pos;
+
+ // copy over anything after current branch
+ lfs_file_t orig = {
+ .head = file->head,
+ .size = file->size,
+ .flags = LFS_O_RDONLY,
+ .pos = file->pos,
+ .cache = lfs->rcache,
+ };
+ lfs_cache_drop(lfs, &lfs->rcache);
+
+ while (file->pos < file->size) {
+ // copy over a byte at a time, leave it up to caching
+ // to make this efficient
+ uint8_t data;
+ lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1);
+ if (res < 0) {
+ return res;
+ }
+
+ res = lfs_file_write(lfs, file, &data, 1);
+ if (res < 0) {
+ return res;
+ }
+
+ // keep our reference to the rcache in sync
+ if (lfs->rcache.block != 0xffffffff) {
+ lfs_cache_drop(lfs, &orig.cache);
+ lfs_cache_drop(lfs, &lfs->rcache);
+ }
+ }
+
+ // write out what we have
+ while (true) {
+ int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ break;
+relocate:
+ err = lfs_file_relocate(lfs, file);
+ if (err) {
+ return err;
+ }
+ }
+
+ // actual file updates
+ file->head = file->block;
+ file->size = file->pos;
+ file->flags &= ~LFS_F_WRITING;
+ file->flags |= LFS_F_DIRTY;
+
+ file->pos = pos;
+ }
+
+ return 0;
+}
+
+int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+
+ if ((file->flags & LFS_F_DIRTY) &&
+ !(file->flags & LFS_F_ERRED) &&
+ !lfs_pairisnull(file->pair)) {
+ // update dir entry
+ lfs_dir_t cwd;
+ err = lfs_dir_fetch(lfs, &cwd, file->pair);
+ if (err) {
+ return err;
+ }
+
+ lfs_entry_t entry = {.off = file->poff};
+ err = lfs_bd_read(lfs, cwd.pair[0], entry.off,
+ &entry.d, sizeof(entry.d));
+ lfs_entry_fromle32(&entry.d);
+ if (err) {
+ return err;
+ }
+
+ LFS_ASSERT(entry.d.type == LFS_TYPE_REG);
+ entry.d.u.file.head = file->head;
+ entry.d.u.file.size = file->size;
+
+ err = lfs_dir_update(lfs, &cwd, &entry, NULL);
+ if (err) {
+ return err;
+ }
+
+ file->flags &= ~LFS_F_DIRTY;
+ }
+
+ return 0;
+}
+
+lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
+ void *buffer, lfs_size_t size) {
+ uint8_t *data = buffer;
+ lfs_size_t nsize = size;
+
+ if ((file->flags & 3) == LFS_O_WRONLY) {
+ return LFS_ERR_BADF;
+ }
+
+ if (file->flags & LFS_F_WRITING) {
+ // flush out any writes
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+ }
+
+ if (file->pos >= file->size) {
+ // eof if past end
+ return 0;
+ }
+
+ size = lfs_min(size, file->size - file->pos);
+ nsize = size;
+
+ while (nsize > 0) {
+ // check if we need a new block
+ if (!(file->flags & LFS_F_READING) ||
+ file->off == lfs->cfg->block_size) {
+ int err = lfs_ctz_find(lfs, &file->cache, NULL,
+ file->head, file->size,
+ file->pos, &file->block, &file->off);
+ if (err) {
+ return err;
+ }
+
+ file->flags |= LFS_F_READING;
+ }
+
+ // read as much as we can in current block
+ lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
+ int err = lfs_cache_read(lfs, &file->cache, NULL,
+ file->block, file->off, data, diff);
+ if (err) {
+ return err;
+ }
+
+ file->pos += diff;
+ file->off += diff;
+ data += diff;
+ nsize -= diff;
+ }
+
+ return size;
+}
+
+lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
+ const void *buffer, lfs_size_t size) {
+ const uint8_t *data = buffer;
+ lfs_size_t nsize = size;
+
+ if ((file->flags & 3) == LFS_O_RDONLY) {
+ return LFS_ERR_BADF;
+ }
+
+ if (file->flags & LFS_F_READING) {
+ // drop any reads
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+ }
+
+ if ((file->flags & LFS_O_APPEND) && file->pos < file->size) {
+ file->pos = file->size;
+ }
+
+ if (!(file->flags & LFS_F_WRITING) && file->pos > file->size) {
+ // fill with zeros
+ lfs_off_t pos = file->pos;
+ file->pos = file->size;
+
+ while (file->pos < pos) {
+ lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
+ if (res < 0) {
+ return res;
+ }
+ }
+ }
+
+ while (nsize > 0) {
+ // check if we need a new block
+ if (!(file->flags & LFS_F_WRITING) ||
+ file->off == lfs->cfg->block_size) {
+ if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
+ // find out which block we're extending from
+ int err = lfs_ctz_find(lfs, &file->cache, NULL,
+ file->head, file->size,
+ file->pos-1, &file->block, &file->off);
+ if (err) {
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+
+ // mark cache as dirty since we may have read data into it
+ lfs_cache_zero(lfs, &file->cache);
+ }
+
+ // extend file with new blocks
+ lfs_alloc_ack(lfs);
+ int err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache,
+ file->block, file->pos,
+ &file->block, &file->off);
+ if (err) {
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+
+ file->flags |= LFS_F_WRITING;
+ }
+
+ // program as much as we can in current block
+ lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
+ while (true) {
+ int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache,
+ file->block, file->off, data, diff);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+
+ break;
+relocate:
+ err = lfs_file_relocate(lfs, file);
+ if (err) {
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+ }
+
+ file->pos += diff;
+ file->off += diff;
+ data += diff;
+ nsize -= diff;
+
+ lfs_alloc_ack(lfs);
+ }
+
+ file->flags &= ~LFS_F_ERRED;
+ return size;
+}
+
+lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
+ lfs_soff_t off, int whence) {
+ // write out everything beforehand, may be noop if rdonly
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+
+ // update pos
+ if (whence == LFS_SEEK_SET) {
+ file->pos = off;
+ } else if (whence == LFS_SEEK_CUR) {
+ if (off < 0 && (lfs_off_t)-off > file->pos) {
+ return LFS_ERR_INVAL;
+ }
+
+ file->pos = file->pos + off;
+ } else if (whence == LFS_SEEK_END) {
+ if (off < 0 && (lfs_off_t)-off > file->size) {
+ return LFS_ERR_INVAL;
+ }
+
+ file->pos = file->size + off;
+ }
+
+ return file->pos;
+}
+
+int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
+ if ((file->flags & 3) == LFS_O_RDONLY) {
+ return LFS_ERR_BADF;
+ }
+
+ lfs_off_t oldsize = lfs_file_size(lfs, file);
+ if (size < oldsize) {
+ // need to flush since directly changing metadata
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+
+ // lookup new head in ctz skip list
+ err = lfs_ctz_find(lfs, &file->cache, NULL,
+ file->head, file->size,
+ size, &file->head, &(lfs_off_t){0});
+ if (err) {
+ return err;
+ }
+
+ file->size = size;
+ file->flags |= LFS_F_DIRTY;
+ } else if (size > oldsize) {
+ lfs_off_t pos = file->pos;
+
+ // flush+seek if not already at end
+ if (file->pos != oldsize) {
+ int err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END);
+ if (err < 0) {
+ return err;
+ }
+ }
+
+ // fill with zeros
+ while (file->pos < size) {
+ lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
+ if (res < 0) {
+ return res;
+ }
+ }
+
+ // restore pos
+ int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET);
+ if (err < 0) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
+ (void)lfs;
+ return file->pos;
+}
+
+int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
+ lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET);
+ if (res < 0) {
+ return res;
+ }
+
+ return 0;
+}
+
+lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
+ (void)lfs;
+ if (file->flags & LFS_F_WRITING) {
+ return lfs_max(file->pos, file->size);
+ } else {
+ return file->size;
+ }
+}
+
+
+/// General fs operations ///
+int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
+ lfs_dir_t cwd;
+ lfs_entry_t entry;
+ int err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ if (err) {
+ return err;
+ }
+
+ memset(info, 0, sizeof(*info));
+ info->type = entry.d.type;
+ if (info->type == LFS_TYPE_REG) {
+ info->size = entry.d.u.file.size;
+ }
+
+ if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) {
+ strcpy(info->name, "/");
+ } else {
+ err = lfs_bd_read(lfs, cwd.pair[0],
+ entry.off + 4+entry.d.elen+entry.d.alen,
+ info->name, entry.d.nlen);
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int lfs_remove(lfs_t *lfs, const char *path) {
+ // deorphan if we haven't yet, needed at most once after poweron
+ if (!lfs->deorphaned) {
+ int err = lfs_deorphan(lfs);
+ if (err) {
+ return err;
+ }
+ }
+
+ lfs_dir_t cwd;
+ lfs_entry_t entry;
+ int err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ if (err) {
+ return err;
+ }
+
+ lfs_dir_t dir;
+ if (entry.d.type == LFS_TYPE_DIR) {
+ // must be empty before removal, checking size
+ // without masking top bit checks for any case where
+ // dir is not empty
+ err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir);
+ if (err) {
+ return err;
+ } else if (dir.d.size != sizeof(dir.d)+4) {
+ return LFS_ERR_NOTEMPTY;
+ }
+ }
+
+ // remove the entry
+ err = lfs_dir_remove(lfs, &cwd, &entry);
+ if (err) {
+ return err;
+ }
+
+ // if we were a directory, find pred, replace tail
+ if (entry.d.type == LFS_TYPE_DIR) {
+ int res = lfs_pred(lfs, dir.pair, &cwd);
+ if (res < 0) {
+ return res;
+ }
+
+ LFS_ASSERT(res); // must have pred
+ cwd.d.tail[0] = dir.d.tail[0];
+ cwd.d.tail[1] = dir.d.tail[1];
+
+ err = lfs_dir_commit(lfs, &cwd, NULL, 0);
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
+ // deorphan if we haven't yet, needed at most once after poweron
+ if (!lfs->deorphaned) {
+ int err = lfs_deorphan(lfs);
+ if (err) {
+ return err;
+ }
+ }
+
+ // find old entry
+ lfs_dir_t oldcwd;
+ lfs_entry_t oldentry;
+ int err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath);
+ if (err) {
+ return err;
+ }
+
+ // allocate new entry
+ lfs_dir_t newcwd;
+ lfs_entry_t preventry;
+ err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath);
+ if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) {
+ return err;
+ }
+
+ bool prevexists = (err != LFS_ERR_NOENT);
+ bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0);
+
+ // must have same type
+ if (prevexists && preventry.d.type != oldentry.d.type) {
+ return LFS_ERR_ISDIR;
+ }
+
+ lfs_dir_t dir;
+ if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
+ // must be empty before removal, checking size
+ // without masking top bit checks for any case where
+ // dir is not empty
+ err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir);
+ if (err) {
+ return err;
+ } else if (dir.d.size != sizeof(dir.d)+4) {
+ return LFS_ERR_NOTEMPTY;
+ }
+ }
+
+ // mark as moving
+ oldentry.d.type |= 0x80;
+ err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL);
+ if (err) {
+ return err;
+ }
+
+ // update pair if newcwd == oldcwd
+ if (samepair) {
+ newcwd = oldcwd;
+ }
+
+ // move to new location
+ lfs_entry_t newentry = preventry;
+ newentry.d = oldentry.d;
+ newentry.d.type &= ~0x80;
+ newentry.d.nlen = strlen(newpath);
+
+ if (prevexists) {
+ err = lfs_dir_update(lfs, &newcwd, &newentry, newpath);
+ if (err) {
+ return err;
+ }
+ } else {
+ err = lfs_dir_append(lfs, &newcwd, &newentry, newpath);
+ if (err) {
+ return err;
+ }
+ }
+
+ // update pair if newcwd == oldcwd
+ if (samepair) {
+ oldcwd = newcwd;
+ }
+
+ // remove old entry
+ err = lfs_dir_remove(lfs, &oldcwd, &oldentry);
+ if (err) {
+ return err;
+ }
+
+ // if we were a directory, find pred, replace tail
+ if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
+ int res = lfs_pred(lfs, dir.pair, &newcwd);
+ if (res < 0) {
+ return res;
+ }
+
+ LFS_ASSERT(res); // must have pred
+ newcwd.d.tail[0] = dir.d.tail[0];
+ newcwd.d.tail[1] = dir.d.tail[1];
+
+ err = lfs_dir_commit(lfs, &newcwd, NULL, 0);
+ if (err) {
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+
+/// Filesystem operations ///
+static void lfs_deinit(lfs_t *lfs) {
+ // free allocated memory
+ if (!lfs->cfg->read_buffer) {
+ lfs_free(lfs->rcache.buffer);
+ }
+
+ if (!lfs->cfg->prog_buffer) {
+ lfs_free(lfs->pcache.buffer);
+ }
+
+ if (!lfs->cfg->lookahead_buffer) {
+ lfs_free(lfs->free.buffer);
+ }
+}
+
+static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
+ lfs->cfg = cfg;
+
+ // setup read cache
+ if (lfs->cfg->read_buffer) {
+ lfs->rcache.buffer = lfs->cfg->read_buffer;
+ } else {
+ lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size);
+ if (!lfs->rcache.buffer) {
+ goto cleanup;
+ }
+ }
+
+ // setup program cache
+ if (lfs->cfg->prog_buffer) {
+ lfs->pcache.buffer = lfs->cfg->prog_buffer;
+ } else {
+ lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size);
+ if (!lfs->pcache.buffer) {
+ goto cleanup;
+ }
+ }
+
+ // zero to avoid information leaks
+ lfs_cache_zero(lfs, &lfs->rcache);
+ lfs_cache_zero(lfs, &lfs->pcache);
+
+ // setup lookahead, round down to nearest 32-bits
+ LFS_ASSERT(lfs->cfg->lookahead % 32 == 0);
+ LFS_ASSERT(lfs->cfg->lookahead > 0);
+ if (lfs->cfg->lookahead_buffer) {
+ lfs->free.buffer = lfs->cfg->lookahead_buffer;
+ } else {
+ lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8);
+ if (!lfs->free.buffer) {
+ goto cleanup;
+ }
+ }
+
+ // check that program and read sizes are multiples of the block size
+ LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0);
+ LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0);
+
+ // check that the block size is large enough to fit ctz pointers
+ LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
+ <= lfs->cfg->block_size);
+
+ // setup default state
+ lfs->root[0] = 0xffffffff;
+ lfs->root[1] = 0xffffffff;
+ lfs->files = NULL;
+ lfs->dirs = NULL;
+ lfs->deorphaned = false;
+
+ return 0;
+
+cleanup:
+ lfs_deinit(lfs);
+ return LFS_ERR_NOMEM;
+}
+
+int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
+ int err = lfs_init(lfs, cfg);
+ if (err) {
+ return err;
+ }
+
+ // create free lookahead
+ memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
+ lfs->free.off = 0;
+ lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
+ lfs->free.i = 0;
+ lfs_alloc_ack(lfs);
+
+ // create superblock dir
+ lfs_dir_t superdir;
+ err = lfs_dir_alloc(lfs, &superdir);
+ if (err) {
+ goto cleanup;
+ }
+
+ // write root directory
+ lfs_dir_t root;
+ err = lfs_dir_alloc(lfs, &root);
+ if (err) {
+ goto cleanup;
+ }
+
+ err = lfs_dir_commit(lfs, &root, NULL, 0);
+ if (err) {
+ goto cleanup;
+ }
+
+ lfs->root[0] = root.pair[0];
+ lfs->root[1] = root.pair[1];
+
+ // write superblocks
+ lfs_superblock_t superblock = {
+ .off = sizeof(superdir.d),
+ .d.type = LFS_TYPE_SUPERBLOCK,
+ .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4,
+ .d.nlen = sizeof(superblock.d.magic),
+ .d.version = LFS_DISK_VERSION,
+ .d.magic = {"littlefs"},
+ .d.block_size = lfs->cfg->block_size,
+ .d.block_count = lfs->cfg->block_count,
+ .d.root = {lfs->root[0], lfs->root[1]},
+ };
+ superdir.d.tail[0] = root.pair[0];
+ superdir.d.tail[1] = root.pair[1];
+ superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4;
+
+ // write both pairs to be safe
+ lfs_superblock_tole32(&superblock.d);
+ bool valid = false;
+ for (int i = 0; i < 2; i++) {
+ err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){
+ {sizeof(superdir.d), sizeof(superblock.d),
+ &superblock.d, sizeof(superblock.d)}
+ }, 1);
+ if (err && err != LFS_ERR_CORRUPT) {
+ goto cleanup;
+ }
+
+ valid = valid || !err;
+ }
+
+ if (!valid) {
+ err = LFS_ERR_CORRUPT;
+ goto cleanup;
+ }
+
+ // sanity check that fetch works
+ err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
+ if (err) {
+ goto cleanup;
+ }
+
+ lfs_alloc_ack(lfs);
+
+cleanup:
+ lfs_deinit(lfs);
+ return err;
+}
+
+int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
+ int err = lfs_init(lfs, cfg);
+ if (err) {
+ return err;
+ }
+
+ // setup free lookahead
+ lfs->free.off = 0;
+ lfs->free.size = 0;
+ lfs->free.i = 0;
+ lfs_alloc_ack(lfs);
+
+ // load superblock
+ lfs_dir_t dir;
+ lfs_superblock_t superblock;
+ err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
+ if (err && err != LFS_ERR_CORRUPT) {
+ goto cleanup;
+ }
+
+ if (!err) {
+ err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
+ &superblock.d, sizeof(superblock.d));
+ lfs_superblock_fromle32(&superblock.d);
+ if (err) {
+ goto cleanup;
+ }
+
+ lfs->root[0] = superblock.d.root[0];
+ lfs->root[1] = superblock.d.root[1];
+ }
+
+ if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) {
+ LFS_ERROR("Invalid superblock at %d %d", 0, 1);
+ err = LFS_ERR_CORRUPT;
+ goto cleanup;
+ }
+
+ uint16_t major_version = (0xffff & (superblock.d.version >> 16));
+ uint16_t minor_version = (0xffff & (superblock.d.version >> 0));
+ if ((major_version != LFS_DISK_VERSION_MAJOR ||
+ minor_version > LFS_DISK_VERSION_MINOR)) {
+ LFS_ERROR("Invalid version %d.%d", major_version, minor_version);
+ err = LFS_ERR_INVAL;
+ goto cleanup;
+ }
+
+ return 0;
+
+cleanup:
+
+ lfs_deinit(lfs);
+ return err;
+}
+
+int lfs_umount(lfs_t *lfs) {
+ lfs_deinit(lfs);
+ return 0;
+}
+
+
+/// Littlefs specific operations ///
+int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ // iterate over metadata pairs
+ lfs_dir_t dir;
+ lfs_entry_t entry;
+ lfs_block_t cwd[2] = {0, 1};
+
+ while (true) {
+ for (int i = 0; i < 2; i++) {
+ int err = cb(data, cwd[i]);
+ if (err) {
+ return err;
+ }
+ }
+
+ int err = lfs_dir_fetch(lfs, &dir, cwd);
+ if (err) {
+ return err;
+ }
+
+ // iterate over contents
+ while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) {
+ err = lfs_bd_read(lfs, dir.pair[0], dir.off,
+ &entry.d, sizeof(entry.d));
+ lfs_entry_fromle32(&entry.d);
+ if (err) {
+ return err;
+ }
+
+ dir.off += lfs_entry_size(&entry);
+ if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) {
+ err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL,
+ entry.d.u.file.head, entry.d.u.file.size, cb, data);
+ if (err) {
+ return err;
+ }
+ }
+ }
+
+ cwd[0] = dir.d.tail[0];
+ cwd[1] = dir.d.tail[1];
+
+ if (lfs_pairisnull(cwd)) {
+ break;
+ }
+ }
+
+ // iterate over any open files
+ for (lfs_file_t *f = lfs->files; f; f = f->next) {
+ if (f->flags & LFS_F_DIRTY) {
+ int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache,
+ f->head, f->size, cb, data);
+ if (err) {
+ return err;
+ }
+ }
+
+ if (f->flags & LFS_F_WRITING) {
+ int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache,
+ f->block, f->pos, cb, data);
+ if (err) {
+ return err;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir) {
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ // iterate over all directory directory entries
+ int err = lfs_dir_fetch(lfs, pdir, (const lfs_block_t[2]){0, 1});
+ if (err) {
+ return err;
+ }
+
+ while (!lfs_pairisnull(pdir->d.tail)) {
+ if (lfs_paircmp(pdir->d.tail, dir) == 0) {
+ return true;
+ }
+
+ err = lfs_dir_fetch(lfs, pdir, pdir->d.tail);
+ if (err) {
+ return err;
+ }
+ }
+
+ return false;
+}
+
+static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
+ lfs_dir_t *parent, lfs_entry_t *entry) {
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ parent->d.tail[0] = 0;
+ parent->d.tail[1] = 1;
+
+ // iterate over all directory directory entries
+ while (!lfs_pairisnull(parent->d.tail)) {
+ int err = lfs_dir_fetch(lfs, parent, parent->d.tail);
+ if (err) {
+ return err;
+ }
+
+ while (true) {
+ err = lfs_dir_next(lfs, parent, entry);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
+ }
+
+ if (err == LFS_ERR_NOENT) {
+ break;
+ }
+
+ if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) &&
+ lfs_paircmp(entry->d.u.dir, dir) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static int lfs_moved(lfs_t *lfs, const void *e) {
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ // skip superblock
+ lfs_dir_t cwd;
+ int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1});
+ if (err) {
+ return err;
+ }
+
+ // iterate over all directory directory entries
+ lfs_entry_t entry;
+ while (!lfs_pairisnull(cwd.d.tail)) {
+ err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
+ if (err) {
+ return err;
+ }
+
+ while (true) {
+ err = lfs_dir_next(lfs, &cwd, &entry);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
+ }
+
+ if (err == LFS_ERR_NOENT) {
+ break;
+ }
+
+ if (!(0x80 & entry.d.type) &&
+ memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static int lfs_relocate(lfs_t *lfs,
+ const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) {
+ // find parent
+ lfs_dir_t parent;
+ lfs_entry_t entry;
+ int res = lfs_parent(lfs, oldpair, &parent, &entry);
+ if (res < 0) {
+ return res;
+ }
+
+ if (res) {
+ // update disk, this creates a desync
+ entry.d.u.dir[0] = newpair[0];
+ entry.d.u.dir[1] = newpair[1];
+
+ int err = lfs_dir_update(lfs, &parent, &entry, NULL);
+ if (err) {
+ return err;
+ }
+
+ // update internal root
+ if (lfs_paircmp(oldpair, lfs->root) == 0) {
+ LFS_DEBUG("Relocating root %" PRIu32 " %" PRIu32,
+ newpair[0], newpair[1]);
+ lfs->root[0] = newpair[0];
+ lfs->root[1] = newpair[1];
+ }
+
+ // clean up bad block, which should now be a desync
+ return lfs_deorphan(lfs);
+ }
+
+ // find pred
+ res = lfs_pred(lfs, oldpair, &parent);
+ if (res < 0) {
+ return res;
+ }
+
+ if (res) {
+ // just replace bad pair, no desync can occur
+ parent.d.tail[0] = newpair[0];
+ parent.d.tail[1] = newpair[1];
+
+ return lfs_dir_commit(lfs, &parent, NULL, 0);
+ }
+
+ // couldn't find dir, must be new
+ return 0;
+}
+
+int lfs_deorphan(lfs_t *lfs) {
+ lfs->deorphaned = true;
+
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ lfs_dir_t pdir = {.d.size = 0x80000000};
+ lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1};
+
+ // iterate over all directory directory entries
+ while (!lfs_pairisnull(cwd.d.tail)) {
+ int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
+ if (err) {
+ return err;
+ }
+
+ // check head blocks for orphans
+ if (!(0x80000000 & pdir.d.size)) {
+ // check if we have a parent
+ lfs_dir_t parent;
+ lfs_entry_t entry;
+ int res = lfs_parent(lfs, pdir.d.tail, &parent, &entry);
+ if (res < 0) {
+ return res;
+ }
+
+ if (!res) {
+ // we are an orphan
+ LFS_DEBUG("Found orphan %" PRIu32 " %" PRIu32,
+ pdir.d.tail[0], pdir.d.tail[1]);
+
+ pdir.d.tail[0] = cwd.d.tail[0];
+ pdir.d.tail[1] = cwd.d.tail[1];
+
+ err = lfs_dir_commit(lfs, &pdir, NULL, 0);
+ if (err) {
+ return err;
+ }
+
+ break;
+ }
+
+ if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) {
+ // we have desynced
+ LFS_DEBUG("Found desync %" PRIu32 " %" PRIu32,
+ entry.d.u.dir[0], entry.d.u.dir[1]);
+
+ pdir.d.tail[0] = entry.d.u.dir[0];
+ pdir.d.tail[1] = entry.d.u.dir[1];
+
+ err = lfs_dir_commit(lfs, &pdir, NULL, 0);
+ if (err) {
+ return err;
+ }
+
+ break;
+ }
+ }
+
+ // check entries for moves
+ lfs_entry_t entry;
+ while (true) {
+ err = lfs_dir_next(lfs, &cwd, &entry);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
+ }
+
+ if (err == LFS_ERR_NOENT) {
+ break;
+ }
+
+ // found moved entry
+ if (entry.d.type & 0x80) {
+ int moved = lfs_moved(lfs, &entry.d.u);
+ if (moved < 0) {
+ return moved;
+ }
+
+ if (moved) {
+ LFS_DEBUG("Found move %" PRIu32 " %" PRIu32,
+ entry.d.u.dir[0], entry.d.u.dir[1]);
+ err = lfs_dir_remove(lfs, &cwd, &entry);
+ if (err) {
+ return err;
+ }
+ } else {
+ LFS_DEBUG("Found partial move %" PRIu32 " %" PRIu32,
+ entry.d.u.dir[0], entry.d.u.dir[1]);
+ entry.d.type &= ~0x80;
+ err = lfs_dir_update(lfs, &cwd, &entry, NULL);
+ if (err) {
+ return err;
+ }
+ }
+ }
+ }
+
+ memcpy(&pdir, &cwd, sizeof(pdir));
+ }
+
+ return 0;
+}
+
+static int lfs_statvfs_count(void *p, lfs_block_t b)
+{
+ *(lfs_size_t *)p += 1;
+ return 0;
+}
+
+int lfs_info(lfs_t *lfs, uint32_t *total, uint32_t *used)
+{
+ lfs_size_t in_use = 0;
+ int err = lfs_traverse(lfs, lfs_statvfs_count, &in_use);
+ if (err) {
+ LFS_ERROR("lfs_info got error %d", err);
+ return err;
+ }
+
+ if (total) {
+ *total = lfs->cfg->block_count * lfs->cfg->block_size;
+ }
+
+ if (used) {
+ *used = in_use * lfs->cfg->block_size;
+ }
+
+ return 0;
+}
diff --git a/libiot/lfs/lfs.h b/libiot/lfs/lfs.h
new file mode 100644
index 0000000..edc86d2
--- /dev/null
+++ b/libiot/lfs/lfs.h
@@ -0,0 +1,495 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef LFS_H
+#define LFS_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+/// Version info ///
+
+// Software library version
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS_VERSION 0x00010006
+#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
+#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
+
+// Version of On-disk data structures
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS_DISK_VERSION 0x00010001
+#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
+#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0))
+
+
+/// Definitions ///
+
+// Type definitions
+typedef uint32_t lfs_size_t;
+typedef uint32_t lfs_off_t;
+
+typedef int32_t lfs_ssize_t;
+typedef int32_t lfs_soff_t;
+
+typedef uint32_t lfs_block_t;
+
+// Max name size in bytes
+#ifndef LFS_NAME_MAX
+#define LFS_NAME_MAX 255
+#endif
+
+// Possible error codes, these are negative to allow
+// valid positive return values
+enum lfs_error {
+ LFS_ERR_OK = 0, // No error
+ LFS_ERR_IO = -5, // Error during device operation
+ LFS_ERR_CORRUPT = -52, // Corrupted
+ LFS_ERR_NOENT = -2, // No directory entry
+ LFS_ERR_EXIST = -17, // Entry already exists
+ LFS_ERR_NOTDIR = -20, // Entry is not a dir
+ LFS_ERR_ISDIR = -21, // Entry is a dir
+ LFS_ERR_NOTEMPTY = -39, // Dir is not empty
+ LFS_ERR_BADF = -9, // Bad file number
+ LFS_ERR_INVAL = -22, // Invalid parameter
+ LFS_ERR_NOSPC = -28, // No space left on device
+ LFS_ERR_NOMEM = -12, // No more memory available
+};
+
+// File types
+enum lfs_type {
+ LFS_TYPE_REG = 0x11,
+ LFS_TYPE_DIR = 0x22,
+ LFS_TYPE_SUPERBLOCK = 0x2e,
+};
+
+// File open flags
+enum lfs_open_flags {
+ // open flags
+ LFS_O_RDONLY = 1, // Open a file as read only
+ LFS_O_WRONLY = 2, // Open a file as write only
+ LFS_O_RDWR = 3, // Open a file as read and write
+ LFS_O_CREAT = 0x0100, // Create a file if it does not exist
+ LFS_O_EXCL = 0x0200, // Fail if a file already exists
+ LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
+ LFS_O_APPEND = 0x0800, // Move to end of file on every write
+
+ // internally used flags
+ LFS_F_DIRTY = 0x10000, // File does not match storage
+ LFS_F_WRITING = 0x20000, // File has been written since last flush
+ LFS_F_READING = 0x40000, // File has been read since last flush
+ LFS_F_ERRED = 0x80000, // An error occured during write
+};
+
+// File seek flags
+enum lfs_whence_flags {
+ LFS_SEEK_SET = 0, // Seek relative to an absolute position
+ LFS_SEEK_CUR = 1, // Seek relative to the current file position
+ LFS_SEEK_END = 2, // Seek relative to the end of the file
+};
+
+
+// Configuration provided during initialization of the littlefs
+struct lfs_config {
+ // Opaque user provided context that can be used to pass
+ // information to the block device operations
+ void *context;
+
+ // Read a region in a block. Negative error codes are propogated
+ // to the user.
+ int (*read)(const struct lfs_config *c, lfs_block_t block,
+ lfs_off_t off, void *buffer, lfs_size_t size);
+
+ // Program a region in a block. The block must have previously
+ // been erased. Negative error codes are propogated to the user.
+ // May return LFS_ERR_CORRUPT if the block should be considered bad.
+ int (*prog)(const struct lfs_config *c, lfs_block_t block,
+ lfs_off_t off, const void *buffer, lfs_size_t size);
+
+ // Erase a block. A block must be erased before being programmed.
+ // The state of an erased block is undefined. Negative error codes
+ // are propogated to the user.
+ // May return LFS_ERR_CORRUPT if the block should be considered bad.
+ int (*erase)(const struct lfs_config *c, lfs_block_t block);
+
+ // Sync the state of the underlying block device. Negative error codes
+ // are propogated to the user.
+ int (*sync)(const struct lfs_config *c);
+
+ // Minimum size of a block read. This determines the size of read buffers.
+ // This may be larger than the physical read size to improve performance
+ // by caching more of the block device.
+ lfs_size_t read_size;
+
+ // Minimum size of a block program. This determines the size of program
+ // buffers. This may be larger than the physical program size to improve
+ // performance by caching more of the block device.
+ // Must be a multiple of the read size.
+ lfs_size_t prog_size;
+
+ // Size of an erasable block. This does not impact ram consumption and
+ // may be larger than the physical erase size. However, this should be
+ // kept small as each file currently takes up an entire block.
+ // Must be a multiple of the program size.
+ lfs_size_t block_size;
+
+ // Number of erasable blocks on the device.
+ lfs_size_t block_count;
+
+ // Number of blocks to lookahead during block allocation. A larger
+ // lookahead reduces the number of passes required to allocate a block.
+ // The lookahead buffer requires only 1 bit per block so it can be quite
+ // large with little ram impact. Should be a multiple of 32.
+ lfs_size_t lookahead;
+
+ // Optional, statically allocated read buffer. Must be read sized.
+ void *read_buffer;
+
+ // Optional, statically allocated program buffer. Must be program sized.
+ void *prog_buffer;
+
+ // Optional, statically allocated lookahead buffer. Must be 1 bit per
+ // lookahead block.
+ void *lookahead_buffer;
+
+ // Optional, statically allocated buffer for files. Must be program sized.
+ // If enabled, only one file may be opened at a time.
+ void *file_buffer;
+};
+
+// Optional configuration provided during lfs_file_opencfg
+struct lfs_file_config {
+ // Optional, statically allocated buffer for files. Must be program sized.
+ // If NULL, malloc will be used by default.
+ void *buffer;
+};
+
+// File info structure
+struct lfs_info {
+ // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
+ uint8_t type;
+
+ // Size of the file, only valid for REG files
+ lfs_size_t size;
+
+ // Name of the file stored as a null-terminated string
+ char name[LFS_NAME_MAX+1];
+};
+
+
+/// littlefs data structures ///
+typedef struct lfs_entry {
+ lfs_off_t off;
+
+ struct lfs_disk_entry {
+ uint8_t type;
+ uint8_t elen;
+ uint8_t alen;
+ uint8_t nlen;
+ union {
+ struct {
+ lfs_block_t head;
+ lfs_size_t size;
+ } file;
+ lfs_block_t dir[2];
+ } u;
+ } d;
+} lfs_entry_t;
+
+typedef struct lfs_cache {
+ lfs_block_t block;
+ lfs_off_t off;
+ uint8_t *buffer;
+} lfs_cache_t;
+
+typedef struct lfs_file {
+ struct lfs_file *next;
+ lfs_block_t pair[2];
+ lfs_off_t poff;
+
+ lfs_block_t head;
+ lfs_size_t size;
+
+ const struct lfs_file_config *cfg;
+ uint32_t flags;
+ lfs_off_t pos;
+ lfs_block_t block;
+ lfs_off_t off;
+ lfs_cache_t cache;
+} lfs_file_t;
+
+typedef struct lfs_dir {
+ struct lfs_dir *next;
+ lfs_block_t pair[2];
+ lfs_off_t off;
+
+ lfs_block_t head[2];
+ lfs_off_t pos;
+
+ struct lfs_disk_dir {
+ uint32_t rev;
+ lfs_size_t size;
+ lfs_block_t tail[2];
+ } d;
+} lfs_dir_t;
+
+typedef struct lfs_superblock {
+ lfs_off_t off;
+
+ struct lfs_disk_superblock {
+ uint8_t type;
+ uint8_t elen;
+ uint8_t alen;
+ uint8_t nlen;
+ lfs_block_t root[2];
+ uint32_t block_size;
+ uint32_t block_count;
+ uint32_t version;
+ char magic[8];
+ } d;
+} lfs_superblock_t;
+
+typedef struct lfs_free {
+ lfs_block_t off;
+ lfs_block_t size;
+ lfs_block_t i;
+ lfs_block_t ack;
+ uint32_t *buffer;
+} lfs_free_t;
+
+// The littlefs type
+typedef struct lfs {
+ const struct lfs_config *cfg;
+
+ lfs_block_t root[2];
+ lfs_file_t *files;
+ lfs_dir_t *dirs;
+
+ lfs_cache_t rcache;
+ lfs_cache_t pcache;
+
+ lfs_free_t free;
+ bool deorphaned;
+} lfs_t;
+
+
+/// Filesystem functions ///
+
+// Format a block device with the littlefs
+//
+// Requires a littlefs object and config struct. This clobbers the littlefs
+// object, and does not leave the filesystem mounted. The config struct must
+// be zeroed for defaults and backwards compatibility.
+//
+// Returns a negative error code on failure.
+int lfs_format(lfs_t *lfs, const struct lfs_config *config);
+
+// Mounts a littlefs
+//
+// Requires a littlefs object and config struct. Multiple filesystems
+// may be mounted simultaneously with multiple littlefs objects. Both
+// lfs and config must be allocated while mounted. The config struct must
+// be zeroed for defaults and backwards compatibility.
+//
+// Returns a negative error code on failure.
+int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
+
+// Unmounts a littlefs
+//
+// Does nothing besides releasing any allocated resources.
+// Returns a negative error code on failure.
+int lfs_umount(lfs_t *lfs);
+
+/// General operations ///
+
+// Removes a file or directory
+//
+// If removing a directory, the directory must be empty.
+// Returns a negative error code on failure.
+int lfs_remove(lfs_t *lfs, const char *path);
+
+// Rename or move a file or directory
+//
+// If the destination exists, it must match the source in type.
+// If the destination is a directory, the directory must be empty.
+//
+// Returns a negative error code on failure.
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
+
+// Find info about a file or directory
+//
+// Fills out the info structure, based on the specified file or directory.
+// Returns a negative error code on failure.
+int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
+
+
+/// File operations ///
+
+// Open a file
+//
+// The mode that the file is opened in is determined by the flags, which
+// are values from the enum lfs_open_flags that are bitwise-ored together.
+//
+// Returns a negative error code on failure.
+int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
+ const char *path, int flags);
+
+// Open a file with extra configuration
+//
+// The mode that the file is opened in is determined by the flags, which
+// are values from the enum lfs_open_flags that are bitwise-ored together.
+//
+// The config struct provides additional config options per file as described
+// above. The config struct must be allocated while the file is open, and the
+// config struct must be zeroed for defaults and backwards compatibility.
+//
+// Returns a negative error code on failure.
+int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
+ const char *path, int flags,
+ const struct lfs_file_config *config);
+
+// Close a file
+//
+// Any pending writes are written out to storage as though
+// sync had been called and releases any allocated resources.
+//
+// Returns a negative error code on failure.
+int lfs_file_close(lfs_t *lfs, lfs_file_t *file);
+
+// Synchronize a file on storage
+//
+// Any pending writes are written out to storage.
+// Returns a negative error code on failure.
+int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
+
+// Read data from file
+//
+// Takes a buffer and size indicating where to store the read data.
+// Returns the number of bytes read, or a negative error code on failure.
+lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
+ void *buffer, lfs_size_t size);
+
+// Write data to file
+//
+// Takes a buffer and size indicating the data to write. The file will not
+// actually be updated on the storage until either sync or close is called.
+//
+// Returns the number of bytes written, or a negative error code on failure.
+lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
+ const void *buffer, lfs_size_t size);
+
+// Change the position of the file
+//
+// The change in position is determined by the offset and whence flag.
+// Returns the old position of the file, or a negative error code on failure.
+lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
+ lfs_soff_t off, int whence);
+
+// Truncates the size of the file to the specified size
+//
+// Returns a negative error code on failure.
+int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
+
+// Return the position of the file
+//
+// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
+// Returns the position of the file, or a negative error code on failure.
+lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file);
+
+// Change the position of the file to the beginning of the file
+//
+// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
+// Returns a negative error code on failure.
+int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file);
+
+// Return the size of the file
+//
+// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END)
+// Returns the size of the file, or a negative error code on failure.
+lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file);
+
+
+/// Directory operations ///
+
+// Create a directory
+//
+// Returns a negative error code on failure.
+int lfs_mkdir(lfs_t *lfs, const char *path);
+
+// Open a directory
+//
+// Once open a directory can be used with read to iterate over files.
+// Returns a negative error code on failure.
+int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path);
+
+// Close a directory
+//
+// Releases any allocated resources.
+// Returns a negative error code on failure.
+int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir);
+
+// Read an entry in the directory
+//
+// Fills out the info structure, based on the specified file or directory.
+// Returns a negative error code on failure.
+int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);
+
+// Change the position of the directory
+//
+// The new off must be a value previous returned from tell and specifies
+// an absolute offset in the directory seek.
+//
+// Returns a negative error code on failure.
+int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off);
+
+// Return the position of the directory
+//
+// The returned offset is only meant to be consumed by seek and may not make
+// sense, but does indicate the current position in the directory iteration.
+//
+// Returns the position of the directory, or a negative error code on failure.
+lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);
+
+// Change the position of the directory to the beginning of the directory
+//
+// Returns a negative error code on failure.
+int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
+
+
+/// Miscellaneous littlefs specific operations ///
+
+// Traverse through all blocks in use by the filesystem
+//
+// The provided callback will be called with each block address that is
+// currently in use by the filesystem. This can be used to determine which
+// blocks are in use or how much of the storage is available.
+//
+// Returns a negative error code on failure.
+int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
+
+// Prunes any recoverable errors that may have occured in the filesystem
+//
+// Not needed to be called by user unless an operation is interrupted
+// but the filesystem is still mounted. This is already called on first
+// allocation.
+//
+// Returns a negative error code on failure.
+int lfs_deorphan(lfs_t *lfs);
+
+int lfs_info(lfs_t *lfs, uint32_t *total, uint32_t *used);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/libiot/lfs/lfs_util.c b/libiot/lfs/lfs_util.c
new file mode 100644
index 0000000..9ca0756
--- /dev/null
+++ b/libiot/lfs/lfs_util.c
@@ -0,0 +1,31 @@
+/*
+ * lfs util functions
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include "lfs_util.h"
+
+// Only compile if user does not provide custom config
+#ifndef LFS_CONFIG
+
+
+// Software CRC implementation with small lookup table
+void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size) {
+ static const uint32_t rtable[16] = {
+ 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
+ 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+ 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
+ };
+
+ const uint8_t *data = buffer;
+
+ for (size_t i = 0; i < size; i++) {
+ *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf];
+ *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf];
+ }
+}
+
+
+#endif
diff --git a/libiot/lfs/lfs_util.h b/libiot/lfs/lfs_util.h
new file mode 100644
index 0000000..b2dc237
--- /dev/null
+++ b/libiot/lfs/lfs_util.h
@@ -0,0 +1,186 @@
+/*
+ * lfs utility functions
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef LFS_UTIL_H
+#define LFS_UTIL_H
+
+// Users can override lfs_util.h with their own configuration by defining
+// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
+//
+// If LFS_CONFIG is used, none of the default utils will be emitted and must be
+// provided by the config file. To start I would suggest copying lfs_util.h and
+// modifying as needed.
+#ifdef LFS_CONFIG
+#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
+#define LFS_STRINGIZE2(x) #x
+#include LFS_STRINGIZE(LFS_CONFIG)
+#else
+
+// System includes
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#ifndef LFS_NO_MALLOC
+#include <stdlib.h>
+#endif
+#ifndef LFS_NO_ASSERT
+#include <assert.h>
+#endif
+#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR)
+#include <stdio.h>
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+// Macros, may be replaced by system specific wrappers. Arguments to these
+// macros must not have side-effects as the macros can be removed for a smaller
+// code footprint
+
+// Logging functions
+#ifndef LFS_NO_DEBUG
+#define LFS_DEBUG(fmt, ...) \
+ printf("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
+#else
+#define LFS_DEBUG(fmt, ...)
+#endif
+
+#ifndef LFS_NO_WARN
+#define LFS_WARN(fmt, ...) \
+ printf("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
+#else
+#define LFS_WARN(fmt, ...)
+#endif
+
+#ifndef LFS_NO_ERROR
+#define LFS_ERROR(fmt, ...) \
+ printf("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
+#else
+#define LFS_ERROR(fmt, ...)
+#endif
+
+// Runtime assertions
+#ifndef LFS_NO_ASSERT
+#define LFS_ASSERT(test) assert(test)
+#else
+#define LFS_ASSERT(test)
+#endif
+
+
+// Builtin functions, these may be replaced by more efficient
+// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
+// expensive basic C implementation for debugging purposes
+
+// Min/max functions for unsigned 32-bit numbers
+static inline uint32_t lfs_max(uint32_t a, uint32_t b) {
+ return (a > b) ? a : b;
+}
+
+static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
+ return (a < b) ? a : b;
+}
+
+// Find the next smallest power of 2 less than or equal to a
+static inline uint32_t lfs_npw2(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
+ return 32 - __builtin_clz(a-1);
+#else
+ uint32_t r = 0;
+ uint32_t s;
+ a -= 1;
+ s = (a > 0xffff) << 4; a >>= s; r |= s;
+ s = (a > 0xff ) << 3; a >>= s; r |= s;
+ s = (a > 0xf ) << 2; a >>= s; r |= s;
+ s = (a > 0x3 ) << 1; a >>= s; r |= s;
+ return (r | (a >> 1)) + 1;
+#endif
+}
+
+// Count the number of trailing binary zeros in a
+// lfs_ctz(0) may be undefined
+static inline uint32_t lfs_ctz(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__)
+ return __builtin_ctz(a);
+#else
+ return lfs_npw2((a & -a) + 1) - 1;
+#endif
+}
+
+// Count the number of binary ones in a
+static inline uint32_t lfs_popc(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
+ return __builtin_popcount(a);
+#else
+ a = a - ((a >> 1) & 0x55555555);
+ a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
+ return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
+#endif
+}
+
+// Find the sequence comparison of a and b, this is the distance
+// between a and b ignoring overflow
+static inline int lfs_scmp(uint32_t a, uint32_t b) {
+ return (int)(unsigned)(a - b);
+}
+
+// Convert from 32-bit little-endian to native order
+static inline uint32_t lfs_fromle32(uint32_t a) {
+#if !defined(LFS_NO_INTRINSICS) && ( \
+ (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
+ (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
+ (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+ return a;
+#elif !defined(LFS_NO_INTRINSICS) && ( \
+ (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
+ (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
+ (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
+ return __builtin_bswap32(a);
+#else
+ return (((uint8_t*)&a)[0] << 0) |
+ (((uint8_t*)&a)[1] << 8) |
+ (((uint8_t*)&a)[2] << 16) |
+ (((uint8_t*)&a)[3] << 24);
+#endif
+}
+
+// Convert to 32-bit little-endian from native order
+static inline uint32_t lfs_tole32(uint32_t a) {
+ return lfs_fromle32(a);
+}
+
+// Calculate CRC-32 with polynomial = 0x04c11db7
+void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
+
+// Allocate memory, only used if buffers are not provided to littlefs
+static inline void *lfs_malloc(size_t size) {
+#ifndef LFS_NO_MALLOC
+ return malloc(size);
+#else
+ (void)size;
+ return NULL;
+#endif
+}
+
+// Deallocate memory, only used if buffers are not provided to littlefs
+static inline void lfs_free(void *p) {
+#ifndef LFS_NO_MALLOC
+ free(p);
+#else
+ (void)p;
+#endif
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
+#endif
diff --git a/libiot/linenoise/linenoise.c b/libiot/linenoise/linenoise.c
new file mode 100644
index 0000000..0b30c92
--- /dev/null
+++ b/libiot/linenoise/linenoise.c
@@ -0,0 +1,637 @@
+/* linenoise.c -- VERSION 1.0
+ *
+ * Guerrilla line editing library against the idea that a line editing lib
+ * needs to be 20,000 lines of C code.
+ *
+ * You can find the latest source code at:
+ *
+ * http://github.com/antirez/linenoise
+ *
+ * Does a number of crazy assumptions that happen to be true in 99.9999% of
+ * the 2010 UNIX computers around.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * References:
+ * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
+ *
+ * Todo list:
+ * - Filter bogus Ctrl+<char> combinations.
+ * - Win32 support
+ *
+ * Bloat:
+ * - History search like Ctrl+r in readline?
+ *
+ * List of escape sequences used by this program, we do everything just
+ * with three sequences. In order to be so cheap we may have some
+ * flickering effect with some slow terminal, but the lesser sequences
+ * the more compatible.
+ *
+ * EL (Erase Line)
+ * Sequence: ESC [ n K
+ * Effect: if n is 0 or missing, clear from cursor to end of line
+ * Effect: if n is 1, clear from beginning of line to cursor
+ * Effect: if n is 2, clear entire line
+ *
+ * CUF (CUrsor Forward)
+ * Sequence: ESC [ n C
+ * Effect: moves cursor forward n chars
+ *
+ * CUB (CUrsor Backward)
+ * Sequence: ESC [ n D
+ * Effect: moves cursor backward n chars
+ *
+ * The following is used to get the terminal width if getting
+ * the width with the TIOCGWINSZ ioctl fails
+ *
+ * DSR (Device Status Report)
+ * Sequence: ESC [ 6 n
+ * Effect: reports the current cusor position as ESC [ n ; m R
+ * where n is the row and m is the column
+ *
+ * When multi line mode is enabled, we also use an additional escape
+ * sequence. However multi line editing is disabled by default.
+ *
+ * CUU (Cursor Up)
+ * Sequence: ESC [ n A
+ * Effect: moves cursor up of n chars.
+ *
+ * CUD (Cursor Down)
+ * Sequence: ESC [ n B
+ * Effect: moves cursor down of n chars.
+ *
+ * When linenoiseClearScreen() is called, two additional escape sequences
+ * are used in order to clear the screen and position the cursor at home
+ * position.
+ *
+ * CUP (Cursor position)
+ * Sequence: ESC [ H
+ * Effect: moves the cursor to upper left corner
+ *
+ * ED (Erase display)
+ * Sequence: ESC [ 2 J
+ * Effect: clear the whole screen
+ *
+ */
+
+#include "luartos.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/status.h>
+#include <sys/types.h>
+#include <sys/list.h>
+#include <reent.h>
+
+#include <sys/mount.h>
+#include <sys/path.h>
+#include <sys/history.h>
+
+#include "linenoise.h"
+#include "lua.h"
+
+#define LINENOISE_MAX_LINE LUA_MAXINPUT
+
+/* The linenoiseState structure represents the state during line editing.
+ * We pass this state to functions implementing specific editing
+ * functionalities. */
+struct linenoiseState {
+ int ifd; /* Terminal stdin file descriptor. */
+ int ofd; /* Terminal stdout file descriptor. */
+ char *buf; /* Edited line buffer. */
+ size_t buflen; /* Edited line buffer size. */
+ const char *prompt; /* Prompt to display. */
+ size_t plen; /* Prompt length. */
+ size_t pos; /* Current cursor position. */
+ size_t oldpos; /* Previous refresh cursor position. */
+ size_t len; /* Current edited line length. */
+ size_t cols; /* Number of columns in terminal-> */
+ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */
+ int history_index; /* The history index we are currently editing. */
+ bool password; /* A password is being entered. */
+};
+
+enum KEY_ACTION{
+ KEY_NULL = 0, /* NULL */
+ CTRL_A = 1, /* Ctrl+a */
+ CTRL_B = 2, /* Ctrl-b */
+ CTRL_C = 3, /* Ctrl-c */
+ CTRL_D = 4, /* Ctrl-d */
+ CTRL_E = 5, /* Ctrl-e */
+ CTRL_F = 6, /* Ctrl-f */
+ CTRL_H = 8, /* Ctrl-h */
+ TAB = 9, /* Tab */
+ CTRL_K = 11, /* Ctrl+k */
+ CTRL_L = 12, /* Ctrl+l */
+ ENTER = 13, /* Enter */
+ CTRL_N = 14, /* Ctrl-n */
+ CTRL_P = 16, /* Ctrl-p */
+ CTRL_T = 20, /* Ctrl-t */
+ CTRL_U = 21, /* Ctrl+u */
+ CTRL_W = 23, /* Ctrl+w */
+ ESC = 27, /* Escape */
+ BACKSPACE = 127 /* Backspace */
+};
+
+static void refreshLine(struct linenoiseState *l);
+
+static void linenoiseHistoryAdd(struct linenoiseState *l);
+static void linenoiseHistoryGet(struct linenoiseState *l, int up);
+
+/* Debugging macro. */
+#if 0
+FILE *lndebug_fp = NULL;
+#define lndebug(...) \
+ do { \
+ if (lndebug_fp == NULL) { \
+ lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
+ fprintf(lndebug_fp, \
+ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
+ (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
+ (int)l->maxrows,old_rows); \
+ } \
+ fprintf(lndebug_fp, ", " __VA_ARGS__); \
+ fflush(lndebug_fp); \
+ } while (0)
+#else
+#define lndebug(fmt, ...)
+#endif
+
+/* ======================= Low level terminal handling ====================== */
+
+/* Clear the screen. Used to handle ctrl+l */
+void linenoiseClearScreen(void) {
+ if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
+ /* nothing to do, just to avoid warning. */
+ }
+}
+
+
+/* =========================== Line editing ================================= */
+
+/* We define a very simple "append buffer" structure, that is an heap
+ * allocated string where we can append to. This is useful in order to
+ * write all the escape sequences in a buffer and flush them to the standard
+ * output in a single call, to avoid flickering effects. */
+struct abuf {
+ char *b;
+ int len;
+};
+
+static void abInit(struct abuf *ab) {
+ ab->b = NULL;
+ ab->len = 0;
+}
+
+static void abAppend(struct abuf *ab, const char *s, int len) {
+ char *new = (ab->b ? realloc(ab->b,ab->len+len) : malloc(ab->len+len));
+
+ if (new == NULL) return;
+ memcpy(new+ab->len,s,len);
+ ab->b = new;
+ ab->len += len;
+}
+
+static void abFree(struct abuf *ab) {
+ free(ab->b);
+}
+
+/* Single line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static void refreshSingleLine(struct linenoiseState *l) {
+ char seq[64];
+ size_t plen = strlen(l->prompt);
+ int fd = l->ofd;
+ char *buf = l->buf;
+ size_t len = l->len;
+ size_t pos = l->pos;
+ struct abuf ab;
+
+ while((plen+pos) >= l->cols) {
+ buf++;
+ len--;
+ pos--;
+ }
+ while (plen+len > l->cols) {
+ len--;
+ }
+
+ abInit(&ab);
+ /* Cursor to left edge */
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+ /* Write the prompt and the current buffer content */
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ if (!l->password) {
+ abAppend(&ab,buf,len);
+ }
+ /* Erase to right */
+ snprintf(seq,64,"\x1b[0K");
+ abAppend(&ab,seq,strlen(seq));
+ /* Move cursor to original position. */
+ if (!l->password) {
+ snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
+ abAppend(&ab,seq,strlen(seq));
+ }
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
+}
+
+/* Calls the two low level functions refreshSingleLine() or
+ * refreshMultiLine() according to the selected mode. */
+static void refreshLine(struct linenoiseState *l) {
+ refreshSingleLine(l);
+}
+
+/* Insert the character 'c' at cursor current position.
+ *
+ * On error writing to the terminal -1 is returned, otherwise 0. */
+int linenoiseEditInsert(struct linenoiseState *l, char c) {
+ if (l->len < l->buflen) {
+ if (l->len == l->pos) {
+ l->buf[l->pos] = c;
+ l->pos++;
+ l->len++;
+ l->buf[l->len] = '\0';
+ if ((l->plen+l->len < l->cols)) {
+ /* Avoid a full update of the line in the
+ * trivial case. */
+ if (!l->password) {
+ if (write(l->ofd,&c,1) == -1) return -1;
+ }
+ } else {
+ refreshLine(l);
+ }
+ } else {
+ memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
+ l->buf[l->pos] = c;
+ l->len++;
+ l->pos++;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+ }
+ return 0;
+}
+
+/* Move cursor on the left. */
+void linenoiseEditMoveLeft(struct linenoiseState *l) {
+ if (l->pos > 0) {
+ l->pos--;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor on the right. */
+void linenoiseEditMoveRight(struct linenoiseState *l) {
+ if (l->pos != l->len) {
+ l->pos++;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor to the start of the line. */
+void linenoiseEditMoveHome(struct linenoiseState *l) {
+ if (l->pos != 0) {
+ l->pos = 0;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor to the end of the line. */
+void linenoiseEditMoveEnd(struct linenoiseState *l) {
+ if (l->pos != l->len) {
+ l->pos = l->len;
+ refreshLine(l);
+ }
+}
+
+/* Delete the character at the right of the cursor without altering the cursor
+ * position. Basically this is what happens with the "Delete" keyboard key. */
+void linenoiseEditDelete(struct linenoiseState *l) {
+ if (l->len > 0 && l->pos < l->len) {
+ memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
+ l->len--;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+}
+
+/* Backspace implementation. */
+void linenoiseEditBackspace(struct linenoiseState *l) {
+ if (l->pos > 0 && l->len > 0) {
+ memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
+ l->pos--;
+ l->len--;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+}
+
+/* Delete the previosu word, maintaining the cursor at the start of the
+ * current word. */
+void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
+ size_t old_pos = l->pos;
+ size_t diff;
+
+ while (l->pos > 0 && l->buf[l->pos-1] == ' ')
+ l->pos--;
+ while (l->pos > 0 && l->buf[l->pos-1] != ' ')
+ l->pos--;
+ diff = old_pos - l->pos;
+ memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
+ l->len -= diff;
+ refreshLine(l);
+}
+
+/* This function is the core of the line editing capability of linenoise.
+ * It expects 'fd' to be already in "raw mode" so that every key pressed
+ * will be returned ASAP to read().
+ *
+ * The resulting string is put into 'buf' when the user type enter, or
+ * when ctrl+d is typed.
+ *
+ * The function returns the length of the current buffer. */
+static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt, bool password)
+{
+ struct linenoiseState *l;
+
+ l = malloc(sizeof(struct linenoiseState));
+ if (!l) {
+ return 0;
+ }
+
+ /* Populate the linenoise state that we pass to functions implementing
+ * specific editing functionalities. */
+ l->ifd = stdin_fd;
+ l->ofd = stdout_fd;
+ l->buf = buf;
+ l->buflen = buflen;
+ l->prompt = prompt;
+ l->plen = strlen(prompt);
+ l->oldpos = l->pos = 0;
+ l->len = 0;
+ l->cols = buflen;
+ l->maxrows = 0;
+ l->history_index = -1;
+ l->password = password;
+
+ /* Buffer starts empty. */
+ l->buf[0] = '\0';
+ l->buflen--; /* Make sure there is always space for the nulterm */
+
+ /* The latest history entry is always our current buffer, that
+ * initially is just an empty string. */
+
+ if (write(l->ofd,prompt,l->plen) == -1) {
+ free(l);
+ return -1;
+ }
+
+ while(1) {
+ char c;
+ int nread;
+ char seq[3];
+
+ nread = read(l->ifd,&c,1);
+
+ if (nread <= 0) {
+ int len = l->len;
+ free(l);
+ return len;
+ }
+
+ switch(c) {
+ case ENTER: /* enter */
+ if (l->len > 0) {
+ if(!l->password) linenoiseHistoryAdd(l);
+ }
+
+ int len = l->len;
+ free(l);
+ return (int)len;
+ case CTRL_C: /* ctrl-c */
+ errno = EAGAIN;
+ free(l);
+ return -1;
+ case BACKSPACE: /* backspace */
+ case 8: /* ctrl-h */
+ linenoiseEditBackspace(l);
+ break;
+ case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the
+ line is empty, act as end-of-file. */
+ if (l->len > 0) {
+ linenoiseEditDelete(l);
+ } else {
+ free(l);
+ return -1;
+ }
+ break;
+ case CTRL_T: /* ctrl-t, swaps current character with previous. */
+ if (l->pos > 0 && l->pos < l->len) {
+ int aux = buf[l->pos-1];
+ buf[l->pos-1] = buf[l->pos];
+ buf[l->pos] = aux;
+ if (l->pos != l->len-1) l->pos++;
+ refreshLine(l);
+ }
+ break;
+ case CTRL_B: /* ctrl-b */
+ linenoiseEditMoveLeft(l);
+ break;
+ case CTRL_F: /* ctrl-f */
+ linenoiseEditMoveRight(l);
+ break;
+ case CTRL_P: /* ctrl-p */
+ break;
+ case CTRL_N: /* ctrl-n */
+ break;
+ case ESC: /* escape sequence */
+ /* Read the next two bytes representing the escape sequence.
+ * Use two calls to handle slow terminals returning the two
+ * chars at different times. */
+ if (read(l->ifd,seq,1) == -1) break;
+ if (read(l->ifd,seq+1,1) == -1) break;
+
+ /* ESC [ sequences. */
+ if (seq[0] == '[') {
+ if (seq[1] >= '0' && seq[1] <= '9') {
+ /* Extended escape, read additional byte. */
+ if (read(l->ifd,seq+2,1) == -1) break;
+ if (seq[2] == '~') {
+ switch(seq[1]) {
+ case '3': /* Delete key. */
+ linenoiseEditDelete(l);
+ break;
+ }
+ }
+ } else {
+ switch(seq[1]) {
+ case 'A': /* Up */
+ linenoiseHistoryGet(l,1);
+ break;
+ case 'B': /* Down */
+ linenoiseHistoryGet(l,0);
+ break;
+ case 'C': /* Right */
+ linenoiseEditMoveRight(l);
+ break;
+ case 'D': /* Left */
+ linenoiseEditMoveLeft(l);
+ break;
+ case 'H': /* Home */
+ linenoiseEditMoveHome(l);
+ break;
+ case 'F': /* End*/
+ linenoiseEditMoveEnd(l);
+ break;
+ }
+ }
+ }
+
+ /* ESC O sequences. */
+ else if (seq[0] == 'O') {
+ switch(seq[1]) {
+ case 'H': /* Home */
+ linenoiseEditMoveHome(l);
+ break;
+ case 'F': /* End*/
+ linenoiseEditMoveEnd(l);
+ break;
+ }
+ }
+ break;
+ default:
+ if (linenoiseEditInsert(l,c)) {
+ free(l);
+ return -1;
+ }
+ break;
+ case CTRL_U: /* Ctrl+u, delete the whole line. */
+ buf[0] = '\0';
+ l->pos = l->len = 0;
+ refreshLine(l);
+ break;
+ case CTRL_K: /* Ctrl+k, delete from current to end of line. */
+ buf[l->pos] = '\0';
+ l->len = l->pos;
+ refreshLine(l);
+ break;
+ case CTRL_A: /* Ctrl+a, go to the start of the line */
+ linenoiseEditMoveHome(l);
+ break;
+ case CTRL_E: /* ctrl+e, go to the end of the line */
+ linenoiseEditMoveEnd(l);
+ break;
+ case CTRL_L: /* ctrl+l, clear screen */
+ linenoiseClearScreen();
+ refreshLine(l);
+ break;
+ case CTRL_W: /* ctrl+w, delete previous word */
+ linenoiseEditDeletePrevWord(l);
+ break;
+ }
+ }
+
+ int len = l->len;
+ free(l);
+
+ return len;
+}
+
+/* This function calls the line editing function linenoiseEdit() using
+ * the STDIN file descriptor set in raw mode. */
+static int linenoiseRaw(char *buf, size_t buflen, const char *prompt, bool password) {
+ int count;
+
+ count = linenoiseEdit(fileno(__getreent()->_stdin), fileno(__getreent()->_stdout), buf, buflen, prompt, password);
+ printf("\n");
+
+ return count;
+}
+
+static void linenoiseHistoryAdd(struct linenoiseState *l) {
+ if (!status_get(STATUS_LUA_HISTORY)) return;
+
+ history_add(l->buf);
+
+ l->history_index = -1;
+}
+
+static void linenoiseHistoryGet(struct linenoiseState *l, int up) {
+ if (!status_get(STATUS_LUA_HISTORY)) return;
+
+ int res = history_get(l->history_index, up, l->buf, l->cols);
+ if (res >= -1) {
+ l->history_index = res;
+ l->len = strlen(l->buf);
+ l->pos = l->len;
+ }
+
+ refreshLine(l);
+}
+
+void linenoiseHistoryClear() {
+ if (status_get(STATUS_LUA_HISTORY)) return;
+
+ char fname[PATH_MAX + 1];
+
+ if (!mount_history_file(fname, sizeof(fname))) {
+ return;
+ }
+
+ remove(fname);
+}
+
+/* The high level function that is the main API of the linenoise library.
+ * This function checks if the terminal has basic capabilities, just checking
+ * for a blacklist of stupid terminals, and later either calls the line
+ * editing function or uses dummy fgets() so that you will be able to type
+ * something even in the most desperate of the conditions. */
+int linenoisePassword(char *buf, const char *prompt, bool password) {
+ int count;
+ count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt,password);
+ if (count == -1) return -1;
+ return count;
+}
+
+int linenoise(char *buf, const char *prompt) {
+ return linenoisePassword(buf, prompt, false);
+}
diff --git a/libiot/linenoise/linenoise.h b/libiot/linenoise/linenoise.h
new file mode 100644
index 0000000..2ed07a9
--- /dev/null
+++ b/libiot/linenoise/linenoise.h
@@ -0,0 +1,56 @@
+/* linenoise.h -- VERSION 1.0
+ *
+ * Guerrilla line editing library against the idea that a line editing lib
+ * needs to be 20,000 lines of C code.
+ *
+ * See linenoise.c for more information.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __LINENOISE_H
+#define __LINENOISE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int linenoise(char *buf, const char *prompt);
+int linenoisePassword(char *buf, const char *prompt, bool password);
+void linenoiseClearScreen(void);
+void linenoiseSetMultiLine(int ml);
+void linenoisePrintKeyCodes(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LINENOISE_H */
diff --git a/libiot/mkfile b/libiot/mkfile
new file mode 100644
index 0000000..516eb9d
--- /dev/null
+++ b/libiot/mkfile
@@ -0,0 +1,26 @@
+# Directories common to all architectures.
+# Build in order:
+# - critical libraries used by the limbo compiler
+# - the limbo compiler (used to build some subsequent libraries)
+# - the remaining libraries
+# - commands
+# - utilities
+
+<$ROOT/mkconfig
+
+LIB=libiot.a
+
+OFILES=\
+
+CFLAGS=\
+ $CFLAGS\
+ -Iinclude\
+
+#<pthread/mkfile
+<freertos/mkfile
+<sys/mkfile
+<hal/mkfile
+<spiffs/mkfile
+<vfs/mkfile
+
+<$ROOT/mkfiles/mksyslib-$SHELLTYPE
diff --git a/libiot/pthread/_pthread.c b/libiot/pthread/_pthread.c
new file mode 100644
index 0000000..d0353e7
--- /dev/null
+++ b/libiot/pthread/_pthread.c
@@ -0,0 +1,743 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "luartos.h"
+
+#include "esp_err.h"
+#include "esp_attr.h"
+
+#include "thread.h"
+#include "_pthread.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/mutex.h>
+#include <sys/queue.h>
+
+// A mutex to sync with critical parts
+static struct mtx thread_mtx;
+
+// List of active threads. An active thread is one that is created
+// and has not yet terminated.
+static struct list active_threads;
+
+// List of inactive threads. An inactive thread is one that has yet
+// terminated, but that was not joined. We must to store this threads
+// in a list to allow joins after the thread termination.
+static struct list inactive_threads;
+
+struct list key_list;
+
+static uint8_t inited = 0;
+
+// Arguments for the thread task
+struct pthreadTaskArg {
+ struct pthread *thread; // Thread data
+ void *(*pthread_function)(void *); // Thread start routine
+ void *args; // Thread start routine arguments
+ xTaskHandle parent_task; // Handle of parent task
+};
+
+void pthreadTask(void *task_arguments);
+
+void _pthread_lock() {
+ mtx_lock(&thread_mtx);
+}
+
+void _pthread_unlock() {
+ mtx_unlock(&thread_mtx);
+}
+
+void _pthread_init() {
+ if (!inited) {
+ // Create mutexes
+ mtx_init(&thread_mtx, NULL, NULL, 0);
+
+ // Init lists
+ lstinit(&active_threads, 1, LIST_NOT_INDEXED);
+ lstinit(&inactive_threads, 1, LIST_NOT_INDEXED);
+
+ lstinit(&key_list, 1, LIST_DEFAULT);
+
+ inited = 1;
+ }
+}
+
+int _pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine)(void *), void *args) {
+ BaseType_t res;
+
+ // Get the creation attributes
+ pthread_attr_t cattr;
+ if (attr) {
+ // Creation attributes passed as function arguments, copy to thread data
+ memcpy(&cattr, attr, sizeof(pthread_attr_t));
+ } else {
+ // Apply default creation attributes
+ pthread_attr_init(&cattr);
+ }
+
+ // Create and populate the thread data
+ struct pthread *threadd;
+
+ threadd = (struct pthread *) calloc(1, sizeof(struct pthread));
+ if (!threadd) {
+ return EAGAIN;
+ }
+
+ // Thread is active
+ threadd->active = 1;
+
+ // Copy creation attributes
+ memcpy(&threadd->attr, &cattr, sizeof(pthread_attr_t));
+
+ // No task joined
+ threadd->joined_task = NULL;
+
+ // No result
+ threadd->res = NULL;
+
+ // Initialize signal handlers
+ pthread_t self;
+
+ if ((self = pthread_self())) {
+ // Copy signal handlers from current thread
+ bcopy(((struct pthread *) self)->signals, threadd->signals,
+ sizeof(sig_t) * PTHREAD_NSIG);
+ } else {
+ // Set default signal handlers
+ int i;
+
+ for (i = 0; i < PTHREAD_NSIG; i++) {
+ threadd->signals[i] = SIG_DFL;
+ }
+ }
+
+ // Init clean list
+ lstinit(&threadd->clean_list, 1, LIST_DEFAULT);
+
+ // Add thread to the thread list
+ res = lstadd(&active_threads, (void *) threadd, NULL);
+ if (res) {
+ free(threadd);
+ return EAGAIN;
+ }
+
+ *thread = (pthread_t) threadd;
+
+ // Create and populate the arguments for the thread task
+ struct pthreadTaskArg *taskArgs;
+
+ taskArgs = (struct pthreadTaskArg *) calloc(1,
+ sizeof(struct pthreadTaskArg));
+ if (!taskArgs) {
+ lstremove(&active_threads, (int) threadd, 1);
+ return EAGAIN;
+ }
+
+ taskArgs->thread = threadd;
+ taskArgs->pthread_function = start_routine;
+ taskArgs->args = args;
+ taskArgs->parent_task = xTaskGetCurrentTaskHandle();
+
+ // This is the parent thread. Now we need to wait for the initialization of critical information
+ // that is provided by the pthreadTask:
+ //
+ // * Allocate Lua RTOS specific TCB parts, using local storage pointer assigned to pthreads
+ // * CPU core id when thread is running
+ // * The thread id, stored in Lua RTOS specific TCB parts
+ // * The Lua state, stored in Lua RTOS specific TCB parts
+ //
+
+ // Create related task
+ int cpu = 0;
+
+ if (cattr.schedparam.affinityset != CPU_INITIALIZER) {
+ if (CPU_ISSET(0, &cattr.schedparam.affinityset)) {
+ cpu = 0;
+ } else if (CPU_ISSET(1, &cattr.schedparam.affinityset)) {
+ cpu = 1;
+ }
+ } else {
+ cpu = tskNO_AFFINITY;
+ }
+
+ xTaskHandle xCreatedTask; // Related task
+
+ if (cpu == tskNO_AFFINITY) {
+ res = xTaskCreate(pthreadTask, "thread", cattr.stacksize, taskArgs,
+ cattr.schedparam.sched_priority, &xCreatedTask);
+ } else {
+ res = xTaskCreatePinnedToCore(pthreadTask, "thread", cattr.stacksize,
+ taskArgs, cattr.schedparam.sched_priority, &xCreatedTask, cpu);
+ }
+
+ if (res != pdPASS) {
+ // Remove from thread list
+ lstremove(&active_threads, *thread, 1);
+ free(taskArgs);
+
+ return EAGAIN;
+ }
+
+ // Wait for the initialization of Lua RTOS specific TCB parts
+ xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
+
+ threadd->task = xCreatedTask;
+
+ return 0;
+}
+
+int _pthread_join(pthread_t id, void **value_ptr) {
+ _pthread_lock();
+
+ // Get thread
+ struct pthread *thread;
+
+ if (lstget(&active_threads, id, (void **) &thread) != 0) {
+ // Thread not found in active threads, may be is inactive
+ if (lstget(&inactive_threads, id, (void **) &thread) != 0) {
+ // Thread not found
+ _pthread_unlock();
+ return ESRCH;
+ }
+ }
+
+ // Check that thread is joinable
+ if (thread->attr.detachstate == PTHREAD_CREATE_DETACHED) {
+ _pthread_unlock();
+
+ return EINVAL;
+ }
+
+ if (thread->active) {
+ if (thread->joined_task != NULL) {
+ _pthread_unlock();
+
+ // Another thread is already waiting to join with this thread
+ return EINVAL;
+ }
+
+ // Join the current task to the thread
+ thread->joined_task = xTaskGetCurrentTaskHandle();
+ }
+
+ if (thread->active) {
+ _pthread_unlock();
+
+ // Wait until the thread ends
+ uint32_t ret;
+
+ xTaskNotifyWait(0, 0, &ret, portMAX_DELAY);
+
+ if (value_ptr) {
+ *value_ptr = (void *) ret;
+ }
+ } else {
+ if (value_ptr) {
+ *value_ptr = (void *) thread->res;
+ }
+
+ _pthread_free((pthread_t) thread, 1);
+
+ _pthread_unlock();
+ }
+
+ return 0;
+}
+
+int _pthread_detach(pthread_t id) {
+ _pthread_lock();
+
+ // Get thread
+ struct pthread *thread;
+
+ if (lstget(&active_threads, id, (void **) &thread) != 0) {
+ // Thread not found in active threads, may be is inactive
+ if (lstget(&inactive_threads, id, (void **) &thread) != 0) {
+ // Thread not found
+ _pthread_unlock();
+ return ESRCH;
+ }
+ }
+
+ if (thread->attr.detachstate == PTHREAD_CREATE_DETACHED) {
+ return EINVAL;
+ }
+
+ thread->attr.detachstate = PTHREAD_CREATE_DETACHED;
+
+ lstremove(&inactive_threads, id, 1);
+
+ _pthread_unlock();
+
+ return 0;
+}
+
+void _pthread_cleanup_push(void (*routine)(void *), void *arg) {
+ struct pthread *thread;
+ struct pthread_clean *clean;
+
+ _pthread_lock();
+
+ // Get current thread
+ if (lstget(&active_threads, pthread_self(), (void **) &thread) == 0) {
+ // Create the clean structure
+ clean = (struct pthread_clean *) malloc(sizeof(struct pthread_clean));
+ if (!clean) {
+ _pthread_unlock();
+ return;
+ }
+
+ clean->clean = routine;
+ clean->args = arg;
+
+ // Add to clean list
+ lstadd(&thread->clean_list, clean, NULL);
+ }
+
+ _pthread_unlock();
+}
+
+void _pthread_cleanup_pop(int execute) {
+ struct pthread *thread;
+ struct pthread_clean *clean;
+
+ _pthread_lock();
+
+ // Get current thread
+ if (lstget(&active_threads, pthread_self(), (void **) &thread) == 0) {
+ // Get last element in clean list, so we must pop handlers in reverse order
+ int index;
+
+ if ((index = lstlast(&thread->clean_list)) >= 0) {
+ if (lstget(&thread->clean_list, index, (void **) &clean) == 0) {
+ // Execute handler
+ if (clean->clean && execute) {
+ clean->clean(clean->args);
+ }
+
+ // Remove handler from list
+ lstremove(&thread->clean_list, index, 1);
+ }
+ }
+ }
+
+ _pthread_unlock();
+}
+
+void _pthread_cleanup() {
+ struct pthread *thread;
+ struct pthread_clean *clean;
+
+ _pthread_lock();
+
+ // Get current thread
+ if (lstget(&active_threads, pthread_self(), (void **) &thread) == 0) {
+ // Get all elements in clean list, in reverse order
+ int index;
+
+ while ((index = lstlast(&thread->clean_list)) >= 0) {
+ if (lstget(&thread->clean_list, index, (void **) &clean) == 0) {
+ // Execute handler
+ if (clean->clean) {
+ clean->clean(clean->args);
+ }
+
+ // Remove handler from list
+ lstremove(&thread->clean_list, index, 1);
+ }
+ }
+ }
+
+ _pthread_unlock();
+}
+
+int _pthread_free(pthread_t id, int destroy) {
+ // Get thread
+ struct pthread *thread = (struct pthread *) id;
+
+ // Destroy clean list
+ lstdestroy(&thread->clean_list, 1);
+
+ // Remove thread
+ lstremove(&active_threads, id, destroy);
+ lstremove(&inactive_threads, id, destroy);
+
+ return 0;
+}
+
+sig_t _pthread_signal(int s, sig_t h) {
+ struct pthread *thread; // Current thread
+ sig_t prev_h; // Previous handler
+
+ if (s > PTHREAD_NSIG) {
+ return NULL;
+ }
+
+ if (lstget(&active_threads, pthread_self(), (void **) &thread) == 0) {
+ // Add handler
+ prev_h = thread->signals[s];
+ thread->signals[s] = h;
+
+ return prev_h;
+ }
+
+ return NULL;
+}
+
+void _pthread_exec_signal(int dst, int s) {
+ if (s > PTHREAD_NSIG) {
+ return;
+ }
+
+ // Get destination thread
+ struct pthread *thread;
+
+ if (lstget(&active_threads, dst, (void **) &thread) == 0) {
+ // If destination thread has a handler for the signal, execute it
+ if ((thread->signals[s] != SIG_DFL)
+ && (thread->signals[s] != SIG_IGN)) {
+ if (thread->is_delayed && ((s == (SIGINT) || (s == SIGABRT)))) {
+ xTaskAbortDelay(thread->task);
+ }
+
+ thread->signals[s](s);
+ }
+ }
+}
+
+int IRAM_ATTR _pthread_has_signal(int dst, int s) {
+ if (s > PTHREAD_NSIG) {
+ return 0;
+ }
+
+ // Get destination thread
+ struct pthread *thread;
+
+ if (lstget(&active_threads, dst, (void **) &thread) == 0) {
+ return ((thread->signals[s] != SIG_DFL)
+ && (thread->signals[s] != SIG_IGN));
+ }
+
+ return 0;
+}
+
+int _pthread_sleep(uint32_t msecs) {
+ struct pthread *thread;
+ int res;
+
+ // Get the current thread
+ res = lstget(&active_threads, pthread_self(), (void **) &thread);
+ if (res) {
+ // Its not a thread, simply delay task
+ vTaskDelay(msecs / portTICK_PERIOD_MS);
+ return 0;
+ }
+
+ // Is a thread. Mark it as delayed.
+ thread->is_delayed = 1;
+ thread->delay_interrupted = 0;
+
+ vTaskDelay(msecs / portTICK_PERIOD_MS);
+
+ thread->is_delayed = 0;
+
+ if (thread->delay_interrupted) {
+ thread->delay_interrupted = 0;
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+int _pthread_stop(pthread_t id) {
+ struct pthread *thread;
+ int res;
+
+ // Get thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return res;
+ }
+
+ // Stop
+ vTaskDelete(thread->task);
+
+ return 0;
+}
+
+int _pthread_core(pthread_t id) {
+ struct pthread *thread;
+ int res;
+
+ // Get thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return res;
+ }
+
+ return (int) ucGetCoreID(thread->task);
+}
+
+int _pthread_stack(pthread_t id) {
+ struct pthread *thread;
+ int res;
+
+ // Get thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return res;
+ }
+
+ return (int) uxGetStack(thread->task);
+}
+
+int _pthread_stack_free(pthread_t id) {
+ struct pthread *thread;
+ int res;
+
+ // Get thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return res;
+ }
+
+ return (int) uxTaskGetStackHighWaterMark(thread->task);
+}
+
+int _pthread_suspend(pthread_t id) {
+ struct pthread *thread;
+ int res;
+
+ // Get thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return res;
+ }
+
+ UBaseType_t priority = uxTaskPriorityGet(NULL);
+ vTaskPrioritySet(NULL, 22);
+
+ // Suspend
+ uxSetThreadStatus(thread->task, StatusSuspended);
+ vTaskSuspend(thread->task);
+
+ vTaskPrioritySet(NULL, priority);
+
+ return 0;
+}
+
+int _pthread_resume(pthread_t id) {
+ struct pthread *thread;
+ int res;
+
+ // Get thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return res;
+ }
+
+ // Resume
+ uxSetThreadStatus(thread->task, StatusRunning);
+ vTaskResume(thread->task);
+
+ return 0;
+}
+
+struct pthread *_pthread_get(pthread_t id) {
+ struct pthread *thread;
+ int res;
+
+ // Get the thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return NULL;
+ }
+
+ return thread;
+}
+
+// This is the callback function for free Lua RTOS specific TCB parts
+static void pthreadLocaleStoragePointerCallback(int index, void* data) {
+ if (index == THREAD_LOCAL_STORAGE_POINTER_ID) {
+ free(data);
+ }
+}
+
+void pthreadTask(void *taskArgs) {
+ struct pthreadTaskArg *args; // Task arguments
+ struct pthread *thread; // Current thread
+ lua_rtos_tcb_t *lua_rtos_tcb; // Lua RTOS specific TCB parts
+
+ // This is the new thread
+ args = (struct pthreadTaskArg *) taskArgs;
+
+ // Get thread
+ thread = (struct pthread *) args->thread;
+
+ // Allocate and initialize Lua RTOS specific TCB parts, and store it into a FreeRTOS
+ // local storage pointer
+ lua_rtos_tcb = (lua_rtos_tcb_t *) calloc(1, sizeof(lua_rtos_tcb_t));
+ assert(lua_rtos_tcb != NULL);
+
+ lua_rtos_tcb->status = StatusRunning;
+
+ vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
+ THREAD_LOCAL_STORAGE_POINTER_ID, (void *) lua_rtos_tcb,
+ pthreadLocaleStoragePointerCallback);
+
+ // Set thread id
+ uxSetThreadId((UBaseType_t) args->thread);
+
+ // Call additional thread init function
+ if (args->thread->attr.init_func) {
+ args->thread->attr.init_func(args->args);
+ }
+
+ // Lua RTOS specific TCB parts are set, parent thread can continue
+ xTaskNotify(args->parent_task, 0, eNoAction);
+
+ if (args->thread->attr.schedparam.initial_state
+ == PTHREAD_INITIAL_STATE_SUSPEND) {
+ vTaskSuspend(NULL);
+ }
+
+ // Call start function
+ void *ret = args->pthread_function(args->args);
+
+ _pthread_lock();
+
+ if (args->thread->attr.detachstate == PTHREAD_CREATE_JOINABLE) {
+ if (thread->joined_task != NULL) {
+ // Notify to joined thread
+ xTaskNotify(thread->joined_task, (uint32_t) ret,
+ eSetValueWithOverwrite);
+
+ thread->joined_task = NULL;
+
+ _pthread_free((pthread_t) thread, 1);
+ } else {
+ _pthread_free((pthread_t) thread, 0);
+
+ // Put the thread into the inactive threads, because join can occur later, and
+ // the thread's result must be stored
+ thread->active = 0;
+ thread->res = ret;
+
+ lstadd(&inactive_threads, (void *) thread, NULL);
+ }
+ } else {
+ _pthread_free((pthread_t) thread, 1);
+ }
+
+ // Free args
+ free(taskArgs);
+
+ _pthread_unlock();
+
+ // End related task
+ vTaskDelete(NULL);
+}
+
+int pthread_cancel(pthread_t thread) {
+ return 0;
+}
+
+esp_err_t esp_pthread_init(void) {
+ _pthread_init();
+
+ return ESP_OK;
+}
+
+int pthread_setname_np(pthread_t id, const char *name) {
+ struct pthread *thread;
+ int res;
+
+ // Sanity checks
+ if (strlen(name) > configMAX_TASK_NAME_LEN - 1) {
+ return ERANGE;
+ }
+
+ // Get the thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return EINVAL;
+ }
+
+ // Get the TCB task for this thread
+ tskTCB_t *task = (tskTCB_t *) (thread->task);
+
+ // Copy the name into the TCB
+ strncpy(task->pcTaskName, name, configMAX_TASK_NAME_LEN - 1);
+
+ return 0;
+}
+
+int pthread_getname_np(pthread_t id, char *name, size_t len) {
+ struct pthread *thread;
+ int res;
+
+ // Get the thread
+ res = lstget(&active_threads, id, (void **) &thread);
+ if (res) {
+ return EINVAL;
+ }
+
+ // Get the TCB task for this thread
+ tskTCB_t *task = (tskTCB_t *) (thread->task);
+
+ // Sanity checks
+ if (strlen(task->pcTaskName) < len - 1) {
+ return ERANGE;
+ }
+
+ // Copy the name from the TCB
+ strncpy(name, task->pcTaskName, configMAX_TASK_NAME_LEN - 1);
+
+ return 0;
+}
diff --git a/libiot/pthread/_pthread.h b/libiot/pthread/_pthread.h
new file mode 100644
index 0000000..c6df9aa
--- /dev/null
+++ b/libiot/pthread/_pthread.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+//#include "luartos.h"
+
+#ifndef __PTHREAD_H
+#define __PTHREAD_H
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "freertos/adds.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+
+#include <pthread.h>
+
+#include <errno.h>
+
+#include <sys/mutex.h>
+#include <sys/list.h>
+#include <sys/time.h>
+
+#include <signal.h>
+
+
+#define CPU_INITIALIZER 0
+
+// Add CPU cpu to set
+#define CPU_SET(ncpu, cpuset) \
+ *(cpuset) |= (1 << ncpu)
+
+// Test to see if CPU cpu is a member of set.
+#define CPU_ISSET(ncpu, cpuset) \
+ (*(cpuset) & (1 << ncpu))
+
+// Each thread maintains a signal handler copy. Typically there are around 32 defined
+// signals, but not signals are required for applications. For example, in Lua only
+// SIGINT is used.
+//
+// This defines how many signals will be available in threads
+#define PTHREAD_NSIG (SIGINT + 1)
+
+// 1 for activate debug log when thread can't lock a mutex
+#define PTHREAD_MTX_DEBUG 0
+
+#if PTHREAD_MTX_DEBUG
+#define PTHREAD_MTX_LOCK_TIMEOUT (3000 / portTICK_PERIOD_MS)
+#define PTHREAD_MTX_DEBUG_LOCK() printf("phread can't lock\n");
+#else
+#define PTHREAD_MTX_LOCK_TIMEOUT portMAX_DELAY
+#define PTHREAD_MTX_DEBUG_LOCK()
+#endif
+
+// Minimal stack size per thread
+#define PTHREAD_STACK_MIN (1024 * 2)
+
+#define PTHREAD_CREATE_DETACHED 0
+
+// Initial states for a thread
+#define PTHREAD_INITIAL_STATE_RUN 1
+#define PTHREAD_INITIAL_STATE_SUSPEND 2
+
+// Thread types
+#define PTHREAD_TYPE_DEFAULT 0
+#define PTHREAD_TYPE_LUA 1
+
+// Required structures and types
+struct pthread_mutex {
+ SemaphoreHandle_t sem;
+ int owner;
+ int type;
+};
+
+struct pthread_cond {
+ struct mtx mutex;
+ EventGroupHandle_t ev;
+ int referenced;
+};
+
+struct pthread_key_specific {
+ pthread_t thread;
+ const void *value;
+};
+
+struct pthread_key {
+ struct list specific;
+ void (*destructor)(void*);
+};
+
+struct pthread_join {
+ QueueHandle_t queue;
+};
+
+struct pthread_clean {
+ void (*clean)(void*);
+ void *args;
+};
+
+struct pthread {
+ struct list clean_list;
+ sig_t signals[PTHREAD_NSIG];
+ xTaskHandle task;
+ uint8_t is_delayed;
+ uint8_t delay_interrupted;
+ uint8_t active;
+ xTaskHandle joined_task;
+ void *res;
+ pthread_attr_t attr;
+};
+
+// Helper functions, only for internal use
+void _pthread_lock();
+void _pthread_unlock();
+void _pthread_init();
+int _pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *args);
+int _pthread_join(pthread_t id, void **value_ptr);
+int _pthread_free(pthread_t id, int destroy);
+sig_t _pthread_signal(int s, sig_t h);
+void _pthread_exec_signal(int dst, int s);
+void _pthread_process_signal();
+int _pthread_has_signal(int dst, int s);
+int _pthread_stop(pthread_t id);
+int _pthread_suspend(pthread_t id);
+int _pthread_resume(pthread_t id);
+int _pthread_core(pthread_t id);
+sig_t _pthread_signal(int s, sig_t h);
+int _pthread_get_prio();
+int _pthread_stack_free(pthread_t id);
+int _pthread_stack(pthread_t id);
+struct pthread *_pthread_get(pthread_t id);
+int _pthread_sleep(uint32_t msecs);
+void _pthread_cleanup_push(void (*routine)(void *), void *arg);
+void _pthread_cleanup_pop(int execute);
+void _pthread_cleanup();
+int _pthread_detach(pthread_t id);
+
+// API functions
+int pthread_attr_init(pthread_attr_t *attr);
+int pthread_attr_destroy(pthread_attr_t *attr);
+int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
+int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
+int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
+int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
+int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
+int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
+
+int pthread_mutex_init(pthread_mutex_t *mut, const pthread_mutexattr_t *attr);
+int pthread_mutex_lock(pthread_mutex_t *mut);
+int pthread_mutex_unlock(pthread_mutex_t *mut);
+int pthread_mutex_trylock(pthread_mutex_t *mut);
+int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
+int pthread_mutexattr_init(pthread_mutexattr_t *attr);
+int pthread_mutex_destroy(pthread_mutex_t *mutex);
+
+int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
+int pthread_cond_destroy(pthread_cond_t *cond);
+int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
+int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
+int pthread_cond_signal(pthread_cond_t *cond);
+
+int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
+int pthread_setcancelstate(int state, int *oldstate);
+int pthread_key_create(pthread_key_t *k, void (*destructor)(void*));
+int pthread_setspecific(pthread_key_t k, const void *value);
+void *pthread_getspecific(pthread_key_t k);
+int pthread_join(pthread_t thread, void **value_ptr);
+int pthread_cancel(pthread_t thread);
+int pthread_kill(pthread_t thread, int signal);
+
+int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset);
+int pthread_attr_getaffinity_np(const pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset);
+int pthread_setname_np(pthread_t id, const char *name);
+int pthread_getname_np(pthread_t id, char *name, size_t len);
+int pthread_attr_setinitialstate_np(pthread_attr_t *attr, int initial_state);
+int pthread_attr_setinitfunc_np(pthread_attr_t *attr, void (*init_func)(void *));
+
+pthread_t pthread_self(void);
+
+int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine) (void *), void *args);
+
+#define pthread_exit(ret) \
+do { \
+ _pthread_cleanup(); \
+ return ((void *)ret); \
+} while (0)
+
+
+#define pthread_cleanup_push(routine, args) \
+do { \
+ _pthread_cleanup_push(routine, args)
+
+#define pthread_cleanup_pop(exec) \
+ _pthread_cleanup_pop(exec); \
+} while(0)
+
+#endif /* __PTHREAD_H */
+
diff --git a/libiot/pthread/attr.c b/libiot/pthread/attr.c
new file mode 100644
index 0000000..fe5c2a6
--- /dev/null
+++ b/libiot/pthread/attr.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "_pthread.h"
+
+int pthread_attr_init(pthread_attr_t *attr) {
+ if (!attr) {
+ return EINVAL;
+ }
+
+ attr->is_initialized = 1;
+
+ attr->stacksize = (CONFIG_LUA_RTOS_LUA_THREAD_STACK_SIZE >= PTHREAD_STACK_MIN?CONFIG_LUA_RTOS_LUA_THREAD_STACK_SIZE:PTHREAD_STACK_MIN);
+
+ attr->schedparam.initial_state = PTHREAD_INITIAL_STATE_RUN;
+ attr->schedparam.sched_priority = CONFIG_LUA_RTOS_LUA_TASK_PRIORITY;
+ attr->schedparam.affinityset = CPU_INITIALIZER; // No affinity
+ attr->init_func = NULL;
+ attr->detachstate = PTHREAD_CREATE_JOINABLE;
+
+ return 0;
+}
+
+int pthread_attr_destroy(pthread_attr_t *attr) {
+ if (!attr) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize) {
+ if (!attr) {
+ return EINVAL;
+ }
+
+ if (stacksize < PTHREAD_STACK_MIN) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ attr->stacksize = stacksize;
+
+ return 0;
+}
+
+int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize) {
+ if (!attr || !stacksize) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ *stacksize = attr->stacksize;
+
+ return 0;
+}
+
+int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param) {
+ if (!attr || !param) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ if ((param->sched_priority > configMAX_PRIORITIES - 1) || (param->sched_priority < 1)) {
+ return EINVAL;
+ }
+
+ attr->schedparam.sched_priority = param->sched_priority;
+
+ return 0;
+}
+
+int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param) {
+ if (!attr || !param) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ param->sched_priority = attr->schedparam.sched_priority;
+
+ return 0;
+}
+
+int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) {
+ if (!attr) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ if ((detachstate != PTHREAD_CREATE_DETACHED) && (detachstate != PTHREAD_CREATE_JOINABLE)) {
+ return EINVAL;
+ }
+
+ attr->detachstate = detachstate;
+
+ return 0;
+}
+
+int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) {
+ if (!attr || !detachstate) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ *detachstate = attr->detachstate;
+
+ return 0;
+}
+
+int pthread_setcancelstate(int state, int *oldstate) {
+ return 0;
+}
+
+int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset) {
+ if (!attr || !cpuset) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ if ((*cpuset < 0) || (*cpuset > portNUM_PROCESSORS)) {
+ return EINVAL;
+ }
+
+ attr->schedparam.affinityset = *cpuset;
+
+ return 0;
+}
+
+int pthread_attr_getaffinity_np(const pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset) {
+ if (!attr || !cpuset) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ if ((*cpuset < 0) || (*cpuset > portNUM_PROCESSORS)) {
+ return EINVAL;
+ }
+
+ *cpuset = attr->schedparam.affinityset;
+
+ return 0;
+}
+
+int pthread_attr_setinitialstate_np(pthread_attr_t *attr, int initial_state) {
+ if (!attr) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ if ((initial_state != PTHREAD_INITIAL_STATE_RUN) && (initial_state != PTHREAD_INITIAL_STATE_SUSPEND)) {
+ return EINVAL;
+ }
+
+ attr->schedparam.initial_state = initial_state;
+
+ return 0;
+}
+
+int pthread_attr_setinitfunc_np(pthread_attr_t *attr, void (*init_routine)(void *)) {
+ if (!attr || !init_routine) {
+ return EINVAL;
+ }
+
+ if (!attr->is_initialized) {
+ return EINVAL;
+ }
+
+ attr->init_func = init_routine;
+
+ return 0;
+}
diff --git a/libiot/pthread/cond.c b/libiot/pthread/cond.c
new file mode 100644
index 0000000..288e548
--- /dev/null
+++ b/libiot/pthread/cond.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "_pthread.h"
+
+#include <sys/mutex.h>
+#include <sys/time.h>
+
+#if configUSE_16_BIT_TICKS
+#define BITS_PER_EVENT_GROUP 8
+#else
+#define BITS_PER_EVENT_GROUP 24
+#endif
+
+int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) {
+ // Avoid to reinitialize the object referenced by cond, a previously
+ // initialized, but not yet destroyed, condition variable
+ if (*cond != PTHREAD_COND_INITIALIZER) {
+ return EBUSY;
+ }
+
+ // Initialize the internal cond structure
+ struct pthread_cond *scond = calloc(1, sizeof(struct pthread_cond));
+ if (!scond) {
+ return ENOMEM;
+ }
+
+ // Init the cond mutex
+ mtx_init(&scond->mutex, NULL, NULL, 0);
+ if (!scond->mutex.lock) {
+ free(scond);
+ return ENOMEM;
+ }
+
+ // Create the event group
+ scond->ev = xEventGroupCreate();
+ if (scond->ev == NULL) {
+ mtx_destroy(&scond->mutex);
+ free(scond);
+ return ENOMEM;
+ }
+
+ // Return the cond reference
+ *cond = (pthread_cond_t)scond;
+
+ return 0;
+}
+
+int pthread_cond_destroy(pthread_cond_t *cond) {
+ if (*cond == PTHREAD_COND_INITIALIZER) {
+ return EINVAL;
+ }
+
+ struct pthread_cond *scond = (struct pthread_cond *) *cond;
+
+ mtx_lock(&scond->mutex);
+
+ if (scond->referenced > 0) {
+ mtx_unlock(&scond->mutex);
+
+ return EBUSY;
+ }
+
+ mtx_destroy(&scond->mutex);
+
+ return 0;
+}
+
+int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) {
+ return pthread_cond_timedwait(cond, mutex, NULL);
+}
+
+int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
+ const struct timespec *abstime) {
+
+ if (*cond == PTHREAD_COND_INITIALIZER) {
+ return EINVAL;
+ }
+
+ struct pthread_cond *scond = (struct pthread_cond *)*cond;
+
+ // Find an unused bit in the event group to sync the condition
+ mtx_lock(&scond->mutex);
+
+ uint8_t bit;
+ for(bit=0;bit < BITS_PER_EVENT_GROUP;bit++) {
+ if (!((1 << bit) & scond->referenced)) {
+ scond->referenced |= (1 << bit);
+
+ break;
+ }
+ }
+
+ mtx_unlock(&scond->mutex);
+
+ if (bit >= BITS_PER_EVENT_GROUP) {
+ // All bits are used in the event group. This is a rare condition, because
+ // in Lua RTOS an event group has 24 bits, which means that 24 conditions
+ // (or 24 threads) can be handled for each condition variable.
+ //
+ // Although is not part of the pthread specification we indicate this
+ // condition returning the EINVAL error code.
+ return EINVAL;
+ }
+
+ // Get the timeout in ticks
+ TickType_t ticks;
+
+ if (!abstime) {
+ ticks = portMAX_DELAY;
+ } else {
+ struct timeval now, future, diff;
+
+ gettimeofday(&now, NULL);
+
+ future.tv_sec = abstime->tv_sec;
+ future.tv_usec = abstime->tv_nsec / 1000;
+
+ if (timercmp(&future, &now, <)) {
+ return ETIMEDOUT;
+ } else {
+ timersub(&future, &now, &diff);
+ ticks = ((diff.tv_sec * 1000) + (diff.tv_usec / 1000)) / portTICK_PERIOD_MS;
+ }
+ }
+
+ pthread_mutex_unlock(mutex);
+ EventBits_t uxBits = xEventGroupWaitBits(scond->ev, (1 << bit), pdTRUE, pdTRUE, ticks);
+ if (!(uxBits & (1 << bit))) {
+ pthread_mutex_lock(mutex);
+ scond->referenced &= ~(1 << bit);
+ pthread_mutex_unlock(mutex);
+
+ return ETIMEDOUT;
+ }
+ pthread_mutex_lock(mutex);
+
+ return 0;
+}
+
+int pthread_cond_signal(pthread_cond_t *cond) {
+ if (*cond == PTHREAD_COND_INITIALIZER) {
+ return EINVAL;
+ }
+
+ struct pthread_cond *scond = (struct pthread_cond *)*cond;
+
+ mtx_lock(&scond->mutex);
+
+ uint8_t bit;
+ for(bit=0;bit < BITS_PER_EVENT_GROUP;bit++) {
+ if ((1 << bit) & scond->referenced) {
+ xEventGroupSetBits(scond->ev, (1 << bit));
+
+ scond->referenced &= ~(1 << bit);
+ }
+ }
+
+ mtx_unlock(&scond->mutex);
+
+ return 0;
+}
diff --git a/libiot/pthread/create.c b/libiot/pthread/create.c
new file mode 100644
index 0000000..fe591c7
--- /dev/null
+++ b/libiot/pthread/create.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "luartos.h"
+#include "_pthread.h"
+
+#include <pthread.h>
+
+int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+ void *(*start_routine)(void *), void *args) {
+
+ return _pthread_create(thread, attr, start_routine, args);
+}
diff --git a/libiot/pthread/join.c b/libiot/pthread/join.c
new file mode 100644
index 0000000..bd173f3
--- /dev/null
+++ b/libiot/pthread/join.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "_pthread.h"
+
+int pthread_join(pthread_t thread, void **value_ptr) {
+ return _pthread_join(thread, value_ptr);
+}
+
+int pthread_detach(pthread_t thread) {
+ return _pthread_detach(thread);
+}
+
+int sched_yield( void ) {
+ return 0;
+}
+
+int pthread_equal(pthread_t t1, pthread_t t2) {
+ return 0;
+}
+
+int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type) {
+ return ENOSYS;
+}
+
+int pthread_mutexattr_destroy(pthread_mutexattr_t *attr) {
+ return 0;
+}
diff --git a/libiot/pthread/key.c b/libiot/pthread/key.c
new file mode 100644
index 0000000..ebb757f
--- /dev/null
+++ b/libiot/pthread/key.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "_pthread.h"
+
+#include <stdlib.h>
+
+extern struct list key_list;
+
+int pthread_key_create(pthread_key_t *k, void (*destructor)(void*)) {
+ struct pthread_key *key;
+ int res;
+
+ // Allocate space for the key
+ key = (struct pthread_key *)malloc(sizeof(struct pthread_key));
+ if (!key) {
+ return ENOMEM;
+ }
+
+ // Init key
+ key->destructor = destructor;
+
+ lstinit(&key->specific, 1, LIST_DEFAULT);
+
+ // Add key to key list
+ res = lstadd(&key_list, (void *)key, (int*)k);
+ if (res) {
+ free(key);
+ return res;
+ }
+
+ return 0;
+}
+
+int pthread_setspecific(pthread_key_t k, const void *value) {
+ struct pthread_key_specific *specific;
+ struct pthread_key *key;
+ pthread_t thread;
+ int res;
+ int index;
+
+ // Get key
+ res = lstget(&key_list, k, (void **)&key);
+ if (res) {
+ return res;
+ }
+
+ if (value) {
+ // Allocate space for specific
+ specific = (struct pthread_key_specific *)malloc(sizeof(struct pthread_key_specific));
+ if (!specific) {
+ return ENOMEM;
+ }
+
+ specific->thread = pthread_self();
+ specific->value = value;
+
+ lstadd(&key->specific, (void **)specific, &index);
+ } else {
+ thread = pthread_self();
+
+ index = lstfirst(&key->specific);
+ while (index >= 0) {
+ lstget(&key->specific, index, (void **)&specific);
+
+ if (specific->thread == thread) {
+ lstremove(&key->specific, k, 1);
+ break;
+ }
+
+ index = lstnext(&key->specific, index);
+ }
+ }
+
+ return 0;
+}
+
+void *pthread_getspecific(pthread_key_t k) {
+ struct pthread_key_specific *specific;
+ struct pthread_key *key;
+ pthread_t thread;
+ int res;
+ int index;
+
+ // Get key
+ res = lstget(&key_list, k, (void **)&key);
+ if (res) {
+ return NULL;
+ }
+
+ // Get specific value
+ thread = pthread_self();
+
+ index = lstfirst(&key->specific);
+ while (index >= 0) {
+ lstget(&key->specific, index, (void **)&specific);
+
+ if (specific->thread == thread) {
+ return (void *)specific->value;
+ }
+
+ index = lstnext(&key->specific, index);
+ }
+
+ return NULL;
+}
+
+int pthread_key_delete(pthread_key_t k) {
+ struct pthread_key *key;
+ int res;
+
+ // Get key
+ res = lstget(&key_list, k, (void **)&key);
+ if (res) {
+ return res;
+ }
+
+ lstremove(&key_list, k, 1);
+
+ return 0;
+}
diff --git a/libiot/pthread/kill.c b/libiot/pthread/kill.c
new file mode 100644
index 0000000..1de80ee
--- /dev/null
+++ b/libiot/pthread/kill.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "_pthread.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/_signal.h>
+
+int pthread_kill(pthread_t thread, int signal) {
+ if (signal > PTHREAD_NSIG) {
+ return EINVAL;
+ }
+
+ _signal_queue(thread, signal);
+
+ return 0;
+}
diff --git a/libiot/pthread/mkfile b/libiot/pthread/mkfile
new file mode 100644
index 0000000..2fc273c
--- /dev/null
+++ b/libiot/pthread/mkfile
@@ -0,0 +1,10 @@
+#<$ROOT/mkconfig
+
+#LIB=libspiffs.a
+OFILES=\
+ $OFILES\
+ pthread/mutex.$O\
+
+#HFILES= $ROOT/include/bio.h
+
+#<$ROOT/mkfiles/mksyslib-$SHELLTYPE
diff --git a/libiot/pthread/mutex.c b/libiot/pthread/mutex.c
new file mode 100644
index 0000000..c4850d1
--- /dev/null
+++ b/libiot/pthread/mutex.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "_pthread.h"
+
+//#include "esp_attr.h"
+
+#include <stdlib.h>
+
+static int _check_attr(const pthread_mutexattr_t *attr) {
+ int type = attr->type;
+
+ if ((type < PTHREAD_MUTEX_NORMAL) || (type > PTHREAD_MUTEX_DEFAULT)) {
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+int pthread_mutex_init(pthread_mutex_t *mut, const pthread_mutexattr_t *attr) {
+ struct pthread_mutex *mutex;
+ int res;
+
+ if (!mut) {
+ return EINVAL;
+ }
+
+ // Check attr
+ if (attr) {
+ res = _check_attr(attr);
+ if (res) {
+ return res;
+ }
+ }
+
+ // Test if it's init yet
+ if (*mut != PTHREAD_MUTEX_INITIALIZER) {
+ return EBUSY;
+ }
+
+ // Create mutex structure
+ mutex = (struct pthread_mutex *)malloc(sizeof(struct pthread_mutex));
+ if (!mutex) {
+ return EINVAL;
+ }
+
+ if (attr) {
+ mutex->type = attr->type;
+ } else {
+ mutex->type = PTHREAD_MUTEX_NORMAL;
+ }
+ // Create semaphore
+ if (mutex->type == PTHREAD_MUTEX_RECURSIVE) {
+ mutex->sem = xSemaphoreCreateRecursiveMutex();
+ } else {
+ mutex->sem = xSemaphoreCreateMutex();
+ }
+ if(!mutex->sem){
+ *mut = PTHREAD_MUTEX_INITIALIZER;
+ free(mutex->sem);
+ free(mutex);
+ return ENOMEM;
+ }
+
+ mutex->owner = pthread_self();
+
+ *mut = (unsigned int )mutex;
+
+ return 0;
+}
+
+int IRAM_ATTR pthread_mutex_lock(pthread_mutex_t *mut) {
+ struct pthread_mutex *mutex;
+ int res;
+
+ if (!mut) {
+ return EINVAL;
+ }
+
+ if ((intptr_t) *mut == PTHREAD_MUTEX_INITIALIZER) {
+ if ((res = pthread_mutex_init(mut, NULL))) {
+ return res;
+ }
+ }
+
+ mutex = (struct pthread_mutex *)(*mut);
+
+ // Lock
+ if (mutex->type == PTHREAD_MUTEX_RECURSIVE) {
+ if (xSemaphoreTakeRecursive(mutex->sem, PTHREAD_MTX_LOCK_TIMEOUT) != pdPASS) {
+ PTHREAD_MTX_DEBUG_LOCK();
+ return EINVAL;
+ }
+ } else {
+ if (xSemaphoreTake(mutex->sem, PTHREAD_MTX_LOCK_TIMEOUT) != pdPASS) {
+ PTHREAD_MTX_DEBUG_LOCK();
+ return EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+int IRAM_ATTR pthread_mutex_unlock(pthread_mutex_t *mut) {
+ if (!mut) {
+ return EINVAL;
+ }
+
+ struct pthread_mutex *mutex = ( struct pthread_mutex *)(*mut);
+
+ // Unlock
+ if (mutex->type == PTHREAD_MUTEX_RECURSIVE) {
+ xSemaphoreGiveRecursive(mutex->sem);
+ } else {
+ xSemaphoreGive(mutex->sem);
+ }
+
+ return 0;
+}
+
+int pthread_mutex_trylock(pthread_mutex_t *mut) {
+ struct pthread_mutex *mutex;
+ int res;
+
+ if (!mut) {
+ return EINVAL;
+ }
+
+ if ((intptr_t) *mut == PTHREAD_MUTEX_INITIALIZER) {
+ if ((res = pthread_mutex_init(mut, NULL))) {
+ return res;
+ }
+ }
+
+ mutex = ( struct pthread_mutex *)(*mut);
+
+ // Try lock
+ if (mutex->type == PTHREAD_MUTEX_RECURSIVE) {
+ if (xSemaphoreTakeRecursive(mutex->sem,0 ) != pdTRUE) {
+ return EBUSY;
+ }
+ } else {
+ if (xSemaphoreTake(mutex->sem,0 ) != pdTRUE) {
+ return EBUSY;
+ }
+ }
+
+ return 0;
+}
+
+int pthread_mutex_destroy(pthread_mutex_t *mut) {
+ if (!mut) {
+ return EINVAL;
+ }
+
+ struct pthread_mutex *mutex = ( struct pthread_mutex *)(*mut);
+
+ if (mutex->type == PTHREAD_MUTEX_RECURSIVE) {
+ xSemaphoreGiveRecursive(mutex->sem);
+ } else {
+ xSemaphoreGive(mutex->sem);
+ }
+
+ vSemaphoreDelete(mutex->sem);
+
+ return 0;
+}
+
+int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) {
+ pthread_mutexattr_t temp_attr;
+ int res;
+
+ // Check attr
+ if (!attr) {
+ return EINVAL;
+ }
+
+ temp_attr.type = type;
+
+ res = _check_attr(&temp_attr);
+ if (res) {
+ return res;
+ }
+
+ attr->type = type;
+
+ return 0;
+}
+
+int pthread_mutexattr_init(pthread_mutexattr_t *attr) {
+ if (!attr) {
+ return EINVAL;
+ }
+
+ attr->type = PTHREAD_MUTEX_NORMAL;
+ attr->is_initialized = 1;
+
+ return 0;
+}
diff --git a/libiot/pthread/once.c b/libiot/pthread/once.c
new file mode 100644
index 0000000..646de27
--- /dev/null
+++ b/libiot/pthread/once.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "_pthread.h"
+
+int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) {
+ if (!once_control || !init_routine) {
+ return EINVAL;
+ }
+
+ if (once_control->is_initialized != 1) {
+ return EINVAL;
+ }
+
+ _pthread_lock();
+ if (!once_control->init_executed) {
+ once_control->init_executed = 1;
+ _pthread_unlock();
+ init_routine();
+ } else {
+ _pthread_unlock();
+ }
+
+ return 0;
+}
diff --git a/libiot/pthread/self.c b/libiot/pthread/self.c
new file mode 100644
index 0000000..017ac5b
--- /dev/null
+++ b/libiot/pthread/self.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS pthread implementation for FreeRTOS
+ *
+ */
+
+#include "_pthread.h"
+#include "esp_attr.h"
+
+extern UBaseType_t uxGetThreadId();
+
+pthread_t IRAM_ATTR pthread_self(void) {
+ return (pthread_t)uxGetThreadId();
+}
diff --git a/libiot/pthread/test/pthread-cleanup.c b/libiot/pthread/test/pthread-cleanup.c
new file mode 100644
index 0000000..c11aeb4
--- /dev/null
+++ b/libiot/pthread/test/pthread-cleanup.c
@@ -0,0 +1,200 @@
+#include "unity.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#include <sys/delay.h>
+
+#define NUM_TERMS 4
+
+static int term[NUM_TERMS];
+
+void term_0(void *args) {
+ term[0] = 0;
+}
+
+void term_1(void *args) {
+ term[1] = 1;
+}
+
+void term_2(void *args) {
+ term[2] = 2;
+}
+
+void term_3(void *args) {
+ term[3] = 3;
+}
+
+// Test that handlers are called in reverse order
+static void *thread1(void *args) {
+ int count = 0;
+
+ memset(term, -1, sizeof(term));
+
+ pthread_cleanup_push(term_0, NULL);
+ pthread_cleanup_push(term_1, NULL);
+ pthread_cleanup_push(term_2, NULL);
+ pthread_cleanup_push(term_3, NULL);
+
+ for (count = 0; count < 100; count++) {
+ // Simulate some work
+ usleep(1000);
+ }
+
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+
+ TEST_ASSERT(term[0] == 0);
+ TEST_ASSERT(term[1] == 1);
+ TEST_ASSERT(term[2] == 2);
+ TEST_ASSERT(term[3] == 3);
+
+ return NULL;
+}
+
+// Test that only handlers poped with execute argument set to true are
+// executed
+static void *thread2(void *args) {
+ int count = 0;
+
+ memset(term, -1, sizeof(term));
+
+ pthread_cleanup_push(term_0, NULL);
+ pthread_cleanup_push(term_1, NULL);
+ pthread_cleanup_push(term_2, NULL);
+ pthread_cleanup_push(term_3, NULL);
+
+ for (count = 0; count < 100; count++) {
+ // Simulate some work
+ usleep(1000);
+ }
+
+ pthread_cleanup_pop(0);
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(0);
+ pthread_cleanup_pop(1);
+
+ TEST_ASSERT(term[0] == 0);
+ TEST_ASSERT(term[1] == -1);
+ TEST_ASSERT(term[2] == 2);
+ TEST_ASSERT(term[3] == -1);
+
+ pthread_exit(NULL);
+}
+
+// Test that pthread_exit execute the cleanups
+static void *thread3(void *args) {
+ int count = 0;
+
+ memset(term, -1, sizeof(term));
+
+ pthread_cleanup_push(term_0, NULL);
+ pthread_cleanup_push(term_1, NULL);
+ pthread_cleanup_push(term_2, NULL);
+ pthread_cleanup_push(term_3, NULL);
+
+ for (count = 0; count < 100; count++) {
+ // Simulate some work
+ usleep(1000);
+
+ if (count == 50) {
+ pthread_exit(NULL);
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+
+ // This point is never reached
+ TEST_ASSERT(0);
+
+ pthread_exit(NULL);
+}
+
+// Test that when exit from thread with return don't execute the cleanups
+static void *thread4(void *args) {
+ int count = 0;
+
+ memset(term, -1, sizeof(term));
+
+ pthread_cleanup_push(term_0, NULL);
+ pthread_cleanup_push(term_1, NULL);
+ pthread_cleanup_push(term_2, NULL);
+ pthread_cleanup_push(term_3, NULL);
+
+ for (count = 0; count < 100; count++) {
+ // Simulate some work
+ usleep(1000);
+
+ if (count == 50) {
+ return NULL;
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+ pthread_cleanup_pop(1);
+
+ // This point is never reached
+ TEST_ASSERT(0);
+
+ return NULL;
+}
+
+TEST_CASE("pthread cleanup", "[pthread]") {
+ pthread_attr_t attr;
+ pthread_t th;
+ int ret;
+
+ pthread_attr_init(&attr);
+
+ // Test that handlers are called in reverse order
+ ret = pthread_create(&th, &attr, thread1, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_join(th, NULL);
+ TEST_ASSERT(ret == 0);
+
+ // Test that only handlers poped with execute argument set to true are
+ // executed
+ ret = pthread_create(&th, &attr, thread2, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_join(th, NULL);
+ TEST_ASSERT(ret == 0);
+
+ // Test that pthread_exit execute the cleanups
+ ret = pthread_create(&th, &attr, thread3, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_join(th, NULL);
+ TEST_ASSERT(ret == 0);
+
+ TEST_ASSERT(term[0] == 0);
+ TEST_ASSERT(term[1] == 1);
+ TEST_ASSERT(term[2] == 2);
+ TEST_ASSERT(term[3] == 3);
+
+ // Test that when exit from thread with return don't execute the cleanups
+ ret = pthread_create(&th, &attr, thread4, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_join(th, NULL);
+ TEST_ASSERT(ret == 0);
+
+ TEST_ASSERT(term[0] == -1);
+ TEST_ASSERT(term[1] == -1);
+ TEST_ASSERT(term[2] == -1);
+ TEST_ASSERT(term[3] == -1);
+
+ ret = pthread_attr_destroy(&attr);
+ TEST_ASSERT(ret == 0);
+}
diff --git a/libiot/pthread/test/pthread-cond.c b/libiot/pthread/test/pthread-cond.c
new file mode 100644
index 0000000..8e1ce41
--- /dev/null
+++ b/libiot/pthread/test/pthread-cond.c
@@ -0,0 +1,107 @@
+#include "unity.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+#include <sys/delay.h>
+
+#define NUM_THREADS 3
+#define COUNT_LIMIT 12
+#define TCOUNT 10
+
+static int count = 0;
+static pthread_t threads[NUM_THREADS];
+static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t count_threshold_cv = PTHREAD_COND_INITIALIZER;
+
+static void *inc_count(void *args) {
+ int i;
+ int ret;
+
+ for (i=0; i< TCOUNT; i++) {
+ ret = pthread_mutex_lock(&count_mutex);
+ TEST_ASSERT(ret == 0);
+
+ count++;
+
+ // If condition is reached signal condition
+ if (count == COUNT_LIMIT) {
+ ret = pthread_cond_signal(&count_threshold_cv);
+ TEST_ASSERT(ret == 0);
+ }
+
+ ret = pthread_mutex_unlock(&count_mutex);
+ TEST_ASSERT(ret == 0);
+
+ // Simulate some work
+ usleep(1000);
+ }
+
+ return NULL;
+ }
+
+static void *watch_count(void *args) {
+ int ret;
+
+ ret = pthread_mutex_lock(&count_mutex);
+ TEST_ASSERT(ret == 0);
+
+ while (count < COUNT_LIMIT) {
+ ret = pthread_cond_wait(&count_threshold_cv, &count_mutex);
+ TEST_ASSERT(ret == 0);
+ }
+
+ count += 125;
+ TEST_ASSERT (count == 125 + COUNT_LIMIT);
+
+ ret = pthread_mutex_unlock(&count_mutex);
+ TEST_ASSERT(ret == 0);
+
+ return NULL;
+}
+
+TEST_CASE("pthread conditions", "[pthread]") {
+ pthread_attr_t attr;
+ int i, ret;
+
+ // Initialize mutex and condition variable objects
+ ret = pthread_mutex_init(&count_mutex, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_cond_init(&count_threshold_cv, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_attr_init(&attr);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_attr_setstacksize(&attr, 10240);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_create(&threads[0], &attr, watch_count, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_create(&threads[1], &attr, inc_count, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_create(&threads[2], &attr, inc_count, NULL);
+ TEST_ASSERT(ret == 0);
+
+ // Wait for all threads completion
+ for (i=0; i< NUM_THREADS; i++) {
+ ret = pthread_join(threads[i], NULL);
+ TEST_ASSERT(ret == 0);
+ }
+
+ // Clean up
+ ret = pthread_attr_destroy(&attr);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_mutex_destroy(&count_mutex);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_cond_destroy(&count_threshold_cv);
+ TEST_ASSERT(ret == 0);
+}
diff --git a/libiot/pthread/test/pthread-join.c b/libiot/pthread/test/pthread-join.c
new file mode 100644
index 0000000..ba5f976
--- /dev/null
+++ b/libiot/pthread/test/pthread-join.c
@@ -0,0 +1,85 @@
+#include "unity.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+#include <sys/delay.h>
+
+#define NUM_THREADS 2
+
+static pthread_t threads[NUM_THREADS];
+
+static void *thread1(void *args) {
+ int count = 0;
+
+ for (count = 0; count < 100; count++) {
+ // Simulate some work
+ usleep(1000);
+ }
+
+ int *ret = malloc(sizeof(int));
+ *ret = count;
+
+ pthread_exit(ret);
+ }
+
+static void *thread2(void *args) {
+ int count;
+
+ for (count = 0; count < 50; count++) {
+ // Simulate some work
+ usleep(1000);
+ }
+
+ int *ret = malloc(sizeof(int));
+ *ret = count;
+
+ pthread_exit(ret);
+ }
+
+
+TEST_CASE("pthread join", "[pthread]") {
+ pthread_attr_t attr;
+ int i, ret;
+ int *res;
+
+ ret = pthread_attr_init(&attr);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_create(&threads[0], &attr, thread1, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_create(&threads[1], &attr, thread2, NULL);
+ TEST_ASSERT(ret == 0);
+
+ // Wait for all threads completion
+ for (i=0; i< NUM_THREADS; i++) {
+ ret = pthread_join(threads[i], (void **)&res);
+ TEST_ASSERT(ret == 0);
+
+ if (i == 0) {
+ TEST_ASSERT(*res == 100);
+ } else if (i == 1) {
+ TEST_ASSERT(*res == 50);
+ }
+
+ free(res);
+ }
+
+ // Check that a detached thread is not joinable
+ ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_create(&threads[0], &attr, thread1, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_join(threads[0], (void **)&res);
+ TEST_ASSERT(ret == EINVAL);
+
+ // Clean up
+ ret = pthread_attr_destroy(&attr);
+ TEST_ASSERT(ret == 0);
+}
diff --git a/libiot/pthread/test/pthread-once.c b/libiot/pthread/test/pthread-once.c
new file mode 100644
index 0000000..d3a3db6
--- /dev/null
+++ b/libiot/pthread/test/pthread-once.c
@@ -0,0 +1,68 @@
+#include "unity.h"
+
+#include <pthread.h>
+#include <sys/delay.h>
+
+#define NUM_THREADS 2
+
+static pthread_t threads[NUM_THREADS];
+
+static pthread_once_t once = PTHREAD_ONCE_INIT;
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static int execs;
+
+static void init() {
+ int ret;
+
+ ret = pthread_mutex_lock(&mutex);
+ TEST_ASSERT(ret == 0);
+
+ execs++;
+
+ ret = pthread_mutex_unlock(&mutex);
+ TEST_ASSERT(ret == 0);
+}
+
+static void *thread1(void *args) {
+ int ret;
+
+ ret = pthread_once(&once, init);
+ TEST_ASSERT(ret == 0);
+
+ // Simulate some work
+ usleep(1000);
+
+ pthread_exit(NULL);
+ }
+
+TEST_CASE("pthread once", "[pthread]") {
+ pthread_attr_t attr;
+ int i, ret;
+
+ ret = pthread_attr_init(&attr);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_mutex_init(&mutex, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_create(&threads[0], &attr, thread1, NULL);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_create(&threads[1], &attr, thread1, NULL);
+ TEST_ASSERT(ret == 0);
+
+ // Wait for all threads completion
+ for (i=0; i< NUM_THREADS; i++) {
+ ret = pthread_join(threads[i], NULL);
+ TEST_ASSERT(ret == 0);
+ }
+
+ TEST_ASSERT(execs == 1);
+
+ // Clean up
+ ret = pthread_attr_destroy(&attr);
+ TEST_ASSERT(ret == 0);
+
+ ret = pthread_mutex_destroy(&mutex);
+ TEST_ASSERT(ret == 0);
+}
diff --git a/libiot/ramfs/ramfs.c b/libiot/ramfs/ramfs.c
new file mode 100644
index 0000000..3362814
--- /dev/null
+++ b/libiot/ramfs/ramfs.c
@@ -0,0 +1,1088 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, RAM file system
+ *
+ */
+
+#include "ramfs.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+static int add_reference(ramfs_t *fs, ramfs_entry_t *entry);
+static void remove_reference(ramfs_t *fs, ramfs_entry_t *entry);
+static int get_reference_uses(ramfs_t *fs, ramfs_entry_t *entry);
+static void remove_block(ramfs_t *fs, ramfs_file_t *file);
+static int add_block(ramfs_t *fs, ramfs_file_t *file);
+static int add_entry(ramfs_t *fs, const char *name, ramfs_entry_t *parent, ramfs_entry_t **entry, ramfs_entry_type_t entry_type);
+static void remove_entry(ramfs_t *fs, ramfs_entry_t *entry, ramfs_entry_t *parent_entry, ramfs_entry_t *prev_entry, int remove);
+static int traverse(ramfs_t *fs, const char *path, ramfs_entry_t **entry, ramfs_entry_t **parent_entry, ramfs_entry_t **prev_entry, int creat, ramfs_entry_type_t type);
+static ramfs_off_t ramfs_file_seek_internal(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t offset, ramfs_whence_t whence);
+static int ramfs_file_truncate_internal(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t size);
+
+static int add_reference(ramfs_t *fs, ramfs_entry_t *entry) {
+ ramfs_entry_ref_t *cref;
+
+ // Find entry into the reference set
+ cref = fs->ref;
+ while (cref && (cref->entry != entry)) {
+ cref = cref->next;
+ }
+
+ if (!cref) {
+ // Entry not found, add it
+ ramfs_entry_ref_t *ref = calloc(1, sizeof(ramfs_entry_ref_t));
+ if (!ref) {
+ return RAMFS_ERR_NOMEM;
+ }
+
+ ref->entry = entry;
+ ref->next = fs->ref;
+ ref->uses = 1;
+
+ fs->ref = ref;
+ } else {
+ // Entry found, just increment the uses counter
+ cref->uses++;
+ }
+
+ return RAMFS_ERR_OK;
+}
+
+static void remove_reference(ramfs_t *fs, ramfs_entry_t *entry) {
+ ramfs_entry_ref_t *cref;
+ ramfs_entry_ref_t *prev_ref = NULL;
+
+ if (!entry) return;
+
+ // Find entry into the reference set
+ cref = fs->ref;
+ while (cref && (cref->entry != entry)) {
+ prev_ref = cref;
+ cref = cref->next;
+ }
+
+ if (cref) {
+ // Entry found
+ if (cref->uses > 0) {
+ // Decrement the uses counter
+ cref->uses--;
+ }
+
+ if (cref->uses == 0) {
+ // Not used, remove it
+ if (!prev_ref) {
+ // It is the first reference
+ fs->ref = cref->next;
+ } else {
+ prev_ref->next = cref->next;
+ }
+
+ free(cref);
+
+ if (entry->flags & RAMFS_ENTRY_RM_LEN_MSK) {
+ // The entry is marked for remove, remove now
+ remove_entry(fs, entry, NULL, NULL, ((entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE));
+ }
+ }
+ }
+}
+
+static int get_reference_uses(ramfs_t *fs, ramfs_entry_t *entry) {
+ ramfs_entry_ref_t *cref;
+
+ // Find entry into the reference set
+ cref = fs->ref;
+ while (cref && (cref->entry != entry)) {
+ cref = cref->next;
+ }
+
+ int uses = 0;
+
+ if (cref) {
+ uses = cref->uses;
+ }
+
+ return uses;
+}
+
+static void remove_block(ramfs_t *fs, ramfs_file_t *file) {
+ ramfs_block_t *block;
+
+ block = file->entry->file.header->tail;
+ if (block) {
+ // Find the block into the file block chain to
+ // update the block chain
+ ramfs_block_t *prev_block = NULL;
+ ramfs_block_t *cblock = file->entry->file.header->head;
+
+ while (cblock && (cblock != block)) {
+ prev_block = cblock;
+ cblock = cblock->next;
+ }
+
+ if (prev_block) {
+ prev_block->next = NULL;
+ file->entry->file.header->tail = prev_block;
+ } else {
+ file->entry->file.header->head = NULL;
+ file->entry->file.header->tail = NULL;
+ }
+
+ // Free block
+ free(block);
+
+ // Update the file system size
+ fs->current_size -= sizeof(ramfs_block_t) + fs->block_size - 1;
+ }
+}
+
+static int add_block(ramfs_t *fs, ramfs_file_t *file) {
+ // Check for space
+ ramfs_size_t size = sizeof(ramfs_block_t) + fs->block_size - 1;
+
+ if (fs->current_size + size > fs->size) {
+ return RAMFS_ERR_NOSPC;
+ }
+
+ // Create block
+ ramfs_block_t *block = (ramfs_block_t *)calloc(1, size);
+ if (!block) {
+ return RAMFS_ERR_NOMEM;
+ }
+
+ if (!file->entry->file.header->head) {
+ // First block of the file
+ file->entry->file.header->head = block;
+ file->entry->file.header->tail = block;
+ } else {
+ // Add the block to the end of the file block chain
+ file->entry->file.header->tail->next = block;
+ file->entry->file.header->tail = block;
+ }
+
+ file->block = block;
+ file->ptr = block->data;
+
+ // Update the file system size
+ fs->current_size += size;
+
+ return RAMFS_ERR_OK;
+}
+
+static int add_entry(ramfs_t *fs, const char *name, ramfs_entry_t *parent, ramfs_entry_t **entry, ramfs_entry_type_t entry_type) {
+ int name_len = strlen(name);
+
+ // Check for space
+ ramfs_size_t entry_size = sizeof(ramfs_entry_t) + name_len - 1;
+ ramfs_size_t header_size = 0;
+
+ if (entry_type == RAMFS_FILE) {
+ header_size = sizeof(ram_file_header_t);
+ }
+
+ if (fs->current_size + entry_size + header_size > fs->size) {
+ return RAMFS_ERR_NOSPC;
+ }
+
+ // Create the entry
+ *entry = (ramfs_entry_t *)calloc(1, entry_size);
+ if (!*entry) {
+ return RAMFS_ERR_NOMEM;
+ }
+
+ (*entry)->flags |= entry_type;
+
+ // Create the file header
+ if (entry_type == RAMFS_FILE) {
+ (*entry)->file.header = (ram_file_header_t *)calloc(1, header_size);
+ if (!((*entry)->file.header)) {
+ free(*entry);
+
+ return RAMFS_ERR_NOMEM;
+ }
+
+ (*entry)->file.header->head = NULL;
+ (*entry)->file.header->tail = NULL;
+ (*entry)->file.header->size = 0;
+ }
+
+ // Set the entry name and len
+ memcpy((*entry)->name, name, name_len);
+
+ (*entry)->flags |= (name_len << RAMFS_ENTRY_NAME_LEN_POS);
+
+ // Update the file system size
+ fs->current_size += entry_size + header_size;
+
+ // Add the entry to the file system
+ if (!fs->child) {
+ // The entry is the first child of the root directory
+ fs->child = *entry;
+ } else {
+ if (parent) {
+ // Add the entry to its parent (which is always a directory)
+ if (!parent->dir.child) {
+ // The entry is the first child of the directory
+ parent->dir.child = *entry;
+ } else {
+ // Add the entry into the directory child chain
+ ramfs_entry_t *centry = parent->dir.child;
+
+ while (centry->next) {
+ centry = centry->next;
+ }
+
+ centry->next = *entry;
+ }
+ } else {
+ // Add the entry to the child chain of the root directory
+ ramfs_entry_t *centry = fs->child;
+
+ while (centry->next) {
+ centry = centry->next;
+ }
+
+ centry->next = *entry;
+ }
+ }
+
+ return RAMFS_ERR_OK;
+}
+
+static void remove_entry(ramfs_t *fs, ramfs_entry_t *entry, ramfs_entry_t *parent_entry, ramfs_entry_t *prev_entry, int remove) {
+ if (!entry) return;
+
+ // Update the chain
+ if (prev_entry) {
+ prev_entry->next = entry->next;
+ }
+
+ if (parent_entry && (parent_entry->dir.child == entry)) {
+ parent_entry->dir.child = entry->next;
+ } else if (!parent_entry) {
+ if (fs->child == entry) {
+ fs->child = entry->next;
+ }
+ }
+
+ // If the entry to remove is used, mark it for remove later
+ if (get_reference_uses(fs, entry) > 0) {
+ entry->flags |= RAMFS_ENTRY_RM_LEN_MSK;
+ return;
+ }
+
+ // While removing the entry, compute the entry size to update the file system size later
+ ramfs_size_t size = sizeof(ramfs_entry_t) + ((entry->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS) - 1;
+
+ if (remove && ((entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE)) {
+ // Free file blocks
+ ramfs_block_t *block = entry->file.header->head;
+ ramfs_block_t *tmp;
+
+ size += sizeof(ram_file_header_t);
+
+ while (block) {
+ size += sizeof(ramfs_block_t) + fs->block_size - 1;
+
+ tmp = block;
+ block = block->next;
+
+ free(tmp);
+ }
+
+ free(entry->file.header);
+ }
+
+ free(entry);
+
+ // Update the file system size
+ fs->current_size -= size;
+}
+
+static int traverse(ramfs_t *fs, const char *path, ramfs_entry_t **entry, ramfs_entry_t **parent_entry, ramfs_entry_t **prev_entry, int creat, ramfs_entry_type_t type) {
+ ramfs_error_t ret;
+
+ ramfs_entry_t *centry; // Curren entry
+
+ // A copy of path to use with strtok_r
+ char path_copy[PATH_MAX + 1];
+
+ char *component; // Current path component
+ char *rest; // Rest of path
+
+ *entry = NULL;
+
+ if (prev_entry) {
+ *prev_entry = NULL;
+ }
+
+ if (!path || !*path) {
+ return RAMFS_ERR_NOENT;
+ }
+
+ if (strlen(path) > PATH_MAX) {
+ return RAMFS_ERR_NAMETOOLONG;
+ }
+
+ // Make a copy of path to use it with strtok_r
+ strcpy(path_copy, path);
+ rest = path_copy;
+
+ // Start at the root directory
+ centry = fs->child;
+ *entry = centry;
+
+ if (!centry) {
+ // The file system doesn't contain any entry yet, then create a new entry
+ component = strtok_r(rest, "/", &rest);
+
+ if (creat && ((ret = add_entry(fs, component, NULL, entry, type)) != RAMFS_ERR_OK)) {
+ return ret;
+ }
+
+ return RAMFS_ERR_NOENT;
+ }
+
+
+ int name_len;
+
+ ramfs_entry_t *parent = NULL;
+
+ if (parent_entry) {
+ *parent_entry = parent;
+ }
+
+ while ((component = strtok_r(rest, "/", &rest))) {
+ while (centry) {
+ name_len = ((centry->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS);
+ if ((strlen(component) == name_len) && bcmp(centry->name, component, name_len) == 0) {
+ // The entry has been found
+ *entry = centry;
+ break;
+ } else {
+ // Next entry
+ if (prev_entry) {
+ *prev_entry = centry;
+ }
+
+ centry = centry->next;
+ }
+ }
+
+ if (!centry) {
+ // The entry was not found, create it, if it corresponds to the last
+ // path component
+ if (!rest) {
+ if (creat && ((ret = add_entry(fs, component, parent, entry, type)) != RAMFS_ERR_OK)) {
+ return ret;
+ }
+ } else {
+ *entry = NULL;
+
+ if (parent_entry) {
+ *parent_entry = NULL;
+ }
+
+ if (prev_entry) {
+ *prev_entry = NULL;
+ }
+ }
+
+ return RAMFS_ERR_NOENT;
+ } else {
+ parent = centry;
+
+ if ((centry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) {
+ // For next path component, start the search into the directory child chain
+ centry = centry->dir.child;
+
+ if (rest) {
+ if (parent_entry) {
+ *parent_entry = parent;
+ }
+
+ if (prev_entry) {
+ *prev_entry = NULL;
+ }
+ }
+ } else {
+ // The current path component is a file, check that there are not more
+ // path components
+ if (rest) {
+ return RAMFS_ERR_NOTDIR;
+ }
+ }
+ }
+ }
+
+ return RAMFS_ERR_OK;
+}
+
+static ramfs_off_t ramfs_file_seek_internal(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t offset, ramfs_whence_t whence) {
+ if (whence == RAMFS_SEEK_SET) {
+ if (offset >= 0) {
+ file->offset = offset;
+ } else {
+ return RAMFS_ERR_INVAL;
+ }
+ } else if (whence == RAMFS_SEEK_CUR) {
+ if (file->offset + offset >= 0) {
+ file->offset += offset;
+ } else {
+ return RAMFS_ERR_INVAL;
+ }
+ } else if (whence == RAMFS_SEEK_END) {
+ if (offset + file->entry->file.header->size >= 0) {
+ file->offset = offset + file->entry->file.header->size;
+ } else {
+ return RAMFS_ERR_INVAL;
+ }
+ } else {
+ return RAMFS_ERR_INVAL;
+ }
+
+ int block_num = file->offset / fs->block_size;
+ int block_off = file->offset % fs->block_size;
+
+ if (file->entry->file.header->head) {
+ ramfs_block_t *block = file->entry->file.header->head;
+ int curr_block;
+
+ for(curr_block = 0;block && (curr_block < block_num);curr_block++) {
+ block = block->next;
+ }
+
+ file->block = block;
+ file->ptr = (block?block->data + block_off:NULL);
+ } else {
+ file->block = NULL;
+ file->ptr = NULL;
+ }
+
+ return block_num * fs->block_size + block_off;
+}
+
+static int ramfs_file_truncate_internal(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t size) {
+ ramfs_error_t ret;
+
+ int access_mode = (file->flags & RAMFS_ACCMODE);
+
+ if ((access_mode != RAMFS_O_WRONLY) && (access_mode != RAMFS_O_RDWR)) {
+ return RAMFS_ERR_BADF;
+ }
+
+ if ((file->entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) {
+ return RAMFS_ERR_ISDIR;
+ }
+
+ if (size < 0) {
+ return RAMFS_ERR_INVAL;
+ }
+
+ int block_delta = ((size - 1) / fs->block_size) - ((file->entry->file.header->size - 1) / fs->block_size);
+
+ if (block_delta < 0) {
+ // Decrease size
+ while (block_delta < 0) {
+ remove_block(fs, file);
+ block_delta++;
+ }
+ } else if (block_delta > 0) {
+ // Increase size
+ while (block_delta > 0) {
+ if ((ret = add_block(fs, file)) != RAMFS_ERR_OK) {
+ return ret;
+ }
+
+ block_delta--;
+ }
+ }
+
+ file->entry->file.header->size = size;
+
+ ramfs_file_seek_internal(fs, file, file->offset, RAMFS_SEEK_SET);
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_mount(ramfs_t *fs, ramfs_config_t *config) {
+ memset(fs, 0, sizeof(ramfs_t));
+
+ ramfs_lock_init(fs->lock);
+
+ fs->size = config->size;
+ fs->block_size = config->block_size;
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_umount(ramfs_t *fs) {
+ int top = 0;
+ ramfs_entry_t *stack[256];
+ ramfs_entry_t *centry;
+ ramfs_entry_t *next_entry = NULL;
+ ramfs_entry_t *parent_entry = NULL;
+
+ ramfs_lock(fs->lock);
+
+ stack[top] = NULL;
+
+ while (top >= 0) {
+ centry = stack[top];
+ if (!centry) {
+ top--;
+ centry = fs->child;
+ parent_entry = NULL;
+ } else {
+ parent_entry = centry;
+ centry = centry->dir.child;
+ }
+
+ while (centry) {
+ next_entry = centry->next;
+
+ if ((centry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE) {
+ remove_entry(fs, centry, NULL, NULL, 1);
+ } else if ((centry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) {
+ if (!centry->dir.child) {
+ remove_entry(fs, centry, NULL, NULL, 1);
+ } else {
+ stack[++top] = centry;
+ }
+ }
+
+ centry = next_entry;
+ }
+
+ if (parent_entry && (stack[top] == parent_entry)) {
+ remove_entry(fs, parent_entry, NULL, NULL, 1);
+ top--;
+ }
+ }
+
+ ramfs_lock_destroy(fs->lock);
+
+ memset(fs, 0, sizeof(ramfs_t));
+
+ return RAMFS_ERR_OK;
+}
+
+
+int ramfs_mkdir(ramfs_t *fs, const char *path) {
+ ramfs_entry_t *entry;
+ ramfs_error_t ret;
+
+ if (strcmp(path,"/") == 0) {
+ return RAMFS_ERR_EXIST;
+ }
+
+ ramfs_lock(fs->lock);
+ ret = traverse(fs, path, &entry, NULL, NULL, 1, RAMFS_DIR);
+ ramfs_unlock(fs->lock);
+
+ if (ret != RAMFS_ERR_OK) {
+ if (ret == RAMFS_ERR_NOENT) {
+ if (entry) {
+ return RAMFS_ERR_OK;
+ } else {
+ return RAMFS_ERR_NOENT;
+ }
+ } else {
+ return ret;
+ }
+ } else {
+ return RAMFS_ERR_EXIST;
+ }
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_dir_open(ramfs_t *fs, ramfs_dir_t *dir, const char *path) {
+ ramfs_entry_t *entry;
+ ramfs_error_t ret;
+
+ memset(dir, 0, sizeof(ramfs_dir_t));
+
+ if (strcmp(path,"/") == 0) {
+ ramfs_lock(fs->lock);
+ dir->child = fs->child;
+ ramfs_unlock(fs->lock);
+ } else {
+ ramfs_lock(fs->lock);
+
+ ret = traverse(fs, path, &entry, NULL, NULL, 0, 0);
+ if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+
+ dir->offset = -1;
+
+ return ret;
+ }
+
+ if ((entry->flags & RAMFS_ENTRY_TYPE_MSK) != RAMFS_DIR) {
+ ramfs_unlock(fs->lock);
+
+ return RAMFS_ERR_NOTDIR;
+ }
+
+ ret = add_reference(fs, entry);
+ if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+
+ return ret;
+ }
+
+ dir->entry = entry;
+ dir->child = entry->dir.child;
+
+ ramfs_unlock(fs->lock);
+ }
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_dir_read(ramfs_t *fs, ramfs_dir_t *dir, ramfs_info_t *info) {
+ if (dir->offset < 0) {
+ return RAMFS_ERR_BADF;
+ }
+
+ if (!dir->child) {
+ dir->offset = -1;
+
+ return RAMFS_ERR_NOENT;
+ }
+
+ int name_len = ((dir->child->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS);
+
+ memcpy(info->name, dir->child->name, name_len);
+ *(info->name + name_len) = 0;
+
+ info->type = (dir->child->flags & RAMFS_ENTRY_TYPE_MSK);
+ info->size = 0;
+
+ if (info->type == RAMFS_FILE) {
+ info->size = dir->child->file.header->size;
+ }
+
+ dir->child = dir->child->next;
+
+ dir->offset++;
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_dir_close(ramfs_t *fs, ramfs_dir_t *dir) {
+ ramfs_lock(fs->lock);
+ remove_reference(fs, dir->entry);
+ ramfs_unlock(fs->lock);
+
+ return RAMFS_ERR_OK;
+}
+
+ramfs_off_t ramfs_telldir(ramfs_t *fs, ramfs_dir_t *dir) {
+ if (dir->offset < 0) {
+ return RAMFS_ERR_BADF;
+ }
+
+ return dir->offset;
+}
+
+int ramfs_stat(ramfs_t *fs, const char *path, ramfs_info_t *info) {
+ ramfs_entry_t *entry;
+ ramfs_error_t ret;
+
+ if (strcmp(path,"/") == 0) {
+ strcpy(info->name, "");
+ info->type = RAMFS_DIR;
+ } else {
+ ramfs_lock(fs->lock);
+
+ ret = traverse(fs, path, &entry, NULL, NULL, 0, 0);
+ if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+
+ return ret;
+ }
+
+ int name_len = ((entry->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS);
+
+ memcpy(info->name, entry->name, name_len);
+ *(info->name + name_len) = 0;
+
+ info->type = entry->flags & RAMFS_ENTRY_TYPE_MSK;
+ info->size = 0;
+
+ if (info->type == RAMFS_FILE) {
+ info->size = entry->file.header->size;
+ }
+
+ ramfs_unlock(fs->lock);
+ }
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_file_stat(ramfs_t *fs, ramfs_file_t *file, ramfs_info_t *info) {
+ if (!file->entry) {
+ return RAMFS_ERR_BADF;
+
+ }
+
+ ramfs_lock(fs->lock);
+
+ int name_len = ((file->entry->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS);
+
+ memcpy(info->name, file->entry->name, name_len);
+ *(info->name + name_len) = 0;
+
+ info->type = file->entry->flags & RAMFS_ENTRY_TYPE_MSK;
+ info->size = 0;
+
+ if (info->type == RAMFS_FILE) {
+ info->size = file->entry->file.header->size;
+ }
+
+ ramfs_unlock(fs->lock);
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_file_open(ramfs_t *fs, ramfs_file_t *file, const char *path, int flags) {
+ ramfs_entry_t *entry;
+ ramfs_error_t ret;
+
+ int access_mode = (flags & RAMFS_ACCMODE);
+ if ((access_mode != RAMFS_O_RDONLY) && (access_mode != RAMFS_O_WRONLY) && (access_mode != RAMFS_O_RDWR)) {
+ return RAMFS_ERR_ACCESS;
+ }
+
+ ramfs_lock(fs->lock);
+
+ ret = traverse(fs, path, &entry, NULL, NULL, flags & RAMFS_O_CREAT, RAMFS_FILE);
+ if (ret == RAMFS_ERR_NOENT) {
+ // File doesn't exist
+ if (!(flags & RAMFS_O_CREAT)) {
+ ramfs_unlock(fs->lock);
+
+ // If no O_CREAT file is present, exit
+ return RAMFS_ERR_NOENT;
+ }
+ } else if ((ret == RAMFS_ERR_OK) && (flags & RAMFS_O_EXCL) && (flags & RAMFS_O_CREAT)) {
+ ramfs_unlock(fs->lock);
+
+ return RAMFS_ERR_EXIST;
+ } else if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+
+ return ret;
+ }
+
+ ret = add_reference(fs, entry);
+ if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+
+ return ret;
+ }
+
+ // Prepare file structure
+ memset(file, 0, sizeof(ramfs_file_t));
+
+ file->entry = entry;
+ file->flags = flags;
+
+ // Truncate file, if required
+ if ((flags & RAMFS_O_TRUNC) && ((access_mode == RAMFS_O_WRONLY) || (access_mode == RAMFS_O_RDWR))) {
+ ret = ramfs_file_truncate_internal(fs, file, 0);
+ if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+
+ return ret;
+ }
+ }
+
+ // Set file position
+ if (flags & RAMFS_O_APPEND){
+ ret = ramfs_file_seek_internal(fs, file, 0, RAMFS_SEEK_END);
+ assert(ret >= 0);
+ } else {
+ ret = ramfs_file_seek_internal(fs, file, 0, RAMFS_SEEK_SET);
+ assert(ret >= 0);
+ }
+
+ ramfs_unlock(fs->lock);
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_file_sync(ramfs_t *fs, ramfs_file_t *file) {
+ return RAMFS_ERR_OK;
+}
+
+ramfs_size_t ramfs_file_read(ramfs_t *fs, ramfs_file_t *file, void *buffer, ramfs_size_t size) {
+ int access_mode = (file->flags & RAMFS_ACCMODE);
+
+ if ((access_mode != RAMFS_O_RDONLY) && (access_mode != RAMFS_O_RDWR)) {
+ return RAMFS_ERR_BADF;
+ }
+
+ ramfs_lock(fs->lock);
+
+ ramfs_size_t reads = 0;
+ while ((reads < size) && (file->offset < file->entry->file.header->size)) {
+ if ((!file->block) || (file->ptr > file->block->data + fs->block_size - 1)) {
+ if (file->block) {
+ file->block = file->block->next;
+ if (file->block) {
+ file->ptr = file->block->data;
+ } else {
+ file->ptr = NULL;
+ ramfs_unlock(fs->lock);
+
+ return reads;
+ }
+ } else {
+ file->block = NULL;
+ file->ptr = NULL;
+
+ ramfs_unlock(fs->lock);
+ return reads;
+ }
+ }
+
+ *(((uint8_t *)buffer++)) = *(file->ptr++);
+
+ reads++;
+ file->offset++;
+ }
+
+ ramfs_unlock(fs->lock);
+
+ return reads;
+}
+
+ramfs_size_t ramfs_file_write(ramfs_t *fs, ramfs_file_t *file, const void *buffer, ramfs_size_t size) {
+ ramfs_error_t ret;
+
+ int access_mode = (file->flags & RAMFS_ACCMODE);
+
+ if ((access_mode != RAMFS_O_WRONLY) && (access_mode != RAMFS_O_RDWR)) {
+ return RAMFS_ERR_BADF;
+ }
+
+ ramfs_lock(fs->lock);
+
+ ramfs_size_t writes = 0;
+ while (writes < size) {
+ if ((!file->block) || (!file->ptr) || (file->ptr > file->block->data + fs->block_size - 1)) {
+ if (file->block && file->block->next) {
+ file->block = file->block->next;
+ file->ptr = file->block->data;
+ } else {
+ ret = add_block(fs, file);
+ if (ret != RAMFS_ERR_OK) {
+ file->block = NULL;
+ file->ptr = NULL;
+ ramfs_unlock(fs->lock);
+ return ret;
+ }
+ }
+ }
+
+ *(file->ptr++) = *(((uint8_t *)buffer++));
+
+ writes++;
+ file->offset++;
+
+ if (file->offset > file->entry->file.header->size) {
+ file->entry->file.header->size++;
+ }
+ }
+
+ ramfs_unlock(fs->lock);
+
+ return writes;
+}
+
+int ramfs_file_truncate(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t size) {
+ ramfs_error_t ret;
+
+ ramfs_lock(fs->lock);
+ ret = ramfs_file_truncate_internal(fs, file, size);
+ ramfs_unlock(fs->lock);
+
+ return ret;
+}
+
+int ramfs_file_close(ramfs_t *fs, ramfs_file_t *file) {
+ ramfs_lock(fs->lock);
+ remove_reference(fs, file->entry);
+ ramfs_unlock(fs->lock);
+
+ memset(file, 0, sizeof(ramfs_file_t));
+
+ return RAMFS_ERR_OK;
+}
+
+ramfs_off_t ramfs_file_seek(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t offset, ramfs_whence_t whence) {
+ ramfs_off_t ret;
+
+ ramfs_lock(fs->lock);
+ ret = ramfs_file_seek_internal(fs, file, offset, whence);
+ ramfs_unlock(fs->lock);
+
+ return ret;
+}
+
+int ramfs_rename(ramfs_t *fs, const char *oldpath, const char *newpath) {
+ ramfs_entry_t *old_entry;
+ ramfs_entry_t *parent_old_entry;
+ ramfs_entry_t *prev_old_entry;
+ ramfs_entry_t *new_entry;
+ ramfs_error_t ret;
+
+ ramfs_lock(fs->lock);
+
+ ret = traverse(fs, oldpath, &old_entry, &parent_old_entry, &prev_old_entry, 0, 0);
+ if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+ return ret;
+ }
+
+ ret = traverse(fs, newpath, &new_entry, NULL, NULL, 1, old_entry->flags & RAMFS_ENTRY_TYPE_MSK);
+ if (ret == RAMFS_ERR_OK) {
+ if (((new_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) && ((old_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE)) {
+ ramfs_unlock(fs->lock);
+ return RAMFS_ERR_ISDIR;
+ } else if (((new_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) && (new_entry->dir.child)) {
+ ramfs_unlock(fs->lock);
+ return RAMFS_ERR_NOTEMPTY;
+ }
+ } else if (ret != RAMFS_ERR_NOENT) {
+ ramfs_unlock(fs->lock);
+ return ret;
+ }
+
+ if ((new_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) {
+ // New and old entries are directories
+ // New directory is empty
+
+ // Move all sub-directories and files of the old entry to the new entry
+ new_entry->dir.child = old_entry->dir.child;
+ } else if ((old_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) {
+ new_entry->dir.child = old_entry->dir.child;
+ } else if ((old_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE) {
+ new_entry->file.header->head = old_entry->file.header->head;
+ new_entry->file.header->tail = old_entry->file.header->tail;
+ new_entry->file.header->size = old_entry->file.header->size;
+ }
+
+ remove_entry(fs, old_entry, parent_old_entry, prev_old_entry, 0);
+
+ ramfs_unlock(fs->lock);
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_rmdir(ramfs_t *fs, const char *path) {
+ ramfs_entry_t *entry;
+ ramfs_entry_t *parent_entry;
+ ramfs_entry_t *prev_entry;
+ ramfs_error_t ret;
+
+ if (strcmp(path,"/") == 0) {
+ return RAMFS_ERR_BUSY;
+ }
+
+ ramfs_lock(fs->lock);
+
+ ret = traverse(fs, path, &entry, &parent_entry, &prev_entry, 0, 0);
+ if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+ return ret;
+ }
+
+ if ((entry->flags & RAMFS_ENTRY_TYPE_MSK) != RAMFS_DIR) {
+ ramfs_unlock(fs->lock);
+ return RAMFS_ERR_NOTDIR;
+ }
+
+ if (entry->dir.child) {
+ ramfs_unlock(fs->lock);
+ return RAMFS_ERR_NOTEMPTY;
+ }
+
+ remove_entry(fs, entry, parent_entry, prev_entry, 0);
+
+ ramfs_unlock(fs->lock);
+
+ return RAMFS_ERR_OK;
+}
+
+int ramfs_unlink(ramfs_t *fs, const char *pathname) {
+ ramfs_entry_t *entry;
+ ramfs_entry_t *parent_entry;
+ ramfs_entry_t *prev_entry;
+ ramfs_error_t ret;
+
+ ramfs_lock(fs->lock);
+
+ ret = traverse(fs, pathname, &entry, &parent_entry, &prev_entry, 0, 0);
+ if (ret != RAMFS_ERR_OK) {
+ ramfs_unlock(fs->lock);
+ return ret;
+ }
+
+ if ((entry->flags & RAMFS_ENTRY_TYPE_MSK) != RAMFS_FILE) {
+ ramfs_unlock(fs->lock);
+ return RAMFS_ERR_PERM;
+ }
+
+ remove_entry(fs, entry, parent_entry, prev_entry, 1);
+
+ ramfs_unlock(fs->lock);
+
+ return RAMFS_ERR_OK;
+}
diff --git a/libiot/ramfs/ramfs.h b/libiot/ramfs/ramfs.h
new file mode 100644
index 0000000..cde76cf
--- /dev/null
+++ b/libiot/ramfs/ramfs.h
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, RAM file system
+ *
+ */
+
+/**
+ * @brief RAM file system (RAMFS)
+ *
+ * The RAMFS is a POSIX-compliance file system entirely stored in RAM, without
+ * persistence, which means that all the data stored in the file system is
+ * lost on each reboot.
+ *
+ * In RAMFS, the file system is stored in a tree structure, in which there are
+ * 2 types of nodes (entries): directories, and files.
+ *
+ * RAMFS structure overview:
+ *
+ * ----------
+ * - RAMFS -
+ * ----------
+ * |
+ * | child
+ * \|/
+ * ---------------- next ---------------- next ----------------
+ * - directory or - ------> - file - ------> - directory -
+ * - file entry - - entry - - entry -
+ * ---------------- ---------------- ----------------
+ * | |
+ * | header | child
+ * \|/ \|/
+ * --------------- ----------------
+ * - file header - - directory or -
+ * --------------- - file entry -
+ * | ----------------
+ * |
+ * \|/
+ * --------- next ---------
+ * - block - ----> - block -
+ * --------- ---------
+ */
+
+#ifndef _RAMFS_H_
+#define _RAMFS_H_
+
+#include <stdint.h>
+#include <stddef.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/mutex.h>
+
+#if PATH_MAX > 64
+#error "ramfs, PATH_MAX must be <= 64"
+#endif
+
+/*
+ * To use the file system in a multi-threaded environment,
+ * define the lock type (mutex) used in your platform.
+ */
+#define ramfs_lock_t struct mtx
+
+/*
+ * If ramfs_lock_t is defined, define the following macros:
+ *
+ * ramfs_lock_init(l): initialize / create the lock
+ * ramfs_lock_destroy(l): destroy the lock
+ * ramfs_lock(l): obtain the lock
+ * ramfs_unlock(l): release the lock
+ */
+#ifdef ramfs_lock_t
+#define ramfs_lock_init(l) \
+ mtx_init(&l, NULL, NULL, 0);
+
+#define ramfs_lock_destroy(l) \
+ mtx_destroy(&l);
+
+#define ramfs_lock(l) \
+ mtx_lock(&l);
+
+#define ramfs_unlock(l) \
+ mtx_unlock(&l);
+#else
+#define ramfs_lock_init()
+#define ramfs_lock_destroy()
+#define ramfs_lock()
+#define ramfs_unlock()
+#endif
+
+typedef int32_t ramfs_off_t;
+typedef int32_t ramfs_size_t;
+
+/**
+ * @brief File system entry flags.
+ *
+ * bit0..bit0: entry type
+ * bit1..bit6: length of the name of the entry
+ * bit7..bit7: entry is deleted from the file system, but not still removed
+ *
+ */
+typedef uint8_t ramfs_entry_flags_t;
+
+#define RAMFS_ENTRY_TYPE_MSK 0b00000001
+#define RAMFS_ENTRY_NAME_LEN_MSK 0b01111110
+#define RAMFS_ENTRY_NAME_LEN_POS 1
+#define RAMFS_ENTRY_RM_LEN_MSK 0b10000000
+
+typedef enum {
+ RAMFS_DIR = 0,
+ RAMFS_FILE = 1
+} ramfs_entry_type_t;
+
+typedef enum {
+ RAMFS_ERR_OK = 0,
+ RAMFS_ERR_NOMEM = -1,
+ RAMFS_ERR_NOENT = -2,
+ RAMFS_ERR_EXIST = -3,
+ RAMFS_ERR_NOTDIR = -4,
+ RAMFS_ERR_BADF = -5,
+ RAMFS_ERR_ACCESS = -6,
+ RAMFS_ERR_NOSPC = -7,
+ RAMFS_ERR_INVAL = -8,
+ RAMFS_ERR_ISDIR = -9,
+ RAMFS_ERR_NOTEMPTY = -10,
+ RAMFS_ERR_BUSY = -11,
+ RAMFS_ERR_PERM = -12,
+ RAMFS_ERR_NAMETOOLONG = -13,
+} ramfs_error_t;
+
+typedef enum {
+ RAMFS_O_RDONLY = 1, // Open a file as read only
+ RAMFS_O_WRONLY = 2, // Open a file as write only
+ RAMFS_O_RDWR = 3, // Open a file as read and write
+ RAMFS_O_CREAT = 0x0100, // Create a file if it does not exist
+ RAMFS_O_EXCL = 0x0200, // Fail if a file already exists
+ RAMFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
+ RAMFS_O_APPEND = 0x0800, // Move to end of file on every write
+} ramfs_flags_t;
+
+#define RAMFS_ACCMODE (RAMFS_O_RDONLY | RAMFS_O_WRONLY | RAMFS_O_RDWR)
+
+typedef enum {
+ RAMFS_SEEK_SET = 1,
+ RAMFS_SEEK_CUR = 2,
+ RAMFS_SEEK_END = 3,
+} ramfs_whence_t;
+
+typedef struct ramfs_block {
+ struct ramfs_block *next; /*!< Next block in chain */
+ uint8_t data[1]; /*!< Block data */
+} ramfs_block_t;
+
+typedef struct ram_file_header {
+ ramfs_block_t *head; /*!< File head */
+ ramfs_block_t *tail; /*!< File tail */
+ ramfs_size_t size; /*!< File size */
+} ram_file_header_t;
+
+typedef struct ramfs_entry {
+ ramfs_entry_flags_t flags; /*!< Entry flags */
+ struct ramfs_entry *next; /*!< Next entry */
+ union {
+ struct {
+ struct ram_file_header *header; /*!< File header */
+ } file;
+ struct {
+ struct ramfs_entry *child; /*!< Entry type */
+ } dir;
+ };
+ char name[1]; /*!< Entry name */
+} ramfs_entry_t;
+
+typedef struct {
+ ramfs_off_t offset; /*!< Current seek offset */
+ ramfs_entry_t *entry; /*!< Directory entry */
+ ramfs_entry_t *child; /*!< Directory child chain */
+} ramfs_dir_t;
+
+typedef struct {
+ ramfs_entry_t *entry; /*!< File entry reference */
+ uint32_t flags; /*!< Open flags */
+ ramfs_off_t offset; /*!< Current seek offset */
+ ramfs_block_t *block; /*!< Current read/write block */
+ uint8_t *ptr; /*!< Current read/write pointer into current block */
+} ramfs_file_t;
+
+typedef struct {
+ char name[PATH_MAX + 1];
+ ramfs_entry_type_t type;
+ ramfs_size_t size;
+} ramfs_info_t;
+
+typedef struct ramfs_entry_ref {
+ ramfs_entry_t *entry;
+ uint8_t uses;
+ struct ramfs_entry_ref *next;
+} ramfs_entry_ref_t;
+
+typedef struct {
+ ramfs_entry_t *child; /*!< Root directory child chain */
+ ramfs_entry_ref_t *ref; /*< Open references to file system entries */
+ ramfs_size_t size;
+ ramfs_size_t current_size;
+ ramfs_size_t block_size;
+#ifdef ramfs_lock_t
+ ramfs_lock_t lock;
+#endif
+} ramfs_t;
+
+typedef struct {
+ ramfs_size_t size;
+ ramfs_size_t block_size;
+} ramfs_config_t;
+
+int ramfs_mount(ramfs_t *fs, ramfs_config_t *config);
+int ramfs_umount(ramfs_t *fs);
+int ramfs_mkdir(ramfs_t *fs, const char *path);
+int ramfs_dir_open(ramfs_t *fs, ramfs_dir_t *dir, const char *path);
+int ramfs_dir_read(ramfs_t *fs, ramfs_dir_t *dir, ramfs_info_t *info);
+int ramfs_dir_close(ramfs_t *fs, ramfs_dir_t *dir);
+int ramfs_stat(ramfs_t *fs, const char *path, ramfs_info_t *info);
+int ramfs_file_open(ramfs_t *fs, ramfs_file_t *file, const char *path, int flags);
+int ramfs_file_sync(ramfs_t *fs, ramfs_file_t *file);
+ramfs_size_t ramfs_file_read(ramfs_t *fs, ramfs_file_t *file, void *buffer, ramfs_size_t size);
+ramfs_size_t ramfs_file_write(ramfs_t *fs, ramfs_file_t *file, const void *buffer, ramfs_size_t size);
+int ramfs_file_close(ramfs_t *fs, ramfs_file_t *file);
+ramfs_off_t ramfs_file_seek(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t offset, ramfs_whence_t whence);
+int ramfs_rename(ramfs_t *fs, const char *oldpath, const char *newpath);
+int ramfs_rmdir(ramfs_t *fs, const char *path);
+int ramfs_unlink(ramfs_t *fs, const char *pathname);
+ramfs_off_t ramfs_telldir(ramfs_t *fs, ramfs_dir_t *dir);
+int ramfs_file_truncate(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t size);
+int ramfs_file_stat(ramfs_t *fs, ramfs_file_t *file, ramfs_info_t *info);
+
+#endif /* _RAMFS_H_ */
diff --git a/libiot/spiffs/k210_spiffs.c b/libiot/spiffs/k210_spiffs.c
new file mode 100644
index 0000000..f87a60c
--- /dev/null
+++ b/libiot/spiffs/k210_spiffs.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, spiffs port functions
+ *
+ */
+
+#include "k210_spiffs.h"
+//#include "esp_attr.h"
+#include "spiffs.h"
+#include "../hal/w25qxx.h"
+
+#include <stdlib.h>
+
+#include "../sys/mutex.h"
+
+#include <devices.h>
+//#include <task.h>
+
+
+#define SPI_FLASH_ALIGN 0
+
+
+
+
+static handle_t spi3 = 0;
+
+
+s32_t k210_spi_flash_init() {
+ spi3 = io_open("/dev/spi3");
+ if (!spi3) {
+ return 0;
+ }
+ w25qxx_init(spi3);
+
+ return 1;
+}
+
+void spiffs_lock(spiffs *fs) {
+ mtx_lock(fs->user_data);
+}
+
+void spiffs_unlock(spiffs *fs) {
+ mtx_unlock(fs->user_data);
+}
+
+s32_t k210_spi_flash_read(u32_t addr, u32_t size, u8_t *dst) {
+#if SPI_FLASH_ALIGN
+ u32_t aaddr;
+ u8_t *buff = NULL;
+ u8_t *abuff = NULL;
+ u32_t asize;
+
+ asize = size;
+
+ // Align address to 4 byte
+ aaddr = (addr + (4 - 1)) & (u32_t) -4;
+ if (aaddr != addr) {
+ aaddr -= 4;
+ asize += (addr - aaddr);
+ }
+
+ // Align size to 4 byte
+ asize = (asize + (4 - 1)) & (u32_t) -4;
+
+ if ((aaddr != addr) || (asize != size)) {
+ // Align buffer
+ buff = malloc(asize + 4);
+ if (!buff) {
+ return SPIFFS_ERR_INTERNAL;
+ }
+
+ abuff = (u8_t *) (((ptrdiff_t) buff + (4 - 1)) & (u32_t) -4);
+
+ if (w25qxx_read_data(aaddr, (void *) abuff, asize) != W25QXX_OK) {
+ free(buff);
+ return SPIFFS_ERR_INTERNAL;
+ }
+
+ memcpy(dst, abuff + (addr - aaddr), size);
+
+ free(buff);
+ } else {
+#endif
+ if (w25qxx_read_data(addr, (void *) dst, size) != W25QXX_OK) {
+ return SPIFFS_ERR_INTERNAL;
+ }
+#if SPI_FLASH_ALIGN
+ }
+#endif
+ return SPIFFS_OK;
+}
+
+s32_t k210_spi_flash_write(u32_t addr, u32_t size, const u8_t *src) {
+#if SPI_FLASH_ALIGN
+ u32_t aaddr;
+ u8_t *buff = NULL;
+ u8_t *abuff = NULL;
+ u32_t asize;
+
+ asize = size;
+
+ // Align address to 4 byte
+ aaddr = (addr + (4 - 1)) & -4;
+ if (aaddr != addr) {
+ aaddr -= 4;
+ asize += (addr - aaddr);
+ }
+
+ // Align size to 4 byte
+ asize = (asize + (4 - 1)) & -4;
+
+ if ((aaddr != addr) || (asize != size)) {
+ // Align buffer
+ buff = malloc(asize + 4);
+ if (!buff) {
+ return SPIFFS_ERR_INTERNAL;
+ }
+
+ abuff = (u8_t *) (((ptrdiff_t) buff + (4 - 1)) & -4);
+
+ if (w25qxx_read_data(aaddr, (void *) abuff, asize) != W25QXX_OK) {
+ free(buff);
+ return SPIFFS_ERR_INTERNAL;
+ }
+
+ memcpy(abuff + (addr - aaddr), src, size);
+
+ if (w25qxx_write_data(aaddr, (uint8_t *) abuff, asize) != W25QXX_OK) {
+ free(buff);
+ return SPIFFS_ERR_INTERNAL;
+ }
+
+ free(buff);
+ } else {
+#endif
+ if (w25qxx_write_data(addr, (uint8_t *) src, size) != W25QXX_OK) {
+ return SPIFFS_ERR_INTERNAL;
+ }
+#if SPI_FLASH_ALIGN
+ }
+#endif
+ return SPIFFS_OK;
+}
+
+s32_t k210_spi_flash_erase(u32_t addr, u32_t size) {
+ if (w25qxx_sector_erase(addr >> 12) != W25QXX_OK) {
+ return SPIFFS_ERR_INTERNAL;
+ }
+
+ return SPIFFS_OK;
+}
diff --git a/libiot/spiffs/k210_spiffs.h b/libiot/spiffs/k210_spiffs.h
new file mode 100644
index 0000000..9edfa2d
--- /dev/null
+++ b/libiot/spiffs/k210_spiffs.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, spiffs port functions
+ *
+ */
+
+#ifndef __K210_SPIFFS_H__
+#define __K210_SPIFFS_H__
+
+#include "spiffs.h"
+
+s32_t k210_spi_flash_read(u32_t addr, u32_t size, u8_t *dst);
+s32_t k210_spi_flash_write(u32_t addr, u32_t size, const u8_t *src);
+s32_t k210_spi_flash_erase(u32_t addr, u32_t size);
+
+#define low_spiffs_read (spiffs_read *)k210_spi_flash_read
+#define low_spiffs_write (spiffs_write *)k210_spi_flash_write
+#define low_spiffs_erase (spiffs_erase *)k210_spi_flash_erase
+
+#endif // __K210_SPIFFS_H__
diff --git a/libiot/spiffs/mkfile b/libiot/spiffs/mkfile
new file mode 100644
index 0000000..8dc4473
--- /dev/null
+++ b/libiot/spiffs/mkfile
@@ -0,0 +1,9 @@
+OFILES=\
+ $OFILES\
+ spiffs/spiffs_check.$O\
+ spiffs/spiffs_hydrogen.$O\
+ spiffs/spiffs_cache.$O\
+ spiffs/spiffs_gc.$O\
+ spiffs/spiffs_nucleus.$O\
+ spiffs/k210_spiffs.$O\
+
diff --git a/libiot/spiffs/spiffs.h b/libiot/spiffs/spiffs.h
new file mode 100644
index 0000000..96c0389
--- /dev/null
+++ b/libiot/spiffs/spiffs.h
@@ -0,0 +1,816 @@
+/*
+ * spiffs.h
+ *
+ * Created on: May 26, 2013
+ * Author: petera
+ */
+
+#ifndef SPIFFS_H_
+#define SPIFFS_H_
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#include "spiffs_config.h"
+
+#define SPIFFS_OK 0
+#define SPIFFS_ERR_NOT_MOUNTED -10000
+#define SPIFFS_ERR_FULL -10001
+#define SPIFFS_ERR_NOT_FOUND -10002
+#define SPIFFS_ERR_END_OF_OBJECT -10003
+#define SPIFFS_ERR_DELETED -10004
+#define SPIFFS_ERR_NOT_FINALIZED -10005
+#define SPIFFS_ERR_NOT_INDEX -10006
+#define SPIFFS_ERR_OUT_OF_FILE_DESCS -10007
+#define SPIFFS_ERR_FILE_CLOSED -10008
+#define SPIFFS_ERR_FILE_DELETED -10009
+#define SPIFFS_ERR_BAD_DESCRIPTOR -10010
+#define SPIFFS_ERR_IS_INDEX -10011
+#define SPIFFS_ERR_IS_FREE -10012
+#define SPIFFS_ERR_INDEX_SPAN_MISMATCH -10013
+#define SPIFFS_ERR_DATA_SPAN_MISMATCH -10014
+#define SPIFFS_ERR_INDEX_REF_FREE -10015
+#define SPIFFS_ERR_INDEX_REF_LU -10016
+#define SPIFFS_ERR_INDEX_REF_INVALID -10017
+#define SPIFFS_ERR_INDEX_FREE -10018
+#define SPIFFS_ERR_INDEX_LU -10019
+#define SPIFFS_ERR_INDEX_INVALID -10020
+#define SPIFFS_ERR_NOT_WRITABLE -10021
+#define SPIFFS_ERR_NOT_READABLE -10022
+#define SPIFFS_ERR_CONFLICTING_NAME -10023
+#define SPIFFS_ERR_NOT_CONFIGURED -10024
+
+#define SPIFFS_ERR_NOT_A_FS -10025
+#define SPIFFS_ERR_MOUNTED -10026
+#define SPIFFS_ERR_ERASE_FAIL -10027
+#define SPIFFS_ERR_MAGIC_NOT_POSSIBLE -10028
+
+#define SPIFFS_ERR_NO_DELETED_BLOCKS -10029
+
+#define SPIFFS_ERR_FILE_EXISTS -10030
+
+#define SPIFFS_ERR_NOT_A_FILE -10031
+#define SPIFFS_ERR_RO_NOT_IMPL -10032
+#define SPIFFS_ERR_RO_ABORTED_OPERATION -10033
+#define SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS -10034
+#define SPIFFS_ERR_PROBE_NOT_A_FS -10035
+#define SPIFFS_ERR_NAME_TOO_LONG -10036
+
+#define SPIFFS_ERR_IX_MAP_UNMAPPED -10037
+#define SPIFFS_ERR_IX_MAP_MAPPED -10038
+#define SPIFFS_ERR_IX_MAP_BAD_RANGE -10039
+
+#define SPIFFS_ERR_INTERNAL -10050
+
+#define SPIFFS_ERR_TEST -10100
+
+
+// spiffs file descriptor index type. must be signed
+typedef s16_t spiffs_file;
+// spiffs file descriptor flags
+typedef u16_t spiffs_flags;
+// spiffs file mode
+typedef u16_t spiffs_mode;
+// object type
+typedef u8_t spiffs_obj_type;
+
+struct spiffs_t;
+
+#if SPIFFS_HAL_CALLBACK_EXTRA
+
+/* spi read call function type */
+typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst);
+/* spi write call function type */
+typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src);
+/* spi erase call function type */
+typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size);
+
+#else // SPIFFS_HAL_CALLBACK_EXTRA
+
+/* spi read call function type */
+typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst);
+/* spi write call function type */
+typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src);
+/* spi erase call function type */
+typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size);
+#endif // SPIFFS_HAL_CALLBACK_EXTRA
+
+/* file system check callback report operation */
+typedef enum {
+ SPIFFS_CHECK_LOOKUP = 0,
+ SPIFFS_CHECK_INDEX,
+ SPIFFS_CHECK_PAGE
+} spiffs_check_type;
+
+/* file system check callback report type */
+typedef enum {
+ SPIFFS_CHECK_PROGRESS = 0,
+ SPIFFS_CHECK_ERROR,
+ SPIFFS_CHECK_FIX_INDEX,
+ SPIFFS_CHECK_FIX_LOOKUP,
+ SPIFFS_CHECK_DELETE_ORPHANED_INDEX,
+ SPIFFS_CHECK_DELETE_PAGE,
+ SPIFFS_CHECK_DELETE_BAD_FILE
+} spiffs_check_report;
+
+/* file system check callback function */
+#if SPIFFS_HAL_CALLBACK_EXTRA
+typedef void (*spiffs_check_callback)(struct spiffs_t *fs, spiffs_check_type type, spiffs_check_report report,
+ u32_t arg1, u32_t arg2);
+#else // SPIFFS_HAL_CALLBACK_EXTRA
+typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_report report,
+ u32_t arg1, u32_t arg2);
+#endif // SPIFFS_HAL_CALLBACK_EXTRA
+
+/* file system listener callback operation */
+typedef enum {
+ /* the file has been created */
+ SPIFFS_CB_CREATED = 0,
+ /* the file has been updated or moved to another page */
+ SPIFFS_CB_UPDATED,
+ /* the file has been deleted */
+ SPIFFS_CB_DELETED
+} spiffs_fileop_type;
+
+/* file system listener callback function */
+typedef void (*spiffs_file_callback)(struct spiffs_t *fs, spiffs_fileop_type op, spiffs_obj_id obj_id, spiffs_page_ix pix);
+
+#ifndef SPIFFS_DBG
+#define SPIFFS_DBG(...) \
+ print(__VA_ARGS__)
+#endif
+#ifndef SPIFFS_GC_DBG
+#define SPIFFS_GC_DBG(...) printf(__VA_ARGS__)
+#endif
+#ifndef SPIFFS_CACHE_DBG
+#define SPIFFS_CACHE_DBG(...) printf(__VA_ARGS__)
+#endif
+#ifndef SPIFFS_CHECK_DBG
+#define SPIFFS_CHECK_DBG(...) printf(__VA_ARGS__)
+#endif
+
+/* Any write to the filehandle is appended to end of the file */
+#define SPIFFS_APPEND (1<<0)
+#define SPIFFS_O_APPEND SPIFFS_APPEND
+/* If the opened file exists, it will be truncated to zero length before opened */
+#define SPIFFS_TRUNC (1<<1)
+#define SPIFFS_O_TRUNC SPIFFS_TRUNC
+/* If the opened file does not exist, it will be created before opened */
+#define SPIFFS_CREAT (1<<2)
+#define SPIFFS_O_CREAT SPIFFS_CREAT
+/* The opened file may only be read */
+#define SPIFFS_RDONLY (1<<3)
+#define SPIFFS_O_RDONLY SPIFFS_RDONLY
+/* The opened file may only be written */
+#define SPIFFS_WRONLY (1<<4)
+#define SPIFFS_O_WRONLY SPIFFS_WRONLY
+/* The opened file may be both read and written */
+#define SPIFFS_RDWR (SPIFFS_RDONLY | SPIFFS_WRONLY)
+#define SPIFFS_O_RDWR SPIFFS_RDWR
+/* Any writes to the filehandle will never be cached but flushed directly */
+#define SPIFFS_DIRECT (1<<5)
+#define SPIFFS_O_DIRECT SPIFFS_DIRECT
+/* If SPIFFS_O_CREAT and SPIFFS_O_EXCL are set, SPIFFS_open() shall fail if the file exists */
+#define SPIFFS_EXCL (1<<6)
+#define SPIFFS_O_EXCL SPIFFS_EXCL
+
+#define SPIFFS_SEEK_SET (0)
+#define SPIFFS_SEEK_CUR (1)
+#define SPIFFS_SEEK_END (2)
+
+#define SPIFFS_TYPE_FILE (1)
+#define SPIFFS_TYPE_DIR (2)
+#define SPIFFS_TYPE_HARD_LINK (3)
+#define SPIFFS_TYPE_SOFT_LINK (4)
+
+#ifndef SPIFFS_LOCK
+#define SPIFFS_LOCK(fs)
+#endif
+
+#ifndef SPIFFS_UNLOCK
+#define SPIFFS_UNLOCK(fs)
+#endif
+
+// phys structs
+
+// spiffs spi configuration struct
+typedef struct {
+ // physical read function
+ spiffs_read hal_read_f;
+ // physical write function
+ spiffs_write hal_write_f;
+ // physical erase function
+ spiffs_erase hal_erase_f;
+#if SPIFFS_SINGLETON == 0
+ // physical size of the spi flash
+ u32_t phys_size;
+ // physical offset in spi flash used for spiffs,
+ // must be on block boundary
+ u32_t phys_addr;
+ // physical size when erasing a block
+ u32_t phys_erase_block;
+
+ // logical size of a block, must be on physical
+ // block size boundary and must never be less than
+ // a physical block
+ u32_t log_block_size;
+ // logical size of a page, must be at least
+ // log_block_size / 8
+ u32_t log_page_size;
+
+#endif
+#if SPIFFS_FILEHDL_OFFSET
+ // an integer offset added to each file handle
+ u16_t fh_ix_offset;
+#endif
+} spiffs_config;
+
+typedef struct spiffs_t {
+ // file system configuration
+ spiffs_config cfg;
+ // number of logical blocks
+ u32_t block_count;
+
+ // cursor for free blocks, block index
+ spiffs_block_ix free_cursor_block_ix;
+ // cursor for free blocks, entry index
+ int free_cursor_obj_lu_entry;
+ // cursor when searching, block index
+ spiffs_block_ix cursor_block_ix;
+ // cursor when searching, entry index
+ int cursor_obj_lu_entry;
+
+ // primary work buffer, size of a logical page
+ u8_t *lu_work;
+ // secondary work buffer, size of a logical page
+ u8_t *work;
+ // file descriptor memory area
+ u8_t *fd_space;
+ // available file descriptors
+ u32_t fd_count;
+
+ // last error
+ s32_t err_code;
+
+ // current number of free blocks
+ u32_t free_blocks;
+ // current number of busy pages
+ u32_t stats_p_allocated;
+ // current number of deleted pages
+ u32_t stats_p_deleted;
+ // flag indicating that garbage collector is cleaning
+ u8_t cleaning;
+ // max erase count amongst all blocks
+ spiffs_obj_id max_erase_count;
+
+#if SPIFFS_GC_STATS
+ u32_t stats_gc_runs;
+#endif
+
+#if SPIFFS_CACHE
+ // cache memory
+ void *cache;
+ // cache size
+ u32_t cache_size;
+#if SPIFFS_CACHE_STATS
+ u32_t cache_hits;
+ u32_t cache_misses;
+#endif
+#endif
+
+ // check callback function
+ spiffs_check_callback check_cb_f;
+ // file callback function
+ spiffs_file_callback file_cb_f;
+ // mounted flag
+ u8_t mounted;
+ // user data
+ void *user_data;
+ // config magic
+ u32_t config_magic;
+} spiffs;
+
+/* spiffs file status struct */
+typedef struct {
+ spiffs_obj_id obj_id;
+ u32_t size;
+ spiffs_obj_type type;
+ spiffs_page_ix pix;
+ u8_t name[SPIFFS_OBJ_NAME_LEN];
+#if SPIFFS_OBJ_META_LEN
+ u8_t meta[SPIFFS_OBJ_META_LEN];
+#endif
+} spiffs_stat;
+
+struct spiffs_dirent {
+ spiffs_obj_id obj_id;
+ u8_t name[SPIFFS_OBJ_NAME_LEN];
+ spiffs_obj_type type;
+ u32_t size;
+ spiffs_page_ix pix;
+#if SPIFFS_OBJ_META_LEN
+ u8_t meta[SPIFFS_OBJ_META_LEN];
+#endif
+};
+
+typedef struct {
+ spiffs *fs;
+ spiffs_block_ix block;
+ int entry;
+} spiffs_DIR;
+
+#if SPIFFS_IX_MAP
+
+typedef struct {
+ // buffer with looked up data pixes
+ spiffs_page_ix *map_buf;
+ // precise file byte offset
+ u32_t offset;
+ // start data span index of lookup buffer
+ spiffs_span_ix start_spix;
+ // end data span index of lookup buffer
+ spiffs_span_ix end_spix;
+} spiffs_ix_map;
+
+#endif
+
+// functions
+
+#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
+/**
+ * Special function. This takes a spiffs config struct and returns the number
+ * of blocks this file system was formatted with. This function relies on
+ * that following info is set correctly in given config struct:
+ *
+ * phys_addr, log_page_size, and log_block_size.
+ *
+ * Also, hal_read_f must be set in the config struct.
+ *
+ * One must be sure of the correct page size and that the physical address is
+ * correct in the probed file system when calling this function. It is not
+ * checked if the phys_addr actually points to the start of the file system,
+ * so one might get a false positive if entering a phys_addr somewhere in the
+ * middle of the file system at block boundary. In addition, it is not checked
+ * if the page size is actually correct. If it is not, weird file system sizes
+ * will be returned.
+ *
+ * If this function detects a file system it returns the assumed file system
+ * size, which can be used to set the phys_size.
+ *
+ * Otherwise, it returns an error indicating why it is not regarded as a file
+ * system.
+ *
+ * Note: this function is not protected with SPIFFS_LOCK and SPIFFS_UNLOCK
+ * macros. It returns the error code directly, instead of as read by
+ * SPIFFS_errno.
+ *
+ * @param config essential parts of the physical and logical
+ * configuration of the file system.
+ */
+s32_t SPIFFS_probe_fs(spiffs_config *config);
+#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
+
+/**
+ * Initializes the file system dynamic parameters and mounts the filesystem.
+ * If SPIFFS_USE_MAGIC is enabled the mounting may fail with SPIFFS_ERR_NOT_A_FS
+ * if the flash does not contain a recognizable file system.
+ * In this case, SPIFFS_format must be called prior to remounting.
+ * @param fs the file system struct
+ * @param config the physical and logical configuration of the file system
+ * @param work a memory work buffer comprising 2*config->log_page_size
+ * bytes used throughout all file system operations
+ * @param fd_space memory for file descriptors
+ * @param fd_space_size memory size of file descriptors
+ * @param cache memory for cache, may be null
+ * @param cache_size memory size of cache
+ * @param check_cb_f callback function for reporting during consistency checks
+ */
+s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work,
+ u8_t *fd_space, u32_t fd_space_size,
+ void *cache, u32_t cache_size,
+ spiffs_check_callback check_cb_f);
+
+/**
+ * Unmounts the file system. All file handles will be flushed of any
+ * cached writes and closed.
+ * @param fs the file system struct
+ */
+void SPIFFS_unmount(spiffs *fs);
+
+/**
+ * Creates a new file.
+ * @param fs the file system struct
+ * @param path the path of the new file
+ * @param mode ignored, for posix compliance
+ */
+s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode);
+
+/**
+ * Opens/creates a file.
+ * @param fs the file system struct
+ * @param path the path of the new file
+ * @param flags the flags for the open command, can be combinations of
+ * SPIFFS_O_APPEND, SPIFFS_O_TRUNC, SPIFFS_O_CREAT, SPIFFS_O_RDONLY,
+ * SPIFFS_O_WRONLY, SPIFFS_O_RDWR, SPIFFS_O_DIRECT, SPIFFS_O_EXCL
+ * @param mode ignored, for posix compliance
+ */
+spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode);
+
+/**
+ * Opens a file by given dir entry.
+ * Optimization purposes, when traversing a file system with SPIFFS_readdir
+ * a normal SPIFFS_open would need to traverse the filesystem again to find
+ * the file, whilst SPIFFS_open_by_dirent already knows where the file resides.
+ * @param fs the file system struct
+ * @param e the dir entry to the file
+ * @param flags the flags for the open command, can be combinations of
+ * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY,
+ * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT.
+ * SPIFFS_CREAT will have no effect in this case.
+ * @param mode ignored, for posix compliance
+ */
+spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode);
+
+/**
+ * Opens a file by given page index.
+ * Optimization purposes, opens a file by directly pointing to the page
+ * index in the spi flash.
+ * If the page index does not point to a file header SPIFFS_ERR_NOT_A_FILE
+ * is returned.
+ * @param fs the file system struct
+ * @param page_ix the page index
+ * @param flags the flags for the open command, can be combinations of
+ * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY,
+ * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT.
+ * SPIFFS_CREAT will have no effect in this case.
+ * @param mode ignored, for posix compliance
+ */
+spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode);
+
+/**
+ * Reads from given filehandle.
+ * @param fs the file system struct
+ * @param fh the filehandle
+ * @param buf where to put read data
+ * @param len how much to read
+ * @returns number of bytes read, or -1 if error
+ */
+s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len);
+
+/**
+ * Writes to given filehandle.
+ * @param fs the file system struct
+ * @param fh the filehandle
+ * @param buf the data to write
+ * @param len how much to write
+ * @returns number of bytes written, or -1 if error
+ */
+s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len);
+
+/**
+ * Moves the read/write file offset. Resulting offset is returned or negative if error.
+ * lseek(fs, fd, 0, SPIFFS_SEEK_CUR) will thus return current offset.
+ * @param fs the file system struct
+ * @param fh the filehandle
+ * @param offs how much/where to move the offset
+ * @param whence if SPIFFS_SEEK_SET, the file offset shall be set to offset bytes
+ * if SPIFFS_SEEK_CUR, the file offset shall be set to its current location plus offset
+ * if SPIFFS_SEEK_END, the file offset shall be set to the size of the file plus offse, which should be negative
+ */
+s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence);
+
+/**
+ * Removes a file by path
+ * @param fs the file system struct
+ * @param path the path of the file to remove
+ */
+s32_t SPIFFS_remove(spiffs *fs, const char *path);
+
+/**
+ * Removes a file by filehandle
+ * @param fs the file system struct
+ * @param fh the filehandle of the file to remove
+ */
+s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh);
+
+/**
+ * Gets file status by path
+ * @param fs the file system struct
+ * @param path the path of the file to stat
+ * @param s the stat struct to populate
+ */
+s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s);
+
+/**
+ * Gets file status by filehandle
+ * @param fs the file system struct
+ * @param fh the filehandle of the file to stat
+ * @param s the stat struct to populate
+ */
+s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s);
+
+/**
+ * Flushes all pending write operations from cache for given file
+ * @param fs the file system struct
+ * @param fh the filehandle of the file to flush
+ */
+s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh);
+
+/**
+ * Closes a filehandle. If there are pending write operations, these are finalized before closing.
+ * @param fs the file system struct
+ * @param fh the filehandle of the file to close
+ */
+s32_t SPIFFS_close(spiffs *fs, spiffs_file fh);
+
+/**
+ * Renames a file
+ * @param fs the file system struct
+ * @param old path of file to rename
+ * @param newPath new path of file
+ */
+s32_t SPIFFS_rename(spiffs *fs, const char *old, const char *newPath);
+
+#if SPIFFS_OBJ_META_LEN
+/**
+ * Updates file's metadata
+ * @param fs the file system struct
+ * @param path path to the file
+ * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long.
+ */
+s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta);
+
+/**
+ * Updates file's metadata
+ * @param fs the file system struct
+ * @param fh file handle of the file
+ * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long.
+ */
+s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta);
+#endif
+
+/**
+ * Returns last error of last file operation.
+ * @param fs the file system struct
+ */
+s32_t SPIFFS_errno(spiffs *fs);
+
+/**
+ * Clears last error.
+ * @param fs the file system struct
+ */
+void SPIFFS_clearerr(spiffs *fs);
+
+/**
+ * Opens a directory stream corresponding to the given name.
+ * The stream is positioned at the first entry in the directory.
+ * On hydrogen builds the name argument is ignored as hydrogen builds always correspond
+ * to a flat file structure - no directories.
+ * @param fs the file system struct
+ * @param name the name of the directory
+ * @param d pointer the directory stream to be populated
+ */
+spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d);
+
+/**
+ * Closes a directory stream
+ * @param d the directory stream to close
+ */
+s32_t SPIFFS_closedir(spiffs_DIR *d);
+
+/**
+ * Reads a directory into given spifs_dirent struct.
+ * @param d pointer to the directory stream
+ * @param e the dirent struct to be populated
+ * @returns null if error or end of stream, else given dirent is returned
+ */
+struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e);
+
+/**
+ * Runs a consistency check on given filesystem.
+ * @param fs the file system struct
+ */
+s32_t SPIFFS_check(spiffs *fs);
+
+/**
+ * Returns number of total bytes available and number of used bytes.
+ * This is an estimation, and depends on if there a many files with little
+ * data or few files with much data.
+ * NB: If used number of bytes exceeds total bytes, a SPIFFS_check should
+ * run. This indicates a power loss in midst of things. In worst case
+ * (repeated powerlosses in mending or gc) you might have to delete some files.
+ *
+ * @param fs the file system struct
+ * @param total total number of bytes in filesystem
+ * @param used used number of bytes in filesystem
+ */
+s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used);
+
+/**
+ * Formats the entire file system. All data will be lost.
+ * The filesystem must not be mounted when calling this.
+ *
+ * NB: formatting is awkward. Due to backwards compatibility, SPIFFS_mount
+ * MUST be called prior to formatting in order to configure the filesystem.
+ * If SPIFFS_mount succeeds, SPIFFS_unmount must be called before calling
+ * SPIFFS_format.
+ * If SPIFFS_mount fails, SPIFFS_format can be called directly without calling
+ * SPIFFS_unmount first.
+ *
+ * @param fs the file system struct
+ */
+s32_t SPIFFS_format(spiffs *fs);
+
+/**
+ * Returns nonzero if spiffs is mounted, or zero if unmounted.
+ * @param fs the file system struct
+ */
+u8_t SPIFFS_mounted(spiffs *fs);
+
+/**
+ * Tries to find a block where most or all pages are deleted, and erase that
+ * block if found. Does not care for wear levelling. Will not move pages
+ * around.
+ * If parameter max_free_pages are set to 0, only blocks with only deleted
+ * pages will be selected.
+ *
+ * NB: the garbage collector is automatically called when spiffs needs free
+ * pages. The reason for this function is to give possibility to do background
+ * tidying when user knows the system is idle.
+ *
+ * Use with care.
+ *
+ * Setting max_free_pages to anything larger than zero will eventually wear
+ * flash more as a block containing free pages can be erased.
+ *
+ * Will set err_no to SPIFFS_OK if a block was found and erased,
+ * SPIFFS_ERR_NO_DELETED_BLOCK if no matching block was found,
+ * or other error.
+ *
+ * @param fs the file system struct
+ * @param max_free_pages maximum number allowed free pages in block
+ */
+s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages);
+
+/**
+ * Will try to make room for given amount of bytes in the filesystem by moving
+ * pages and erasing blocks.
+ * If it is physically impossible, err_no will be set to SPIFFS_ERR_FULL. If
+ * there already is this amount (or more) of free space, SPIFFS_gc will
+ * silently return. It is recommended to call SPIFFS_info before invoking
+ * this method in order to determine what amount of bytes to give.
+ *
+ * NB: the garbage collector is automatically called when spiffs needs free
+ * pages. The reason for this function is to give possibility to do background
+ * tidying when user knows the system is idle.
+ *
+ * Use with care.
+ *
+ * @param fs the file system struct
+ * @param size amount of bytes that should be freed
+ */
+s32_t SPIFFS_gc(spiffs *fs, u32_t size);
+
+/**
+ * Check if EOF reached.
+ * @param fs the file system struct
+ * @param fh the filehandle of the file to check
+ */
+s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh);
+
+/**
+ * Get position in file.
+ * @param fs the file system struct
+ * @param fh the filehandle of the file to check
+ */
+s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh);
+
+/**
+ * Registers a callback function that keeps track on operations on file
+ * headers. Do note, that this callback is called from within internal spiffs
+ * mechanisms. Any operations on the actual file system being callbacked from
+ * in this callback will mess things up for sure - do not do this.
+ * This can be used to track where files are and move around during garbage
+ * collection, which in turn can be used to build location tables in ram.
+ * Used in conjuction with SPIFFS_open_by_page this may improve performance
+ * when opening a lot of files.
+ * Must be invoked after mount.
+ *
+ * @param fs the file system struct
+ * @param cb_func the callback on file operations
+ */
+s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func);
+
+#if SPIFFS_IX_MAP
+
+/**
+ * Maps the first level index lookup to a given memory map.
+ * This will make reading big files faster, as the memory map will be used for
+ * looking up data pages instead of searching for the indices on the physical
+ * medium. When mapping, all affected indicies are found and the information is
+ * copied to the array.
+ * Whole file or only parts of it may be mapped. The index map will cover file
+ * contents from argument offset until and including arguments (offset+len).
+ * It is valid to map a longer range than the current file size. The map will
+ * then be populated when the file grows.
+ * On garbage collections and file data page movements, the map array will be
+ * automatically updated. Do not tamper with the map array, as this contains
+ * the references to the data pages. Modifying it from outside will corrupt any
+ * future readings using this file descriptor.
+ * The map will no longer be used when the file descriptor closed or the file
+ * is unmapped.
+ * This can be useful to get faster and more deterministic timing when reading
+ * large files, or when seeking and reading a lot within a file.
+ * @param fs the file system struct
+ * @param fh the file handle of the file to map
+ * @param map a spiffs_ix_map struct, describing the index map
+ * @param offset absolute file offset where to start the index map
+ * @param len length of the mapping in actual file bytes
+ * @param map_buf the array buffer for the look up data - number of required
+ * elements in the array can be derived from function
+ * SPIFFS_bytes_to_ix_map_entries given the length
+ */
+s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map,
+ u32_t offset, u32_t len, spiffs_page_ix *map_buf);
+
+/**
+ * Unmaps the index lookup from this filehandle. All future readings will
+ * proceed as normal, requiring reading of the first level indices from
+ * physical media.
+ * The map and map buffer given in function SPIFFS_ix_map will no longer be
+ * referenced by spiffs.
+ * It is not strictly necessary to unmap a file before closing it, as closing
+ * a file will automatically unmap it.
+ * @param fs the file system struct
+ * @param fh the file handle of the file to unmap
+ */
+s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh);
+
+/**
+ * Moves the offset for the index map given in function SPIFFS_ix_map. Parts or
+ * all of the map buffer will repopulated.
+ * @param fs the file system struct
+ * @param fh the mapped file handle of the file to remap
+ * @param offset new absolute file offset where to start the index map
+ */
+s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offs);
+
+/**
+ * Utility function to get number of spiffs_page_ix entries a map buffer must
+ * contain on order to map given amount of file data in bytes.
+ * See function SPIFFS_ix_map and SPIFFS_ix_map_entries_to_bytes.
+ * @param fs the file system struct
+ * @param bytes number of file data bytes to map
+ * @return needed number of elements in a spiffs_page_ix array needed to
+ * map given amount of bytes in a file
+ */
+s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes);
+
+/**
+ * Utility function to amount of file data bytes that can be mapped when
+ * mapping a file with buffer having given number of spiffs_page_ix entries.
+ * See function SPIFFS_ix_map and SPIFFS_bytes_to_ix_map_entries.
+ * @param fs the file system struct
+ * @param map_page_ix_entries number of entries in a spiffs_page_ix array
+ * @return amount of file data in bytes that can be mapped given a map
+ * buffer having given amount of spiffs_page_ix entries
+ */
+s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries);
+
+#endif // SPIFFS_IX_MAP
+
+
+#if SPIFFS_TEST_VISUALISATION
+/**
+ * Prints out a visualization of the filesystem.
+ * @param fs the file system struct
+ */
+s32_t SPIFFS_vis(spiffs *fs);
+#endif
+
+#if SPIFFS_BUFFER_HELP
+/**
+ * Returns number of bytes needed for the filedescriptor buffer given
+ * amount of file descriptors.
+ */
+u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs);
+
+#if SPIFFS_CACHE
+/**
+ * Returns number of bytes needed for the cache buffer given
+ * amount of cache pages.
+ */
+u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages);
+#endif
+#endif
+
+#if SPIFFS_CACHE
+#endif
+#if defined(__cplusplus)
+}
+#endif
+
+void spiffs_lock(spiffs *fs);
+void spiffs_unlock(spiffs *fs);
+
+#endif /* SPIFFS_H_ */
diff --git a/libiot/spiffs/spiffs_cache.c b/libiot/spiffs/spiffs_cache.c
new file mode 100644
index 0000000..018f763
--- /dev/null
+++ b/libiot/spiffs/spiffs_cache.c
@@ -0,0 +1,314 @@
+/*
+ * spiffs_cache.c
+ *
+ * Created on: Jun 23, 2013
+ * Author: petera
+ */
+
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+#if SPIFFS_CACHE
+
+// returns cached page for give page index, or null if no such cached page
+static spiffs_cache_page *spiffs_cache_page_get(spiffs *fs, spiffs_page_ix pix) {
+ spiffs_cache *cache = spiffs_get_cache(fs);
+ if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) return 0;
+ int i;
+ for (i = 0; i < cache->cpage_count; i++) {
+ spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+ if ((cache->cpage_use_map & (1<<i)) &&
+ (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 &&
+ cp->pix == pix ) {
+ SPIFFS_CACHE_DBG("CACHE_GET: have cache page "_SPIPRIi" for "_SPIPRIpg"\n", i, pix);
+ cp->last_access = cache->last_access;
+ return cp;
+ }
+ }
+ //SPIFFS_CACHE_DBG("CACHE_GET: no cache for "_SPIPRIpg"\n", pix);
+ return 0;
+}
+
+// frees cached page
+static s32_t spiffs_cache_page_free(spiffs *fs, int ix, u8_t write_back) {
+ s32_t res = SPIFFS_OK;
+ spiffs_cache *cache = spiffs_get_cache(fs);
+ spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, ix);
+ if (cache->cpage_use_map & (1<<ix)) {
+ if (write_back &&
+ (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 &&
+ (cp->flags & SPIFFS_CACHE_FLAG_DIRTY)) {
+ u8_t *mem = spiffs_get_cache_page(fs, cache, ix);
+ res = SPIFFS_HAL_WRITE(fs, SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem);
+ }
+
+ cp->flags = 0;
+ cache->cpage_use_map &= ~(1 << ix);
+
+ if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) {
+ SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" objid "_SPIPRIid"\n", ix, cp->obj_id);
+ } else {
+ SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" pix "_SPIPRIpg"\n", ix, cp->pix);
+ }
+ }
+
+ return res;
+}
+
+// removes the oldest accessed cached page
+static s32_t spiffs_cache_page_remove_oldest(spiffs *fs, u8_t flag_mask, u8_t flags) {
+ s32_t res = SPIFFS_OK;
+ spiffs_cache *cache = spiffs_get_cache(fs);
+
+ if ((cache->cpage_use_map & cache->cpage_use_mask) != cache->cpage_use_mask) {
+ // at least one free cpage
+ return SPIFFS_OK;
+ }
+
+ // all busy, scan thru all to find the cpage which has oldest access
+ int i;
+ int cand_ix = -1;
+ u32_t oldest_val = 0;
+ for (i = 0; i < cache->cpage_count; i++) {
+ spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+ if ((cache->last_access - cp->last_access) > oldest_val &&
+ (cp->flags & flag_mask) == flags) {
+ oldest_val = cache->last_access - cp->last_access;
+ cand_ix = i;
+ }
+ }
+
+ if (cand_ix >= 0) {
+ res = spiffs_cache_page_free(fs, cand_ix, 1);
+ }
+
+ return res;
+}
+
+// allocates a new cached page and returns it, or null if all cache pages are busy
+static spiffs_cache_page *spiffs_cache_page_allocate(spiffs *fs) {
+ spiffs_cache *cache = spiffs_get_cache(fs);
+ if (cache->cpage_use_map == 0xffffffff) {
+ // out of cache memory
+ return 0;
+ }
+ int i;
+ for (i = 0; i < cache->cpage_count; i++) {
+ if ((cache->cpage_use_map & (1<<i)) == 0) {
+ spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+ cache->cpage_use_map |= (1<<i);
+ cp->last_access = cache->last_access;
+ SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi"\n", i);
+ return cp;
+ }
+ }
+ // out of cache entries
+ return 0;
+}
+
+// drops the cache page for give page index
+void spiffs_cache_drop_page(spiffs *fs, spiffs_page_ix pix) {
+ spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix);
+ if (cp) {
+ spiffs_cache_page_free(fs, cp->ix, 0);
+ }
+}
+
+// ------------------------------
+
+// reads from spi flash or the cache
+s32_t spiffs_phys_rd(
+ spiffs *fs,
+ u8_t op,
+ spiffs_file fh,
+ u32_t addr,
+ u32_t len,
+ u8_t *dst) {
+ (void)fh;
+ s32_t res = SPIFFS_OK;
+ spiffs_cache *cache = spiffs_get_cache(fs);
+ spiffs_cache_page *cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr));
+ cache->last_access++;
+ if (cp) {
+ // we've already got one, you see
+#if SPIFFS_CACHE_STATS
+ fs->cache_hits++;
+#endif
+ cp->last_access = cache->last_access;
+ u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix);
+ memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len);
+ } else {
+ if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) {
+ // for second layer lookup functions, we do not cache in order to prevent shredding
+ return SPIFFS_HAL_READ(fs, addr, len, dst);
+ }
+#if SPIFFS_CACHE_STATS
+ fs->cache_misses++;
+#endif
+ // this operation will always free one cache page (unless all already free),
+ // the result code stems from the write operation of the possibly freed cache page
+ res = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0);
+
+ cp = spiffs_cache_page_allocate(fs);
+ if (cp) {
+ cp->flags = SPIFFS_CACHE_FLAG_WRTHRU;
+ cp->pix = SPIFFS_PADDR_TO_PAGE(fs, addr);
+
+ s32_t res2 = SPIFFS_HAL_READ(fs,
+ addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr),
+ SPIFFS_CFG_LOG_PAGE_SZ(fs),
+ spiffs_get_cache_page(fs, cache, cp->ix));
+ if (res2 != SPIFFS_OK) {
+ // honor read failure before possible write failure (bad idea?)
+ res = res2;
+ }
+ u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix);
+ memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len);
+ } else {
+ // this will never happen, last resort for sake of symmetry
+ s32_t res2 = SPIFFS_HAL_READ(fs, addr, len, dst);
+ if (res2 != SPIFFS_OK) {
+ // honor read failure before possible write failure (bad idea?)
+ res = res2;
+ }
+ }
+ }
+ return res;
+}
+
+// writes to spi flash and/or the cache
+s32_t spiffs_phys_wr(
+ spiffs *fs,
+ u8_t op,
+ spiffs_file fh,
+ u32_t addr,
+ u32_t len,
+ u8_t *src) {
+ (void)fh;
+ spiffs_page_ix pix = SPIFFS_PADDR_TO_PAGE(fs, addr);
+ spiffs_cache *cache = spiffs_get_cache(fs);
+ spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix);
+
+ if (cp && (op & SPIFFS_OP_COM_MASK) != SPIFFS_OP_C_WRTHRU) {
+ // have a cache page
+ // copy in data to cache page
+
+ if ((op & SPIFFS_OP_COM_MASK) == SPIFFS_OP_C_DELE &&
+ (op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) {
+ // page is being deleted, wipe from cache - unless it is a lookup page
+ spiffs_cache_page_free(fs, cp->ix, 0);
+ return SPIFFS_HAL_WRITE(fs, addr, len, src);
+ }
+
+ u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix);
+ memcpy(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len);
+
+ cache->last_access++;
+ cp->last_access = cache->last_access;
+
+ if (cp->flags & SPIFFS_CACHE_FLAG_WRTHRU) {
+ // page is being updated, no write-cache, just pass thru
+ return SPIFFS_HAL_WRITE(fs, addr, len, src);
+ } else {
+ return SPIFFS_OK;
+ }
+ } else {
+ // no cache page, no write cache - just write thru
+ return SPIFFS_HAL_WRITE(fs, addr, len, src);
+ }
+}
+
+#if SPIFFS_CACHE_WR
+// returns the cache page that this fd refers, or null if no cache page
+spiffs_cache_page *spiffs_cache_page_get_by_fd(spiffs *fs, spiffs_fd *fd) {
+ spiffs_cache *cache = spiffs_get_cache(fs);
+
+ if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) {
+ // all cpages free, no cpage cannot be assigned to obj_id
+ return 0;
+ }
+
+ int i;
+ for (i = 0; i < cache->cpage_count; i++) {
+ spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+ if ((cache->cpage_use_map & (1<<i)) &&
+ (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) &&
+ cp->obj_id == fd->obj_id) {
+ return cp;
+ }
+ }
+
+ return 0;
+}
+
+// allocates a new cache page and refers this to given fd - flushes an old cache
+// page if all cache is busy
+spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd) {
+ // before this function is called, it is ensured that there is no already existing
+ // cache page with same object id
+ spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0);
+ spiffs_cache_page *cp = spiffs_cache_page_allocate(fs);
+ if (cp == 0) {
+ // could not get cache page
+ return 0;
+ }
+
+ cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR;
+ cp->obj_id = fd->obj_id;
+ fd->cache_page = cp;
+ return cp;
+}
+
+// unrefers all fds that this cache page refers to and releases the cache page
+void spiffs_cache_fd_release(spiffs *fs, spiffs_cache_page *cp) {
+ if (cp == 0) return;
+ u32_t i;
+ spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+ for (i = 0; i < fs->fd_count; i++) {
+ spiffs_fd *cur_fd = &fds[i];
+ if (cur_fd->file_nbr != 0 && cur_fd->cache_page == cp) {
+ cur_fd->cache_page = 0;
+ }
+ }
+ spiffs_cache_page_free(fs, cp->ix, 0);
+
+ cp->obj_id = 0;
+}
+
+#endif
+
+// initializes the cache
+void spiffs_cache_init(spiffs *fs) {
+ if (fs->cache == 0) return;
+ u32_t sz = fs->cache_size;
+ u32_t cache_mask = 0;
+ int i;
+ int cache_entries =
+ (sz - sizeof(spiffs_cache)) / (SPIFFS_CACHE_PAGE_SIZE(fs));
+ if (cache_entries <= 0) return;
+
+ for (i = 0; i < cache_entries; i++) {
+ cache_mask <<= 1;
+ cache_mask |= 1;
+ }
+
+ spiffs_cache cache;
+ memset(&cache, 0, sizeof(spiffs_cache));
+ cache.cpage_count = cache_entries;
+ cache.cpages = (u8_t *)((u8_t *)fs->cache + sizeof(spiffs_cache));
+
+ cache.cpage_use_map = 0xffffffff;
+ cache.cpage_use_mask = cache_mask;
+ memcpy(fs->cache, &cache, sizeof(spiffs_cache));
+
+ spiffs_cache *c = spiffs_get_cache(fs);
+
+ memset(c->cpages, 0, c->cpage_count * SPIFFS_CACHE_PAGE_SIZE(fs));
+
+ c->cpage_use_map &= ~(c->cpage_use_mask);
+ for (i = 0; i < cache.cpage_count; i++) {
+ spiffs_get_cache_page_hdr(fs, c, i)->ix = i;
+ }
+}
+
+#endif // SPIFFS_CACHE
diff --git a/libiot/spiffs/spiffs_check.c b/libiot/spiffs/spiffs_check.c
new file mode 100644
index 0000000..dde85ef
--- /dev/null
+++ b/libiot/spiffs/spiffs_check.c
@@ -0,0 +1,995 @@
+/*
+ * spiffs_check.c
+ *
+ * Contains functionality for checking file system consistency
+ * and mending problems.
+ * Three levels of consistency checks are implemented:
+ *
+ * Look up consistency
+ * Checks if indices in lookup pages are coherent with page headers
+ * Object index consistency
+ * Checks if there are any orphaned object indices (missing object index headers).
+ * If an object index is found but not its header, the object index is deleted.
+ * This is critical for the following page consistency check.
+ * Page consistency
+ * Checks for pages that ought to be indexed, ought not to be indexed, are multiple indexed
+ *
+ *
+ * Created on: Jul 7, 2013
+ * Author: petera
+ */
+
+
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+#if !SPIFFS_READ_ONLY
+
+#if SPIFFS_HAL_CALLBACK_EXTRA
+#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \
+ do { \
+ if ((_fs)->check_cb_f) (_fs)->check_cb_f((_fs), (_type), (_rep), (_arg1), (_arg2)); \
+ } while (0)
+#else
+#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \
+ do { \
+ if ((_fs)->check_cb_f) (_fs)->check_cb_f((_type), (_rep), (_arg1), (_arg2)); \
+ } while (0)
+#endif
+
+//---------------------------------------
+// Look up consistency
+
+// searches in the object indices and returns the referenced page index given
+// the object id and the data span index
+// destroys fs->lu_work
+static s32_t spiffs_object_get_data_page_index_reference(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_span_ix data_spix,
+ spiffs_page_ix *pix,
+ spiffs_page_ix *objix_pix) {
+ s32_t res;
+
+ // calculate object index span index for given data page span index
+ spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+
+ // find obj index for obj id and span index
+ res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, objix_pix);
+ SPIFFS_CHECK_RES(res);
+
+ // load obj index entry
+ u32_t addr = SPIFFS_PAGE_TO_PADDR(fs, *objix_pix);
+ if (objix_spix == 0) {
+ // get referenced page from object index header
+ addr += sizeof(spiffs_page_object_ix_header) + data_spix * sizeof(spiffs_page_ix);
+ } else {
+ // get referenced page from object index
+ addr += sizeof(spiffs_page_object_ix) + SPIFFS_OBJ_IX_ENTRY(fs, data_spix) * sizeof(spiffs_page_ix);
+ }
+
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, addr, sizeof(spiffs_page_ix), (u8_t *)pix);
+
+ return res;
+}
+
+// copies page contents to a new page
+static s32_t spiffs_rewrite_page(spiffs *fs, spiffs_page_ix cur_pix, spiffs_page_header *p_hdr, spiffs_page_ix *new_pix) {
+ s32_t res;
+ res = spiffs_page_allocate_data(fs, p_hdr->obj_id, p_hdr, 0,0,0,0, new_pix);
+ SPIFFS_CHECK_RES(res);
+ res = spiffs_phys_cpy(fs, 0,
+ SPIFFS_PAGE_TO_PADDR(fs, *new_pix) + sizeof(spiffs_page_header),
+ SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header),
+ SPIFFS_DATA_PAGE_SIZE(fs));
+ SPIFFS_CHECK_RES(res);
+ return res;
+}
+
+// rewrites the object index for given object id and replaces the
+// data page index to a new page index
+static s32_t spiffs_rewrite_index(spiffs *fs, spiffs_obj_id obj_id, spiffs_span_ix data_spix, spiffs_page_ix new_data_pix, spiffs_page_ix objix_pix) {
+ s32_t res;
+ spiffs_block_ix bix;
+ int entry;
+ spiffs_page_ix free_pix;
+ obj_id |= SPIFFS_OBJ_ID_IX_FLAG;
+
+ // find free entry
+ res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry);
+ SPIFFS_CHECK_RES(res);
+ free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+
+ // calculate object index span index for given data page span index
+ spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+ if (objix_spix == 0) {
+ // calc index in index header
+ entry = data_spix;
+ } else {
+ // calc entry in index
+ entry = SPIFFS_OBJ_IX_ENTRY(fs, data_spix);
+
+ }
+ // load index
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ SPIFFS_CHECK_RES(res);
+ spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work;
+
+ // be ultra safe, double check header against provided data
+ if (objix_p_hdr->obj_id != obj_id) {
+ spiffs_page_delete(fs, free_pix);
+ return SPIFFS_ERR_CHECK_OBJ_ID_MISM;
+ }
+ if (objix_p_hdr->span_ix != objix_spix) {
+ spiffs_page_delete(fs, free_pix);
+ return SPIFFS_ERR_CHECK_SPIX_MISM;
+ }
+ if ((objix_p_hdr->flags & (SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_INDEX |
+ SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) !=
+ (SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_DELET)) {
+ spiffs_page_delete(fs, free_pix);
+ return SPIFFS_ERR_CHECK_FLAGS_BAD;
+ }
+
+ // rewrite in mem
+ if (objix_spix == 0) {
+ ((spiffs_page_ix*)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix;
+ } else {
+ ((spiffs_page_ix*)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix;
+ }
+
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ SPIFFS_CHECK_RES(res);
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix),
+ sizeof(spiffs_obj_id),
+ (u8_t *)&obj_id);
+ SPIFFS_CHECK_RES(res);
+ res = spiffs_page_delete(fs, objix_pix);
+
+ return res;
+}
+
+// deletes an object just by marking object index header as deleted
+static s32_t spiffs_delete_obj_lazy(spiffs *fs, spiffs_obj_id obj_id) {
+ spiffs_page_ix objix_hdr_pix;
+ s32_t res;
+ res = spiffs_obj_lu_find_id_and_span(fs, obj_id, 0, 0, &objix_hdr_pix);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ return SPIFFS_OK;
+ }
+ SPIFFS_CHECK_RES(res);
+ u8_t flags = 0xff & ~SPIFFS_PH_FLAG_IXDELE;
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix) + offsetof(spiffs_page_header, flags),
+ sizeof(u8_t),
+ (u8_t *)&flags);
+ return res;
+}
+
+// validates the given look up entry
+static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, spiffs_page_header *p_hdr,
+ spiffs_page_ix cur_pix, spiffs_block_ix cur_block, int cur_entry, int *reload_lu) {
+ (void)cur_block;
+ (void)cur_entry;
+ u8_t delete_page = 0;
+ s32_t res = SPIFFS_OK;
+ spiffs_page_ix objix_pix;
+ spiffs_page_ix ref_pix;
+ // check validity, take actions
+ if (((lu_obj_id == SPIFFS_OBJ_ID_DELETED) && (p_hdr->flags & SPIFFS_PH_FLAG_DELET)) ||
+ ((lu_obj_id == SPIFFS_OBJ_ID_FREE) && (p_hdr->flags & SPIFFS_PH_FLAG_USED) == 0)) {
+ // look up entry deleted / free but used in page header
+ SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" deleted/free in lu but not on page\n", cur_pix);
+ *reload_lu = 1;
+ delete_page = 1;
+ if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) {
+ // header says data page
+ // data page can be removed if not referenced by some object index
+ res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ // no object with this id, so remove page safely
+ res = SPIFFS_OK;
+ } else {
+ SPIFFS_CHECK_RES(res);
+ if (ref_pix == cur_pix) {
+ // data page referenced by object index but deleted in lu
+ // copy page to new place and re-write the object index to new place
+ spiffs_page_ix new_pix;
+ res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix);
+ SPIFFS_CHECK_DBG("LU: FIXUP: data page not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix);
+ SPIFFS_CHECK_RES(res);
+ *reload_lu = 1;
+ SPIFFS_CHECK_DBG("LU: FIXUP: "_SPIPRIpg" rewritten to "_SPIPRIpg", affected objix_pix "_SPIPRIpg"\n", cur_pix, new_pix, objix_pix);
+ res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix);
+ if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
+ // index bad also, cannot mend this file
+ SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res);
+ res = spiffs_page_delete(fs, new_pix);
+ SPIFFS_CHECK_RES(res);
+ res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id);
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0);
+ } else {
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, p_hdr->obj_id, p_hdr->span_ix);
+ }
+ SPIFFS_CHECK_RES(res);
+ }
+ }
+ } else {
+ // header says index page
+ // index page can be removed if other index with same obj_id and spanix is found
+ res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, 0);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ // no such index page found, check for a data page amongst page headers
+ // lu cannot be trusted
+ res = spiffs_obj_lu_find_id_and_span_by_phdr(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, 0);
+ if (res == SPIFFS_OK) { // ignore other errors
+ // got a data page also, assume lu corruption only, rewrite to new page
+ spiffs_page_ix new_pix;
+ res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix);
+ SPIFFS_CHECK_DBG("LU: FIXUP: ix page with data not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix);
+ SPIFFS_CHECK_RES(res);
+ *reload_lu = 1;
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix);
+ }
+ } else {
+ SPIFFS_CHECK_RES(res);
+ }
+ }
+ }
+ if (lu_obj_id != SPIFFS_OBJ_ID_FREE && lu_obj_id != SPIFFS_OBJ_ID_DELETED) {
+ // look up entry used
+ if ((p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG) != (lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG)) {
+ SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" differ in obj_id lu:"_SPIPRIid" ph:"_SPIPRIid"\n", cur_pix, lu_obj_id, p_hdr->obj_id);
+ delete_page = 1;
+ if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0 ||
+ (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) ||
+ (p_hdr->flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_IXDELE)) == 0) {
+ // page deleted or not finalized, just remove it
+ } else {
+ if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) {
+ // if data page, check for reference to this page
+ res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ // no object with this id, so remove page safely
+ res = SPIFFS_OK;
+ } else {
+ SPIFFS_CHECK_RES(res);
+ // if found, rewrite page with object id, update index, and delete current
+ if (ref_pix == cur_pix) {
+ spiffs_page_ix new_pix;
+ res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix);
+ SPIFFS_CHECK_RES(res);
+ res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix);
+ if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
+ // index bad also, cannot mend this file
+ SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res);
+ res = spiffs_page_delete(fs, new_pix);
+ SPIFFS_CHECK_RES(res);
+ res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id);
+ *reload_lu = 1;
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0);
+ }
+ SPIFFS_CHECK_RES(res);
+ }
+ }
+ } else {
+ // else if index, check for other pages with both obj_id's and spanix
+ spiffs_page_ix objix_pix_lu, objix_pix_ph;
+ // see if other object index page exists for lookup obj id and span index
+ res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_lu);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ objix_pix_lu = 0;
+ }
+ SPIFFS_CHECK_RES(res);
+ // see if other object index exists for page header obj id and span index
+ res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_ph);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ objix_pix_ph = 0;
+ }
+ SPIFFS_CHECK_RES(res);
+ // if both obj_id's found, just delete current
+ if (objix_pix_ph == 0 || objix_pix_lu == 0) {
+ // otherwise try finding first corresponding data pages
+ spiffs_page_ix data_pix_lu, data_pix_ph;
+ // see if other data page exists for look up obj id and span index
+ res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_lu);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ objix_pix_lu = 0;
+ }
+ SPIFFS_CHECK_RES(res);
+ // see if other data page exists for page header obj id and span index
+ res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_ph);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ objix_pix_ph = 0;
+ }
+ SPIFFS_CHECK_RES(res);
+
+ spiffs_page_header new_ph;
+ new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL);
+ new_ph.span_ix = p_hdr->span_ix;
+ spiffs_page_ix new_pix;
+ if ((objix_pix_lu && data_pix_lu && data_pix_ph && objix_pix_ph == 0) ||
+ (objix_pix_lu == 0 && data_pix_ph && objix_pix_ph == 0)) {
+ // got a data page for page header obj id
+ // rewrite as obj_id_ph
+ new_ph.obj_id = p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG;
+ res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix);
+ SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid" to pix "_SPIPRIpg"\n", cur_pix, new_ph.obj_id, new_pix);
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix);
+ SPIFFS_CHECK_RES(res);
+ *reload_lu = 1;
+ } else if ((objix_pix_ph && data_pix_ph && data_pix_lu && objix_pix_lu == 0) ||
+ (objix_pix_ph == 0 && data_pix_lu && objix_pix_lu == 0)) {
+ // got a data page for look up obj id
+ // rewrite as obj_id_lu
+ new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG;
+ SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid"\n", cur_pix, new_ph.obj_id);
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix);
+ res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix);
+ SPIFFS_CHECK_RES(res);
+ *reload_lu = 1;
+ } else {
+ // cannot safely do anything
+ SPIFFS_CHECK_DBG("LU: FIXUP: nothing to do, just delete\n");
+ }
+ }
+ }
+ }
+ } else if (((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX)) ||
+ ((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0 && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) == 0)) {
+ SPIFFS_CHECK_DBG("LU: "_SPIPRIpg" lu/page index marking differ\n", cur_pix);
+ spiffs_page_ix data_pix, objix_pix_d;
+ // see if other data page exists for given obj id and span index
+ res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &data_pix);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ data_pix = 0;
+ }
+ SPIFFS_CHECK_RES(res);
+ // see if other object index exists for given obj id and span index
+ res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &objix_pix_d);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ objix_pix_d = 0;
+ }
+ SPIFFS_CHECK_RES(res);
+
+ delete_page = 1;
+ // if other data page exists and object index exists, just delete page
+ if (data_pix && objix_pix_d) {
+ SPIFFS_CHECK_DBG("LU: FIXUP: other index and data page exists, simply remove\n");
+ } else
+ // if only data page exists, make this page index
+ if (data_pix && objix_pix_d == 0) {
+ SPIFFS_CHECK_DBG("LU: FIXUP: other data page exists, make this index\n");
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, lu_obj_id, p_hdr->span_ix);
+ spiffs_page_header new_ph;
+ spiffs_page_ix new_pix;
+ new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX);
+ new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG;
+ new_ph.span_ix = p_hdr->span_ix;
+ res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix);
+ SPIFFS_CHECK_RES(res);
+ res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header),
+ SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header),
+ SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header));
+ SPIFFS_CHECK_RES(res);
+ } else
+ // if only index exists, make data page
+ if (data_pix == 0 && objix_pix_d) {
+ SPIFFS_CHECK_DBG("LU: FIXUP: other index page exists, make this data\n");
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, lu_obj_id, p_hdr->span_ix);
+ spiffs_page_header new_ph;
+ spiffs_page_ix new_pix;
+ new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL);
+ new_ph.obj_id = lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+ new_ph.span_ix = p_hdr->span_ix;
+ res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix);
+ SPIFFS_CHECK_RES(res);
+ res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header),
+ SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header),
+ SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header));
+ SPIFFS_CHECK_RES(res);
+ } else {
+ // if nothing exists, we cannot safely make a decision - delete
+ }
+ }
+ else if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0) {
+ SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy in lu but deleted on page\n", cur_pix);
+ delete_page = 1;
+ } else if ((p_hdr->flags & SPIFFS_PH_FLAG_FINAL)) {
+ SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy but not final\n", cur_pix);
+ // page can be removed if not referenced by object index
+ *reload_lu = 1;
+ res = spiffs_object_get_data_page_index_reference(fs, lu_obj_id, p_hdr->span_ix, &ref_pix, &objix_pix);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ // no object with this id, so remove page safely
+ res = SPIFFS_OK;
+ delete_page = 1;
+ } else {
+ SPIFFS_CHECK_RES(res);
+ if (ref_pix != cur_pix) {
+ SPIFFS_CHECK_DBG("LU: FIXUP: other finalized page is referred, just delete\n");
+ delete_page = 1;
+ } else {
+ // page referenced by object index but not final
+ // just finalize
+ SPIFFS_CHECK_DBG("LU: FIXUP: unfinalized page is referred, finalizing\n");
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix);
+ u8_t flags = 0xff & ~SPIFFS_PH_FLAG_FINAL;
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + offsetof(spiffs_page_header, flags),
+ sizeof(u8_t), (u8_t*)&flags);
+ }
+ }
+ }
+ }
+
+ if (delete_page) {
+ SPIFFS_CHECK_DBG("LU: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix);
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0);
+ res = spiffs_page_delete(fs, cur_pix);
+ SPIFFS_CHECK_RES(res);
+ }
+
+ return res;
+}
+
+static s32_t spiffs_lookup_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, int cur_entry,
+ const void *user_const_p, void *user_var_p) {
+ (void)user_const_p;
+ (void)user_var_p;
+ s32_t res = SPIFFS_OK;
+ spiffs_page_header p_hdr;
+ spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry);
+
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS,
+ (cur_block * 256)/fs->block_count, 0);
+
+ // load header
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
+ SPIFFS_CHECK_RES(res);
+
+ int reload_lu = 0;
+
+ res = spiffs_lookup_check_validate(fs, obj_id, &p_hdr, cur_pix, cur_block, cur_entry, &reload_lu);
+ SPIFFS_CHECK_RES(res);
+
+ if (res == SPIFFS_OK) {
+ return reload_lu ? SPIFFS_VIS_COUNTINUE_RELOAD : SPIFFS_VIS_COUNTINUE;
+ }
+ return res;
+}
+
+
+// Scans all object look up. For each entry, corresponding page header is checked for validity.
+// If an object index header page is found, this is also checked
+s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t check_all_objects) {
+ (void)check_all_objects;
+ s32_t res = SPIFFS_OK;
+
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 0, 0);
+
+ res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_lookup_check_v, 0, 0, 0, 0);
+
+ if (res == SPIFFS_VIS_END) {
+ res = SPIFFS_OK;
+ }
+
+ if (res != SPIFFS_OK) {
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_ERROR, res, 0);
+ }
+
+ CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0);
+
+ return res;
+}
+
+//---------------------------------------
+// Page consistency
+
+// Scans all pages (except lu pages), reserves 4 bits in working memory for each page
+// bit 0: 0 == FREE|DELETED, 1 == USED
+// bit 1: 0 == UNREFERENCED, 1 == REFERENCED
+// bit 2: 0 == NOT_INDEX, 1 == INDEX
+// bit 3: unused
+// A consistent file system will have only pages being
+// * x000 free, unreferenced, not index
+// * x011 used, referenced only once, not index
+// * x101 used, unreferenced, index
+// The working memory might not fit all pages so several scans might be needed
+static s32_t spiffs_page_consistency_check_i(spiffs *fs) {
+ const u32_t bits = 4;
+ const spiffs_page_ix pages_per_scan = SPIFFS_CFG_LOG_PAGE_SZ(fs) * 8 / bits;
+
+ s32_t res = SPIFFS_OK;
+ spiffs_page_ix pix_offset = 0;
+
+ // for each range of pages fitting into work memory
+ while (pix_offset < SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) {
+ // set this flag to abort all checks and rescan the page range
+ u8_t restart = 0;
+ memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs));
+
+ spiffs_block_ix cur_block = 0;
+ // build consistency bitmap for id range traversing all blocks
+ while (!restart && cur_block < fs->block_count) {
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS,
+ (pix_offset*256)/(SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) +
+ ((((cur_block * pages_per_scan * 256)/ (SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count))) / fs->block_count),
+ 0);
+ // traverse each page except for lookup pages
+ spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_PAGES(fs) + SPIFFS_PAGES_PER_BLOCK(fs) * cur_block;
+ while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block+1)) {
+ //if ((cur_pix & 0xff) == 0)
+ // SPIFFS_CHECK_DBG("PA: processing pix "_SPIPRIpg", block "_SPIPRIbl" of pix "_SPIPRIpg", block "_SPIPRIbl"\n",
+ // cur_pix, cur_block, SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count, fs->block_count);
+
+ // read header
+ spiffs_page_header p_hdr;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
+ SPIFFS_CHECK_RES(res);
+
+ u8_t within_range = (cur_pix >= pix_offset && cur_pix < pix_offset + pages_per_scan);
+ const u32_t pix_byte_ix = (cur_pix - pix_offset) / (8/bits);
+ const u8_t pix_bit_ix = (cur_pix & ((8/bits)-1)) * bits;
+
+ if (within_range &&
+ (p_hdr.flags & SPIFFS_PH_FLAG_DELET) && (p_hdr.flags & SPIFFS_PH_FLAG_USED) == 0) {
+ // used
+ fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 0));
+ }
+ if ((p_hdr.flags & SPIFFS_PH_FLAG_DELET) &&
+ (p_hdr.flags & SPIFFS_PH_FLAG_IXDELE) &&
+ (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) == 0) {
+ // found non-deleted index
+ if (within_range) {
+ fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 2));
+ }
+
+ // load non-deleted index
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ SPIFFS_CHECK_RES(res);
+
+ // traverse index for referenced pages
+ spiffs_page_ix *object_page_index;
+ spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work;
+
+ int entries;
+ int i;
+ spiffs_span_ix data_spix_offset;
+ if (p_hdr.span_ix == 0) {
+ // object header page index
+ entries = SPIFFS_OBJ_HDR_IX_LEN(fs);
+ data_spix_offset = 0;
+ object_page_index = (spiffs_page_ix *)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix_header));
+ } else {
+ // object page index
+ entries = SPIFFS_OBJ_IX_LEN(fs);
+ data_spix_offset = SPIFFS_OBJ_HDR_IX_LEN(fs) + SPIFFS_OBJ_IX_LEN(fs) * (p_hdr.span_ix - 1);
+ object_page_index = (spiffs_page_ix *)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix));
+ }
+
+ // for all entries in index
+ for (i = 0; !restart && i < entries; i++) {
+ spiffs_page_ix rpix = object_page_index[i];
+ u8_t rpix_within_range = rpix >= pix_offset && rpix < pix_offset + pages_per_scan;
+
+ if ((rpix != (spiffs_page_ix)-1 && rpix > SPIFFS_MAX_PAGES(fs))
+ || (rpix_within_range && SPIFFS_IS_LOOKUP_PAGE(fs, rpix))) {
+
+ // bad reference
+ SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg"x bad pix / LU referenced from page "_SPIPRIpg"\n",
+ rpix, cur_pix);
+ // check for data page elsewhere
+ spiffs_page_ix data_pix;
+ res = spiffs_obj_lu_find_id_and_span(fs, objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+ data_spix_offset + i, 0, &data_pix);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ data_pix = 0;
+ }
+ SPIFFS_CHECK_RES(res);
+ if (data_pix == 0) {
+ // if not, allocate free page
+ spiffs_page_header new_ph;
+ new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL);
+ new_ph.obj_id = objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+ new_ph.span_ix = data_spix_offset + i;
+ res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &data_pix);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_CHECK_DBG("PA: FIXUP: found no existing data page, created new @ "_SPIPRIpg"\n", data_pix);
+ }
+ // remap index
+ SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix "_SPIPRIpg"\n", cur_pix);
+ res = spiffs_rewrite_index(fs, objix_p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG,
+ data_spix_offset + i, data_pix, cur_pix);
+ if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
+ // index bad also, cannot mend this file
+ SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend - delete object\n", res);
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, objix_p_hdr->obj_id, 0);
+ // delete file
+ res = spiffs_page_delete(fs, cur_pix);
+ } else {
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, objix_p_hdr->obj_id, objix_p_hdr->span_ix);
+ }
+ SPIFFS_CHECK_RES(res);
+ restart = 1;
+
+ } else if (rpix_within_range) {
+
+ // valid reference
+ // read referenced page header
+ spiffs_page_header rp_hdr;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr);
+ SPIFFS_CHECK_RES(res);
+
+ // cross reference page header check
+ if (rp_hdr.obj_id != (p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) ||
+ rp_hdr.span_ix != data_spix_offset + i ||
+ (rp_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) !=
+ (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX)) {
+ SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" has inconsistent page header ix id/span:"_SPIPRIid"/"_SPIPRIsp", ref id/span:"_SPIPRIid"/"_SPIPRIsp" flags:"_SPIPRIfl"\n",
+ rpix, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, data_spix_offset + i,
+ rp_hdr.obj_id, rp_hdr.span_ix, rp_hdr.flags);
+ // try finding correct page
+ spiffs_page_ix data_pix;
+ res = spiffs_obj_lu_find_id_and_span(fs, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+ data_spix_offset + i, rpix, &data_pix);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ data_pix = 0;
+ }
+ SPIFFS_CHECK_RES(res);
+ if (data_pix == 0) {
+ // not found, this index is badly borked
+ SPIFFS_CHECK_DBG("PA: FIXUP: index bad, delete object id "_SPIPRIid"\n", p_hdr.obj_id);
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0);
+ res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
+ SPIFFS_CHECK_RES(res);
+ break;
+ } else {
+ // found it, so rewrite index
+ SPIFFS_CHECK_DBG("PA: FIXUP: found correct data pix "_SPIPRIpg", rewrite ix pix "_SPIPRIpg" id "_SPIPRIid"\n",
+ data_pix, cur_pix, p_hdr.obj_id);
+ res = spiffs_rewrite_index(fs, p_hdr.obj_id, data_spix_offset + i, data_pix, cur_pix);
+ if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
+ // index bad also, cannot mend this file
+ SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res);
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0);
+ res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
+ } else {
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix);
+ }
+ SPIFFS_CHECK_RES(res);
+ restart = 1;
+ }
+ }
+ else {
+ // mark rpix as referenced
+ const u32_t rpix_byte_ix = (rpix - pix_offset) / (8/bits);
+ const u8_t rpix_bit_ix = (rpix & ((8/bits)-1)) * bits;
+ if (fs->work[rpix_byte_ix] & (1<<(rpix_bit_ix + 1))) {
+ SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" multiple referenced from page "_SPIPRIpg"\n",
+ rpix, cur_pix);
+ // Here, we should have fixed all broken references - getting this means there
+ // must be multiple files with same object id. Only solution is to delete
+ // the object which is referring to this page
+ SPIFFS_CHECK_DBG("PA: FIXUP: removing object "_SPIPRIid" and page "_SPIPRIpg"\n",
+ p_hdr.obj_id, cur_pix);
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0);
+ res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
+ SPIFFS_CHECK_RES(res);
+ // extra precaution, delete this page also
+ res = spiffs_page_delete(fs, cur_pix);
+ SPIFFS_CHECK_RES(res);
+ restart = 1;
+ }
+ fs->work[rpix_byte_ix] |= (1<<(rpix_bit_ix + 1));
+ }
+ }
+ } // for all index entries
+ } // found index
+
+ // next page
+ cur_pix++;
+ }
+ // next block
+ cur_block++;
+ }
+ // check consistency bitmap
+ if (!restart) {
+ spiffs_page_ix objix_pix;
+ spiffs_page_ix rpix;
+
+ u32_t byte_ix;
+ u8_t bit_ix;
+ for (byte_ix = 0; !restart && byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs); byte_ix++) {
+ for (bit_ix = 0; !restart && bit_ix < 8/bits; bit_ix ++) {
+ u8_t bitmask = (fs->work[byte_ix] >> (bit_ix * bits)) & 0x7;
+ spiffs_page_ix cur_pix = pix_offset + byte_ix * (8/bits) + bit_ix;
+
+ // 000 ok - free, unreferenced, not index
+
+ if (bitmask == 0x1) {
+
+ // 001
+ SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, UNREFERENCED, not index\n", cur_pix);
+
+ u8_t rewrite_ix_to_this = 0;
+ u8_t delete_page = 0;
+ // check corresponding object index entry
+ spiffs_page_header p_hdr;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
+ SPIFFS_CHECK_RES(res);
+
+ res = spiffs_object_get_data_page_index_reference(fs, p_hdr.obj_id, p_hdr.span_ix,
+ &rpix, &objix_pix);
+ if (res == SPIFFS_OK) {
+ if (((rpix == (spiffs_page_ix)-1 || rpix > SPIFFS_MAX_PAGES(fs)) || (SPIFFS_IS_LOOKUP_PAGE(fs, rpix)))) {
+ // pointing to a bad page altogether, rewrite index to this
+ rewrite_ix_to_this = 1;
+ SPIFFS_CHECK_DBG("PA: corresponding ref is bad: "_SPIPRIpg", rewrite to this "_SPIPRIpg"\n", rpix, cur_pix);
+ } else {
+ // pointing to something else, check what
+ spiffs_page_header rp_hdr;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr);
+ SPIFFS_CHECK_RES(res);
+ if (((p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) == rp_hdr.obj_id) &&
+ ((rp_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL)) ==
+ (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET))) {
+ // pointing to something else valid, just delete this page then
+ SPIFFS_CHECK_DBG("PA: corresponding ref is good but different: "_SPIPRIpg", delete this "_SPIPRIpg"\n", rpix, cur_pix);
+ delete_page = 1;
+ } else {
+ // pointing to something weird, update index to point to this page instead
+ if (rpix != cur_pix) {
+ SPIFFS_CHECK_DBG("PA: corresponding ref is weird: "_SPIPRIpg" %s%s%s%s, rewrite this "_SPIPRIpg"\n", rpix,
+ (rp_hdr.flags & SPIFFS_PH_FLAG_INDEX) ? "" : "INDEX ",
+ (rp_hdr.flags & SPIFFS_PH_FLAG_DELET) ? "" : "DELETED ",
+ (rp_hdr.flags & SPIFFS_PH_FLAG_USED) ? "NOTUSED " : "",
+ (rp_hdr.flags & SPIFFS_PH_FLAG_FINAL) ? "NOTFINAL " : "",
+ cur_pix);
+ rewrite_ix_to_this = 1;
+ } else {
+ // should not happen, destined for fubar
+ }
+ }
+ }
+ } else if (res == SPIFFS_ERR_NOT_FOUND) {
+ SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete "_SPIPRIpg"\n", cur_pix);
+ delete_page = 1;
+ res = SPIFFS_OK;
+ }
+
+ if (rewrite_ix_to_this) {
+ // if pointing to invalid page, redirect index to this page
+ SPIFFS_CHECK_DBG("PA: FIXUP: rewrite index id "_SPIPRIid" data spix "_SPIPRIsp" to point to this pix: "_SPIPRIpg"\n",
+ p_hdr.obj_id, p_hdr.span_ix, cur_pix);
+ res = spiffs_rewrite_index(fs, p_hdr.obj_id, p_hdr.span_ix, cur_pix, objix_pix);
+ if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) {
+ // index bad also, cannot mend this file
+ SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res);
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0);
+ res = spiffs_page_delete(fs, cur_pix);
+ SPIFFS_CHECK_RES(res);
+ res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id);
+ } else {
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix);
+ }
+ SPIFFS_CHECK_RES(res);
+ restart = 1;
+ continue;
+ } else if (delete_page) {
+ SPIFFS_CHECK_DBG("PA: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix);
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0);
+ res = spiffs_page_delete(fs, cur_pix);
+ }
+ SPIFFS_CHECK_RES(res);
+ }
+ if (bitmask == 0x2) {
+
+ // 010
+ SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, not index\n", cur_pix);
+
+ // no op, this should be taken care of when checking valid references
+ }
+
+ // 011 ok - busy, referenced, not index
+
+ if (bitmask == 0x4) {
+
+ // 100
+ SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, unreferenced, INDEX\n", cur_pix);
+
+ // this should never happen, major fubar
+ }
+
+ // 101 ok - busy, unreferenced, index
+
+ if (bitmask == 0x6) {
+
+ // 110
+ SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, INDEX\n", cur_pix);
+
+ // no op, this should be taken care of when checking valid references
+ }
+ if (bitmask == 0x7) {
+
+ // 111
+ SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, REFERENCED, INDEX\n", cur_pix);
+
+ // no op, this should be taken care of when checking valid references
+ }
+ }
+ }
+ }
+
+ SPIFFS_CHECK_DBG("PA: processed "_SPIPRIpg", restart "_SPIPRIi"\n", pix_offset, restart);
+ // next page range
+ if (!restart) {
+ pix_offset += pages_per_scan;
+ }
+ } // while page range not reached end
+ return res;
+}
+
+// Checks consistency amongst all pages and fixes irregularities
+s32_t spiffs_page_consistency_check(spiffs *fs) {
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 0, 0);
+ s32_t res = spiffs_page_consistency_check_i(fs);
+ if (res != SPIFFS_OK) {
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_ERROR, res, 0);
+ }
+ CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0);
+ return res;
+}
+
+//---------------------------------------
+// Object index consistency
+
+// searches for given object id in temporary object id index,
+// returns the index or -1
+static int spiffs_object_index_search(spiffs *fs, spiffs_obj_id obj_id) {
+ u32_t i;
+ spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work;
+ obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG;
+ for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id); i++) {
+ if ((obj_table[i] & ~SPIFFS_OBJ_ID_IX_FLAG) == obj_id) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block,
+ int cur_entry, const void *user_const_p, void *user_var_p) {
+ (void)user_const_p;
+ s32_t res_c = SPIFFS_VIS_COUNTINUE;
+ s32_t res = SPIFFS_OK;
+ u32_t *log_ix = (u32_t*)user_var_p;
+ spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work;
+
+ CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS,
+ (cur_block * 256)/fs->block_count, 0);
+
+ if (obj_id != SPIFFS_OBJ_ID_FREE && obj_id != SPIFFS_OBJ_ID_DELETED && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) {
+ spiffs_page_header p_hdr;
+ spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry);
+
+ // load header
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
+ SPIFFS_CHECK_RES(res);
+
+ if (p_hdr.span_ix == 0 &&
+ (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) ==
+ (SPIFFS_PH_FLAG_DELET)) {
+ SPIFFS_CHECK_DBG("IX: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" header not fully deleted - deleting\n",
+ cur_pix, obj_id, p_hdr.span_ix);
+ CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_PAGE, cur_pix, obj_id);
+ res = spiffs_page_delete(fs, cur_pix);
+ SPIFFS_CHECK_RES(res);
+ return res_c;
+ }
+
+ if ((p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) ==
+ (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) {
+ return res_c;
+ }
+
+ if (p_hdr.span_ix == 0) {
+ // objix header page, register objid as reachable
+ int r = spiffs_object_index_search(fs, obj_id);
+ if (r == -1) {
+ // not registered, do it
+ obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+ (*log_ix)++;
+ if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) {
+ *log_ix = 0;
+ }
+ }
+ } else { // span index
+ // objix page, see if header can be found
+ int r = spiffs_object_index_search(fs, obj_id);
+ u8_t delete = 0;
+ if (r == -1) {
+ // not in temporary index, try finding it
+ spiffs_page_ix objix_hdr_pix;
+ res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &objix_hdr_pix);
+ res_c = SPIFFS_VIS_COUNTINUE_RELOAD;
+ if (res == SPIFFS_OK) {
+ // found, register as reachable
+ obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+ } else if (res == SPIFFS_ERR_NOT_FOUND) {
+ // not found, register as unreachable
+ delete = 1;
+ obj_table[*log_ix] = obj_id | SPIFFS_OBJ_ID_IX_FLAG;
+ } else {
+ SPIFFS_CHECK_RES(res);
+ }
+ (*log_ix)++;
+ if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) {
+ *log_ix = 0;
+ }
+ } else {
+ // in temporary index, check reachable flag
+ if ((obj_table[r] & SPIFFS_OBJ_ID_IX_FLAG)) {
+ // registered as unreachable
+ delete = 1;
+ }
+ }
+
+ if (delete) {
+ SPIFFS_CHECK_DBG("IX: FIXUP: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" is orphan index - deleting\n",
+ cur_pix, obj_id, p_hdr.span_ix);
+ CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, cur_pix, obj_id);
+ res = spiffs_page_delete(fs, cur_pix);
+ SPIFFS_CHECK_RES(res);
+ }
+ } // span index
+ } // valid object index id
+
+ return res_c;
+}
+
+// Removes orphaned and partially deleted index pages.
+// Scans for index pages. When an index page is found, corresponding index header is searched for.
+// If no such page exists, the index page cannot be reached as no index header exists and must be
+// deleted.
+s32_t spiffs_object_index_consistency_check(spiffs *fs) {
+ s32_t res = SPIFFS_OK;
+ // impl note:
+ // fs->work is used for a temporary object index memory, listing found object ids and
+ // indicating whether they can be reached or not. Acting as a fifo if object ids cannot fit.
+ // In the temporary object index memory, SPIFFS_OBJ_ID_IX_FLAG bit is used to indicate
+ // a reachable/unreachable object id.
+ memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs));
+ u32_t obj_id_log_ix = 0;
+ CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 0, 0);
+ res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_object_index_consistency_check_v, 0, &obj_id_log_ix,
+ 0, 0);
+ if (res == SPIFFS_VIS_END) {
+ res = SPIFFS_OK;
+ }
+ if (res != SPIFFS_OK) {
+ CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_ERROR, res, 0);
+ }
+ CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0);
+ return res;
+}
+
+#endif // !SPIFFS_READ_ONLY
diff --git a/libiot/spiffs/spiffs_config.h b/libiot/spiffs/spiffs_config.h
new file mode 100644
index 0000000..60f48a1
--- /dev/null
+++ b/libiot/spiffs/spiffs_config.h
@@ -0,0 +1,360 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <ctype.h>
+// ----------- >8 ------------
+
+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;
+
+// compile time switches
+
+// Set generic spiffs debug output call.
+#ifndef SPIFFS_DBG
+#define SPIFFS_DBG(...) //printf(__VA_ARGS__)
+#endif
+// Set spiffs debug output call for garbage collecting.
+#ifndef SPIFFS_GC_DBG
+#define SPIFFS_GC_DBG(...) //printf(__VA_ARGS__)
+#endif
+// Set spiffs debug output call for caching.
+#ifndef SPIFFS_CACHE_DBG
+#define SPIFFS_CACHE_DBG(...) //printf(__VA_ARGS__)
+#endif
+// Set spiffs debug output call for system consistency checks.
+#ifndef SPIFFS_CHECK_DBG
+#define SPIFFS_CHECK_DBG(...) //printf(__VA_ARGS__)
+#endif
+
+// Defines spiffs debug print formatters
+// some general signed number
+#ifndef _SPIPRIi
+#define _SPIPRIi "%d"
+#endif
+// address
+#ifndef _SPIPRIad
+#define _SPIPRIad "%08x"
+#endif
+// block
+#ifndef _SPIPRIbl
+#define _SPIPRIbl "%04x"
+#endif
+// page
+#ifndef _SPIPRIpg
+#define _SPIPRIpg "%04x"
+#endif
+// span index
+#ifndef _SPIPRIsp
+#define _SPIPRIsp "%04x"
+#endif
+// file descriptor
+#ifndef _SPIPRIfd
+#define _SPIPRIfd "%d"
+#endif
+// file object id
+#ifndef _SPIPRIid
+#define _SPIPRIid "%04x"
+#endif
+// file flags
+#ifndef _SPIPRIfl
+#define _SPIPRIfl "%02x"
+#endif
+
+
+// Enable/disable API functions to determine exact number of bytes
+// for filedescriptor and cache buffers. Once decided for a configuration,
+// this can be disabled to reduce flash.
+#ifndef SPIFFS_BUFFER_HELP
+#define SPIFFS_BUFFER_HELP 0
+#endif
+
+// Enables/disable memory read caching of nucleus file system operations.
+// If enabled, memory area must be provided for cache in SPIFFS_mount.
+#ifndef SPIFFS_CACHE
+#define SPIFFS_CACHE 1
+#endif
+#if SPIFFS_CACHE
+// Enables memory write caching for file descriptors in hydrogen
+#ifndef SPIFFS_CACHE_WR
+#define SPIFFS_CACHE_WR 1
+#endif
+
+// Enable/disable statistics on caching. Debug/test purpose only.
+#ifndef SPIFFS_CACHE_STATS
+#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.
+#ifndef SPIFFS_PAGE_CHECK
+#define SPIFFS_PAGE_CHECK 1
+#endif
+
+// Define maximum number of gc runs to perform to reach desired free pages.
+#ifndef SPIFFS_GC_MAX_RUNS
+#define SPIFFS_GC_MAX_RUNS 3
+#endif
+
+// Enable/disable statistics on gc. Debug/test purpose only.
+#ifndef SPIFFS_GC_STATS
+#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.
+#ifndef SPIFFS_GC_HEUR_W_DELET
+#define SPIFFS_GC_HEUR_W_DELET (5)
+#endif
+// Garbage collecting heuristics - weight used for used pages.
+#ifndef SPIFFS_GC_HEUR_W_USED
+#define SPIFFS_GC_HEUR_W_USED (-1)
+#endif
+// Garbage collecting heuristics - weight used for time between
+// last erased and erase of this block.
+#ifndef SPIFFS_GC_HEUR_W_ERASE_AGE
+#define SPIFFS_GC_HEUR_W_ERASE_AGE (50)
+#endif
+
+// Object name maximum length. Note that this length include the
+// zero-termination character, meaning maximum string of characters
+// can at most be SPIFFS_OBJ_NAME_LEN - 1.
+#ifndef SPIFFS_OBJ_NAME_LEN
+#define SPIFFS_OBJ_NAME_LEN (64)
+#endif
+
+// Maximum length of the metadata associated with an object.
+// Setting to non-zero value enables metadata-related API but also
+// changes the on-disk format, so the change is not backward-compatible.
+//
+// Do note: the meta length must never exceed
+// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64)
+//
+// This is derived from following:
+// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) +
+// spiffs_object_ix_header fields + at least some LUT entries)
+#ifndef SPIFFS_OBJ_META_LEN
+#define SPIFFS_OBJ_META_LEN (1)
+#endif
+
+// 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.
+#ifndef SPIFFS_COPY_BUFFER_STACK
+#define SPIFFS_COPY_BUFFER_STACK (64)
+#endif
+
+// 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.
+#ifndef SPIFFS_USE_MAGIC
+#define SPIFFS_USE_MAGIC (1)
+#endif
+
+#if SPIFFS_USE_MAGIC
+// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is
+// enabled, the magic will also be dependent on the length of the filesystem.
+// For example, a filesystem configured and formatted for 4 megabytes will not
+// be accepted for mounting with a configuration defining the filesystem as 2
+// megabytes.
+#ifndef SPIFFS_USE_MAGIC_LENGTH
+#define SPIFFS_USE_MAGIC_LENGTH (1)
+#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
+#ifndef SPIFFS_LOCK
+#define SPIFFS_LOCK(fs) spiffs_lock(fs)
+#endif
+// define this to exit a mutex if you're running on a multithreaded system
+#ifndef SPIFFS_UNLOCK
+#define SPIFFS_UNLOCK(fs) spiffs_unlock(fs)
+#endif
+
+// 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.
+#ifndef SPIFFS_SINGLETON
+#define SPIFFS_SINGLETON 0
+#endif
+
+#if SPIFFS_SINGLETON
+// Instead of giving parameters in config struct, singleton build must
+// give parameters in defines below.
+#ifndef SPIFFS_CFG_PHYS_SZ
+#define SPIFFS_CFG_PHYS_SZ(ignore) (1024*1024*2)
+#endif
+#ifndef SPIFFS_CFG_PHYS_ERASE_SZ
+#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore) (65536)
+#endif
+#ifndef SPIFFS_CFG_PHYS_ADDR
+#define SPIFFS_CFG_PHYS_ADDR(ignore) (0)
+#endif
+#ifndef SPIFFS_CFG_LOG_PAGE_SZ
+#define SPIFFS_CFG_LOG_PAGE_SZ(ignore) (256)
+#endif
+#ifndef SPIFFS_CFG_LOG_BLOCK_SZ
+#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore) (65536)
+#endif
+#endif
+
+// Enable this if your target needs aligned data for index tables
+#ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES
+#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 1
+#endif
+
+// Enable this if you want the HAL callbacks to be called with the spiffs struct
+#ifndef SPIFFS_HAL_CALLBACK_EXTRA
+#define SPIFFS_HAL_CALLBACK_EXTRA 0
+#endif
+
+// Enable this if you want to add an integer offset to all file handles
+// (spiffs_file). This is useful if running multiple instances of spiffs on
+// same target, in order to recognise to what spiffs instance a file handle
+// belongs.
+// NB: This adds config field fh_ix_offset in the configuration struct when
+// mounting, which must be defined.
+#ifndef SPIFFS_FILEHDL_OFFSET
+#define SPIFFS_FILEHDL_OFFSET 0
+#endif
+
+// Enable this to compile a read only version of spiffs.
+// This will reduce binary size of spiffs. All code comprising modification
+// of the file system will not be compiled. Some config will be ignored.
+// HAL functions for erasing and writing to spi-flash may be null. Cache
+// can be disabled for even further binary size reduction (and ram savings).
+// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL.
+// If the file system cannot be mounted due to aborted erase operation and
+// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be
+// returned.
+// Might be useful for e.g. bootloaders and such.
+#ifndef SPIFFS_READ_ONLY
+#define SPIFFS_READ_ONLY 0
+#endif
+
+// Enable this to add a temporal file cache using the fd buffer.
+// The effects of the cache is that SPIFFS_open will find the file faster in
+// certain cases. It will make it a lot easier for spiffs to find files
+// opened frequently, reducing number of readings from the spi flash for
+// finding those files.
+// This will grow each fd by 6 bytes. If your files are opened in patterns
+// with a degree of temporal locality, the system is optimized.
+// Examples can be letting spiffs serve web content, where one file is the css.
+// The css is accessed for each html file that is opened, meaning it is
+// accessed almost every second time a file is opened. Another example could be
+// a log file that is often opened, written, and closed.
+// The size of the cache is number of given file descriptors, as it piggybacks
+// on the fd update mechanism. The cache lives in the closed file descriptors.
+// When closed, the fd know the whereabouts of the file. Instead of forgetting
+// this, the temporal cache will keep handling updates to that file even if the
+// fd is closed. If the file is opened again, the location of the file is found
+// directly. If all available descriptors become opened, all cache memory is
+// lost.
+#ifndef SPIFFS_TEMPORAL_FD_CACHE
+#define SPIFFS_TEMPORAL_FD_CACHE 1
+#endif
+
+// Temporal file cache hit score. Each time a file is opened, all cached files
+// will lose one point. If the opened file is found in cache, that entry will
+// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this
+// value for the specific access patterns of the application. However, it must
+// be between 1 (no gain for hitting a cached entry often) and 255.
+#ifndef SPIFFS_TEMPORAL_CACHE_HIT_SCORE
+#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 4
+#endif
+
+// Enable to be able to map object indices to memory.
+// This allows for faster and more deterministic reading if cases of reading
+// large files and when changing file offset by seeking around a lot.
+// When mapping a file's index, the file system will be scanned for index pages
+// and the info will be put in memory provided by user. When reading, the
+// memory map can be looked up instead of searching for index pages on the
+// medium. This way, user can trade memory against performance.
+// Whole, parts of, or future parts not being written yet can be mapped. The
+// memory array will be owned by spiffs and updated accordingly during garbage
+// collecting or when modifying the indices. The latter is invoked by when the
+// file is modified in some way. The index buffer is tied to the file
+// descriptor.
+#ifndef SPIFFS_IX_MAP
+#define SPIFFS_IX_MAP 1
+#endif
+
+// 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.
+#ifndef SPIFFS_TEST_VISUALISATION
+#define SPIFFS_TEST_VISUALISATION 0
+#endif
+#if SPIFFS_TEST_VISUALISATION
+#ifndef spiffs_printf
+#define spiffs_printf(...) printf(__VA_ARGS__)
+#endif
+// spiffs_printf argument for a free page
+#ifndef SPIFFS_TEST_VIS_FREE_STR
+#define SPIFFS_TEST_VIS_FREE_STR "_"
+#endif
+// spiffs_printf argument for a deleted page
+#ifndef SPIFFS_TEST_VIS_DELE_STR
+#define SPIFFS_TEST_VIS_DELE_STR "/"
+#endif
+// spiffs_printf argument for an index page for given object id
+#ifndef SPIFFS_TEST_VIS_INDX_STR
+#define SPIFFS_TEST_VIS_INDX_STR(id) "i"
+#endif
+// spiffs_printf argument for a data page for given object id
+#ifndef SPIFFS_TEST_VIS_DATA_STR
+#define SPIFFS_TEST_VIS_DATA_STR(id) "d"
+#endif
+#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/libiot/spiffs/spiffs_gc.c b/libiot/spiffs/spiffs_gc.c
new file mode 100644
index 0000000..c8fb01a
--- /dev/null
+++ b/libiot/spiffs/spiffs_gc.c
@@ -0,0 +1,606 @@
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+#if !SPIFFS_READ_ONLY
+
+// Erases a logical block and updates the erase counter.
+// If cache is enabled, all pages that might be cached in this block
+// is dropped.
+static s32_t spiffs_gc_erase_block(
+ spiffs *fs,
+ spiffs_block_ix bix) {
+ s32_t res;
+
+ SPIFFS_GC_DBG("gc: erase block "_SPIPRIbl"\n", bix);
+ res = spiffs_erase_block(fs, bix);
+ SPIFFS_CHECK_RES(res);
+
+#if SPIFFS_CACHE
+ {
+ u32_t i;
+ for (i = 0; i < SPIFFS_PAGES_PER_BLOCK(fs); i++) {
+ spiffs_cache_drop_page(fs, SPIFFS_PAGE_FOR_BLOCK(fs, bix) + i);
+ }
+ }
+#endif
+ return res;
+}
+
+// Searches for blocks where all entries are deleted - if one is found,
+// the block is erased. Compared to the non-quick gc, the quick one ensures
+// that no updates are needed on existing objects on pages that are erased.
+s32_t spiffs_gc_quick(
+ spiffs *fs, u16_t max_free_pages) {
+ s32_t res = SPIFFS_OK;
+ u32_t blocks = fs->block_count;
+ spiffs_block_ix cur_block = 0;
+ u32_t cur_block_addr = 0;
+ int cur_entry = 0;
+ spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+
+ SPIFFS_GC_DBG("gc_quick: running\n");
+#if SPIFFS_GC_STATS
+ fs->stats_gc_runs++;
+#endif
+
+ int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+
+ // find fully deleted blocks
+ // check each block
+ while (res == SPIFFS_OK && blocks--) {
+ u16_t deleted_pages_in_block = 0;
+ u16_t free_pages_in_block = 0;
+
+ int obj_lookup_page = 0;
+ // check each object lookup page
+ while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+ int entry_offset = obj_lookup_page * entries_per_page;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ // check each entry
+ while (res == SPIFFS_OK &&
+ cur_entry - entry_offset < entries_per_page &&
+ cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) {
+ spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+ if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+ deleted_pages_in_block++;
+ } else if (obj_id == SPIFFS_OBJ_ID_FREE) {
+ // kill scan, go for next block
+ free_pages_in_block++;
+ if (free_pages_in_block > max_free_pages) {
+ obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs);
+ res = 1; // kill object lu loop
+ break;
+ }
+ } else {
+ // kill scan, go for next block
+ obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs);
+ res = 1; // kill object lu loop
+ break;
+ }
+ cur_entry++;
+ } // per entry
+ obj_lookup_page++;
+ } // per object lookup page
+ if (res == 1) res = SPIFFS_OK;
+
+ if (res == SPIFFS_OK &&
+ deleted_pages_in_block + free_pages_in_block == SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs) &&
+ free_pages_in_block <= max_free_pages) {
+ // found a fully deleted block
+ fs->stats_p_deleted -= deleted_pages_in_block;
+ res = spiffs_gc_erase_block(fs, cur_block);
+ return res;
+ }
+
+ cur_entry = 0;
+ cur_block++;
+ cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+ } // per block
+
+ if (res == SPIFFS_OK) {
+ res = SPIFFS_ERR_NO_DELETED_BLOCKS;
+ }
+ return res;
+}
+
+// Checks if garbage collecting is necessary. If so a candidate block is found,
+// cleansed and erased
+s32_t spiffs_gc_check(
+ spiffs *fs,
+ u32_t len) {
+ s32_t res;
+ s32_t free_pages =
+ (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count-2)
+ - fs->stats_p_allocated - fs->stats_p_deleted;
+ int tries = 0;
+
+ if (fs->free_blocks > 3 &&
+ (s32_t)len < free_pages * (s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) {
+ return SPIFFS_OK;
+ }
+
+ u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs);
+// if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) {
+// SPIFFS_GC_DBG("gc: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted);
+// return SPIFFS_ERR_FULL;
+// }
+ if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) {
+ SPIFFS_GC_DBG("gc_check: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted);
+ return SPIFFS_ERR_FULL;
+ }
+
+ do {
+ SPIFFS_GC_DBG("\ngc_check #"_SPIPRIi": run gc free_blocks:"_SPIPRIi" pfree:"_SPIPRIi" pallo:"_SPIPRIi" pdele:"_SPIPRIi" ["_SPIPRIi"] len:"_SPIPRIi" of "_SPIPRIi"\n",
+ tries,
+ fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted),
+ len, (u32_t)(free_pages*SPIFFS_DATA_PAGE_SIZE(fs)));
+
+ spiffs_block_ix *cands;
+ int count;
+ spiffs_block_ix cand;
+ s32_t prev_free_pages = free_pages;
+ // if the fs is crammed, ignore block age when selecting candidate - kind of a bad state
+ res = spiffs_gc_find_candidate(fs, &cands, &count, free_pages <= 0);
+ SPIFFS_CHECK_RES(res);
+ if (count == 0) {
+ SPIFFS_GC_DBG("gc_check: no candidates, return\n");
+ return (s32_t)needed_pages < free_pages ? SPIFFS_OK : SPIFFS_ERR_FULL;
+ }
+#if SPIFFS_GC_STATS
+ fs->stats_gc_runs++;
+#endif
+ cand = cands[0];
+ fs->cleaning = 1;
+ //SPIFFS_GC_DBG("gcing: cleaning block "_SPIPRIi"\n", cand);
+ res = spiffs_gc_clean(fs, cand);
+ fs->cleaning = 0;
+ if (res < 0) {
+ SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res);
+ } else {
+ SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res);
+ }
+ SPIFFS_CHECK_RES(res);
+
+ res = spiffs_gc_erase_page_stats(fs, cand);
+ SPIFFS_CHECK_RES(res);
+
+ res = spiffs_gc_erase_block(fs, cand);
+ SPIFFS_CHECK_RES(res);
+
+ free_pages =
+ (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2)
+ - fs->stats_p_allocated - fs->stats_p_deleted;
+
+ if (prev_free_pages <= 0 && prev_free_pages == free_pages) {
+ // abort early to reduce wear, at least tried once
+ SPIFFS_GC_DBG("gc_check: early abort, no result on gc when fs crammed\n");
+ break;
+ }
+
+ } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 ||
+ (s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)));
+
+ free_pages =
+ (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2)
+ - fs->stats_p_allocated - fs->stats_p_deleted;
+ if ((s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) {
+ res = SPIFFS_ERR_FULL;
+ }
+
+ SPIFFS_GC_DBG("gc_check: finished, "_SPIPRIi" dirty, blocks "_SPIPRIi" free, "_SPIPRIi" pages free, "_SPIPRIi" tries, res "_SPIPRIi"\n",
+ fs->stats_p_allocated + fs->stats_p_deleted,
+ fs->free_blocks, free_pages, tries, res);
+
+ return res;
+}
+
+// Updates page statistics for a block that is about to be erased
+s32_t spiffs_gc_erase_page_stats(
+ spiffs *fs,
+ spiffs_block_ix bix) {
+ s32_t res = SPIFFS_OK;
+ int obj_lookup_page = 0;
+ int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+ spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+ int cur_entry = 0;
+ u32_t dele = 0;
+ u32_t allo = 0;
+
+ // check each object lookup page
+ while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+ int entry_offset = obj_lookup_page * entries_per_page;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ // check each entry
+ while (res == SPIFFS_OK &&
+ cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) {
+ spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+ if (obj_id == SPIFFS_OBJ_ID_FREE) {
+ } else if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+ dele++;
+ } else {
+ allo++;
+ }
+ cur_entry++;
+ } // per entry
+ obj_lookup_page++;
+ } // per object lookup page
+ SPIFFS_GC_DBG("gc_check: wipe pallo:"_SPIPRIi" pdele:"_SPIPRIi"\n", allo, dele);
+ fs->stats_p_allocated -= allo;
+ fs->stats_p_deleted -= dele;
+ return res;
+}
+
+// Finds block candidates to erase
+s32_t spiffs_gc_find_candidate(
+ spiffs *fs,
+ spiffs_block_ix **block_candidates,
+ int *candidate_count,
+ char fs_crammed) {
+ s32_t res = SPIFFS_OK;
+ u32_t blocks = fs->block_count;
+ spiffs_block_ix cur_block = 0;
+ u32_t cur_block_addr = 0;
+ spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+ int cur_entry = 0;
+
+ // using fs->work area as sorted candidate memory, (spiffs_block_ix)cand_bix/(s32_t)score
+ int max_candidates = MIN(fs->block_count, (SPIFFS_CFG_LOG_PAGE_SZ(fs)-8)/(sizeof(spiffs_block_ix) + sizeof(s32_t)));
+ *candidate_count = 0;
+ memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs));
+
+ // divide up work area into block indices and scores
+ spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work;
+ s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix));
+
+ // align cand_scores on s32_t boundary
+ cand_scores = (s32_t*)(((intptr_t)cand_scores + sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1));
+
+ *block_candidates = cand_blocks;
+
+ int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+
+ // check each block
+ while (res == SPIFFS_OK && blocks--) {
+ u16_t deleted_pages_in_block = 0;
+ u16_t used_pages_in_block = 0;
+
+ int obj_lookup_page = 0;
+ // check each object lookup page
+ while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+ int entry_offset = obj_lookup_page * entries_per_page;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ // check each entry
+ while (res == SPIFFS_OK &&
+ cur_entry - entry_offset < entries_per_page &&
+ cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) {
+ spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+ if (obj_id == SPIFFS_OBJ_ID_FREE) {
+ // when a free entry is encountered, scan logic ensures that all following entries are free also
+ res = 1; // kill object lu loop
+ break;
+ } else if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+ deleted_pages_in_block++;
+ } else {
+ used_pages_in_block++;
+ }
+ cur_entry++;
+ } // per entry
+ obj_lookup_page++;
+ } // per object lookup page
+ if (res == 1) res = SPIFFS_OK;
+
+ // calculate score and insert into candidate table
+ // stoneage sort, but probably not so many blocks
+ if (res == SPIFFS_OK && deleted_pages_in_block > 0) {
+ // read erase count
+ spiffs_obj_id erase_count;
+ res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0,
+ SPIFFS_ERASE_COUNT_PADDR(fs, cur_block),
+ sizeof(spiffs_obj_id), (u8_t *)&erase_count);
+ SPIFFS_CHECK_RES(res);
+
+ spiffs_obj_id erase_age;
+ if (fs->max_erase_count > erase_count) {
+ erase_age = fs->max_erase_count - erase_count;
+ } else {
+ erase_age = SPIFFS_OBJ_ID_FREE - (erase_count - fs->max_erase_count);
+ }
+
+ s32_t score =
+ deleted_pages_in_block * SPIFFS_GC_HEUR_W_DELET +
+ used_pages_in_block * SPIFFS_GC_HEUR_W_USED +
+ erase_age * (fs_crammed ? 0 : SPIFFS_GC_HEUR_W_ERASE_AGE);
+ int cand_ix = 0;
+ SPIFFS_GC_DBG("gc_check: bix:"_SPIPRIbl" del:"_SPIPRIi" use:"_SPIPRIi" score:"_SPIPRIi"\n", cur_block, deleted_pages_in_block, used_pages_in_block, score);
+ while (cand_ix < max_candidates) {
+ if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) {
+ cand_blocks[cand_ix] = cur_block;
+ cand_scores[cand_ix] = score;
+ break;
+ } else if (cand_scores[cand_ix] < score) {
+ int reorder_cand_ix = max_candidates - 2;
+ while (reorder_cand_ix >= cand_ix) {
+ cand_blocks[reorder_cand_ix + 1] = cand_blocks[reorder_cand_ix];
+ cand_scores[reorder_cand_ix + 1] = cand_scores[reorder_cand_ix];
+ reorder_cand_ix--;
+ }
+ cand_blocks[cand_ix] = cur_block;
+ cand_scores[cand_ix] = score;
+ break;
+ }
+ cand_ix++;
+ }
+ (*candidate_count)++;
+ }
+
+ cur_entry = 0;
+ cur_block++;
+ cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+ } // per block
+
+ return res;
+}
+
+typedef enum {
+ FIND_OBJ_DATA,
+ MOVE_OBJ_DATA,
+ MOVE_OBJ_IX,
+ FINISHED
+} spiffs_gc_clean_state;
+
+typedef struct {
+ spiffs_gc_clean_state state;
+ spiffs_obj_id cur_obj_id;
+ spiffs_span_ix cur_objix_spix;
+ spiffs_page_ix cur_objix_pix;
+ spiffs_page_ix cur_data_pix;
+ int stored_scan_entry_index;
+ u8_t obj_id_found;
+} spiffs_gc;
+
+// Empties given block by moving all data into free pages of another block
+// Strategy:
+// loop:
+// scan object lookup for object data pages
+// for first found id, check spix and load corresponding object index page to memory
+// push object scan lookup entry index
+// rescan object lookup, find data pages with same id and referenced by same object index
+// move data page, update object index in memory
+// when reached end of lookup, store updated object index
+// pop object scan lookup entry index
+// repeat loop until end of object lookup
+// scan object lookup again for remaining object index pages, move to new page in other block
+//
+s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) {
+ s32_t res = SPIFFS_OK;
+ const int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+ // this is the global localizer being pushed and popped
+ int cur_entry = 0;
+ spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+ spiffs_gc gc; // our stack frame/state
+ spiffs_page_ix cur_pix = 0;
+ spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work;
+ spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work;
+
+ SPIFFS_GC_DBG("gc_clean: cleaning block "_SPIPRIbl"\n", bix);
+
+ memset(&gc, 0, sizeof(spiffs_gc));
+ gc.state = FIND_OBJ_DATA;
+
+ if (fs->free_cursor_block_ix == bix) {
+ // move free cursor to next block, cannot use free pages from the block we want to clean
+ fs->free_cursor_block_ix = (bix+1)%fs->block_count;
+ fs->free_cursor_obj_lu_entry = 0;
+ SPIFFS_GC_DBG("gc_clean: move free cursor to block "_SPIPRIbl"\n", fs->free_cursor_block_ix);
+ }
+
+ while (res == SPIFFS_OK && gc.state != FINISHED) {
+ SPIFFS_GC_DBG("gc_clean: state = "_SPIPRIi" entry:"_SPIPRIi"\n", gc.state, cur_entry);
+ gc.obj_id_found = 0; // reset (to no found data page)
+
+ // scan through lookup pages
+ int obj_lookup_page = cur_entry / entries_per_page;
+ u8_t scan = 1;
+ // check each object lookup page
+ while (scan && res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+ int entry_offset = obj_lookup_page * entries_per_page;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page),
+ SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ // check each object lookup entry
+ while (scan && res == SPIFFS_OK &&
+ cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) {
+ spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+ cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, cur_entry);
+
+ // act upon object id depending on gc state
+ switch (gc.state) {
+ case FIND_OBJ_DATA:
+ // find a data page
+ if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE &&
+ ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) {
+ // found a data page, stop scanning and handle in switch case below
+ SPIFFS_GC_DBG("gc_clean: FIND_DATA state:"_SPIPRIi" - found obj id "_SPIPRIid"\n", gc.state, obj_id);
+ gc.obj_id_found = 1;
+ gc.cur_obj_id = obj_id;
+ gc.cur_data_pix = cur_pix;
+ scan = 0;
+ }
+ break;
+ case MOVE_OBJ_DATA:
+ // evacuate found data pages for corresponding object index we have in memory,
+ // update memory representation
+ if (obj_id == gc.cur_obj_id) {
+ spiffs_page_header p_hdr;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page "_SPIPRIid":"_SPIPRIsp" @ "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix);
+ if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) {
+ SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n");
+ } else {
+ spiffs_page_ix new_data_pix;
+ if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) {
+ // move page
+ res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix);
+ SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix);
+ SPIFFS_CHECK_RES(res);
+ // move wipes obj_lu, reload it
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page),
+ SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ SPIFFS_CHECK_RES(res);
+ } else {
+ // page is deleted but not deleted in lookup, scrap it -
+ // might seem unnecessary as we will erase this block, but
+ // we might get aborted
+ SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix);
+ res = spiffs_page_delete(fs, cur_pix);
+ SPIFFS_CHECK_RES(res);
+ new_data_pix = SPIFFS_OBJ_ID_FREE;
+ }
+ // update memory representation of object index page with new data page
+ if (gc.cur_objix_spix == 0) {
+ // update object index header page
+ ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix;
+ SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix));
+ } else {
+ // update object index page
+ ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix;
+ SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix));
+ }
+ }
+ }
+ break;
+ case MOVE_OBJ_IX:
+ // find and evacuate object index pages
+ if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE &&
+ (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) {
+ // found an index object id
+ spiffs_page_header p_hdr;
+ spiffs_page_ix new_pix;
+ // load header
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
+ SPIFFS_CHECK_RES(res);
+ if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) {
+ // move page
+ res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix);
+ SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix, new_pix);
+ SPIFFS_CHECK_RES(res);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&p_hdr,
+ SPIFFS_EV_IX_MOV, obj_id, p_hdr.span_ix, new_pix, 0);
+ // move wipes obj_lu, reload it
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page),
+ SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ SPIFFS_CHECK_RES(res);
+ } else {
+ // page is deleted but not deleted in lookup, scrap it -
+ // might seem unnecessary as we will erase this block, but
+ // we might get aborted
+ SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix);
+ res = spiffs_page_delete(fs, cur_pix);
+ if (res == SPIFFS_OK) {
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0,
+ SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0);
+ }
+ }
+ SPIFFS_CHECK_RES(res);
+ }
+ break;
+ default:
+ scan = 0;
+ break;
+ } // switch gc state
+ cur_entry++;
+ } // per entry
+ obj_lookup_page++; // no need to check scan variable here, obj_lookup_page is set in start of loop
+ } // per object lookup page
+ if (res != SPIFFS_OK) break;
+
+ // state finalization and switch
+ switch (gc.state) {
+ case FIND_OBJ_DATA:
+ if (gc.obj_id_found) {
+ // handle found data page -
+ // find out corresponding obj ix page and load it to memory
+ spiffs_page_header p_hdr;
+ spiffs_page_ix objix_pix;
+ gc.stored_scan_entry_index = cur_entry; // push cursor
+ cur_entry = 0; // restart scan from start
+ gc.state = MOVE_OBJ_DATA;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr);
+ SPIFFS_CHECK_RES(res);
+ gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix);
+ SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:"_SPIPRIsp"\n", gc.cur_objix_spix);
+ res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ // on borked systems we might get an ERR_NOT_FOUND here -
+ // this is handled by simply deleting the page as it is not referenced
+ // from anywhere
+ SPIFFS_GC_DBG("gc_clean: FIND_OBJ_DATA objix not found! Wipe page "_SPIPRIpg"\n", gc.cur_data_pix);
+ res = spiffs_page_delete(fs, gc.cur_data_pix);
+ SPIFFS_CHECK_RES(res);
+ // then we restore states and continue scanning for data pages
+ cur_entry = gc.stored_scan_entry_index; // pop cursor
+ gc.state = FIND_OBJ_DATA;
+ break; // done
+ }
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page "_SPIPRIpg"\n", objix_pix);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ // cannot allow a gc if the presumed index in fact is no index, a
+ // check must run or lot of data may be lost
+ SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix);
+ gc.cur_objix_pix = objix_pix;
+ } else {
+ // no more data pages found, passed thru all block, start evacuating object indices
+ gc.state = MOVE_OBJ_IX;
+ cur_entry = 0; // restart entry scan index
+ }
+ break;
+ case MOVE_OBJ_DATA: {
+ // store modified objix (hdr) page residing in memory now that all
+ // data pages belonging to this object index and residing in the block
+ // we want to evacuate
+ spiffs_page_ix new_objix_pix;
+ gc.state = FIND_OBJ_DATA;
+ cur_entry = gc.stored_scan_entry_index; // pop cursor
+ if (gc.cur_objix_spix == 0) {
+ // store object index header page
+ res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, 0, &new_objix_pix);
+ SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, 0);
+ SPIFFS_CHECK_RES(res);
+ } else {
+ // store object index page
+ res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix);
+ SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, objix->p_hdr.span_ix);
+ SPIFFS_CHECK_RES(res);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work,
+ SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0);
+ }
+ }
+ break;
+ case MOVE_OBJ_IX:
+ // scanned thru all block, no more object indices found - our work here is done
+ gc.state = FINISHED;
+ break;
+ default:
+ cur_entry = 0;
+ break;
+ } // switch gc.state
+ SPIFFS_GC_DBG("gc_clean: state-> "_SPIPRIi"\n", gc.state);
+ } // while state != FINISHED
+
+
+ return res;
+}
+
+#endif // !SPIFFS_READ_ONLY
diff --git a/libiot/spiffs/spiffs_hydrogen.c b/libiot/spiffs/spiffs_hydrogen.c
new file mode 100644
index 0000000..9ff3e7a
--- /dev/null
+++ b/libiot/spiffs/spiffs_hydrogen.c
@@ -0,0 +1,1405 @@
+/*
+ * spiffs_hydrogen.c
+ *
+ * Created on: Jun 16, 2013
+ * Author: petera
+ */
+
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+#if SPIFFS_FILEHDL_OFFSET
+#define SPIFFS_FH_OFFS(fs, fh) ((fh) != 0 ? ((fh) + (fs)->cfg.fh_ix_offset) : 0)
+#define SPIFFS_FH_UNOFFS(fs, fh) ((fh) != 0 ? ((fh) - (fs)->cfg.fh_ix_offset) : 0)
+#else
+#define SPIFFS_FH_OFFS(fs, fh) (fh)
+#define SPIFFS_FH_UNOFFS(fs, fh) (fh)
+#endif
+
+#if SPIFFS_CACHE == 1
+static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh);
+#endif
+
+#if SPIFFS_BUFFER_HELP
+u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs) {
+ return num_descs * sizeof(spiffs_fd);
+}
+#if SPIFFS_CACHE
+u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages) {
+ return sizeof(spiffs_cache) + num_pages * (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs));
+}
+#endif
+#endif
+
+u8_t SPIFFS_mounted(spiffs *fs) {
+ return SPIFFS_CHECK_MOUNT(fs);
+}
+
+s32_t SPIFFS_format(spiffs *fs) {
+#if SPIFFS_READ_ONLY
+ (void)fs;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ SPIFFS_API_CHECK_CFG(fs);
+ if (SPIFFS_CHECK_MOUNT(fs)) {
+ fs->err_code = SPIFFS_ERR_MOUNTED;
+ return -1;
+ }
+
+ s32_t res;
+ SPIFFS_LOCK(fs);
+
+ spiffs_block_ix bix = 0;
+ while (bix < fs->block_count) {
+ fs->max_erase_count = 0;
+ res = spiffs_erase_block(fs, bix);
+ if (res != SPIFFS_OK) {
+ res = SPIFFS_ERR_ERASE_FAIL;
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ bix++;
+ }
+
+ SPIFFS_UNLOCK(fs);
+
+ return 0;
+#endif // SPIFFS_READ_ONLY
+}
+
+#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
+
+s32_t SPIFFS_probe_fs(spiffs_config *config) {
+ s32_t res = spiffs_probe(config);
+ return res;
+}
+
+#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
+
+s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work,
+ u8_t *fd_space, u32_t fd_space_size,
+ void *cache, u32_t cache_size,
+ spiffs_check_callback check_cb_f) {
+ void *user_data;
+ SPIFFS_LOCK(fs);
+ user_data = fs->user_data;
+ memset(fs, 0, sizeof(spiffs));
+ memcpy(&fs->cfg, config, sizeof(spiffs_config));
+ fs->user_data = user_data;
+ fs->block_count = SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+ fs->work = &work[0];
+ fs->lu_work = &work[SPIFFS_CFG_LOG_PAGE_SZ(fs)];
+ memset(fd_space, 0, fd_space_size);
+ // align fd_space pointer to pointer size byte boundary
+ u8_t ptr_size = sizeof(void*);
+ u8_t addr_lsb = ((u8_t)(intptr_t)fd_space) & (ptr_size-1);
+ if (addr_lsb) {
+ fd_space += (ptr_size-addr_lsb);
+ fd_space_size -= (ptr_size-addr_lsb);
+ }
+ fs->fd_space = fd_space;
+ fs->fd_count = (fd_space_size/sizeof(spiffs_fd));
+
+ // align cache pointer to 4 byte boundary
+ addr_lsb = ((u8_t)(intptr_t)cache) & (ptr_size-1);
+ if (addr_lsb) {
+ u8_t *cache_8 = (u8_t *)cache;
+ cache_8 += (ptr_size-addr_lsb);
+ cache = cache_8;
+ cache_size -= (ptr_size-addr_lsb);
+ }
+ if (cache_size & (ptr_size-1)) {
+ cache_size -= (cache_size & (ptr_size-1));
+ }
+
+#if SPIFFS_CACHE
+ fs->cache = cache;
+ fs->cache_size = (cache_size > (SPIFFS_CFG_LOG_PAGE_SZ(fs)*32)) ? SPIFFS_CFG_LOG_PAGE_SZ(fs)*32 : cache_size;
+ spiffs_cache_init(fs);
+#endif
+
+ s32_t res;
+
+#if SPIFFS_USE_MAGIC
+ res = SPIFFS_CHECK_MAGIC_POSSIBLE(fs) ? SPIFFS_OK : SPIFFS_ERR_MAGIC_NOT_POSSIBLE;
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+#endif
+
+ fs->config_magic = SPIFFS_CONFIG_MAGIC;
+
+ res = spiffs_obj_lu_scan(fs);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ SPIFFS_DBG("page index byte len: "_SPIPRIi"\n", (u32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs));
+ SPIFFS_DBG("object lookup pages: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_LOOKUP_PAGES(fs));
+ SPIFFS_DBG("page pages per block: "_SPIPRIi"\n", (u32_t)SPIFFS_PAGES_PER_BLOCK(fs));
+ SPIFFS_DBG("page header length: "_SPIPRIi"\n", (u32_t)sizeof(spiffs_page_header));
+ SPIFFS_DBG("object header index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_HDR_IX_LEN(fs));
+ SPIFFS_DBG("object index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_IX_LEN(fs));
+ SPIFFS_DBG("available file descriptors: "_SPIPRIi"\n", (u32_t)fs->fd_count);
+ SPIFFS_DBG("free blocks: "_SPIPRIi"\n", (u32_t)fs->free_blocks);
+
+ fs->check_cb_f = check_cb_f;
+
+ fs->mounted = 1;
+
+ SPIFFS_UNLOCK(fs);
+
+ return 0;
+}
+
+void SPIFFS_unmount(spiffs *fs) {
+ if (!SPIFFS_CHECK_CFG(fs) || !SPIFFS_CHECK_MOUNT(fs)) return;
+ SPIFFS_LOCK(fs);
+ u32_t i;
+ spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+ for (i = 0; i < fs->fd_count; i++) {
+ spiffs_fd *cur_fd = &fds[i];
+ if (cur_fd->file_nbr != 0) {
+#if SPIFFS_CACHE
+ (void)spiffs_fflush_cache(fs, cur_fd->file_nbr);
+#endif
+ spiffs_fd_return(fs, cur_fd->file_nbr);
+ }
+ }
+ fs->mounted = 0;
+
+ SPIFFS_UNLOCK(fs);
+}
+
+s32_t SPIFFS_errno(spiffs *fs) {
+ return fs->err_code;
+}
+
+void SPIFFS_clearerr(spiffs *fs) {
+ fs->err_code = SPIFFS_OK;
+}
+
+s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)path; (void)mode;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ (void)mode;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) {
+ SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG);
+ }
+ SPIFFS_LOCK(fs);
+ spiffs_obj_id obj_id;
+ s32_t res;
+
+ res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, (const u8_t*)path);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, 0);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ SPIFFS_UNLOCK(fs);
+ return 0;
+#endif // SPIFFS_READ_ONLY
+}
+
+spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode) {
+ (void)mode;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) {
+ SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG);
+ }
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+ spiffs_page_ix pix;
+
+#if SPIFFS_READ_ONLY
+ // not valid flags in read only mode
+ flags &= ~(SPIFFS_WRONLY | SPIFFS_CREAT | SPIFFS_TRUNC);
+#endif // SPIFFS_READ_ONLY
+
+ s32_t res = spiffs_fd_find_new(fs, &fd, path);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix);
+ if ((flags & SPIFFS_O_CREAT) == 0) {
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+ if (res == SPIFFS_OK &&
+ (flags & (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) == (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) {
+ // creat and excl and file exists - fail
+ res = SPIFFS_ERR_FILE_EXISTS;
+ spiffs_fd_return(fs, fd->file_nbr);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+ if ((flags & SPIFFS_O_CREAT) && res == SPIFFS_ERR_NOT_FOUND) {
+#if !SPIFFS_READ_ONLY
+ spiffs_obj_id obj_id;
+ // no need to enter conflicting name here, already looked for it above
+ res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, 0);
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, &pix);
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ flags &= ~SPIFFS_O_TRUNC;
+#endif // !SPIFFS_READ_ONLY
+ } else {
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+ res = spiffs_object_open_by_page(fs, pix, fd, flags, mode);
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+#if !SPIFFS_READ_ONLY
+ if (flags & SPIFFS_O_TRUNC) {
+ res = spiffs_object_truncate(fd, 0, 0);
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+#endif // !SPIFFS_READ_ONLY
+
+ fd->fdoffset = 0;
+
+ SPIFFS_UNLOCK(fs);
+
+ return SPIFFS_FH_OFFS(fs, fd->file_nbr);
+}
+
+spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode) {
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+
+ s32_t res = spiffs_fd_find_new(fs, &fd, 0);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_open_by_page(fs, e->pix, fd, flags, mode);
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+#if !SPIFFS_READ_ONLY
+ if (flags & SPIFFS_O_TRUNC) {
+ res = spiffs_object_truncate(fd, 0, 0);
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+#endif // !SPIFFS_READ_ONLY
+
+ fd->fdoffset = 0;
+
+ SPIFFS_UNLOCK(fs);
+
+ return SPIFFS_FH_OFFS(fs, fd->file_nbr);
+}
+
+spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode) {
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+
+ s32_t res = spiffs_fd_find_new(fs, &fd, 0);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ if (SPIFFS_IS_LOOKUP_PAGE(fs, page_ix)) {
+ res = SPIFFS_ERR_NOT_A_FILE;
+ spiffs_fd_return(fs, fd->file_nbr);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+ res = spiffs_object_open_by_page(fs, page_ix, fd, flags, mode);
+ if (res == SPIFFS_ERR_IS_FREE ||
+ res == SPIFFS_ERR_DELETED ||
+ res == SPIFFS_ERR_NOT_FINALIZED ||
+ res == SPIFFS_ERR_NOT_INDEX ||
+ res == SPIFFS_ERR_INDEX_SPAN_MISMATCH) {
+ res = SPIFFS_ERR_NOT_A_FILE;
+ }
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+#if !SPIFFS_READ_ONLY
+ if (flags & SPIFFS_O_TRUNC) {
+ res = spiffs_object_truncate(fd, 0, 0);
+ if (res < SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+#endif // !SPIFFS_READ_ONLY
+
+ fd->fdoffset = 0;
+
+ SPIFFS_UNLOCK(fs);
+
+ return SPIFFS_FH_OFFS(fs, fd->file_nbr);
+}
+
+static s32_t spiffs_hydro_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) {
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+ s32_t res;
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ if ((fd->flags & SPIFFS_O_RDONLY) == 0) {
+ res = SPIFFS_ERR_NOT_READABLE;
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+ if (fd->size == SPIFFS_UNDEFINED_LEN && len > 0) {
+ // special case for zero sized files
+ res = SPIFFS_ERR_END_OF_OBJECT;
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+#if SPIFFS_CACHE_WR
+ spiffs_fflush_cache(fs, fh);
+#endif
+
+ if (fd->fdoffset + len >= fd->size) {
+ // reading beyond file size
+ s32_t avail = fd->size - fd->fdoffset;
+ if (avail <= 0) {
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_END_OF_OBJECT);
+ }
+ res = spiffs_object_read(fd, fd->fdoffset, avail, (u8_t*)buf);
+ if (res == SPIFFS_ERR_END_OF_OBJECT) {
+ fd->fdoffset += avail;
+ SPIFFS_UNLOCK(fs);
+ return avail;
+ } else {
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ len = avail;
+ }
+ } else {
+ // reading within file size
+ res = spiffs_object_read(fd, fd->fdoffset, len, (u8_t*)buf);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+ fd->fdoffset += len;
+
+ SPIFFS_UNLOCK(fs);
+
+ return len;
+}
+
+s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) {
+ s32_t res = spiffs_hydro_read(fs, fh, buf, len);
+ if (res == SPIFFS_ERR_END_OF_OBJECT) {
+ res = 0;
+ }
+ return res;
+}
+
+
+#if !SPIFFS_READ_ONLY
+static s32_t spiffs_hydro_write(spiffs *fs, spiffs_fd *fd, void *buf, u32_t offset, s32_t len) {
+ (void)fs;
+ s32_t res = SPIFFS_OK;
+ s32_t remaining = len;
+ if (fd->size != SPIFFS_UNDEFINED_LEN && offset < fd->size) {
+ s32_t m_len = MIN((s32_t)(fd->size - offset), len);
+ res = spiffs_object_modify(fd, offset, (u8_t *)buf, m_len);
+ SPIFFS_CHECK_RES(res);
+ remaining -= m_len;
+ u8_t *buf_8 = (u8_t *)buf;
+ buf_8 += m_len;
+ buf = buf_8;
+ offset += m_len;
+ }
+ if (remaining > 0) {
+ res = spiffs_object_append(fd, offset, (u8_t *)buf, remaining);
+ SPIFFS_CHECK_RES(res);
+ }
+ return len;
+
+}
+#endif // !SPIFFS_READ_ONLY
+
+s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)fh; (void)buf; (void)len;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+ s32_t res;
+ u32_t offset;
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ if ((fd->flags & SPIFFS_O_WRONLY) == 0) {
+ res = SPIFFS_ERR_NOT_WRITABLE;
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+ if ((fd->flags & SPIFFS_O_APPEND)) {
+ fd->fdoffset = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size;
+ }
+
+ offset = fd->fdoffset;
+
+#if SPIFFS_CACHE_WR
+ if (fd->cache_page == 0) {
+ // see if object id is associated with cache already
+ fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd);
+ }
+#endif
+ if (fd->flags & SPIFFS_O_APPEND) {
+ if (fd->size == SPIFFS_UNDEFINED_LEN) {
+ offset = 0;
+ } else {
+ offset = fd->size;
+ }
+#if SPIFFS_CACHE_WR
+ if (fd->cache_page) {
+ offset = MAX(offset, fd->cache_page->offset + fd->cache_page->size);
+ }
+#endif
+ }
+
+#if SPIFFS_CACHE_WR
+ if ((fd->flags & SPIFFS_O_DIRECT) == 0) {
+ if (len < (s32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)) {
+ // small write, try to cache it
+ u8_t alloc_cpage = 1;
+ if (fd->cache_page) {
+ // have a cached page for this fd already, check cache page boundaries
+ if (offset < fd->cache_page->offset || // writing before cache
+ offset > fd->cache_page->offset + fd->cache_page->size || // writing after cache
+ offset + len > fd->cache_page->offset + SPIFFS_CFG_LOG_PAGE_SZ(fs)) // writing beyond cache page
+ {
+ // boundary violation, write back cache first and allocate new
+ SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", boundary viol, offs:"_SPIPRIi" size:"_SPIPRIi"\n",
+ fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size);
+ res = spiffs_hydro_write(fs, fd,
+ spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix),
+ fd->cache_page->offset, fd->cache_page->size);
+ spiffs_cache_fd_release(fs, fd->cache_page);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ } else {
+ // writing within cache
+ alloc_cpage = 0;
+ }
+ }
+
+ if (alloc_cpage) {
+ fd->cache_page = spiffs_cache_page_allocate_by_fd(fs, fd);
+ if (fd->cache_page) {
+ fd->cache_page->offset = offset;
+ fd->cache_page->size = 0;
+ SPIFFS_CACHE_DBG("CACHE_WR_ALLO: allocating cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid"\n",
+ fd->cache_page->ix, fd->file_nbr, fd->obj_id);
+ }
+ }
+
+ if (fd->cache_page) {
+ u32_t offset_in_cpage = offset - fd->cache_page->offset;
+ SPIFFS_CACHE_DBG("CACHE_WR_WRITE: storing to cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", offs "_SPIPRIi":"_SPIPRIi" len "_SPIPRIi"\n",
+ fd->cache_page->ix, fd->file_nbr, fd->obj_id,
+ offset, offset_in_cpage, len);
+ spiffs_cache *cache = spiffs_get_cache(fs);
+ u8_t *cpage_data = spiffs_get_cache_page(fs, cache, fd->cache_page->ix);
+ memcpy(&cpage_data[offset_in_cpage], buf, len);
+ fd->cache_page->size = MAX(fd->cache_page->size, offset_in_cpage + len);
+ fd->fdoffset += len;
+ SPIFFS_UNLOCK(fs);
+ return len;
+ } else {
+ res = spiffs_hydro_write(fs, fd, buf, offset, len);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ fd->fdoffset += len;
+ SPIFFS_UNLOCK(fs);
+ return res;
+ }
+ } else {
+ // big write, no need to cache it - but first check if there is a cached write already
+ if (fd->cache_page) {
+ // write back cache first
+ SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", big write, offs:"_SPIPRIi" size:"_SPIPRIi"\n",
+ fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size);
+ res = spiffs_hydro_write(fs, fd,
+ spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix),
+ fd->cache_page->offset, fd->cache_page->size);
+ spiffs_cache_fd_release(fs, fd->cache_page);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ // data written below
+ }
+ }
+ }
+#endif
+
+ res = spiffs_hydro_write(fs, fd, buf, offset, len);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ fd->fdoffset += len;
+
+ SPIFFS_UNLOCK(fs);
+
+ return res;
+#endif // SPIFFS_READ_ONLY
+}
+
+s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) {
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+ s32_t res;
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+#if SPIFFS_CACHE_WR
+ spiffs_fflush_cache(fs, fh);
+#endif
+
+ s32_t fileSize = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size;
+
+ switch (whence) {
+ case SPIFFS_SEEK_CUR:
+ offs = fd->fdoffset+offs;
+ break;
+ case SPIFFS_SEEK_END:
+ offs = fileSize + offs;
+ break;
+ }
+
+ if ((offs > fileSize)) {
+ fd->fdoffset = fileSize;
+ res = SPIFFS_ERR_END_OF_OBJECT;
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ spiffs_span_ix data_spix = offs / SPIFFS_DATA_PAGE_SIZE(fs);
+ spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+ if (fd->cursor_objix_spix != objix_spix) {
+ spiffs_page_ix pix;
+ res = spiffs_obj_lu_find_id_and_span(
+ fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, &pix);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ fd->cursor_objix_spix = objix_spix;
+ fd->cursor_objix_pix = pix;
+ }
+ fd->fdoffset = offs;
+
+ SPIFFS_UNLOCK(fs);
+
+ return offs;
+}
+
+s32_t SPIFFS_remove(spiffs *fs, const char *path) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)path;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) {
+ SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG);
+ }
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+ spiffs_page_ix pix;
+ s32_t res;
+
+ res = spiffs_fd_find_new(fs, &fd, 0);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix);
+ if (res != SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_open_by_page(fs, pix, fd, 0,0);
+ if (res != SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_truncate(fd, 0, 1);
+ if (res != SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ SPIFFS_UNLOCK(fs);
+ return 0;
+#endif // SPIFFS_READ_ONLY
+}
+
+s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)fh;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+ s32_t res;
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ if ((fd->flags & SPIFFS_O_WRONLY) == 0) {
+ res = SPIFFS_ERR_NOT_WRITABLE;
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+#if SPIFFS_CACHE_WR
+ spiffs_cache_fd_release(fs, fd->cache_page);
+#endif
+
+ res = spiffs_object_truncate(fd, 0, 1);
+
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ SPIFFS_UNLOCK(fs);
+
+ return 0;
+#endif // SPIFFS_READ_ONLY
+}
+
+static s32_t spiffs_stat_pix(spiffs *fs, spiffs_page_ix pix, spiffs_file fh, spiffs_stat *s) {
+ (void)fh;
+ spiffs_page_object_ix_header objix_hdr;
+ spiffs_obj_id obj_id;
+ s32_t res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fh,
+ SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr);
+ SPIFFS_API_CHECK_RES(fs, res);
+
+ u32_t obj_id_addr = SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs , pix)) +
+ SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_obj_id);
+ res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, fh,
+ obj_id_addr, sizeof(spiffs_obj_id), (u8_t *)&obj_id);
+ SPIFFS_API_CHECK_RES(fs, res);
+
+ s->obj_id = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+ s->type = objix_hdr.type;
+ s->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size;
+ s->pix = pix;
+ strncpy((char *)s->name, (char *)objix_hdr.name, SPIFFS_OBJ_NAME_LEN);
+#if SPIFFS_OBJ_META_LEN
+ memcpy(s->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN);
+#endif
+
+ return res;
+}
+
+s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s) {
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) {
+ SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG);
+ }
+ SPIFFS_LOCK(fs);
+
+ s32_t res;
+ spiffs_page_ix pix;
+
+ res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_stat_pix(fs, pix, 0, s);
+
+ SPIFFS_UNLOCK(fs);
+
+ return res;
+}
+
+s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) {
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ spiffs_fd *fd;
+ s32_t res;
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+#if SPIFFS_CACHE_WR
+ spiffs_fflush_cache(fs, fh);
+#endif
+
+ res = spiffs_stat_pix(fs, fd->objix_hdr_pix, fh, s);
+
+ SPIFFS_UNLOCK(fs);
+
+ return res;
+}
+
+// Checks if there are any cached writes for the object id associated with
+// given filehandle. If so, these writes are flushed.
+#if SPIFFS_CACHE == 1
+static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh) {
+ (void)fs;
+ (void)fh;
+ s32_t res = SPIFFS_OK;
+#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR
+
+ spiffs_fd *fd;
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES(fs, res);
+
+ if ((fd->flags & SPIFFS_O_DIRECT) == 0) {
+ if (fd->cache_page == 0) {
+ // see if object id is associated with cache already
+ fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd);
+ }
+ if (fd->cache_page) {
+ SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", flush, offs:"_SPIPRIi" size:"_SPIPRIi"\n",
+ fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size);
+ res = spiffs_hydro_write(fs, fd,
+ spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix),
+ fd->cache_page->offset, fd->cache_page->size);
+ if (res < SPIFFS_OK) {
+ fs->err_code = res;
+ }
+ spiffs_cache_fd_release(fs, fd->cache_page);
+ }
+ }
+#endif
+
+ return res;
+}
+#endif
+
+s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh) {
+ (void)fh;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ s32_t res = SPIFFS_OK;
+#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR
+ SPIFFS_LOCK(fs);
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+ res = spiffs_fflush_cache(fs, fh);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs,res);
+ SPIFFS_UNLOCK(fs);
+#endif
+
+ return res;
+}
+
+s32_t SPIFFS_close(spiffs *fs, spiffs_file fh) {
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+
+ s32_t res = SPIFFS_OK;
+ SPIFFS_LOCK(fs);
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+#if SPIFFS_CACHE
+ res = spiffs_fflush_cache(fs, fh);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+#endif
+ res = spiffs_fd_return(fs, fh);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ SPIFFS_UNLOCK(fs);
+
+ return res;
+}
+
+s32_t SPIFFS_rename(spiffs *fs, const char *old_path, const char *new_path) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)old_path; (void)new_path;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ if (strlen(new_path) > SPIFFS_OBJ_NAME_LEN - 1 ||
+ strlen(old_path) > SPIFFS_OBJ_NAME_LEN - 1) {
+ SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG);
+ }
+ SPIFFS_LOCK(fs);
+
+ spiffs_page_ix pix_old, pix_dummy;
+ spiffs_fd *fd;
+
+ s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)old_path, &pix_old);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)new_path, &pix_dummy);
+ if (res == SPIFFS_ERR_NOT_FOUND) {
+ res = SPIFFS_OK;
+ } else if (res == SPIFFS_OK) {
+ res = SPIFFS_ERR_CONFLICTING_NAME;
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_fd_find_new(fs, &fd, 0);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_open_by_page(fs, pix_old, fd, 0, 0);
+ if (res != SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, (const u8_t*)new_path,
+ 0, 0, &pix_dummy);
+#if SPIFFS_TEMPORAL_FD_CACHE
+ if (res == SPIFFS_OK) {
+ spiffs_fd_temporal_cache_rehash(fs, old_path, new_path);
+ }
+#endif
+
+ spiffs_fd_return(fs, fd->file_nbr);
+
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ SPIFFS_UNLOCK(fs);
+
+ return res;
+#endif // SPIFFS_READ_ONLY
+}
+
+#if SPIFFS_OBJ_META_LEN
+s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)name; (void)meta;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ spiffs_page_ix pix, pix_dummy;
+ spiffs_fd *fd;
+
+ s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)name, &pix);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_fd_find_new(fs, &fd, 0);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_open_by_page(fs, pix, fd, 0, 0);
+ if (res != SPIFFS_OK) {
+ spiffs_fd_return(fs, fd->file_nbr);
+ }
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta,
+ 0, &pix_dummy);
+
+ spiffs_fd_return(fs, fd->file_nbr);
+
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ SPIFFS_UNLOCK(fs);
+
+ return res;
+#endif // SPIFFS_READ_ONLY
+}
+
+s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)fh; (void)meta;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ s32_t res;
+ spiffs_fd *fd;
+ spiffs_page_ix pix_dummy;
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ if ((fd->flags & SPIFFS_O_WRONLY) == 0) {
+ res = SPIFFS_ERR_NOT_WRITABLE;
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta,
+ 0, &pix_dummy);
+
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ SPIFFS_UNLOCK(fs);
+
+ return res;
+#endif // SPIFFS_READ_ONLY
+}
+#endif // SPIFFS_OBJ_META_LEN
+
+spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d) {
+ (void)name;
+
+ if (!SPIFFS_CHECK_CFG((fs))) {
+ (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED;
+ return 0;
+ }
+
+ if (!SPIFFS_CHECK_MOUNT(fs)) {
+ fs->err_code = SPIFFS_ERR_NOT_MOUNTED;
+ return 0;
+ }
+
+ d->fs = fs;
+ d->block = 0;
+ d->entry = 0;
+ return d;
+}
+
+static s32_t spiffs_read_dir_v(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_block_ix bix,
+ int ix_entry,
+ const void *user_const_p,
+ void *user_var_p) {
+ (void)user_const_p;
+ s32_t res;
+ spiffs_page_object_ix_header objix_hdr;
+ if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED ||
+ (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) {
+ return SPIFFS_VIS_COUNTINUE;
+ }
+
+ spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr);
+ if (res != SPIFFS_OK) return res;
+ if ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) &&
+ objix_hdr.p_hdr.span_ix == 0 &&
+ (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) ==
+ (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) {
+ struct spiffs_dirent *e = (struct spiffs_dirent*)user_var_p;
+ e->obj_id = obj_id;
+ strcpy((char *)e->name, (char *)objix_hdr.name);
+ e->type = objix_hdr.type;
+ e->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size;
+ e->pix = pix;
+#if SPIFFS_OBJ_META_LEN
+ memcpy(e->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN);
+#endif
+ return SPIFFS_OK;
+ }
+ return SPIFFS_VIS_COUNTINUE;
+}
+
+struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e) {
+ if (!SPIFFS_CHECK_MOUNT(d->fs)) {
+ d->fs->err_code = SPIFFS_ERR_NOT_MOUNTED;
+ return 0;
+ }
+ SPIFFS_LOCK(d->fs);
+
+ spiffs_block_ix bix;
+ int entry;
+ s32_t res;
+ struct spiffs_dirent *ret = 0;
+
+ res = spiffs_obj_lu_find_entry_visitor(d->fs,
+ d->block,
+ d->entry,
+ SPIFFS_VIS_NO_WRAP,
+ 0,
+ spiffs_read_dir_v,
+ 0,
+ e,
+ &bix,
+ &entry);
+ if (res == SPIFFS_OK) {
+ d->block = bix;
+ d->entry = entry + 1;
+ e->obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG;
+ ret = e;
+ } else {
+ d->fs->err_code = res;
+ }
+ SPIFFS_UNLOCK(d->fs);
+ return ret;
+}
+
+s32_t SPIFFS_closedir(spiffs_DIR *d) {
+ SPIFFS_API_CHECK_CFG(d->fs);
+ SPIFFS_API_CHECK_MOUNT(d->fs);
+ return 0;
+}
+
+s32_t SPIFFS_check(spiffs *fs) {
+#if SPIFFS_READ_ONLY
+ (void)fs;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ s32_t res;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ res = spiffs_lookup_consistency_check(fs, 0);
+
+ res = spiffs_object_index_consistency_check(fs);
+
+ res = spiffs_page_consistency_check(fs);
+
+ res = spiffs_obj_lu_scan(fs);
+
+ SPIFFS_UNLOCK(fs);
+ return res;
+#endif // SPIFFS_READ_ONLY
+}
+
+s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used) {
+ s32_t res = SPIFFS_OK;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ u32_t pages_per_block = SPIFFS_PAGES_PER_BLOCK(fs);
+ u32_t blocks = fs->block_count;
+ u32_t obj_lu_pages = SPIFFS_OBJ_LOOKUP_PAGES(fs);
+ u32_t data_page_size = SPIFFS_DATA_PAGE_SIZE(fs);
+ u32_t total_data_pages = (blocks - 2) * (pages_per_block - obj_lu_pages) + 1; // -2 for spare blocks, +1 for emergency page
+
+ if (total) {
+ *total = total_data_pages * data_page_size;
+ }
+
+ if (used) {
+ *used = fs->stats_p_allocated * data_page_size;
+ }
+
+ SPIFFS_UNLOCK(fs);
+ return res;
+}
+
+s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)max_free_pages;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ s32_t res;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ res = spiffs_gc_quick(fs, max_free_pages);
+
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ SPIFFS_UNLOCK(fs);
+ return 0;
+#endif // SPIFFS_READ_ONLY
+}
+
+
+s32_t SPIFFS_gc(spiffs *fs, u32_t size) {
+#if SPIFFS_READ_ONLY
+ (void)fs; (void)size;
+ return SPIFFS_ERR_RO_NOT_IMPL;
+#else
+ s32_t res;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ res = spiffs_gc_check(fs, size);
+
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ SPIFFS_UNLOCK(fs);
+ return 0;
+#endif // SPIFFS_READ_ONLY
+}
+
+s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh) {
+ s32_t res;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+
+ spiffs_fd *fd;
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+#if SPIFFS_CACHE_WR
+ res = spiffs_fflush_cache(fs, fh);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+#endif
+
+ res = (fd->fdoffset >= (fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size));
+
+ SPIFFS_UNLOCK(fs);
+ return res;
+}
+
+s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh) {
+ s32_t res;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+
+ spiffs_fd *fd;
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+#if SPIFFS_CACHE_WR
+ res = spiffs_fflush_cache(fs, fh);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+#endif
+
+ res = fd->fdoffset;
+
+ SPIFFS_UNLOCK(fs);
+ return res;
+}
+
+s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func) {
+ SPIFFS_LOCK(fs);
+ fs->file_cb_f = cb_func;
+ SPIFFS_UNLOCK(fs);
+ return 0;
+}
+
+#if SPIFFS_IX_MAP
+
+s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map,
+ u32_t offset, u32_t len, spiffs_page_ix *map_buf) {
+ s32_t res;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+
+ spiffs_fd *fd;
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ if (fd->ix_map) {
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_MAPPED);
+ }
+
+ map->map_buf = map_buf;
+ map->offset = offset;
+ // nb: spix range includes last
+ map->start_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs);
+ map->end_spix = (offset + len) / SPIFFS_DATA_PAGE_SIZE(fs);
+ memset(map_buf, 0, sizeof(spiffs_page_ix) * (map->end_spix - map->start_spix + 1));
+ fd->ix_map = map;
+
+ // scan for pixes
+ res = spiffs_populate_ix_map(fs, fd, 0, map->end_spix - map->start_spix + 1);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ SPIFFS_UNLOCK(fs);
+ return res;
+}
+
+s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh) {
+ s32_t res;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+
+ spiffs_fd *fd;
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ if (fd->ix_map == 0) {
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED);
+ }
+
+ fd->ix_map = 0;
+
+ SPIFFS_UNLOCK(fs);
+ return res;
+}
+
+s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offset) {
+ s32_t res = SPIFFS_OK;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ fh = SPIFFS_FH_UNOFFS(fs, fh);
+
+ spiffs_fd *fd;
+ res = spiffs_fd_get(fs, fh, &fd);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+
+ if (fd->ix_map == 0) {
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED);
+ }
+
+ spiffs_ix_map *map = fd->ix_map;
+
+ s32_t spix_diff = offset / SPIFFS_DATA_PAGE_SIZE(fs) - map->start_spix;
+ map->offset = offset;
+
+ // move existing pixes if within map offs
+ if (spix_diff != 0) {
+ // move vector
+ int i;
+ const s32_t vec_len = map->end_spix - map->start_spix + 1; // spix range includes last
+ map->start_spix += spix_diff;
+ map->end_spix += spix_diff;
+ if (spix_diff >= vec_len) {
+ // moving beyond range
+ memset(&map->map_buf, 0, vec_len * sizeof(spiffs_page_ix));
+ // populate_ix_map is inclusive
+ res = spiffs_populate_ix_map(fs, fd, 0, vec_len-1);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ } else if (spix_diff > 0) {
+ // diff positive
+ for (i = 0; i < vec_len - spix_diff; i++) {
+ map->map_buf[i] = map->map_buf[i + spix_diff];
+ }
+ // memset is non-inclusive
+ memset(&map->map_buf[vec_len - spix_diff], 0, spix_diff * sizeof(spiffs_page_ix));
+ // populate_ix_map is inclusive
+ res = spiffs_populate_ix_map(fs, fd, vec_len - spix_diff, vec_len-1);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ } else {
+ // diff negative
+ for (i = vec_len - 1; i >= -spix_diff; i--) {
+ map->map_buf[i] = map->map_buf[i + spix_diff];
+ }
+ // memset is non-inclusive
+ memset(&map->map_buf[0], 0, -spix_diff * sizeof(spiffs_page_ix));
+ // populate_ix_map is inclusive
+ res = spiffs_populate_ix_map(fs, fd, 0, -spix_diff - 1);
+ SPIFFS_API_CHECK_RES_UNLOCK(fs, res);
+ }
+
+ }
+
+ SPIFFS_UNLOCK(fs);
+ return res;
+}
+
+s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes) {
+ SPIFFS_API_CHECK_CFG(fs);
+ // always add one extra page, the offset might change to the middle of a page
+ return (bytes + SPIFFS_DATA_PAGE_SIZE(fs) ) / SPIFFS_DATA_PAGE_SIZE(fs);
+}
+
+s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries) {
+ SPIFFS_API_CHECK_CFG(fs);
+ return map_page_ix_entries * SPIFFS_DATA_PAGE_SIZE(fs);
+}
+
+#endif // SPIFFS_IX_MAP
+
+#if SPIFFS_TEST_VISUALISATION
+s32_t SPIFFS_vis(spiffs *fs) {
+ s32_t res = SPIFFS_OK;
+ SPIFFS_API_CHECK_CFG(fs);
+ SPIFFS_API_CHECK_MOUNT(fs);
+ SPIFFS_LOCK(fs);
+
+ int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+ spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+ spiffs_block_ix bix = 0;
+
+ while (bix < fs->block_count) {
+ // check each object lookup page
+ int obj_lookup_page = 0;
+ int cur_entry = 0;
+
+ while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+ int entry_offset = obj_lookup_page * entries_per_page;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ // check each entry
+ while (res == SPIFFS_OK &&
+ cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) {
+ spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset];
+ if (cur_entry == 0) {
+ spiffs_printf(_SPIPRIbl" ", bix);
+ } else if ((cur_entry & 0x3f) == 0) {
+ spiffs_printf(" ");
+ }
+ if (obj_id == SPIFFS_OBJ_ID_FREE) {
+ spiffs_printf(SPIFFS_TEST_VIS_FREE_STR);
+ } else if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+ spiffs_printf(SPIFFS_TEST_VIS_DELE_STR);
+ } else if (obj_id & SPIFFS_OBJ_ID_IX_FLAG){
+ spiffs_printf(SPIFFS_TEST_VIS_INDX_STR(obj_id));
+ } else {
+ spiffs_printf(SPIFFS_TEST_VIS_DATA_STR(obj_id));
+ }
+ cur_entry++;
+ if ((cur_entry & 0x3f) == 0) {
+ spiffs_printf("\n");
+ }
+ } // per entry
+ obj_lookup_page++;
+ } // per object lookup page
+
+ spiffs_obj_id erase_count;
+ res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0,
+ SPIFFS_ERASE_COUNT_PADDR(fs, bix),
+ sizeof(spiffs_obj_id), (u8_t *)&erase_count);
+ SPIFFS_CHECK_RES(res);
+
+ if (erase_count != (spiffs_obj_id)-1) {
+ spiffs_printf("\tera_cnt: "_SPIPRIi"\n", erase_count);
+ } else {
+ spiffs_printf("\tera_cnt: N/A\n");
+ }
+
+ bix++;
+ } // per block
+
+ spiffs_printf("era_cnt_max: "_SPIPRIi"\n", fs->max_erase_count);
+ spiffs_printf("last_errno: "_SPIPRIi"\n", fs->err_code);
+ spiffs_printf("blocks: "_SPIPRIi"\n", fs->block_count);
+ spiffs_printf("free_blocks: "_SPIPRIi"\n", fs->free_blocks);
+ spiffs_printf("page_alloc: "_SPIPRIi"\n", fs->stats_p_allocated);
+ spiffs_printf("page_delet: "_SPIPRIi"\n", fs->stats_p_deleted);
+ SPIFFS_UNLOCK(fs);
+ u32_t total, used;
+ SPIFFS_info(fs, &total, &used);
+ spiffs_printf("used: "_SPIPRIi" of "_SPIPRIi"\n", used, total);
+ return res;
+}
+#endif
diff --git a/libiot/spiffs/spiffs_nucleus.c b/libiot/spiffs/spiffs_nucleus.c
new file mode 100644
index 0000000..44ba711
--- /dev/null
+++ b/libiot/spiffs/spiffs_nucleus.c
@@ -0,0 +1,2327 @@
+#include "spiffs.h"
+#include "spiffs_nucleus.h"
+
+static s32_t spiffs_page_data_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) {
+ s32_t res = SPIFFS_OK;
+ if (pix == (spiffs_page_ix)-1) {
+ // referring to page 0xffff...., bad object index
+ return SPIFFS_ERR_INDEX_REF_FREE;
+ }
+ if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+ // referring to an object lookup page, bad object index
+ return SPIFFS_ERR_INDEX_REF_LU;
+ }
+ if (pix > SPIFFS_MAX_PAGES(fs)) {
+ // referring to a bad page
+ return SPIFFS_ERR_INDEX_REF_INVALID;
+ }
+#if SPIFFS_PAGE_CHECK
+ spiffs_page_header ph;
+ res = _spiffs_rd(
+ fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ,
+ fd->file_nbr,
+ SPIFFS_PAGE_TO_PADDR(fs, pix),
+ sizeof(spiffs_page_header),
+ (u8_t *)&ph);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_DATA(ph, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, spix);
+#endif
+ return res;
+}
+
+#if !SPIFFS_READ_ONLY
+static s32_t spiffs_page_index_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) {
+ s32_t res = SPIFFS_OK;
+ if (pix == (spiffs_page_ix)-1) {
+ // referring to page 0xffff...., bad object index
+ return SPIFFS_ERR_INDEX_FREE;
+ }
+ if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+ // referring to an object lookup page, bad object index
+ return SPIFFS_ERR_INDEX_LU;
+ }
+ if (pix > SPIFFS_MAX_PAGES(fs)) {
+ // referring to a bad page
+ return SPIFFS_ERR_INDEX_INVALID;
+ }
+#if SPIFFS_PAGE_CHECK
+ spiffs_page_header ph;
+ res = _spiffs_rd(
+ fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr,
+ SPIFFS_PAGE_TO_PADDR(fs, pix),
+ sizeof(spiffs_page_header),
+ (u8_t *)&ph);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_OBJIX(ph, fd->obj_id, spix);
+#endif
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+#if !SPIFFS_CACHE
+
+s32_t spiffs_phys_rd(
+ spiffs *fs,
+ u32_t addr,
+ u32_t len,
+ u8_t *dst) {
+ return SPIFFS_HAL_READ(fs, addr, len, dst);
+}
+
+s32_t spiffs_phys_wr(
+ spiffs *fs,
+ u32_t addr,
+ u32_t len,
+ u8_t *src) {
+ return SPIFFS_HAL_WRITE(fs, addr, len, src);
+}
+
+#endif
+
+#if !SPIFFS_READ_ONLY
+s32_t spiffs_phys_cpy(
+ spiffs *fs,
+ spiffs_file fh,
+ u32_t dst,
+ u32_t src,
+ u32_t len) {
+ (void)fh;
+ s32_t res;
+ u8_t b[SPIFFS_COPY_BUFFER_STACK];
+ while (len > 0) {
+ u32_t chunk_size = MIN(SPIFFS_COPY_BUFFER_STACK, len);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVS, fh, src, chunk_size, b);
+ SPIFFS_CHECK_RES(res);
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVD, fh, dst, chunk_size, b);
+ SPIFFS_CHECK_RES(res);
+ len -= chunk_size;
+ src += chunk_size;
+ dst += chunk_size;
+ }
+ return SPIFFS_OK;
+}
+#endif // !SPIFFS_READ_ONLY
+
+// Find object lookup entry containing given id with visitor.
+// Iterate over object lookup pages in each block until a given object id entry is found.
+// When found, the visitor function is called with block index, entry index and user data.
+// If visitor returns SPIFFS_VIS_CONTINUE, the search goes on. Otherwise, the search will be
+// ended and visitor's return code is returned to caller.
+// If no visitor is given (0) the search returns on first entry with matching object id.
+// If no match is found in all look up, SPIFFS_VIS_END is returned.
+// @param fs the file system
+// @param starting_block the starting block to start search in
+// @param starting_lu_entry the look up index entry to start search in
+// @param flags ored combination of SPIFFS_VIS_CHECK_ID, SPIFFS_VIS_CHECK_PH,
+// SPIFFS_VIS_NO_WRAP
+// @param obj_id argument object id
+// @param v visitor callback function
+// @param user_const_p any const pointer, passed to the callback visitor function
+// @param user_var_p any pointer, passed to the callback visitor function
+// @param block_ix reported block index where match was found
+// @param lu_entry reported look up index where match was found
+s32_t spiffs_obj_lu_find_entry_visitor(
+ spiffs *fs,
+ spiffs_block_ix starting_block,
+ int starting_lu_entry,
+ u8_t flags,
+ spiffs_obj_id obj_id,
+ spiffs_visitor_f v,
+ const void *user_const_p,
+ void *user_var_p,
+ spiffs_block_ix *block_ix,
+ int *lu_entry) {
+ s32_t res = SPIFFS_OK;
+ s32_t entry_count = fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs);
+ spiffs_block_ix cur_block = starting_block;
+ u32_t cur_block_addr = starting_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+
+ spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work;
+ int cur_entry = starting_lu_entry;
+ int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id));
+
+ // wrap initial
+ if (cur_entry > (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) {
+ cur_entry = 0;
+ cur_block++;
+ cur_block_addr = cur_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+ if (cur_block >= fs->block_count) {
+ if (flags & SPIFFS_VIS_NO_WRAP) {
+ return SPIFFS_VIS_END;
+ } else {
+ // block wrap
+ cur_block = 0;
+ cur_block_addr = 0;
+ }
+ }
+ }
+
+ // check each block
+ while (res == SPIFFS_OK && entry_count > 0) {
+ int obj_lookup_page = cur_entry / entries_per_page;
+ // check each object lookup page
+ while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
+ int entry_offset = obj_lookup_page * entries_per_page;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ // check each entry
+ while (res == SPIFFS_OK &&
+ cur_entry - entry_offset < entries_per_page && // for non-last obj lookup pages
+ cur_entry < (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) // for last obj lookup page
+ {
+ if ((flags & SPIFFS_VIS_CHECK_ID) == 0 || obj_lu_buf[cur_entry-entry_offset] == obj_id) {
+ if (block_ix) *block_ix = cur_block;
+ if (lu_entry) *lu_entry = cur_entry;
+ if (v) {
+ res = v(
+ fs,
+ (flags & SPIFFS_VIS_CHECK_PH) ? obj_id : obj_lu_buf[cur_entry-entry_offset],
+ cur_block,
+ cur_entry,
+ user_const_p,
+ user_var_p);
+ if (res == SPIFFS_VIS_COUNTINUE || res == SPIFFS_VIS_COUNTINUE_RELOAD) {
+ if (res == SPIFFS_VIS_COUNTINUE_RELOAD) {
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work);
+ SPIFFS_CHECK_RES(res);
+ }
+ res = SPIFFS_OK;
+ cur_entry++;
+ entry_count--;
+ continue;
+ } else {
+ return res;
+ }
+ } else {
+ return SPIFFS_OK;
+ }
+ }
+ entry_count--;
+ cur_entry++;
+ } // per entry
+ obj_lookup_page++;
+ } // per object lookup page
+ cur_entry = 0;
+ cur_block++;
+ cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+ if (cur_block >= fs->block_count) {
+ if (flags & SPIFFS_VIS_NO_WRAP) {
+ return SPIFFS_VIS_END;
+ } else {
+ // block wrap
+ cur_block = 0;
+ cur_block_addr = 0;
+ }
+ }
+ } // per block
+
+ SPIFFS_CHECK_RES(res);
+
+ return SPIFFS_VIS_END;
+}
+
+#if !SPIFFS_READ_ONLY
+s32_t spiffs_erase_block(
+ spiffs *fs,
+ spiffs_block_ix bix) {
+ s32_t res;
+ u32_t addr = SPIFFS_BLOCK_TO_PADDR(fs, bix);
+ s32_t size = SPIFFS_CFG_LOG_BLOCK_SZ(fs);
+
+ // here we ignore res, just try erasing the block
+ while (size > 0) {
+ SPIFFS_DBG("erase "_SPIPRIad":"_SPIPRIi"\n", addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs));
+ SPIFFS_HAL_ERASE(fs, addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs));
+
+ addr += SPIFFS_CFG_PHYS_ERASE_SZ(fs);
+ size -= SPIFFS_CFG_PHYS_ERASE_SZ(fs);
+ }
+ fs->free_blocks++;
+
+ // register erase count for this block
+ res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0,
+ SPIFFS_ERASE_COUNT_PADDR(fs, bix),
+ sizeof(spiffs_obj_id), (u8_t *)&fs->max_erase_count);
+ SPIFFS_CHECK_RES(res);
+
+#if SPIFFS_USE_MAGIC
+ // finally, write magic
+ spiffs_obj_id magic = SPIFFS_MAGIC(fs, bix);
+ res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0,
+ SPIFFS_MAGIC_PADDR(fs, bix),
+ sizeof(spiffs_obj_id), (u8_t *)&magic);
+ SPIFFS_CHECK_RES(res);
+#endif
+
+ fs->max_erase_count++;
+ if (fs->max_erase_count == SPIFFS_OBJ_ID_IX_FLAG) {
+ fs->max_erase_count = 0;
+ }
+
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
+s32_t spiffs_probe(
+ spiffs_config *cfg) {
+ s32_t res;
+ u32_t paddr;
+ spiffs dummy_fs; // create a dummy fs struct just to be able to use macros
+ memcpy(&dummy_fs.cfg, cfg, sizeof(spiffs_config));
+ dummy_fs.block_count = 0;
+
+ // Read three magics, as one block may be in an aborted erase state.
+ // At least two of these must contain magic and be in decreasing order.
+ spiffs_obj_id magic[3];
+ spiffs_obj_id bix_count[3];
+
+ spiffs_block_ix bix;
+ for (bix = 0; bix < 3; bix++) {
+ paddr = SPIFFS_MAGIC_PADDR(&dummy_fs, bix);
+#if SPIFFS_HAL_CALLBACK_EXTRA
+ // not any proper fs to report here, so callback with null
+ // (cross fingers that no-one gets angry)
+ res = cfg->hal_read_f((void *)0, paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]);
+#else
+ res = cfg->hal_read_f(paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]);
+#endif
+ bix_count[bix] = magic[bix] ^ SPIFFS_MAGIC(&dummy_fs, 0);
+ SPIFFS_CHECK_RES(res);
+ }
+
+ // check that we have sane number of blocks
+ if (bix_count[0] < 3) return SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS;
+ // check that the order is correct, take aborted erases in calculation
+ // first block aborted erase
+ if (magic[0] == (spiffs_obj_id)(-1) && bix_count[1] - bix_count[2] == 1) {
+ return (bix_count[1]+1) * cfg->log_block_size;
+ }
+ // second block aborted erase
+ if (magic[1] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[2] == 2) {
+ return bix_count[0] * cfg->log_block_size;
+ }
+ // third block aborted erase
+ if (magic[2] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[1] == 1) {
+ return bix_count[0] * cfg->log_block_size;
+ }
+ // no block has aborted erase
+ if (bix_count[0] - bix_count[1] == 1 && bix_count[1] - bix_count[2] == 1) {
+ return bix_count[0] * cfg->log_block_size;
+ }
+
+ return SPIFFS_ERR_PROBE_NOT_A_FS;
+}
+#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0
+
+
+static s32_t spiffs_obj_lu_scan_v(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_block_ix bix,
+ int ix_entry,
+ const void *user_const_p,
+ void *user_var_p) {
+ (void)bix;
+ (void)user_const_p;
+ (void)user_var_p;
+ if (obj_id == SPIFFS_OBJ_ID_FREE) {
+ if (ix_entry == 0) {
+ fs->free_blocks++;
+ // todo optimize further, return SPIFFS_NEXT_BLOCK
+ }
+ } else if (obj_id == SPIFFS_OBJ_ID_DELETED) {
+ fs->stats_p_deleted++;
+ } else {
+ fs->stats_p_allocated++;
+ }
+
+ return SPIFFS_VIS_COUNTINUE;
+}
+
+
+// Scans thru all obj lu and counts free, deleted and used pages
+// Find the maximum block erase count
+// Checks magic if enabled
+s32_t spiffs_obj_lu_scan(
+ spiffs *fs) {
+ s32_t res;
+ spiffs_block_ix bix;
+ int entry;
+#if SPIFFS_USE_MAGIC
+ spiffs_block_ix unerased_bix = (spiffs_block_ix)-1;
+#endif
+
+ // find out erase count
+ // if enabled, check magic
+ bix = 0;
+ spiffs_obj_id erase_count_final;
+ spiffs_obj_id erase_count_min = SPIFFS_OBJ_ID_FREE;
+ spiffs_obj_id erase_count_max = 0;
+ while (bix < fs->block_count) {
+#if SPIFFS_USE_MAGIC
+ spiffs_obj_id magic;
+ res = _spiffs_rd(fs,
+ SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_MAGIC_PADDR(fs, bix) ,
+ sizeof(spiffs_obj_id), (u8_t *)&magic);
+
+ SPIFFS_CHECK_RES(res);
+ if (magic != SPIFFS_MAGIC(fs, bix)) {
+ if (unerased_bix == (spiffs_block_ix)-1) {
+ // allow one unerased block as it might be powered down during an erase
+ unerased_bix = bix;
+ } else {
+ // more than one unerased block, bail out
+ SPIFFS_CHECK_RES(SPIFFS_ERR_NOT_A_FS);
+ }
+ }
+#endif
+ spiffs_obj_id erase_count;
+ res = _spiffs_rd(fs,
+ SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_ERASE_COUNT_PADDR(fs, bix) ,
+ sizeof(spiffs_obj_id), (u8_t *)&erase_count);
+ SPIFFS_CHECK_RES(res);
+ if (erase_count != SPIFFS_OBJ_ID_FREE) {
+ erase_count_min = MIN(erase_count_min, erase_count);
+ erase_count_max = MAX(erase_count_max, erase_count);
+ }
+ bix++;
+ }
+
+ if (erase_count_min == 0 && erase_count_max == SPIFFS_OBJ_ID_FREE) {
+ // clean system, set counter to zero
+ erase_count_final = 0;
+ } else if (erase_count_max - erase_count_min > (SPIFFS_OBJ_ID_FREE)/2) {
+ // wrap, take min
+ erase_count_final = erase_count_min+1;
+ } else {
+ erase_count_final = erase_count_max+1;
+ }
+
+ fs->max_erase_count = erase_count_final;
+
+#if SPIFFS_USE_MAGIC
+ if (unerased_bix != (spiffs_block_ix)-1) {
+ // found one unerased block, remedy
+ SPIFFS_DBG("mount: erase block "_SPIPRIbl"\n", bix);
+#if SPIFFS_READ_ONLY
+ res = SPIFFS_ERR_RO_ABORTED_OPERATION;
+#else
+ res = spiffs_erase_block(fs, unerased_bix);
+#endif // SPIFFS_READ_ONLY
+ SPIFFS_CHECK_RES(res);
+ }
+#endif
+
+ // count blocks
+
+ fs->free_blocks = 0;
+ fs->stats_p_allocated = 0;
+ fs->stats_p_deleted = 0;
+
+ res = spiffs_obj_lu_find_entry_visitor(fs,
+ 0,
+ 0,
+ 0,
+ 0,
+ spiffs_obj_lu_scan_v,
+ 0,
+ 0,
+ &bix,
+ &entry);
+
+ if (res == SPIFFS_VIS_END) {
+ res = SPIFFS_OK;
+ }
+
+ SPIFFS_CHECK_RES(res);
+
+ return res;
+}
+
+#if !SPIFFS_READ_ONLY
+// Find free object lookup entry
+// Iterate over object lookup pages in each block until a free object id entry is found
+s32_t spiffs_obj_lu_find_free(
+ spiffs *fs,
+ spiffs_block_ix starting_block,
+ int starting_lu_entry,
+ spiffs_block_ix *block_ix,
+ int *lu_entry) {
+ s32_t res;
+ if (!fs->cleaning && fs->free_blocks < 2) {
+ res = spiffs_gc_quick(fs, 0);
+ if (res == SPIFFS_ERR_NO_DELETED_BLOCKS) {
+ res = SPIFFS_OK;
+ }
+ SPIFFS_CHECK_RES(res);
+ if (fs->free_blocks < 2) {
+ return SPIFFS_ERR_FULL;
+ }
+ }
+ res = spiffs_obj_lu_find_id(fs, starting_block, starting_lu_entry,
+ SPIFFS_OBJ_ID_FREE, block_ix, lu_entry);
+ if (res == SPIFFS_OK) {
+ fs->free_cursor_block_ix = *block_ix;
+ fs->free_cursor_obj_lu_entry = (*lu_entry) + 1;
+ if (*lu_entry == 0) {
+ fs->free_blocks--;
+ }
+ }
+ if (res == SPIFFS_ERR_FULL) {
+ SPIFFS_DBG("fs full\n");
+ }
+
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+// Find object lookup entry containing given id
+// Iterate over object lookup pages in each block until a given object id entry is found
+s32_t spiffs_obj_lu_find_id(
+ spiffs *fs,
+ spiffs_block_ix starting_block,
+ int starting_lu_entry,
+ spiffs_obj_id obj_id,
+ spiffs_block_ix *block_ix,
+ int *lu_entry) {
+ s32_t res = spiffs_obj_lu_find_entry_visitor(
+ fs, starting_block, starting_lu_entry, SPIFFS_VIS_CHECK_ID, obj_id, 0, 0, 0, block_ix, lu_entry);
+ if (res == SPIFFS_VIS_END) {
+ res = SPIFFS_ERR_NOT_FOUND;
+ }
+ return res;
+}
+
+
+static s32_t spiffs_obj_lu_find_id_and_span_v(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_block_ix bix,
+ int ix_entry,
+ const void *user_const_p,
+ void *user_var_p) {
+ s32_t res;
+ spiffs_page_header ph;
+ spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry);
+ res = _spiffs_rd(fs, 0, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_header), (u8_t *)&ph);
+ SPIFFS_CHECK_RES(res);
+ if (ph.obj_id == obj_id &&
+ ph.span_ix == *((spiffs_span_ix*)user_var_p) &&
+ (ph.flags & (SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED)) == SPIFFS_PH_FLAG_DELET &&
+ !((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (ph.flags & SPIFFS_PH_FLAG_IXDELE) == 0 && ph.span_ix == 0) &&
+ (user_const_p == 0 || *((const spiffs_page_ix*)user_const_p) != pix)) {
+ return SPIFFS_OK;
+ } else {
+ return SPIFFS_VIS_COUNTINUE;
+ }
+}
+
+// Find object lookup entry containing given id and span index
+// Iterate over object lookup pages in each block until a given object id entry is found
+s32_t spiffs_obj_lu_find_id_and_span(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_span_ix spix,
+ spiffs_page_ix exclusion_pix,
+ spiffs_page_ix *pix) {
+ s32_t res;
+ spiffs_block_ix bix;
+ int entry;
+
+ res = spiffs_obj_lu_find_entry_visitor(fs,
+ fs->cursor_block_ix,
+ fs->cursor_obj_lu_entry,
+ SPIFFS_VIS_CHECK_ID,
+ obj_id,
+ spiffs_obj_lu_find_id_and_span_v,
+ exclusion_pix ? &exclusion_pix : 0,
+ &spix,
+ &bix,
+ &entry);
+
+ if (res == SPIFFS_VIS_END) {
+ res = SPIFFS_ERR_NOT_FOUND;
+ }
+
+ SPIFFS_CHECK_RES(res);
+
+ if (pix) {
+ *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+ }
+
+ fs->cursor_block_ix = bix;
+ fs->cursor_obj_lu_entry = entry;
+
+ return res;
+}
+
+// Find object lookup entry containing given id and span index in page headers only
+// Iterate over object lookup pages in each block until a given object id entry is found
+s32_t spiffs_obj_lu_find_id_and_span_by_phdr(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_span_ix spix,
+ spiffs_page_ix exclusion_pix,
+ spiffs_page_ix *pix) {
+ s32_t res;
+ spiffs_block_ix bix;
+ int entry;
+
+ res = spiffs_obj_lu_find_entry_visitor(fs,
+ fs->cursor_block_ix,
+ fs->cursor_obj_lu_entry,
+ SPIFFS_VIS_CHECK_PH,
+ obj_id,
+ spiffs_obj_lu_find_id_and_span_v,
+ exclusion_pix ? &exclusion_pix : 0,
+ &spix,
+ &bix,
+ &entry);
+
+ if (res == SPIFFS_VIS_END) {
+ res = SPIFFS_ERR_NOT_FOUND;
+ }
+
+ SPIFFS_CHECK_RES(res);
+
+ if (pix) {
+ *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+ }
+
+ fs->cursor_block_ix = bix;
+ fs->cursor_obj_lu_entry = entry;
+
+ return res;
+}
+
+#if SPIFFS_IX_MAP
+
+// update index map of given fd with given object index data
+static void spiffs_update_ix_map(spiffs *fs,
+ spiffs_fd *fd, spiffs_span_ix objix_spix, spiffs_page_object_ix *objix) {
+#if SPIFFS_SINGLETON
+ (void)fs;
+#endif
+ spiffs_ix_map *map = fd->ix_map;
+ spiffs_span_ix map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix);
+ spiffs_span_ix map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->end_spix);
+
+ // check if updated ix is within map range
+ if (objix_spix < map_objix_start_spix || objix_spix > map_objix_end_spix) {
+ return;
+ }
+
+ // update memory mapped page index buffer to new pages
+
+ // get range of updated object index map data span indices
+ spiffs_span_ix objix_data_spix_start =
+ SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, objix_spix);
+ spiffs_span_ix objix_data_spix_end = objix_data_spix_start +
+ (objix_spix == 0 ? SPIFFS_OBJ_HDR_IX_LEN(fs) : SPIFFS_OBJ_IX_LEN(fs));
+
+ // calc union of object index range and index map range array
+ spiffs_span_ix map_spix = MAX(map->start_spix, objix_data_spix_start);
+ spiffs_span_ix map_spix_end = MIN(map->end_spix + 1, objix_data_spix_end);
+
+ while (map_spix < map_spix_end) {
+ spiffs_page_ix objix_data_pix;
+ if (objix_spix == 0) {
+ // get data page from object index header page
+ objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix_header)))[map_spix];
+ } else {
+ // get data page from object index page
+ objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, map_spix)];
+ }
+
+ if (objix_data_pix == (spiffs_page_ix)-1) {
+ // reached end of object, abort
+ break;
+ }
+
+ map->map_buf[map_spix - map->start_spix] = objix_data_pix;
+ SPIFFS_DBG("map "_SPIPRIid":"_SPIPRIsp" ("_SPIPRIsp"--"_SPIPRIsp") objix.spix:"_SPIPRIsp" to pix "_SPIPRIpg"\n",
+ fd->obj_id, map_spix - map->start_spix,
+ map->start_spix, map->end_spix,
+ objix->p_hdr.span_ix,
+ objix_data_pix);
+
+ map_spix++;
+ }
+}
+
+typedef struct {
+ spiffs_fd *fd;
+ u32_t remaining_objix_pages_to_visit;
+ spiffs_span_ix map_objix_start_spix;
+ spiffs_span_ix map_objix_end_spix;
+} spiffs_ix_map_populate_state;
+
+static s32_t spiffs_populate_ix_map_v(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_block_ix bix,
+ int ix_entry,
+ const void *user_const_p,
+ void *user_var_p) {
+ (void)user_const_p;
+ s32_t res;
+ spiffs_ix_map_populate_state *state = (spiffs_ix_map_populate_state *)user_var_p;
+ spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry);
+
+ // load header to check it
+ spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix), (u8_t *)objix);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_OBJIX(objix->p_hdr, obj_id, objix->p_hdr.span_ix);
+
+ // check if hdr is ok, and if objix range overlap with ix map range
+ if ((objix->p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) ==
+ (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE) &&
+ objix->p_hdr.span_ix >= state->map_objix_start_spix &&
+ objix->p_hdr.span_ix <= state->map_objix_end_spix) {
+ // ok, load rest of object index
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, pix) + sizeof(spiffs_page_object_ix),
+ SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix),
+ (u8_t *)objix + sizeof(spiffs_page_object_ix));
+ SPIFFS_CHECK_RES(res);
+
+ spiffs_update_ix_map(fs, state->fd, objix->p_hdr.span_ix, objix);
+
+ state->remaining_objix_pages_to_visit--;
+ SPIFFS_DBG("map "_SPIPRIid" ("_SPIPRIsp"--"_SPIPRIsp") remaining objix pages "_SPIPRIi"\n",
+ state->fd->obj_id,
+ state->fd->ix_map->start_spix, state->fd->ix_map->end_spix,
+ state->remaining_objix_pages_to_visit);
+ }
+
+ if (res == SPIFFS_OK) {
+ res = state->remaining_objix_pages_to_visit ? SPIFFS_VIS_COUNTINUE : SPIFFS_VIS_END;
+ }
+ return res;
+}
+
+// populates index map, from vector entry start to vector entry end, inclusive
+s32_t spiffs_populate_ix_map(spiffs *fs, spiffs_fd *fd, u32_t vec_entry_start, u32_t vec_entry_end) {
+ s32_t res;
+ spiffs_ix_map *map = fd->ix_map;
+ spiffs_ix_map_populate_state state;
+ vec_entry_start = MIN((map->end_spix - map->start_spix + 1) - 1, (s32_t)vec_entry_start);
+ vec_entry_end = MAX((map->end_spix - map->start_spix + 1) - 1, (s32_t)vec_entry_end);
+ if (vec_entry_start > vec_entry_end) {
+ return SPIFFS_ERR_IX_MAP_BAD_RANGE;
+ }
+ state.map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_start);
+ state.map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_end);
+ state.remaining_objix_pages_to_visit =
+ state.map_objix_end_spix - state.map_objix_start_spix + 1;
+ state.fd = fd;
+
+ res = spiffs_obj_lu_find_entry_visitor(
+ fs,
+ SPIFFS_BLOCK_FOR_PAGE(fs, fd->objix_hdr_pix),
+ SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, fd->objix_hdr_pix),
+ SPIFFS_VIS_CHECK_ID,
+ fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG,
+ spiffs_populate_ix_map_v,
+ 0,
+ &state,
+ 0,
+ 0);
+
+ if (res == SPIFFS_VIS_END) {
+ res = SPIFFS_OK;
+ }
+
+ return res;
+}
+
+#endif
+
+
+#if !SPIFFS_READ_ONLY
+// Allocates a free defined page with given obj_id
+// Occupies object lookup entry and page
+// data may be NULL; where only page header is stored, len and page_offs is ignored
+s32_t spiffs_page_allocate_data(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_page_header *ph,
+ u8_t *data,
+ u32_t len,
+ u32_t page_offs,
+ u8_t finalize,
+ spiffs_page_ix *pix) {
+ s32_t res = SPIFFS_OK;
+ spiffs_block_ix bix;
+ int entry;
+
+ // find free entry
+ res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry);
+ SPIFFS_CHECK_RES(res);
+
+ // occupy page in object lookup
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id);
+ SPIFFS_CHECK_RES(res);
+
+ fs->stats_p_allocated++;
+
+ // write page header
+ ph->flags &= ~SPIFFS_PH_FLAG_USED;
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_header), (u8_t*)ph);
+ SPIFFS_CHECK_RES(res);
+
+ // write page data
+ if (data) {
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ 0,SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + sizeof(spiffs_page_header) + page_offs, len, data);
+ SPIFFS_CHECK_RES(res);
+ }
+
+ // finalize header if necessary
+ if (finalize && (ph->flags & SPIFFS_PH_FLAG_FINAL)) {
+ ph->flags &= ~SPIFFS_PH_FLAG_FINAL;
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + offsetof(spiffs_page_header, flags),
+ sizeof(u8_t),
+ (u8_t *)&ph->flags);
+ SPIFFS_CHECK_RES(res);
+ }
+
+ // return written page
+ if (pix) {
+ *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+ }
+
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+#if !SPIFFS_READ_ONLY
+// Moves a page from src to a free page and finalizes it. Updates page index. Page data is given in param page.
+// If page data is null, provided header is used for metainfo and page data is physically copied.
+s32_t spiffs_page_move(
+ spiffs *fs,
+ spiffs_file fh,
+ u8_t *page_data,
+ spiffs_obj_id obj_id,
+ spiffs_page_header *page_hdr,
+ spiffs_page_ix src_pix,
+ spiffs_page_ix *dst_pix) {
+ s32_t res;
+ u8_t was_final = 0;
+ spiffs_page_header *p_hdr;
+ spiffs_block_ix bix;
+ int entry;
+ spiffs_page_ix free_pix;
+
+ // find free entry
+ res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry);
+ SPIFFS_CHECK_RES(res);
+ free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+
+ if (dst_pix) *dst_pix = free_pix;
+
+ p_hdr = page_data ? (spiffs_page_header *)page_data : page_hdr;
+ if (page_data) {
+ // got page data
+ was_final = (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) == 0;
+ // write unfinalized page
+ p_hdr->flags |= SPIFFS_PH_FLAG_FINAL;
+ p_hdr->flags &= ~SPIFFS_PH_FLAG_USED;
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), page_data);
+ } else {
+ // copy page data
+ res = spiffs_phys_cpy(fs, fh, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_PAGE_TO_PADDR(fs, src_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs));
+ }
+ SPIFFS_CHECK_RES(res);
+
+ // mark entry in destination object lookup
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix),
+ sizeof(spiffs_obj_id),
+ (u8_t *)&obj_id);
+ SPIFFS_CHECK_RES(res);
+
+ fs->stats_p_allocated++;
+
+ if (was_final) {
+ // mark finalized in destination page
+ p_hdr->flags &= ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_USED);
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ fh,
+ SPIFFS_PAGE_TO_PADDR(fs, free_pix) + offsetof(spiffs_page_header, flags),
+ sizeof(u8_t),
+ (u8_t *)&p_hdr->flags);
+ SPIFFS_CHECK_RES(res);
+ }
+ // mark source deleted
+ res = spiffs_page_delete(fs, src_pix);
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+#if !SPIFFS_READ_ONLY
+// Deletes a page and removes it from object lookup.
+s32_t spiffs_page_delete(
+ spiffs *fs,
+ spiffs_page_ix pix) {
+ s32_t res;
+ spiffs_page_header hdr;
+ hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED);
+ // mark deleted entry in source object lookup
+ spiffs_obj_id d_obj_id = SPIFFS_OBJ_ID_DELETED;
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_DELE,
+ 0,
+ SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_page_ix),
+ sizeof(spiffs_obj_id),
+ (u8_t *)&d_obj_id);
+ SPIFFS_CHECK_RES(res);
+
+ fs->stats_p_deleted++;
+ fs->stats_p_allocated--;
+
+ // mark deleted in source page
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_DELE,
+ 0,
+ SPIFFS_PAGE_TO_PADDR(fs, pix) + offsetof(spiffs_page_header, flags),
+ sizeof(u8_t),
+ (u8_t *)&hdr.flags);
+
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+#if !SPIFFS_READ_ONLY
+// Create an object index header page with empty index and undefined length
+s32_t spiffs_object_create(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ const u8_t name[],
+ const u8_t meta[],
+ spiffs_obj_type type,
+ spiffs_page_ix *objix_hdr_pix) {
+ s32_t res = SPIFFS_OK;
+ spiffs_block_ix bix;
+ spiffs_page_object_ix_header oix_hdr;
+ int entry;
+
+ res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs));
+ SPIFFS_CHECK_RES(res);
+
+ obj_id |= SPIFFS_OBJ_ID_IX_FLAG;
+
+ // find free entry
+ res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_DBG("create: found free page @ "_SPIPRIpg" bix:"_SPIPRIbl" entry:"_SPIPRIsp"\n", (spiffs_page_ix)SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), bix, entry);
+
+ // occupy page in object lookup
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id);
+ SPIFFS_CHECK_RES(res);
+
+ fs->stats_p_allocated++;
+
+ // write empty object index page
+ oix_hdr.p_hdr.obj_id = obj_id;
+ oix_hdr.p_hdr.span_ix = 0;
+ oix_hdr.p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED);
+ oix_hdr.type = type;
+ oix_hdr.size = SPIFFS_UNDEFINED_LEN; // keep ones so we can update later without wasting this page
+ strncpy((char*)oix_hdr.name, (const char*)name, SPIFFS_OBJ_NAME_LEN);
+#if SPIFFS_OBJ_META_LEN
+ if (meta) {
+ memcpy(oix_hdr.meta, meta, SPIFFS_OBJ_META_LEN);
+ } else {
+ memset(oix_hdr.meta, 0xff, SPIFFS_OBJ_META_LEN);
+ }
+#else
+ (void) meta;
+#endif
+
+ // update page
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_object_ix_header), (u8_t*)&oix_hdr);
+
+ SPIFFS_CHECK_RES(res);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&oix_hdr,
+ SPIFFS_EV_IX_NEW, obj_id, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), SPIFFS_UNDEFINED_LEN);
+
+ if (objix_hdr_pix) {
+ *objix_hdr_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+ }
+
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+#if !SPIFFS_READ_ONLY
+// update object index header with any combination of name/size/index
+// new_objix_hdr_data may be null, if so the object index header page is loaded
+// name may be null, if so name is not changed
+// size may be null, if so size is not changed
+s32_t spiffs_object_update_index_hdr(
+ spiffs *fs,
+ spiffs_fd *fd,
+ spiffs_obj_id obj_id,
+ spiffs_page_ix objix_hdr_pix,
+ u8_t *new_objix_hdr_data,
+ const u8_t name[],
+ const u8_t meta[],
+ u32_t size,
+ spiffs_page_ix *new_pix) {
+ s32_t res = SPIFFS_OK;
+ spiffs_page_object_ix_header *objix_hdr;
+ spiffs_page_ix new_objix_hdr_pix;
+
+ obj_id |= SPIFFS_OBJ_ID_IX_FLAG;
+
+ if (new_objix_hdr_data) {
+ // object index header page already given to us, no need to load it
+ objix_hdr = (spiffs_page_object_ix_header *)new_objix_hdr_data;
+ } else {
+ // read object index header page
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ objix_hdr = (spiffs_page_object_ix_header *)fs->work;
+ }
+
+ SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, obj_id, 0);
+
+ // change name
+ if (name) {
+ strncpy((char*)objix_hdr->name, (const char*)name, SPIFFS_OBJ_NAME_LEN);
+ }
+#if SPIFFS_OBJ_META_LEN
+ if (meta) {
+ memcpy(objix_hdr->meta, meta, SPIFFS_OBJ_META_LEN);
+ }
+#else
+ (void) meta;
+#endif
+ if (size) {
+ objix_hdr->size = size;
+ }
+
+ // move and update page
+ res = spiffs_page_move(fs, fd == 0 ? 0 : fd->file_nbr, (u8_t*)objix_hdr, obj_id, 0, objix_hdr_pix, &new_objix_hdr_pix);
+
+ if (res == SPIFFS_OK) {
+ if (new_pix) {
+ *new_pix = new_objix_hdr_pix;
+ }
+ // callback on object index update
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr,
+ new_objix_hdr_data ? SPIFFS_EV_IX_UPD : SPIFFS_EV_IX_UPD_HDR,
+ obj_id, objix_hdr->p_hdr.span_ix, new_objix_hdr_pix, objix_hdr->size);
+ if (fd) fd->objix_hdr_pix = new_objix_hdr_pix; // if this is not in the registered cluster
+ }
+
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+void spiffs_cb_object_event(
+ spiffs *fs,
+ spiffs_page_object_ix *objix,
+ int ev,
+ spiffs_obj_id obj_id_raw,
+ spiffs_span_ix spix,
+ spiffs_page_ix new_pix,
+ u32_t new_size) {
+#if SPIFFS_IX_MAP == 0
+ (void)objix;
+#endif
+ // update index caches in all file descriptors
+ spiffs_obj_id obj_id = obj_id_raw & ~SPIFFS_OBJ_ID_IX_FLAG;
+ u32_t i;
+ spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+ for (i = 0; i < fs->fd_count; i++) {
+ spiffs_fd *cur_fd = &fds[i];
+#if SPIFFS_TEMPORAL_FD_CACHE
+ if (cur_fd->score == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue;
+#else
+ if (cur_fd->file_nbr == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue;
+#endif
+ if (spix == 0) {
+ if (ev != SPIFFS_EV_IX_DEL) {
+ SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" objix_hdr_pix to "_SPIPRIpg", size:"_SPIPRIi"\n", cur_fd->file_nbr, cur_fd->obj_id, new_pix, new_size);
+ cur_fd->objix_hdr_pix = new_pix;
+ if (new_size != 0) {
+ cur_fd->size = new_size;
+ }
+ } else {
+ cur_fd->file_nbr = 0;
+ cur_fd->obj_id = SPIFFS_OBJ_ID_DELETED;
+ }
+ }
+ if (cur_fd->cursor_objix_spix == spix) {
+ if (ev != SPIFFS_EV_IX_DEL) {
+ SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp" objix_pix to "_SPIPRIpg"\n", cur_fd->file_nbr, cur_fd->obj_id, spix, new_pix);
+ cur_fd->cursor_objix_pix = new_pix;
+ } else {
+ cur_fd->cursor_objix_pix = 0;
+ }
+ }
+ }
+
+#if SPIFFS_IX_MAP
+
+ // update index maps
+ if (ev == SPIFFS_EV_IX_UPD || ev == SPIFFS_EV_IX_NEW) {
+ for (i = 0; i < fs->fd_count; i++) {
+ spiffs_fd *cur_fd = &fds[i];
+ // check fd opened, having ix map, match obj id
+ if (cur_fd->file_nbr == 0 ||
+ cur_fd->ix_map == 0 ||
+ (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue;
+ SPIFFS_DBG(" callback: map ix update fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp"\n", cur_fd->file_nbr, cur_fd->obj_id, spix);
+ spiffs_update_ix_map(fs, cur_fd, spix, objix);
+ }
+ }
+
+#endif
+
+ // callback to user if object index header
+ if (fs->file_cb_f && spix == 0 && (obj_id_raw & SPIFFS_OBJ_ID_IX_FLAG)) {
+ spiffs_fileop_type op;
+ if (ev == SPIFFS_EV_IX_NEW) {
+ op = SPIFFS_CB_CREATED;
+ } else if (ev == SPIFFS_EV_IX_UPD ||
+ ev == SPIFFS_EV_IX_MOV ||
+ ev == SPIFFS_EV_IX_UPD_HDR) {
+ op = SPIFFS_CB_UPDATED;
+ } else if (ev == SPIFFS_EV_IX_DEL) {
+ op = SPIFFS_CB_DELETED;
+ } else {
+ SPIFFS_DBG(" callback: WARNING unknown callback event "_SPIPRIi"\n", ev);
+ return; // bail out
+ }
+ fs->file_cb_f(fs, op, obj_id, new_pix);
+ }
+}
+
+// Open object by id
+s32_t spiffs_object_open_by_id(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_fd *fd,
+ spiffs_flags flags,
+ spiffs_mode mode) {
+ s32_t res = SPIFFS_OK;
+ spiffs_page_ix pix;
+
+ res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
+ SPIFFS_CHECK_RES(res);
+
+ res = spiffs_object_open_by_page(fs, pix, fd, flags, mode);
+
+ return res;
+}
+
+// Open object by page index
+s32_t spiffs_object_open_by_page(
+ spiffs *fs,
+ spiffs_page_ix pix,
+ spiffs_fd *fd,
+ spiffs_flags flags,
+ spiffs_mode mode) {
+ (void)mode;
+ s32_t res = SPIFFS_OK;
+ spiffs_page_object_ix_header oix_hdr;
+ spiffs_obj_id obj_id;
+
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&oix_hdr);
+ SPIFFS_CHECK_RES(res);
+
+ spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(fs, pix);
+ int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix);
+
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ,
+ 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t *)&obj_id);
+
+ fd->fs = fs;
+ fd->objix_hdr_pix = pix;
+ fd->size = oix_hdr.size;
+ fd->offset = 0;
+ fd->cursor_objix_pix = pix;
+ fd->cursor_objix_spix = 0;
+ fd->obj_id = obj_id;
+ fd->flags = flags;
+
+ SPIFFS_VALIDATE_OBJIX(oix_hdr.p_hdr, fd->obj_id, 0);
+
+ SPIFFS_DBG("open: fd "_SPIPRIfd" is obj id "_SPIPRIid"\n", fd->file_nbr, fd->obj_id);
+
+ return res;
+}
+
+#if !SPIFFS_READ_ONLY
+// Append to object
+// keep current object index (header) page in fs->work buffer
+s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) {
+ spiffs *fs = fd->fs;
+ s32_t res = SPIFFS_OK;
+ u32_t written = 0;
+
+ SPIFFS_DBG("append: "_SPIPRIi" bytes @ offs "_SPIPRIi" of size "_SPIPRIi"\n", len, offset, fd->size);
+
+ if (offset > fd->size) {
+ SPIFFS_DBG("append: offset reversed to size\n");
+ offset = fd->size;
+ }
+
+ res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); // add an extra page of data worth for meta
+ if (res != SPIFFS_OK) {
+ SPIFFS_DBG("append: gc check fail "_SPIPRIi"\n", res);
+ }
+ SPIFFS_CHECK_RES(res);
+
+ spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work;
+ spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work;
+ spiffs_page_header p_hdr;
+
+ spiffs_span_ix cur_objix_spix = 0;
+ spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1;
+ spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix;
+ spiffs_page_ix new_objix_hdr_page;
+
+ spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs);
+ spiffs_page_ix data_page;
+ u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs);
+
+ // write all data
+ while (res == SPIFFS_OK && written < len) {
+ // calculate object index page span index
+ cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+
+ // handle storing and loading of object indices
+ if (cur_objix_spix != prev_objix_spix) {
+ // new object index page
+ // within this clause we return directly if something fails, object index mess-up
+ if (written > 0) {
+ // store previous object index page, unless first pass
+ SPIFFS_DBG("append: "_SPIPRIid" store objix "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id,
+ cur_objix_pix, prev_objix_spix, written);
+ if (prev_objix_spix == 0) {
+ // this is an update to object index header page
+ objix_hdr->size = offset+written;
+ if (offset == 0) {
+ // was an empty object, update same page (size was 0xffffffff)
+ res = spiffs_page_index_check(fs, fd, cur_objix_pix, 0);
+ SPIFFS_CHECK_RES(res);
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ } else {
+ // was a nonempty object, update to new page
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_DBG("append: "_SPIPRIid" store new objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id,
+ new_objix_hdr_page, 0, written);
+ }
+ } else {
+ // this is an update to an object index page
+ res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix);
+ SPIFFS_CHECK_RES(res);
+
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work,
+ SPIFFS_EV_IX_UPD,fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0);
+ // update length in object index header page
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_DBG("append: "_SPIPRIid" store new size I "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id,
+ offset+written, new_objix_hdr_page, 0, written);
+ }
+ fd->size = offset+written;
+ fd->offset = offset+written;
+ }
+
+ // create or load new object index page
+ if (cur_objix_spix == 0) {
+ // load object index header page, must always exist
+ SPIFFS_DBG("append: "_SPIPRIid" load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", fd->obj_id, cur_objix_pix, cur_objix_spix);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+ } else {
+ spiffs_span_ix len_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, (fd->size-1)/SPIFFS_DATA_PAGE_SIZE(fs));
+ // on subsequent passes, create a new object index page
+ if (written > 0 || cur_objix_spix > len_objix_spix) {
+ p_hdr.obj_id = fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG;
+ p_hdr.span_ix = cur_objix_spix;
+ p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX);
+ res = spiffs_page_allocate_data(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG,
+ &p_hdr, 0, 0, 0, 1, &cur_objix_pix);
+ SPIFFS_CHECK_RES(res);
+ // quick "load" of new object index page
+ memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs));
+ memcpy(fs->work, &p_hdr, sizeof(spiffs_page_header));
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work,
+ SPIFFS_EV_IX_NEW, fd->obj_id, cur_objix_spix, cur_objix_pix, 0);
+ SPIFFS_DBG("append: "_SPIPRIid" create objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id
+ , cur_objix_pix, cur_objix_spix, written);
+ } else {
+ // on first pass, we load existing object index page
+ spiffs_page_ix pix;
+ SPIFFS_DBG("append: "_SPIPRIid" find objix span_ix:"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix);
+ if (fd->cursor_objix_spix == cur_objix_spix) {
+ pix = fd->cursor_objix_pix;
+ } else {
+ res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix);
+ SPIFFS_CHECK_RES(res);
+ }
+ SPIFFS_DBG("append: "_SPIPRIid" found object index at page "_SPIPRIpg" [fd size "_SPIPRIi"]\n", fd->obj_id, pix, fd->size);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+ cur_objix_pix = pix;
+ }
+ fd->cursor_objix_pix = cur_objix_pix;
+ fd->cursor_objix_spix = cur_objix_spix;
+ fd->offset = offset+written;
+ fd->size = offset+written;
+ }
+ prev_objix_spix = cur_objix_spix;
+ }
+
+ // write data
+ u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs);
+ if (page_offs == 0) {
+ // at beginning of a page, allocate and write a new page of data
+ p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+ p_hdr.span_ix = data_spix;
+ p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL); // finalize immediately
+ res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+ &p_hdr, &data[written], to_write, page_offs, 1, &data_page);
+ SPIFFS_DBG("append: "_SPIPRIid" store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id,
+ data_page, data_spix, page_offs, to_write, written);
+ } else {
+ // append to existing page, fill out free data in existing page
+ if (cur_objix_spix == 0) {
+ // get data page from object index header page
+ data_page = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix];
+ } else {
+ // get data page from object index page
+ data_page = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)];
+ }
+
+ res = spiffs_page_data_check(fs, fd, data_page, data_spix);
+ SPIFFS_CHECK_RES(res);
+
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, data_page) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]);
+ SPIFFS_DBG("append: "_SPIPRIid" store to existing data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id
+ , data_page, data_spix, page_offs, to_write, written);
+ }
+
+ if (res != SPIFFS_OK) break;
+
+ // update memory representation of object index page with new data page
+ if (cur_objix_spix == 0) {
+ // update object index header page
+ ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_page;
+ SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", fd->obj_id
+ , data_page, data_spix);
+ objix_hdr->size = offset+written;
+ } else {
+ // update object index page
+ ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_page;
+ SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", fd->obj_id
+ , data_page, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix));
+ }
+
+ // update internals
+ page_offs = 0;
+ data_spix++;
+ written += to_write;
+ } // while all data
+
+ fd->size = offset+written;
+ fd->offset = offset+written;
+ fd->cursor_objix_pix = cur_objix_pix;
+ fd->cursor_objix_spix = cur_objix_spix;
+
+ // finalize updated object indices
+ s32_t res2 = SPIFFS_OK;
+ if (cur_objix_spix != 0) {
+ // wrote beyond object index header page
+ // write last modified object index page, unless object header index page
+ SPIFFS_DBG("append: "_SPIPRIid" store objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id,
+ cur_objix_pix, cur_objix_spix, written);
+
+ res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix);
+ SPIFFS_CHECK_RES(res2);
+
+ res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res2);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work,
+ SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0);
+
+ // update size in object header index page
+ res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page);
+ SPIFFS_DBG("append: "_SPIPRIid" store new size II "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi", res "_SPIPRIi"\n", fd->obj_id
+ , offset+written, new_objix_hdr_page, 0, written, res2);
+ SPIFFS_CHECK_RES(res2);
+ } else {
+ // wrote within object index header page
+ if (offset == 0) {
+ // wrote to empty object - simply update size and write whole page
+ objix_hdr->size = offset+written;
+ SPIFFS_DBG("append: "_SPIPRIid" store fresh objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id
+ , cur_objix_pix, cur_objix_spix, written);
+
+ res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix);
+ SPIFFS_CHECK_RES(res2);
+
+ res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res2);
+ // callback on object index update
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work,
+ SPIFFS_EV_IX_UPD_HDR, fd->obj_id, objix_hdr->p_hdr.span_ix, cur_objix_pix, objix_hdr->size);
+ } else {
+ // modifying object index header page, update size and make new copy
+ res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page);
+ SPIFFS_DBG("append: "_SPIPRIid" store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id
+ , new_objix_hdr_page, 0, written);
+ SPIFFS_CHECK_RES(res2);
+ }
+ }
+
+ return res;
+} // spiffs_object_append
+#endif // !SPIFFS_READ_ONLY
+
+#if !SPIFFS_READ_ONLY
+// Modify object
+// keep current object index (header) page in fs->work buffer
+s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) {
+ spiffs *fs = fd->fs;
+ s32_t res = SPIFFS_OK;
+ u32_t written = 0;
+
+ res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs));
+ SPIFFS_CHECK_RES(res);
+
+ spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work;
+ spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work;
+ spiffs_page_header p_hdr;
+
+ spiffs_span_ix cur_objix_spix = 0;
+ spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1;
+ spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix;
+ spiffs_page_ix new_objix_hdr_pix;
+
+ spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs);
+ spiffs_page_ix data_pix;
+ u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs);
+
+
+ // write all data
+ while (res == SPIFFS_OK && written < len) {
+ // calculate object index page span index
+ cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+
+ // handle storing and loading of object indices
+ if (cur_objix_spix != prev_objix_spix) {
+ // new object index page
+ // within this clause we return directly if something fails, object index mess-up
+ if (written > 0) {
+ // store previous object index (header) page, unless first pass
+ if (prev_objix_spix == 0) {
+ // store previous object index header page
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix);
+ SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written);
+ SPIFFS_CHECK_RES(res);
+ } else {
+ // store new version of previous object index page
+ spiffs_page_ix new_objix_pix;
+
+ res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix);
+ SPIFFS_CHECK_RES(res);
+
+ res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix);
+ SPIFFS_DBG("modify: store previous modified objix page, "_SPIPRIid":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, objix->p_hdr.span_ix, written);
+ SPIFFS_CHECK_RES(res);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix,
+ SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0);
+ }
+ }
+
+ // load next object index page
+ if (cur_objix_spix == 0) {
+ // load object index header page, must exist
+ SPIFFS_DBG("modify: load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", cur_objix_pix, cur_objix_spix);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+ } else {
+ // load existing object index page on first pass
+ spiffs_page_ix pix;
+ SPIFFS_DBG("modify: find objix span_ix:"_SPIPRIsp"\n", cur_objix_spix);
+ if (fd->cursor_objix_spix == cur_objix_spix) {
+ pix = fd->cursor_objix_pix;
+ } else {
+ res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix);
+ SPIFFS_CHECK_RES(res);
+ }
+ SPIFFS_DBG("modify: found object index at page "_SPIPRIpg"\n", pix);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+ cur_objix_pix = pix;
+ }
+ fd->cursor_objix_pix = cur_objix_pix;
+ fd->cursor_objix_spix = cur_objix_spix;
+ fd->offset = offset+written;
+ prev_objix_spix = cur_objix_spix;
+ }
+
+ // write partial data
+ u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs);
+ spiffs_page_ix orig_data_pix;
+ if (cur_objix_spix == 0) {
+ // get data page from object index header page
+ orig_data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix];
+ } else {
+ // get data page from object index page
+ orig_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)];
+ }
+
+ p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+ p_hdr.span_ix = data_spix;
+ p_hdr.flags = 0xff;
+ if (page_offs == 0 && to_write == SPIFFS_DATA_PAGE_SIZE(fs)) {
+ // a full page, allocate and write a new page of data
+ res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+ &p_hdr, &data[written], to_write, page_offs, 1, &data_pix);
+ SPIFFS_DBG("modify: store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", data_pix, data_spix, page_offs, to_write, written);
+ } else {
+ // write to existing page, allocate new and copy unmodified data
+
+ res = spiffs_page_data_check(fs, fd, orig_data_pix, data_spix);
+ SPIFFS_CHECK_RES(res);
+
+ res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+ &p_hdr, 0, 0, 0, 0, &data_pix);
+ if (res != SPIFFS_OK) break;
+
+ // copy unmodified data
+ if (page_offs > 0) {
+ // before modification
+ res = spiffs_phys_cpy(fs, fd->file_nbr,
+ SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header),
+ SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header),
+ page_offs);
+ if (res != SPIFFS_OK) break;
+ }
+ if (page_offs + to_write < SPIFFS_DATA_PAGE_SIZE(fs)) {
+ // after modification
+ res = spiffs_phys_cpy(fs, fd->file_nbr,
+ SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs + to_write,
+ SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header) + page_offs + to_write,
+ SPIFFS_DATA_PAGE_SIZE(fs) - (page_offs + to_write));
+ if (res != SPIFFS_OK) break;
+ }
+
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ fd->file_nbr,
+ SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]);
+ if (res != SPIFFS_OK) break;
+ p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL;
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ fd->file_nbr,
+ SPIFFS_PAGE_TO_PADDR(fs, data_pix) + offsetof(spiffs_page_header, flags),
+ sizeof(u8_t),
+ (u8_t *)&p_hdr.flags);
+ if (res != SPIFFS_OK) break;
+
+ SPIFFS_DBG("modify: store to existing data page, src:"_SPIPRIpg", dst:"_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", orig_data_pix, data_pix, data_spix, page_offs, to_write, written);
+ }
+
+ // delete original data page
+ res = spiffs_page_delete(fs, orig_data_pix);
+ if (res != SPIFFS_OK) break;
+ // update memory representation of object index page with new data page
+ if (cur_objix_spix == 0) {
+ // update object index header page
+ ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_pix;
+ SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", data_pix, data_spix);
+ } else {
+ // update object index page
+ ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_pix;
+ SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix));
+ }
+
+ // update internals
+ page_offs = 0;
+ data_spix++;
+ written += to_write;
+ } // while all data
+
+ fd->offset = offset+written;
+ fd->cursor_objix_pix = cur_objix_pix;
+ fd->cursor_objix_spix = cur_objix_spix;
+
+ // finalize updated object indices
+ s32_t res2 = SPIFFS_OK;
+ if (cur_objix_spix != 0) {
+ // wrote beyond object index header page
+ // write last modified object index page
+ // move and update page
+ spiffs_page_ix new_objix_pix;
+
+ res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix);
+ SPIFFS_CHECK_RES(res2);
+
+ res2 = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix);
+ SPIFFS_DBG("modify: store modified objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, cur_objix_spix, written);
+ fd->cursor_objix_pix = new_objix_pix;
+ fd->cursor_objix_spix = cur_objix_spix;
+ SPIFFS_CHECK_RES(res2);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix,
+ SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0);
+
+ } else {
+ // wrote within object index header page
+ res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix);
+ SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written);
+ SPIFFS_CHECK_RES(res2);
+ }
+
+ return res;
+} // spiffs_object_modify
+#endif // !SPIFFS_READ_ONLY
+
+static s32_t spiffs_object_find_object_index_header_by_name_v(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_block_ix bix,
+ int ix_entry,
+ const void *user_const_p,
+ void *user_var_p) {
+ (void)user_var_p;
+ s32_t res;
+ spiffs_page_object_ix_header objix_hdr;
+ spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry);
+ if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED ||
+ (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) {
+ return SPIFFS_VIS_COUNTINUE;
+ }
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr);
+ SPIFFS_CHECK_RES(res);
+ if (objix_hdr.p_hdr.span_ix == 0 &&
+ (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) ==
+ (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) {
+ if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) {
+ return SPIFFS_OK;
+ }
+ }
+
+ return SPIFFS_VIS_COUNTINUE;
+}
+
+// Finds object index header page by name
+s32_t spiffs_object_find_object_index_header_by_name(
+ spiffs *fs,
+ const u8_t name[SPIFFS_OBJ_NAME_LEN],
+ spiffs_page_ix *pix) {
+ s32_t res;
+ spiffs_block_ix bix;
+ int entry;
+
+ res = spiffs_obj_lu_find_entry_visitor(fs,
+ fs->cursor_block_ix,
+ fs->cursor_obj_lu_entry,
+ 0,
+ 0,
+ spiffs_object_find_object_index_header_by_name_v,
+ name,
+ 0,
+ &bix,
+ &entry);
+
+ if (res == SPIFFS_VIS_END) {
+ res = SPIFFS_ERR_NOT_FOUND;
+ }
+ SPIFFS_CHECK_RES(res);
+
+ if (pix) {
+ *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry);
+ }
+
+ fs->cursor_block_ix = bix;
+ fs->cursor_obj_lu_entry = entry;
+
+ return res;
+}
+
+#if !SPIFFS_READ_ONLY
+// Truncates object to new size. If new size is null, object may be removed totally
+s32_t spiffs_object_truncate(
+ spiffs_fd *fd,
+ u32_t new_size,
+ u8_t remove_full) {
+ s32_t res = SPIFFS_OK;
+ spiffs *fs = fd->fs;
+
+ if ((fd->size == SPIFFS_UNDEFINED_LEN || fd->size == 0) && !remove_full) {
+ // no op
+ return res;
+ }
+
+ // need 2 pages if not removing: object index page + possibly chopped data page
+ if (remove_full == 0) {
+ res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs) * 2);
+ SPIFFS_CHECK_RES(res);
+ }
+
+ spiffs_page_ix objix_pix = fd->objix_hdr_pix;
+ spiffs_span_ix data_spix = (fd->size > 0 ? fd->size-1 : 0) / SPIFFS_DATA_PAGE_SIZE(fs);
+ u32_t cur_size = fd->size == (u32_t)SPIFFS_UNDEFINED_LEN ? 0 : fd->size ;
+ spiffs_span_ix cur_objix_spix = 0;
+ spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1;
+ spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work;
+ spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work;
+ spiffs_page_ix data_pix;
+ spiffs_page_ix new_objix_hdr_pix;
+
+ // before truncating, check if object is to be fully removed and mark this
+ if (remove_full && new_size == 0) {
+ u8_t flags = ~( SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE);
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, fd->objix_hdr_pix) + offsetof(spiffs_page_header, flags),
+ sizeof(u8_t),
+ (u8_t *)&flags);
+ SPIFFS_CHECK_RES(res);
+ }
+
+ // delete from end of object until desired len is reached
+ while (cur_size > new_size) {
+ cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+
+ // put object index for current data span index in work buffer
+ if (prev_objix_spix != cur_objix_spix) {
+ if (prev_objix_spix != (spiffs_span_ix)-1) {
+ // remove previous object index page
+ SPIFFS_DBG("truncate: delete objix page "_SPIPRIpg":"_SPIPRIsp"\n", objix_pix, prev_objix_spix);
+
+ res = spiffs_page_index_check(fs, fd, objix_pix, prev_objix_spix);
+ SPIFFS_CHECK_RES(res);
+
+ res = spiffs_page_delete(fs, objix_pix);
+ SPIFFS_CHECK_RES(res);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0,
+ SPIFFS_EV_IX_DEL, fd->obj_id, objix->p_hdr.span_ix, objix_pix, 0);
+ if (prev_objix_spix > 0) {
+ // Update object index header page, unless we totally want to remove the file.
+ // If fully removing, we're not keeping consistency as good as when storing the header between chunks,
+ // would we be aborted. But when removing full files, a crammed system may otherwise
+ // report ERR_FULL a la windows. We cannot have that.
+ // Hence, take the risk - if aborted, a file check would free the lost pages and mend things
+ // as the file is marked as fully deleted in the beginning.
+ if (remove_full == 0) {
+ SPIFFS_DBG("truncate: update objix hdr page "_SPIPRIpg":"_SPIPRIsp" to size "_SPIPRIi"\n", fd->objix_hdr_pix, prev_objix_spix, cur_size);
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix);
+ SPIFFS_CHECK_RES(res);
+ }
+ fd->size = cur_size;
+ }
+ }
+ // load current object index (header) page
+ if (cur_objix_spix == 0) {
+ objix_pix = fd->objix_hdr_pix;
+ } else {
+ res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix);
+ SPIFFS_CHECK_RES(res);
+ }
+
+ SPIFFS_DBG("truncate: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix);
+ fd->cursor_objix_pix = objix_pix;
+ fd->cursor_objix_spix = cur_objix_spix;
+ fd->offset = cur_size;
+
+ prev_objix_spix = cur_objix_spix;
+ }
+
+ if (cur_objix_spix == 0) {
+ // get data page from object index header page
+ data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix];
+ ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = SPIFFS_OBJ_ID_FREE;
+ } else {
+ // get data page from object index page
+ data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)];
+ ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = SPIFFS_OBJ_ID_FREE;
+ }
+
+ SPIFFS_DBG("truncate: got data pix "_SPIPRIpg"\n", data_pix);
+
+ if (new_size == 0 || remove_full || cur_size - new_size >= SPIFFS_DATA_PAGE_SIZE(fs)) {
+ // delete full data page
+ res = spiffs_page_data_check(fs, fd, data_pix, data_spix);
+ if (res != SPIFFS_ERR_DELETED && res != SPIFFS_OK && res != SPIFFS_ERR_INDEX_REF_FREE) {
+ SPIFFS_DBG("truncate: err validating data pix "_SPIPRIi"\n", res);
+ break;
+ }
+
+ if (res == SPIFFS_OK) {
+ res = spiffs_page_delete(fs, data_pix);
+ if (res != SPIFFS_OK) {
+ SPIFFS_DBG("truncate: err deleting data pix "_SPIPRIi"\n", res);
+ break;
+ }
+ } else if (res == SPIFFS_ERR_DELETED || res == SPIFFS_ERR_INDEX_REF_FREE) {
+ res = SPIFFS_OK;
+ }
+
+ // update current size
+ if (cur_size % SPIFFS_DATA_PAGE_SIZE(fs) == 0) {
+ cur_size -= SPIFFS_DATA_PAGE_SIZE(fs);
+ } else {
+ cur_size -= cur_size % SPIFFS_DATA_PAGE_SIZE(fs);
+ }
+ fd->size = cur_size;
+ fd->offset = cur_size;
+ SPIFFS_DBG("truncate: delete data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", data_pix, data_spix, cur_size);
+ } else {
+ // delete last page, partially
+ spiffs_page_header p_hdr;
+ spiffs_page_ix new_data_pix;
+ u32_t bytes_to_remove = SPIFFS_DATA_PAGE_SIZE(fs) - (new_size % SPIFFS_DATA_PAGE_SIZE(fs));
+ SPIFFS_DBG("truncate: delete "_SPIPRIi" bytes from data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", bytes_to_remove, data_pix, data_spix, cur_size);
+
+ res = spiffs_page_data_check(fs, fd, data_pix, data_spix);
+ if (res != SPIFFS_OK) break;
+
+ p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG;
+ p_hdr.span_ix = data_spix;
+ p_hdr.flags = 0xff;
+ // allocate new page and copy unmodified data
+ res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG,
+ &p_hdr, 0, 0, 0, 0, &new_data_pix);
+ if (res != SPIFFS_OK) break;
+ res = spiffs_phys_cpy(fs, 0,
+ SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + sizeof(spiffs_page_header),
+ SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header),
+ SPIFFS_DATA_PAGE_SIZE(fs) - bytes_to_remove);
+ if (res != SPIFFS_OK) break;
+ // delete original data page
+ res = spiffs_page_delete(fs, data_pix);
+ if (res != SPIFFS_OK) break;
+ p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL;
+ res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT,
+ fd->file_nbr,
+ SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + offsetof(spiffs_page_header, flags),
+ sizeof(u8_t),
+ (u8_t *)&p_hdr.flags);
+ if (res != SPIFFS_OK) break;
+
+ // update memory representation of object index page with new data page
+ if (cur_objix_spix == 0) {
+ // update object index header page
+ ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix;
+ SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix));
+ } else {
+ // update object index page
+ ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix;
+ SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix));
+ }
+ cur_size = new_size;
+ fd->size = new_size;
+ fd->offset = cur_size;
+ break;
+ }
+ data_spix--;
+ } // while all data
+
+ // update object indices
+ if (cur_objix_spix == 0) {
+ // update object index header page
+ if (cur_size == 0) {
+ if (remove_full) {
+ // remove object altogether
+ SPIFFS_DBG("truncate: remove object index header page "_SPIPRIpg"\n", objix_pix);
+
+ res = spiffs_page_index_check(fs, fd, objix_pix, 0);
+ SPIFFS_CHECK_RES(res);
+
+ res = spiffs_page_delete(fs, objix_pix);
+ SPIFFS_CHECK_RES(res);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0,
+ SPIFFS_EV_IX_DEL, fd->obj_id, 0, objix_pix, 0);
+ } else {
+ // make uninitialized object
+ SPIFFS_DBG("truncate: reset objix_hdr page "_SPIPRIpg"\n", objix_pix);
+ memset(fs->work + sizeof(spiffs_page_object_ix_header), 0xff,
+ SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header));
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ objix_pix, fs->work, 0, 0, SPIFFS_UNDEFINED_LEN, &new_objix_hdr_pix);
+ SPIFFS_CHECK_RES(res);
+ }
+ } else {
+ // update object index header page
+ SPIFFS_DBG("truncate: update object index header page with indices and size\n");
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ objix_pix, fs->work, 0, 0, cur_size, &new_objix_hdr_pix);
+ SPIFFS_CHECK_RES(res);
+ }
+ } else {
+ // update both current object index page and object index header page
+ spiffs_page_ix new_objix_pix;
+
+ res = spiffs_page_index_check(fs, fd, objix_pix, cur_objix_spix);
+ SPIFFS_CHECK_RES(res);
+
+ // move and update object index page
+ res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix_hdr, fd->obj_id, 0, objix_pix, &new_objix_pix);
+ SPIFFS_CHECK_RES(res);
+ spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr,
+ SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0);
+ SPIFFS_DBG("truncate: store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, cur_objix_spix);
+ fd->cursor_objix_pix = new_objix_pix;
+ fd->cursor_objix_spix = cur_objix_spix;
+ fd->offset = cur_size;
+ // update object index header page with new size
+ res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id,
+ fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix);
+ SPIFFS_CHECK_RES(res);
+ }
+ fd->size = cur_size;
+
+ return res;
+} // spiffs_object_truncate
+#endif // !SPIFFS_READ_ONLY
+
+s32_t spiffs_object_read(
+ spiffs_fd *fd,
+ u32_t offset,
+ u32_t len,
+ u8_t *dst) {
+ s32_t res = SPIFFS_OK;
+ spiffs *fs = fd->fs;
+ spiffs_page_ix objix_pix;
+ spiffs_page_ix data_pix;
+ spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs);
+ u32_t cur_offset = offset;
+ spiffs_span_ix cur_objix_spix;
+ spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1;
+ spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work;
+ spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work;
+
+ while (cur_offset < offset + len) {
+#if SPIFFS_IX_MAP
+ // check if we have a memory, index map and if so, if we're within index map's range
+ // and if so, if the entry is populated
+ if (fd->ix_map && data_spix >= fd->ix_map->start_spix && data_spix <= fd->ix_map->end_spix
+ && fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]) {
+ data_pix = fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix];
+ } else {
+#endif
+ cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix);
+ if (prev_objix_spix != cur_objix_spix) {
+ // load current object index (header) page
+ if (cur_objix_spix == 0) {
+ objix_pix = fd->objix_hdr_pix;
+ } else {
+ SPIFFS_DBG("read: find objix "_SPIPRIid":"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix);
+ if (fd->cursor_objix_spix == cur_objix_spix) {
+ objix_pix = fd->cursor_objix_pix;
+ } else {
+ res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix);
+ SPIFFS_CHECK_RES(res);
+ }
+ }
+ SPIFFS_DBG("read: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix);
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ,
+ fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work);
+ SPIFFS_CHECK_RES(res);
+ SPIFFS_VALIDATE_OBJIX(objix->p_hdr, fd->obj_id, cur_objix_spix);
+
+ fd->offset = cur_offset;
+ fd->cursor_objix_pix = objix_pix;
+ fd->cursor_objix_spix = cur_objix_spix;
+
+ prev_objix_spix = cur_objix_spix;
+ }
+
+ if (cur_objix_spix == 0) {
+ // get data page from object index header page
+ data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix];
+ } else {
+ // get data page from object index page
+ data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)];
+ }
+#if SPIFFS_IX_MAP
+ }
+#endif
+ // all remaining data
+ u32_t len_to_read = offset + len - cur_offset;
+ // remaining data in page
+ len_to_read = MIN(len_to_read, SPIFFS_DATA_PAGE_SIZE(fs) - (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)));
+ // remaining data in file
+ len_to_read = MIN(len_to_read, fd->size);
+ SPIFFS_DBG("read: offset:"_SPIPRIi" rd:"_SPIPRIi" data spix:"_SPIPRIsp" is data_pix:"_SPIPRIpg" addr:"_SPIPRIad"\n", cur_offset, len_to_read, data_spix, data_pix,
+ (u32_t)(SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs))));
+ if (len_to_read <= 0) {
+ res = SPIFFS_ERR_END_OF_OBJECT;
+ break;
+ }
+ res = spiffs_page_data_check(fs, fd, data_pix, data_spix);
+ SPIFFS_CHECK_RES(res);
+ res = _spiffs_rd(
+ fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ,
+ fd->file_nbr,
+ SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)),
+ len_to_read,
+ dst);
+ SPIFFS_CHECK_RES(res);
+ dst += len_to_read;
+ cur_offset += len_to_read;
+ fd->offset = cur_offset;
+ data_spix++;
+ }
+
+ return res;
+}
+
+#if !SPIFFS_READ_ONLY
+typedef struct {
+ spiffs_obj_id min_obj_id;
+ spiffs_obj_id max_obj_id;
+ u32_t compaction;
+ const u8_t *conflicting_name;
+} spiffs_free_obj_id_state;
+
+static s32_t spiffs_obj_lu_find_free_obj_id_bitmap_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry,
+ const void *user_const_p, void *user_var_p) {
+ if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED) {
+ spiffs_obj_id min_obj_id = *((spiffs_obj_id*)user_var_p);
+ const u8_t *conflicting_name = (const u8_t*)user_const_p;
+
+ // if conflicting name parameter is given, also check if this name is found in object index hdrs
+ if (conflicting_name && (id & SPIFFS_OBJ_ID_IX_FLAG)) {
+ spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry);
+ int res;
+ spiffs_page_object_ix_header objix_hdr;
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr);
+ SPIFFS_CHECK_RES(res);
+ if (objix_hdr.p_hdr.span_ix == 0 &&
+ (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) ==
+ (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) {
+ if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) {
+ return SPIFFS_ERR_CONFLICTING_NAME;
+ }
+ }
+ }
+
+ id &= ~SPIFFS_OBJ_ID_IX_FLAG;
+ u32_t bit_ix = (id-min_obj_id) & 7;
+ int byte_ix = (id-min_obj_id) >> 3;
+ if (byte_ix >= 0 && (u32_t)byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs)) {
+ fs->work[byte_ix] |= (1<<bit_ix);
+ }
+ }
+ return SPIFFS_VIS_COUNTINUE;
+}
+
+static s32_t spiffs_obj_lu_find_free_obj_id_compact_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry,
+ const void *user_const_p, void *user_var_p) {
+ (void)user_var_p;
+ if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED && (id & SPIFFS_OBJ_ID_IX_FLAG)) {
+ s32_t res;
+ const spiffs_free_obj_id_state *state = (const spiffs_free_obj_id_state*)user_const_p;
+ spiffs_page_object_ix_header objix_hdr;
+
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, ix_entry), sizeof(spiffs_page_object_ix_header), (u8_t*)&objix_hdr);
+ if (res == SPIFFS_OK && objix_hdr.p_hdr.span_ix == 0 &&
+ ((objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) ==
+ (SPIFFS_PH_FLAG_DELET))) {
+ // ok object look up entry
+ if (state->conflicting_name && strcmp((const char *)state->conflicting_name, (char *)objix_hdr.name) == 0) {
+ return SPIFFS_ERR_CONFLICTING_NAME;
+ }
+
+ id &= ~SPIFFS_OBJ_ID_IX_FLAG;
+ if (id >= state->min_obj_id && id <= state->max_obj_id) {
+ u8_t *map = (u8_t *)fs->work;
+ int ix = (id - state->min_obj_id) / state->compaction;
+ //SPIFFS_DBG("free_obj_id: add ix "_SPIPRIi" for id "_SPIPRIid" min"_SPIPRIid" max"_SPIPRIid" comp:"_SPIPRIi"\n", ix, id, state->min_obj_id, state->max_obj_id, state->compaction);
+ map[ix]++;
+ }
+ }
+ }
+ return SPIFFS_VIS_COUNTINUE;
+}
+
+// Scans thru all object lookup for object index header pages. If total possible number of
+// object ids cannot fit into a work buffer, these are grouped. When a group containing free
+// object ids is found, the object lu is again scanned for object ids within group and bitmasked.
+// Finally, the bitmask is searched for a free id
+s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, const u8_t *conflicting_name) {
+ s32_t res = SPIFFS_OK;
+ u32_t max_objects = (fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) / 2;
+ spiffs_free_obj_id_state state;
+ spiffs_obj_id free_obj_id = SPIFFS_OBJ_ID_FREE;
+ state.min_obj_id = 1;
+ state.max_obj_id = max_objects + 1;
+ if (state.max_obj_id & SPIFFS_OBJ_ID_IX_FLAG) {
+ state.max_obj_id = ((spiffs_obj_id)-1) & ~SPIFFS_OBJ_ID_IX_FLAG;
+ }
+ state.compaction = 0;
+ state.conflicting_name = conflicting_name;
+ while (res == SPIFFS_OK && free_obj_id == SPIFFS_OBJ_ID_FREE) {
+ if (state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8) {
+ // possible to represent in bitmap
+ u32_t i, j;
+ SPIFFS_DBG("free_obj_id: BITM min:"_SPIPRIid" max:"_SPIPRIid"\n", state.min_obj_id, state.max_obj_id);
+
+ memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs));
+ res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_bitmap_v,
+ conflicting_name, &state.min_obj_id, 0, 0);
+ if (res == SPIFFS_VIS_END) res = SPIFFS_OK;
+ SPIFFS_CHECK_RES(res);
+ // traverse bitmask until found free obj_id
+ for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs); i++) {
+ u8_t mask = fs->work[i];
+ if (mask == 0xff) {
+ continue;
+ }
+ for (j = 0; j < 8; j++) {
+ if ((mask & (1<<j)) == 0) {
+ *obj_id = (i<<3)+j+state.min_obj_id;
+ return SPIFFS_OK;
+ }
+ }
+ }
+ return SPIFFS_ERR_FULL;
+ } else {
+ // not possible to represent all ids in range in a bitmap, compact and count
+ if (state.compaction != 0) {
+ // select element in compacted table, decrease range and recompact
+ u32_t i, min_i = 0;
+ u8_t *map = (u8_t *)fs->work;
+ u8_t min_count = 0xff;
+
+ for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(u8_t); i++) {
+ if (map[i] < min_count) {
+ min_count = map[i];
+ min_i = i;
+ if (min_count == 0) {
+ break;
+ }
+ }
+ }
+
+ if (min_count == state.compaction) {
+ // there are no free objids!
+ SPIFFS_DBG("free_obj_id: compacted table is full\n");
+ return SPIFFS_ERR_FULL;
+ }
+
+ SPIFFS_DBG("free_obj_id: COMP select index:"_SPIPRIi" min_count:"_SPIPRIi" min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", min_i, min_count, state.min_obj_id, state.max_obj_id, state.compaction);
+
+ if (min_count == 0) {
+ // no id in this range, skip compacting and use directly
+ *obj_id = min_i * state.compaction + state.min_obj_id;
+ return SPIFFS_OK;
+ } else {
+ SPIFFS_DBG("free_obj_id: COMP SEL chunk:"_SPIPRIi" min:"_SPIPRIid" -> "_SPIPRIid"\n", state.compaction, state.min_obj_id, state.min_obj_id + min_i * state.compaction);
+ state.min_obj_id += min_i * state.compaction;
+ state.max_obj_id = state.min_obj_id + state.compaction;
+ // decrease compaction
+ }
+ if ((state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8)) {
+ // no need for compacting, use bitmap
+ continue;
+ }
+ }
+ // in a work memory of log_page_size bytes, we may fit in log_page_size ids
+ // todo what if compaction is > 255 - then we cannot fit it in a byte
+ state.compaction = (state.max_obj_id-state.min_obj_id) / ((SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(u8_t)));
+ SPIFFS_DBG("free_obj_id: COMP min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", state.min_obj_id, state.max_obj_id, state.compaction);
+
+ memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs));
+ res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_compact_v, &state, 0, 0, 0);
+ if (res == SPIFFS_VIS_END) res = SPIFFS_OK;
+ SPIFFS_CHECK_RES(res);
+ state.conflicting_name = 0; // searched for conflicting name once, no need to do it again
+ }
+ }
+
+ return res;
+}
+#endif // !SPIFFS_READ_ONLY
+
+#if SPIFFS_TEMPORAL_FD_CACHE
+// djb2 hash
+static u32_t spiffs_hash(spiffs *fs, const u8_t *name) {
+ (void)fs;
+ u32_t hash = 5381;
+ u8_t c;
+ int i = 0;
+ while ((c = name[i++]) && i < SPIFFS_OBJ_NAME_LEN) {
+ hash = (hash * 33) ^ c;
+ }
+ return hash;
+}
+#endif
+
+s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd, const char *name) {
+#if SPIFFS_TEMPORAL_FD_CACHE
+ u32_t i;
+ u16_t min_score = 0xffff;
+ u32_t cand_ix = (u32_t)-1;
+ u32_t name_hash = name ? spiffs_hash(fs, (const u8_t *)name) : 0;
+ spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+
+ if (name) {
+ // first, decrease score of all closed descriptors
+ for (i = 0; i < fs->fd_count; i++) {
+ spiffs_fd *cur_fd = &fds[i];
+ if (cur_fd->file_nbr == 0) {
+ if (cur_fd->score > 1) { // score == 0 indicates never used fd
+ cur_fd->score--;
+ }
+ }
+ }
+ }
+
+ // find the free fd with least score
+ for (i = 0; i < fs->fd_count; i++) {
+ spiffs_fd *cur_fd = &fds[i];
+ if (cur_fd->file_nbr == 0) {
+ if (name && cur_fd->name_hash == name_hash) {
+ cand_ix = i;
+ break;
+ }
+ if (cur_fd->score < min_score) {
+ min_score = cur_fd->score;
+ cand_ix = i;
+ }
+ }
+ }
+
+ if (cand_ix != (u32_t)-1) {
+ spiffs_fd *cur_fd = &fds[cand_ix];
+ if (name) {
+ if (cur_fd->name_hash == name_hash && cur_fd->score > 0) {
+ // opened an fd with same name hash, assume same file
+ // set search point to saved obj index page and hope we have a correct match directly
+ // when start searching - if not, we will just keep searching until it is found
+ fs->cursor_block_ix = SPIFFS_BLOCK_FOR_PAGE(fs, cur_fd->objix_hdr_pix);
+ fs->cursor_obj_lu_entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, cur_fd->objix_hdr_pix);
+ // update score
+ if (cur_fd->score < 0xffff-SPIFFS_TEMPORAL_CACHE_HIT_SCORE) {
+ cur_fd->score += SPIFFS_TEMPORAL_CACHE_HIT_SCORE;
+ } else {
+ cur_fd->score = 0xffff;
+ }
+ } else {
+ // no hash hit, restore this fd to initial state
+ cur_fd->score = SPIFFS_TEMPORAL_CACHE_HIT_SCORE;
+ cur_fd->name_hash = name_hash;
+ }
+ }
+ cur_fd->file_nbr = cand_ix+1;
+ *fd = cur_fd;
+ return SPIFFS_OK;
+ } else {
+ return SPIFFS_ERR_OUT_OF_FILE_DESCS;
+ }
+#else
+ (void)name;
+ u32_t i;
+ spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+ for (i = 0; i < fs->fd_count; i++) {
+ spiffs_fd *cur_fd = &fds[i];
+ if (cur_fd->file_nbr == 0) {
+ cur_fd->file_nbr = i+1;
+ *fd = cur_fd;
+ return SPIFFS_OK;
+ }
+ }
+ return SPIFFS_ERR_OUT_OF_FILE_DESCS;
+#endif
+}
+
+s32_t spiffs_fd_return(spiffs *fs, spiffs_file f) {
+ if (f <= 0 || f > (s16_t)fs->fd_count) {
+ return SPIFFS_ERR_BAD_DESCRIPTOR;
+ }
+ spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+ spiffs_fd *fd = &fds[f-1];
+ if (fd->file_nbr == 0) {
+ return SPIFFS_ERR_FILE_CLOSED;
+ }
+ fd->file_nbr = 0;
+#if SPIFFS_IX_MAP
+ fd->ix_map = 0;
+#endif
+ return SPIFFS_OK;
+}
+
+s32_t spiffs_fd_get(spiffs *fs, spiffs_file f, spiffs_fd **fd) {
+ if (f <= 0 || f > (s16_t)fs->fd_count) {
+ return SPIFFS_ERR_BAD_DESCRIPTOR;
+ }
+ spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+ *fd = &fds[f-1];
+ if ((*fd)->file_nbr == 0) {
+ return SPIFFS_ERR_FILE_CLOSED;
+ }
+ return SPIFFS_OK;
+}
+
+#if SPIFFS_TEMPORAL_FD_CACHE
+void spiffs_fd_temporal_cache_rehash(
+ spiffs *fs,
+ const char *old_path,
+ const char *new_path) {
+ u32_t i;
+ u32_t old_hash = spiffs_hash(fs, (const u8_t *)old_path);
+ u32_t new_hash = spiffs_hash(fs, (const u8_t *)new_path);
+ spiffs_fd *fds = (spiffs_fd *)fs->fd_space;
+ for (i = 0; i < fs->fd_count; i++) {
+ spiffs_fd *cur_fd = &fds[i];
+ if (cur_fd->score > 0 && cur_fd->name_hash == old_hash) {
+ cur_fd->name_hash = new_hash;
+ }
+ }
+}
+#endif
diff --git a/libiot/spiffs/spiffs_nucleus.h b/libiot/spiffs/spiffs_nucleus.h
new file mode 100644
index 0000000..7d676ee
--- /dev/null
+++ b/libiot/spiffs/spiffs_nucleus.h
@@ -0,0 +1,797 @@
+/*
+ * spiffs_nucleus.h
+ *
+ * Created on: Jun 15, 2013
+ * Author: petera
+ */
+
+/* SPIFFS layout
+ *
+ * spiffs is designed for following spi flash characteristics:
+ * - only big areas of data (blocks) can be erased
+ * - erasing resets all bits in a block to ones
+ * - writing pulls ones to zeroes
+ * - zeroes cannot be pulled to ones, without erase
+ * - wear leveling
+ *
+ * spiffs is also meant to be run on embedded, memory constraint devices.
+ *
+ * Entire area is divided in blocks. Entire area is also divided in pages.
+ * Each block contains same number of pages. A page cannot be erased, but a
+ * block can be erased.
+ *
+ * Entire area must be block_size * x
+ * page_size must be block_size / (2^y) where y > 2
+ *
+ * ex: area = 1024*1024 bytes, block size = 65536 bytes, page size = 256 bytes
+ *
+ * BLOCK 0 PAGE 0 object lookup 1
+ * PAGE 1 object lookup 2
+ * ...
+ * PAGE n-1 object lookup n
+ * PAGE n object data 1
+ * PAGE n+1 object data 2
+ * ...
+ * PAGE n+m-1 object data m
+ *
+ * BLOCK 1 PAGE n+m object lookup 1
+ * PAGE n+m+1 object lookup 2
+ * ...
+ * PAGE 2n+m-1 object lookup n
+ * PAGE 2n+m object data 1
+ * PAGE 2n+m object data 2
+ * ...
+ * PAGE 2n+2m-1 object data m
+ * ...
+ *
+ * n is number of object lookup pages, which is number of pages needed to index all pages
+ * in a block by object id
+ * : block_size / page_size * sizeof(obj_id) / page_size
+ * m is number data pages, which is number of pages in block minus number of lookup pages
+ * : block_size / page_size - block_size / page_size * sizeof(obj_id) / page_size
+ * thus, n+m is total number of pages in a block
+ * : block_size / page_size
+ *
+ * ex: n = 65536/256*2/256 = 2, m = 65536/256 - 2 = 254 => n+m = 65536/256 = 256
+ *
+ * Object lookup pages contain object id entries. Each entry represent the corresponding
+ * data page.
+ * Assuming a 16 bit object id, an object id being 0xffff represents a free page.
+ * An object id being 0x0000 represents a deleted page.
+ *
+ * ex: page 0 : lookup : 0008 0001 0aaa ffff ffff ffff ffff ffff ..
+ * page 1 : lookup : ffff ffff ffff ffff ffff ffff ffff ffff ..
+ * page 2 : data : data for object id 0008
+ * page 3 : data : data for object id 0001
+ * page 4 : data : data for object id 0aaa
+ * ...
+ *
+ *
+ * Object data pages can be either object index pages or object content.
+ * All object data pages contains a data page header, containing object id and span index.
+ * The span index denotes the object page ordering amongst data pages with same object id.
+ * This applies to both object index pages (when index spans more than one page of entries),
+ * and object data pages.
+ * An object index page contains page entries pointing to object content page. The entry index
+ * in a object index page correlates to the span index in the actual object data page.
+ * The first object index page (span index 0) is called object index header page, and also
+ * contains object flags (directory/file), size, object name etc.
+ *
+ * ex:
+ * BLOCK 1
+ * PAGE 256: objectl lookup page 1
+ * [*123] [ 123] [ 123] [ 123]
+ * [ 123] [*123] [ 123] [ 123]
+ * [free] [free] [free] [free] ...
+ * PAGE 257: objectl lookup page 2
+ * [free] [free] [free] [free] ...
+ * PAGE 258: object index page (header)
+ * obj.id:0123 span.ix:0000 flags:INDEX
+ * size:1600 name:ex.txt type:file
+ * [259] [260] [261] [262]
+ * PAGE 259: object data page
+ * obj.id:0123 span.ix:0000 flags:DATA
+ * PAGE 260: object data page
+ * obj.id:0123 span.ix:0001 flags:DATA
+ * PAGE 261: object data page
+ * obj.id:0123 span.ix:0002 flags:DATA
+ * PAGE 262: object data page
+ * obj.id:0123 span.ix:0003 flags:DATA
+ * PAGE 263: object index page
+ * obj.id:0123 span.ix:0001 flags:INDEX
+ * [264] [265] [fre] [fre]
+ * [fre] [fre] [fre] [fre]
+ * PAGE 264: object data page
+ * obj.id:0123 span.ix:0004 flags:DATA
+ * PAGE 265: object data page
+ * obj.id:0123 span.ix:0005 flags:DATA
+ *
+ */
+#ifndef SPIFFS_NUCLEUS_H_
+#define SPIFFS_NUCLEUS_H_
+
+#define _SPIFFS_ERR_CHECK_FIRST (SPIFFS_ERR_INTERNAL - 1)
+#define SPIFFS_ERR_CHECK_OBJ_ID_MISM (SPIFFS_ERR_INTERNAL - 1)
+#define SPIFFS_ERR_CHECK_SPIX_MISM (SPIFFS_ERR_INTERNAL - 2)
+#define SPIFFS_ERR_CHECK_FLAGS_BAD (SPIFFS_ERR_INTERNAL - 3)
+#define _SPIFFS_ERR_CHECK_LAST (SPIFFS_ERR_INTERNAL - 4)
+
+// visitor result, continue searching
+#define SPIFFS_VIS_COUNTINUE (SPIFFS_ERR_INTERNAL - 20)
+// visitor result, continue searching after reloading lu buffer
+#define SPIFFS_VIS_COUNTINUE_RELOAD (SPIFFS_ERR_INTERNAL - 21)
+// visitor result, stop searching
+#define SPIFFS_VIS_END (SPIFFS_ERR_INTERNAL - 22)
+
+// updating an object index contents
+#define SPIFFS_EV_IX_UPD (0)
+// creating a new object index
+#define SPIFFS_EV_IX_NEW (1)
+// deleting an object index
+#define SPIFFS_EV_IX_DEL (2)
+// moving an object index without updating contents
+#define SPIFFS_EV_IX_MOV (3)
+// updating an object index header data only, not the table itself
+#define SPIFFS_EV_IX_UPD_HDR (4)
+
+#define SPIFFS_OBJ_ID_IX_FLAG ((spiffs_obj_id)(1<<(8*sizeof(spiffs_obj_id)-1)))
+
+#define SPIFFS_UNDEFINED_LEN (u32_t)(-1)
+
+#define SPIFFS_OBJ_ID_DELETED ((spiffs_obj_id)0)
+#define SPIFFS_OBJ_ID_FREE ((spiffs_obj_id)-1)
+
+#if SPIFFS_USE_MAGIC
+#if !SPIFFS_USE_MAGIC_LENGTH
+#define SPIFFS_MAGIC(fs, bix) \
+ ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs)))
+#else // SPIFFS_USE_MAGIC_LENGTH
+#define SPIFFS_MAGIC(fs, bix) \
+ ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs) ^ ((fs)->block_count - (bix))))
+#endif // SPIFFS_USE_MAGIC_LENGTH
+#endif // SPIFFS_USE_MAGIC
+
+#define SPIFFS_CONFIG_MAGIC (0x20090315)
+
+#if SPIFFS_SINGLETON == 0
+#define SPIFFS_CFG_LOG_PAGE_SZ(fs) \
+ ((fs)->cfg.log_page_size)
+#define SPIFFS_CFG_LOG_BLOCK_SZ(fs) \
+ ((fs)->cfg.log_block_size)
+#define SPIFFS_CFG_PHYS_SZ(fs) \
+ ((fs)->cfg.phys_size)
+#define SPIFFS_CFG_PHYS_ERASE_SZ(fs) \
+ ((fs)->cfg.phys_erase_block)
+#define SPIFFS_CFG_PHYS_ADDR(fs) \
+ ((fs)->cfg.phys_addr)
+#endif
+
+// total number of pages
+#define SPIFFS_MAX_PAGES(fs) \
+ ( SPIFFS_CFG_PHYS_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// total number of pages per block, including object lookup pages
+#define SPIFFS_PAGES_PER_BLOCK(fs) \
+ ( SPIFFS_CFG_LOG_BLOCK_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// number of object lookup pages per block
+#define SPIFFS_OBJ_LOOKUP_PAGES(fs) \
+ (MAX(1, (SPIFFS_PAGES_PER_BLOCK(fs) * sizeof(spiffs_obj_id)) / SPIFFS_CFG_LOG_PAGE_SZ(fs)) )
+// checks if page index belongs to object lookup
+#define SPIFFS_IS_LOOKUP_PAGE(fs,pix) \
+ (((pix) % SPIFFS_PAGES_PER_BLOCK(fs)) < SPIFFS_OBJ_LOOKUP_PAGES(fs))
+// number of object lookup entries in all object lookup pages
+#define SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) \
+ (SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))
+// converts a block to physical address
+#define SPIFFS_BLOCK_TO_PADDR(fs, block) \
+ ( SPIFFS_CFG_PHYS_ADDR(fs) + (block)* SPIFFS_CFG_LOG_BLOCK_SZ(fs) )
+// converts a object lookup entry to page index
+#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, block, entry) \
+ ((block)*SPIFFS_PAGES_PER_BLOCK(fs) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry))
+// converts a object lookup entry to physical address of corresponding page
+#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, block, entry) \
+ (SPIFFS_BLOCK_TO_PADDR(fs, block) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry) * SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// converts a page to physical address
+#define SPIFFS_PAGE_TO_PADDR(fs, page) \
+ ( SPIFFS_CFG_PHYS_ADDR(fs) + (page) * SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// converts a physical address to page
+#define SPIFFS_PADDR_TO_PAGE(fs, addr) \
+ ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) / SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// gives index in page for a physical address
+#define SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr) \
+ ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) % SPIFFS_CFG_LOG_PAGE_SZ(fs) )
+// returns containing block for given page
+#define SPIFFS_BLOCK_FOR_PAGE(fs, page) \
+ ( (page) / SPIFFS_PAGES_PER_BLOCK(fs) )
+// returns starting page for block
+#define SPIFFS_PAGE_FOR_BLOCK(fs, block) \
+ ( (block) * SPIFFS_PAGES_PER_BLOCK(fs) )
+// converts page to entry in object lookup page
+#define SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, page) \
+ ( (page) % SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs) )
+// returns data size in a data page
+#define SPIFFS_DATA_PAGE_SIZE(fs) \
+ ( SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header) )
+// returns physical address for block's erase count,
+// always in the physical last entry of the last object lookup page
+#define SPIFFS_ERASE_COUNT_PADDR(fs, bix) \
+ ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id) )
+// returns physical address for block's magic,
+// always in the physical second last entry of the last object lookup page
+#define SPIFFS_MAGIC_PADDR(fs, bix) \
+ ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id)*2 )
+// checks if there is any room for magic in the object luts
+#define SPIFFS_CHECK_MAGIC_POSSIBLE(fs) \
+ ( (SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) % (SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(spiffs_obj_id))) * sizeof(spiffs_obj_id) \
+ <= (SPIFFS_CFG_LOG_PAGE_SZ(fs)-sizeof(spiffs_obj_id)*2) )
+
+// define helpers object
+
+// entries in an object header page index
+#define SPIFFS_OBJ_HDR_IX_LEN(fs) \
+ ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header))/sizeof(spiffs_page_ix))
+// entries in an object page index
+#define SPIFFS_OBJ_IX_LEN(fs) \
+ ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix))/sizeof(spiffs_page_ix))
+// object index entry for given data span index
+#define SPIFFS_OBJ_IX_ENTRY(fs, spix) \
+ ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? (spix) : (((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))%SPIFFS_OBJ_IX_LEN(fs)))
+// object index span index number for given data span index or entry
+#define SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, spix) \
+ ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? 0 : (1+((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))/SPIFFS_OBJ_IX_LEN(fs)))
+// get data span index for object index span index
+#define SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, spix) \
+ ( (spix) == 0 ? 0 : (SPIFFS_OBJ_HDR_IX_LEN(fs) + (((spix)-1) * SPIFFS_OBJ_IX_LEN(fs))) )
+
+#define SPIFFS_OP_T_OBJ_LU (0<<0)
+#define SPIFFS_OP_T_OBJ_LU2 (1<<0)
+#define SPIFFS_OP_T_OBJ_IX (2<<0)
+#define SPIFFS_OP_T_OBJ_DA (3<<0)
+#define SPIFFS_OP_C_DELE (0<<2)
+#define SPIFFS_OP_C_UPDT (1<<2)
+#define SPIFFS_OP_C_MOVS (2<<2)
+#define SPIFFS_OP_C_MOVD (3<<2)
+#define SPIFFS_OP_C_FLSH (4<<2)
+#define SPIFFS_OP_C_READ (5<<2)
+#define SPIFFS_OP_C_WRTHRU (6<<2)
+
+#define SPIFFS_OP_TYPE_MASK (3<<0)
+#define SPIFFS_OP_COM_MASK (7<<2)
+
+
+// if 0, this page is written to, else clean
+#define SPIFFS_PH_FLAG_USED (1<<0)
+// if 0, writing is finalized, else under modification
+#define SPIFFS_PH_FLAG_FINAL (1<<1)
+// if 0, this is an index page, else a data page
+#define SPIFFS_PH_FLAG_INDEX (1<<2)
+// if 0, page is deleted, else valid
+#define SPIFFS_PH_FLAG_DELET (1<<7)
+// if 0, this index header is being deleted
+#define SPIFFS_PH_FLAG_IXDELE (1<<6)
+
+
+#define SPIFFS_CHECK_MOUNT(fs) \
+ ((fs)->mounted != 0)
+
+#define SPIFFS_CHECK_CFG(fs) \
+ ((fs)->config_magic == SPIFFS_CONFIG_MAGIC)
+
+#define SPIFFS_CHECK_RES(res) \
+ do { \
+ if ((res) < SPIFFS_OK) return (res); \
+ } while (0);
+
+#define SPIFFS_API_CHECK_MOUNT(fs) \
+ if (!SPIFFS_CHECK_MOUNT((fs))) { \
+ (fs)->err_code = SPIFFS_ERR_NOT_MOUNTED; \
+ return SPIFFS_ERR_NOT_MOUNTED; \
+ }
+
+#define SPIFFS_API_CHECK_CFG(fs) \
+ if (!SPIFFS_CHECK_CFG((fs))) { \
+ (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; \
+ return SPIFFS_ERR_NOT_CONFIGURED; \
+ }
+
+#define SPIFFS_API_CHECK_RES(fs, res) \
+ if ((res) < SPIFFS_OK) { \
+ (fs)->err_code = (res); \
+ return (res); \
+ }
+
+#define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \
+ if ((res) < SPIFFS_OK) { \
+ (fs)->err_code = (res); \
+ SPIFFS_UNLOCK(fs); \
+ return (res); \
+ }
+
+#define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \
+ if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \
+ if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \
+ if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \
+ if (((ph).flags & SPIFFS_PH_FLAG_INDEX) != 0) return SPIFFS_ERR_NOT_INDEX; \
+ if (((objid) & SPIFFS_OBJ_ID_IX_FLAG) == 0) return SPIFFS_ERR_NOT_INDEX; \
+ if ((ph).span_ix != (spix)) return SPIFFS_ERR_INDEX_SPAN_MISMATCH;
+ //if ((spix) == 0 && ((ph).flags & SPIFFS_PH_FLAG_IXDELE) == 0) return SPIFFS_ERR_DELETED;
+
+#define SPIFFS_VALIDATE_DATA(ph, objid, spix) \
+ if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \
+ if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \
+ if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \
+ if (((ph).flags & SPIFFS_PH_FLAG_INDEX) == 0) return SPIFFS_ERR_IS_INDEX; \
+ if ((objid) & SPIFFS_OBJ_ID_IX_FLAG) return SPIFFS_ERR_IS_INDEX; \
+ if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH;
+
+
+// check id, only visit matching objec ids
+#define SPIFFS_VIS_CHECK_ID (1<<0)
+// report argument object id to visitor - else object lookup id is reported
+#define SPIFFS_VIS_CHECK_PH (1<<1)
+// stop searching at end of all look up pages
+#define SPIFFS_VIS_NO_WRAP (1<<2)
+
+#if SPIFFS_HAL_CALLBACK_EXTRA
+
+#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \
+ (_fs)->cfg.hal_write_f((_fs), (_paddr), (_len), (_src))
+#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \
+ (_fs)->cfg.hal_read_f((_fs), (_paddr), (_len), (_dst))
+#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \
+ (_fs)->cfg.hal_erase_f((_fs), (_paddr), (_len))
+
+#else // SPIFFS_HAL_CALLBACK_EXTRA
+
+#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \
+ (_fs)->cfg.hal_write_f((_paddr), (_len), (_src))
+#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \
+ (_fs)->cfg.hal_read_f((_paddr), (_len), (_dst))
+#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \
+ (_fs)->cfg.hal_erase_f((_paddr), (_len))
+
+#endif // SPIFFS_HAL_CALLBACK_EXTRA
+
+#if SPIFFS_CACHE
+
+#define SPIFFS_CACHE_FLAG_DIRTY (1<<0)
+#define SPIFFS_CACHE_FLAG_WRTHRU (1<<1)
+#define SPIFFS_CACHE_FLAG_OBJLU (1<<2)
+#define SPIFFS_CACHE_FLAG_OBJIX (1<<3)
+#define SPIFFS_CACHE_FLAG_DATA (1<<4)
+#define SPIFFS_CACHE_FLAG_TYPE_WR (1<<7)
+
+#define SPIFFS_CACHE_PAGE_SIZE(fs) \
+ (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs))
+
+#define spiffs_get_cache(fs) \
+ ((spiffs_cache *)((fs)->cache))
+
+#define spiffs_get_cache_page_hdr(fs, c, ix) \
+ ((spiffs_cache_page *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)])))
+
+#define spiffs_get_cache_page(fs, c, ix) \
+ ((u8_t *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)])) + sizeof(spiffs_cache_page))
+
+// cache page struct
+typedef struct {
+ // cache flags
+ u8_t flags;
+ // cache page index
+ u8_t ix;
+ // last access of this cache page
+ u32_t last_access;
+ union {
+ // type read cache
+ struct {
+ // read cache page index
+ spiffs_page_ix pix;
+ };
+#if SPIFFS_CACHE_WR
+ // type write cache
+ struct {
+ // write cache
+ spiffs_obj_id obj_id;
+ // offset in cache page
+ u32_t offset;
+ // size of cache page
+ u16_t size;
+ };
+#endif
+ };
+} spiffs_cache_page;
+
+// cache struct
+typedef struct {
+ u8_t cpage_count;
+ u32_t last_access;
+ u32_t cpage_use_map;
+ u32_t cpage_use_mask;
+ u8_t *cpages;
+} spiffs_cache;
+
+#endif
+
+
+// spiffs nucleus file descriptor
+typedef struct {
+ // the filesystem of this descriptor
+ spiffs *fs;
+ // number of file descriptor - if 0, the file descriptor is closed
+ spiffs_file file_nbr;
+ // object id - if SPIFFS_OBJ_ID_ERASED, the file was deleted
+ spiffs_obj_id obj_id;
+ // size of the file
+ u32_t size;
+ // cached object index header page index
+ spiffs_page_ix objix_hdr_pix;
+ // cached offset object index page index
+ spiffs_page_ix cursor_objix_pix;
+ // cached offset object index span index
+ spiffs_span_ix cursor_objix_spix;
+ // current absolute offset
+ u32_t offset;
+ // current file descriptor offset
+ u32_t fdoffset;
+ // fd flags
+ spiffs_flags flags;
+#if SPIFFS_CACHE_WR
+ spiffs_cache_page *cache_page;
+#endif
+#if SPIFFS_TEMPORAL_FD_CACHE
+ // djb2 hash of filename
+ u32_t name_hash;
+ // hit score (score == 0 indicates never used fd)
+ u16_t score;
+#endif
+#if SPIFFS_IX_MAP
+ // spiffs index map, if 0 it means unmapped
+ spiffs_ix_map *ix_map;
+#endif
+} spiffs_fd;
+
+
+// object structs
+
+// page header, part of each page except object lookup pages
+// NB: this is always aligned when the data page is an object index,
+// as in this case struct spiffs_page_object_ix is used
+typedef struct __attribute(( packed )) {
+ // object id
+ spiffs_obj_id obj_id;
+ // object span index
+ spiffs_span_ix span_ix;
+ // flags
+ u8_t flags;
+} spiffs_page_header;
+
+// object index header page header
+typedef struct __attribute(( packed ))
+#if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES
+ __attribute(( aligned(sizeof(spiffs_page_ix)) ))
+#endif
+{
+ // common page header
+ spiffs_page_header p_hdr;
+ // alignment
+ u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))];
+ // size of object
+ u32_t size;
+ // type of object
+ spiffs_obj_type type;
+ // name of object
+ u8_t name[SPIFFS_OBJ_NAME_LEN];
+#if SPIFFS_OBJ_META_LEN
+ // metadata. not interpreted by SPIFFS in any way.
+ u8_t meta[SPIFFS_OBJ_META_LEN];
+#endif
+} spiffs_page_object_ix_header;
+
+// object index page header
+typedef struct __attribute(( packed )) {
+ spiffs_page_header p_hdr;
+ u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))];
+} spiffs_page_object_ix;
+
+// callback func for object lookup visitor
+typedef s32_t (*spiffs_visitor_f)(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry,
+ const void *user_const_p, void *user_var_p);
+
+
+#if SPIFFS_CACHE
+#define _spiffs_rd(fs, op, fh, addr, len, dst) \
+ spiffs_phys_rd((fs), (op), (fh), (addr), (len), (dst))
+#define _spiffs_wr(fs, op, fh, addr, len, src) \
+ spiffs_phys_wr((fs), (op), (fh), (addr), (len), (src))
+#else
+#define _spiffs_rd(fs, op, fh, addr, len, dst) \
+ spiffs_phys_rd((fs), (addr), (len), (dst))
+#define _spiffs_wr(fs, op, fh, addr, len, src) \
+ spiffs_phys_wr((fs), (addr), (len), (src))
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+// ---------------
+
+s32_t spiffs_phys_rd(
+ spiffs *fs,
+#if SPIFFS_CACHE
+ u8_t op,
+ spiffs_file fh,
+#endif
+ u32_t addr,
+ u32_t len,
+ u8_t *dst);
+
+s32_t spiffs_phys_wr(
+ spiffs *fs,
+#if SPIFFS_CACHE
+ u8_t op,
+ spiffs_file fh,
+#endif
+ u32_t addr,
+ u32_t len,
+ u8_t *src);
+
+s32_t spiffs_phys_cpy(
+ spiffs *fs,
+ spiffs_file fh,
+ u32_t dst,
+ u32_t src,
+ u32_t len);
+
+s32_t spiffs_phys_count_free_blocks(
+ spiffs *fs);
+
+s32_t spiffs_obj_lu_find_entry_visitor(
+ spiffs *fs,
+ spiffs_block_ix starting_block,
+ int starting_lu_entry,
+ u8_t flags,
+ spiffs_obj_id obj_id,
+ spiffs_visitor_f v,
+ const void *user_const_p,
+ void *user_var_p,
+ spiffs_block_ix *block_ix,
+ int *lu_entry);
+
+s32_t spiffs_erase_block(
+ spiffs *fs,
+ spiffs_block_ix bix);
+
+#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH
+s32_t spiffs_probe(
+ spiffs_config *cfg);
+#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH
+
+// ---------------
+
+s32_t spiffs_obj_lu_scan(
+ spiffs *fs);
+
+s32_t spiffs_obj_lu_find_free_obj_id(
+ spiffs *fs,
+ spiffs_obj_id *obj_id,
+ const u8_t *conflicting_name);
+
+s32_t spiffs_obj_lu_find_free(
+ spiffs *fs,
+ spiffs_block_ix starting_block,
+ int starting_lu_entry,
+ spiffs_block_ix *block_ix,
+ int *lu_entry);
+
+s32_t spiffs_obj_lu_find_id(
+ spiffs *fs,
+ spiffs_block_ix starting_block,
+ int starting_lu_entry,
+ spiffs_obj_id obj_id,
+ spiffs_block_ix *block_ix,
+ int *lu_entry);
+
+s32_t spiffs_obj_lu_find_id_and_span(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_span_ix spix,
+ spiffs_page_ix exclusion_pix,
+ spiffs_page_ix *pix);
+
+s32_t spiffs_obj_lu_find_id_and_span_by_phdr(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_span_ix spix,
+ spiffs_page_ix exclusion_pix,
+ spiffs_page_ix *pix);
+
+// ---------------
+
+s32_t spiffs_page_allocate_data(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_page_header *ph,
+ u8_t *data,
+ u32_t len,
+ u32_t page_offs,
+ u8_t finalize,
+ spiffs_page_ix *pix);
+
+s32_t spiffs_page_move(
+ spiffs *fs,
+ spiffs_file fh,
+ u8_t *page_data,
+ spiffs_obj_id obj_id,
+ spiffs_page_header *page_hdr,
+ spiffs_page_ix src_pix,
+ spiffs_page_ix *dst_pix);
+
+s32_t spiffs_page_delete(
+ spiffs *fs,
+ spiffs_page_ix pix);
+
+// ---------------
+
+s32_t spiffs_object_create(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ const u8_t name[],
+ const u8_t meta[],
+ spiffs_obj_type type,
+ spiffs_page_ix *objix_hdr_pix);
+
+s32_t spiffs_object_update_index_hdr(
+ spiffs *fs,
+ spiffs_fd *fd,
+ spiffs_obj_id obj_id,
+ spiffs_page_ix objix_hdr_pix,
+ u8_t *new_objix_hdr_data,
+ const u8_t name[],
+ const u8_t meta[],
+ u32_t size,
+ spiffs_page_ix *new_pix);
+
+#if SPIFFS_IX_MAP
+
+s32_t spiffs_populate_ix_map(
+ spiffs *fs,
+ spiffs_fd *fd,
+ u32_t vec_entry_start,
+ u32_t vec_entry_end);
+
+#endif
+
+void spiffs_cb_object_event(
+ spiffs *fs,
+ spiffs_page_object_ix *objix,
+ int ev,
+ spiffs_obj_id obj_id,
+ spiffs_span_ix spix,
+ spiffs_page_ix new_pix,
+ u32_t new_size);
+
+s32_t spiffs_object_open_by_id(
+ spiffs *fs,
+ spiffs_obj_id obj_id,
+ spiffs_fd *f,
+ spiffs_flags flags,
+ spiffs_mode mode);
+
+s32_t spiffs_object_open_by_page(
+ spiffs *fs,
+ spiffs_page_ix pix,
+ spiffs_fd *f,
+ spiffs_flags flags,
+ spiffs_mode mode);
+
+s32_t spiffs_object_append(
+ spiffs_fd *fd,
+ u32_t offset,
+ u8_t *data,
+ u32_t len);
+
+s32_t spiffs_object_modify(
+ spiffs_fd *fd,
+ u32_t offset,
+ u8_t *data,
+ u32_t len);
+
+s32_t spiffs_object_read(
+ spiffs_fd *fd,
+ u32_t offset,
+ u32_t len,
+ u8_t *dst);
+
+s32_t spiffs_object_truncate(
+ spiffs_fd *fd,
+ u32_t new_len,
+ u8_t remove_object);
+
+s32_t spiffs_object_find_object_index_header_by_name(
+ spiffs *fs,
+ const u8_t name[SPIFFS_OBJ_NAME_LEN],
+ spiffs_page_ix *pix);
+
+// ---------------
+
+s32_t spiffs_gc_check(
+ spiffs *fs,
+ u32_t len);
+
+s32_t spiffs_gc_erase_page_stats(
+ spiffs *fs,
+ spiffs_block_ix bix);
+
+s32_t spiffs_gc_find_candidate(
+ spiffs *fs,
+ spiffs_block_ix **block_candidate,
+ int *candidate_count,
+ char fs_crammed);
+
+s32_t spiffs_gc_clean(
+ spiffs *fs,
+ spiffs_block_ix bix);
+
+s32_t spiffs_gc_quick(
+ spiffs *fs, u16_t max_free_pages);
+
+// ---------------
+
+s32_t spiffs_fd_find_new(
+ spiffs *fs,
+ spiffs_fd **fd,
+ const char *name);
+
+s32_t spiffs_fd_return(
+ spiffs *fs,
+ spiffs_file f);
+
+s32_t spiffs_fd_get(
+ spiffs *fs,
+ spiffs_file f,
+ spiffs_fd **fd);
+
+#if SPIFFS_TEMPORAL_FD_CACHE
+void spiffs_fd_temporal_cache_rehash(
+ spiffs *fs,
+ const char *old_path,
+ const char *new_path);
+#endif
+
+#if SPIFFS_CACHE
+void spiffs_cache_init(
+ spiffs *fs);
+
+void spiffs_cache_drop_page(
+ spiffs *fs,
+ spiffs_page_ix pix);
+
+#if SPIFFS_CACHE_WR
+spiffs_cache_page *spiffs_cache_page_allocate_by_fd(
+ spiffs *fs,
+ spiffs_fd *fd);
+
+void spiffs_cache_fd_release(
+ spiffs *fs,
+ spiffs_cache_page *cp);
+
+spiffs_cache_page *spiffs_cache_page_get_by_fd(
+ spiffs *fs,
+ spiffs_fd *fd);
+#endif
+#endif
+
+s32_t spiffs_lookup_consistency_check(
+ spiffs *fs,
+ u8_t check_all_objects);
+
+s32_t spiffs_page_consistency_check(
+ spiffs *fs);
+
+s32_t spiffs_object_index_consistency_check(
+ spiffs *fs);
+
+#endif /* SPIFFS_NUCLEUS_H_ */
diff --git a/libiot/sys/console.c b/libiot/sys/console.c
new file mode 100644
index 0000000..0c0c293
--- /dev/null
+++ b/libiot/sys/console.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS console utility functions
+ *
+ */
+
+#include "sdkconfig.h"
+
+#if CONFIG_LUA_RTOS_USE_CONSOLE
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/console.h>
+#include <sys/fcntl.h>
+#include <sys/time.h>
+
+void console_clear() {
+ printf("\033[2J\033[1;1H");
+}
+
+void console_hide_cursor() {
+ printf("\033[25h");
+}
+
+void console_show_cursor() {
+ printf("\033[25l");
+}
+
+void console_size(int *rows, int *cols) {
+ struct timeval start; // Start time
+ struct timeval now; // Current time
+ int msectimeout = 100;
+ char buf[6];
+ char *cbuf;
+ char c;
+
+ // Save cursor position
+ printf("\033[s");
+
+ // Set cursor out of the screen
+ printf("\033[999;999H");
+
+ // Get cursor position
+ printf("\033[6n");
+
+ // Return to saved cursor position
+ printf("\033[u");
+
+ int flags = fcntl(fileno(stdin), F_GETFL, 0);
+ fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK);
+
+ // Skip scape sequence
+ gettimeofday(&start, NULL);
+ for(;;) {
+ if (read(fileno(stdin), &c, 1) == 1) {
+ if (c == '\033') {
+ break;
+ }
+ }
+
+ gettimeofday(&now, NULL);
+ if ((now.tv_sec - start.tv_sec) * 1000 - (((now.tv_usec - start.tv_usec) + 500) / 1000) >= msectimeout) {
+ break;
+ }
+ }
+
+ gettimeofday(&start, NULL);
+ for(;;) {
+ if (read(fileno(stdin), &c, 1) == 1) {
+ if (c == '[') {
+ break;
+ }
+ }
+
+ gettimeofday(&now, NULL);
+ if ((now.tv_sec - start.tv_sec) * 1000 - (((now.tv_usec - start.tv_usec) + 500) / 1000) >= msectimeout) {
+ break;
+ }
+ }
+
+ // Read rows
+ c = '\0';
+ cbuf = buf;
+
+ gettimeofday(&start, NULL);
+ for(;;) {
+ if (read(fileno(stdin), &c, 1) == 1) {
+ if (c == ';') {
+ break;
+ }
+ *cbuf++ = c;
+ }
+
+ gettimeofday(&now, NULL);
+ if ((now.tv_sec - start.tv_sec) * 1000 - (((now.tv_usec - start.tv_usec) + 500) / 1000) >= msectimeout) {
+ break;
+ }
+ }
+
+ *cbuf = '\0';
+
+ if (*buf != '\0') {
+ *rows = atoi(buf);
+ }
+
+ // Read cols
+ c = '\0';
+ cbuf = buf;
+
+ gettimeofday(&start, NULL);
+ for(;;) {
+ if (read(fileno(stdin), &c, 1) == 1) {
+ if (c == 'R') {
+ break;
+ }
+ *cbuf++ = c;
+ }
+
+ gettimeofday(&now, NULL);
+ if ((now.tv_sec - start.tv_sec) * 1000 - (((now.tv_usec - start.tv_usec) + 500) / 1000) >= msectimeout) {
+ break;
+ }
+ }
+
+ fcntl(fileno(stdin), F_SETFL, flags);
+
+ *cbuf = '\0';
+
+ if (*buf != '\0') {
+ *cols = atoi(buf);
+ }
+}
+
+void console_gotoxy(int col, int line) {
+ printf("\033[%d;%dH", line + 1, col + 1);
+}
+
+void console_statusline(const char *text1, const char *text2) {
+ int rows = 0;
+ int cols = 0;
+
+ console_size(&rows, &cols);
+
+ console_gotoxy(0, rows);
+ printf("\033[1m\033[7m%s%s\033[K\033[0m", text1, text2);
+}
+
+void console_clearstatusline() {
+ int rows = 0;
+ int cols = 0;
+
+ console_size(&rows, &cols);
+
+ console_gotoxy(0, rows);
+ printf("\033[K\033[0m");
+}
+
+void console_erase_eol() {
+ printf("\033[K");
+}
+
+void console_erase_sol() {
+ printf("\033[1K");
+}
+
+void console_erase_l() {
+ printf("\033[2K");
+}
+
+#endif
diff --git a/libiot/sys/console.h b/libiot/sys/console.h
new file mode 100644
index 0000000..7348890
--- /dev/null
+++ b/libiot/sys/console.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS console utility functions
+ *
+ */
+
+#ifndef CONSOLE_H
+#define CONSOLE_H
+
+void console_clear();
+void console_size(int *rows, int *cols);
+void console_gotoxy(int col, int line);
+void console_statusline(const char *text1, const char *text2);
+void console_clearstatusline();
+void console_erase_eol();
+void console_erase_sol();
+void console_erase_l();
+void console_hide_cursor();
+void console_show_cursor();
+
+#endif /* CONSOLE_H */
+
diff --git a/libiot/sys/history.c b/libiot/sys/history.c
new file mode 100644
index 0000000..e01e767
--- /dev/null
+++ b/libiot/sys/history.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, shell history functions
+ *
+ */
+
+#include "history.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/tail.h>
+#include <sys/mount.h>
+#include <sys/status.h>
+#include <sys/path.h>
+
+static void history_add_internal(const char *buf) {
+ // Get the history file name
+ char fname[PATH_MAX + 1];
+ if (!mount_history_file(fname, sizeof(fname))) {
+ return;
+ }
+
+ int retries = 0;
+
+again: {
+ // Open the history file
+ FILE *fp = fopen(fname,"a");
+ if (!fp) {
+ // File not found, try to create it, and open again
+ mkfile(fname);
+ fp = fopen(fname,"a");
+ if (!fp) {
+ return;
+ }
+ }
+
+ // Get file size
+ long size;
+
+ if ((size = ftell(fp)) < 0) {
+ fclose(fp);
+ return;
+ }
+
+ // Write to history file
+ int len = strlen(buf);
+
+ int no_space = 0;
+
+ if (fwrite(buf, 1, len, fp) != len) {
+ no_space = (errno == ENOSPC);
+ }
+
+ if (!no_space) {
+ if (fflush(fp) == EOF) {
+ no_space = (errno == ENOSPC);
+ }
+ }
+
+ if (no_space) {
+ // Truncate file to the original size
+ if (ftruncate(fileno(fp), size) < 0) {
+ fclose(fp);
+ return;
+ }
+
+ if (retries == 0) {
+ // Tail file
+ fclose(fp);
+ file_tails(fname, fp->_blksize);
+
+ retries++;
+ goto again;
+ }
+ }
+
+ fclose(fp);
+ }
+}
+
+void history_add(const char *line) {
+ history_add_internal("\n");
+ history_add_internal(line);
+}
+
+int history_get(int index, int up, char *buf, int buflen) {
+ // Get the history file name
+ char fname[PATH_MAX + 1];
+ if (!mount_history_file(fname, sizeof(fname))) {
+ return -2;
+ }
+
+ // Open history file
+ FILE *fp = fopen(fname,"r");
+ if (!fp) {
+ // File not found, try to create it, and open again
+ mkfile(fname);
+ fp = fopen(fname,"r");
+ if (!fp) {
+ return -2;
+ }
+ }
+
+ int pos, len, new_pos;
+ char c;
+
+ if (index == -1) {
+ fseek(fp, 0, SEEK_END);
+ pos = ftell(fp);
+ } else {
+ pos = index;
+ }
+
+ if ((pos == 0) && (up)) {
+ fclose(fp);
+ return -3;
+ }
+
+ new_pos = pos;
+
+ while ((pos >= 0) && !feof(fp)) {
+ if (up)
+ fseek(fp, --pos, SEEK_SET);
+ else
+ fseek(fp, ++pos, SEEK_SET);
+
+ c = fgetc(fp);
+ if (c == '\n') {
+ break;
+ }
+ }
+
+ new_pos = pos;
+
+ if ((pos >= 0) && !feof(fp)) {
+ // Get line
+ fgets(buf, buflen, fp);
+ len = strlen(buf);
+
+ // Strip \r \n chars at the end, if present
+ while ((buf[len - 1] == '\r') || (buf[len - 1] == '\n')) {
+ len--;
+ }
+
+ buf[len] = '\0';
+ } else {
+ if (feof(fp))
+ new_pos = -1;
+ else
+ new_pos = 0;
+
+ buf[0] = 0;
+ }
+
+ fclose(fp);
+
+ return new_pos;
+}
diff --git a/libiot/sys/history.h b/libiot/sys/history.h
new file mode 100644
index 0000000..56a22fd
--- /dev/null
+++ b/libiot/sys/history.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, shell history functions
+ *
+ */
+#ifndef _HISTORY_H_
+#define _HISTORY_H_
+
+/**
+ * @brief Add a line to the history file. If the is not left space on the device
+ * the history file is tailed from the beginning.
+ *
+ * @param buf The line to add.
+ */
+void history_add(const char *line);
+int history_get(int index, int up, char *buf, int buflen);
+
+#endif /* _HISTORY_H_ */
diff --git a/libiot/sys/list.c b/libiot/sys/list.c
new file mode 100644
index 0000000..ef965c8
--- /dev/null
+++ b/libiot/sys/list.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS list data structure
+ *
+ */
+
+//#include "esp_attr.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <assert.h>
+
+#include "list.h"
+#include "mutex.h"
+
+void lstinit(struct list *list, int first_index, uint8_t flags) {
+ // Create the mutex
+ mtx_init(&list->mutex, NULL, NULL, 0);
+
+ mtx_lock(&list->mutex);
+
+ list->indexes = 0;
+ list->free = NULL;
+ list->index = NULL;
+ list->last = NULL;
+ list->first_index = first_index;
+ list->flags = flags;
+ list->init = 1;
+
+ mtx_unlock(&list->mutex);
+}
+
+int lstadd(struct list *list, void *item, long *item_index) {
+ struct lstindex *index = NULL;
+ struct lstindex *indexa = NULL;
+ int grow = 0;
+
+ mtx_lock(&list->mutex);
+
+ if (list->flags & LIST_DEFAULT) {
+ // Get an index
+ if (list->free) {
+ // Get first free element
+ index = list->free;
+ list->free = index->next;
+ } else {
+ // Must grow index array
+ grow = 1;
+ }
+
+ if (grow) {
+ // Increment index count
+ list->indexes++;
+
+ // Create a new index array for allocate new index
+ indexa = (struct lstindex *)malloc(sizeof(struct lstindex) * list->indexes);
+ if (!indexa) {
+ mtx_unlock(&list->mutex);
+ return ENOMEM;
+ }
+
+ if (list->index) {
+ // Copy current index array to new created
+ bcopy(list->index, indexa, sizeof(struct lstindex) * (list->indexes - 1));
+
+ // Free current index array
+ free(list->index);
+ }
+
+ // Store new index array
+ list->index = indexa;
+
+ // Current index
+ index = list->index + list->indexes - 1;
+
+ // Initialize new index
+ index->index = list->indexes - 1;
+ }
+
+ index->next = NULL;
+ index->item = item;
+ index->deleted = 0;
+
+ // Return index
+ if (item_index) {
+ *item_index = index->index + list->first_index;
+ }
+ } else if (list->flags & LIST_NOT_INDEXED) {
+ // Create a new element
+ index = (struct lstindex *)calloc(1, sizeof(struct lstindex));
+ if (!index) {
+ return ENOMEM;
+ }
+
+ index->item = item;
+
+ if ((list->index == NULL) && (list->last == NULL)) {
+ // First element
+ list->index = index;
+ } else {
+ // Almost there is one element in list
+ assert(list->last != NULL);
+
+ list->last->next = index;
+ index->previous = list->last;
+ }
+
+ list->last = index;
+
+ if (item_index) {
+ *item_index = (long)item;
+ }
+ }
+
+ mtx_unlock(&list->mutex);
+
+ return 0;
+}
+
+int lstget(struct list *list, long index, void **item) {
+ struct lstindex *cindex = NULL;
+ long iindex;
+
+ mtx_lock(&list->mutex);
+
+ if (list->flags & LIST_DEFAULT) {
+ if (!list->indexes) {
+ mtx_unlock(&list->mutex);
+ return EINVAL;
+ }
+
+ // Check index
+ if (index < list->first_index) {
+ mtx_unlock(&list->mutex);
+ return EINVAL;
+ }
+
+ // Get new internal index
+ iindex = index - list->first_index;
+
+ // Test for a valid index
+ if (iindex > list->indexes) {
+ mtx_unlock(&list->mutex);
+ return EINVAL;
+ }
+
+ cindex = list->index + iindex;
+
+ if (cindex->deleted) {
+ mtx_unlock(&list->mutex);
+ return EINVAL;
+ }
+ } else if (list->flags & LIST_NOT_INDEXED) {
+ cindex = list->index;
+
+ while (cindex) {
+ if (cindex->item == (void *)index) {
+ *item = cindex->item;
+ break;
+ }
+
+ cindex = cindex->next;
+ }
+
+ if (!cindex) {
+ mtx_unlock(&list->mutex);
+ return EINVAL;
+ }
+ }
+
+ *item = cindex->item;
+
+ mtx_unlock(&list->mutex);
+
+ return 0;
+}
+
+int lstremovec(struct list *list, long index, int destroy, bool compact) {
+ struct lstindex *cindex = NULL;
+ long iindex;
+
+ mtx_lock(&list->mutex);
+
+ if (list->flags & LIST_DEFAULT) {
+ // Check index
+ if (index < list->first_index) {
+ mtx_unlock(&list->mutex);
+ return EINVAL;
+ }
+
+ // Get new internal index
+ iindex = index - list->first_index;
+
+ // Test for a valid index
+ if ((iindex < 0) || (iindex > list->indexes)) {
+ mtx_unlock(&list->mutex);
+ return EINVAL;
+ }
+
+ cindex = &list->index[iindex];
+
+ if (destroy) {
+ free(cindex->item);
+ }
+
+ if (compact) {
+ bcopy(&list->index[iindex+1], &list->index[iindex], sizeof(struct lstindex) * (list->indexes - iindex - 1));
+ iindex = list->indexes-1;
+ cindex = &list->index[iindex];
+ }
+
+ cindex->next = list->free;
+ cindex->deleted = 1;
+ list->free = cindex;
+ } else if (list->flags & LIST_NOT_INDEXED) {
+ cindex = list->index;
+
+ while (cindex) {
+ if (cindex->item == (void *)index) {
+ if (cindex->next) {
+ cindex->next->previous = cindex->previous;
+ } else {
+ list->last = cindex->previous;
+ }
+
+ if (cindex->previous) {
+ cindex->previous->next = cindex->next;
+ } else {
+ list->index = cindex->next;
+ }
+
+ if (destroy) {
+ free(cindex->item);
+ }
+
+ free(cindex);
+
+ break;
+ }
+
+ cindex = cindex->next;
+ }
+
+ }
+
+ mtx_unlock(&list->mutex);
+
+ return 0;
+}
+
+int lstremove(struct list *list, long index, int destroy) {
+ return lstremovec(list, index, destroy, false);
+}
+
+int lstfirst(struct list *list) {
+ long index;
+ long res = -1;
+
+ mtx_lock(&list->mutex);
+
+ if (list->flags & LIST_DEFAULT) {
+ for(index=0;index < list->indexes;index++) {
+ if (!list->index[index].deleted) {
+ res = index + list->first_index;
+ break;
+ }
+ }
+ } else if (list->flags & LIST_NOT_INDEXED) {
+ if ((list->index != NULL) && (list->last != NULL)) {
+ res = (long)list->index;
+ }
+ }
+
+ mtx_unlock(&list->mutex);
+
+ return res;
+}
+
+int lstlast(struct list *list) {
+ long index;
+ long res = -1;
+
+ mtx_lock(&list->mutex);
+
+ if (list->flags & LIST_DEFAULT) {
+ for(index = list->indexes - 1;index >= 0;index--) {
+ if (!list->index[index].deleted) {
+ res = index + list->first_index;
+ break;
+ }
+ }
+ } else if (list->flags & LIST_NOT_INDEXED) {
+ if ((list->index != NULL) && (list->last != NULL)) {
+ res = (long)list->last;
+ }
+ }
+
+ mtx_unlock(&list->mutex);
+
+ return res;
+}
+
+int lstnext(struct list *list, long index) {
+ long res = -1;
+ long iindex;
+
+ mtx_lock(&list->mutex);
+
+ if (list->flags & LIST_DEFAULT) {
+ // Check index
+ if (index < list->first_index) {
+ mtx_unlock(&list->mutex);
+ return -1;
+ }
+
+ // Get new internal index
+ iindex = index - list->first_index + 1;
+
+ // Get next non deleted item on list
+ for(;iindex < list->indexes;iindex++) {
+ if (!list->index[iindex].deleted) {
+ res = iindex + list->first_index;
+ break;
+ }
+ }
+ } else if (list->flags & LIST_NOT_INDEXED) {
+ struct lstindex *cindex = NULL;
+
+ cindex = list->index;
+
+ while (cindex) {
+ if (cindex->item == (void *)index) {
+ if (cindex->next) {
+ res = (long)cindex->next;
+ }
+
+ break;
+ }
+
+ cindex = cindex->next;
+ }
+ }
+
+ mtx_unlock(&list->mutex);
+
+ return res;
+}
+
+void lstdestroy(struct list *list, int items) {
+ long index;
+
+ if (!list->init) return;
+
+ mtx_lock(&list->mutex);
+
+ if (list->flags & LIST_DEFAULT) {
+ if (items) {
+ for(index=0;index < list->indexes;index++) {
+ if (!list->index[index].deleted) {
+ free(list->index[index].item);
+ }
+ }
+ }
+
+ if (0 != list->index) {
+ free(list->index);
+ list->index = 0;
+ }
+
+ } else if (list->flags & LIST_NOT_INDEXED) {
+ struct lstindex *cindex = NULL;
+ struct lstindex *pindex = NULL;
+
+ cindex = list->index;
+
+ while (cindex) {
+ pindex = cindex;
+ cindex = cindex->next;
+
+ free(pindex);
+ }
+ }
+
+ list->init = 0;
+
+ mtx_unlock(&list->mutex);
+ mtx_destroy(&list->mutex);
+}
diff --git a/libiot/sys/list.h b/libiot/sys/list.h
new file mode 100644
index 0000000..ea397e0
--- /dev/null
+++ b/libiot/sys/list.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS driver common functions
+ *
+ */
+
+#ifndef _LIST_H
+#define _LIST_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "mutex.h"
+
+struct list {
+ struct mtx mutex;
+ struct lstindex *index;
+ struct lstindex *free;
+ struct lstindex *last;
+ uint8_t indexes;
+ uint8_t first_index;
+ uint8_t flags;
+ uint8_t init;
+};
+
+struct lstindex {
+ void *item;
+ uint8_t index;
+ uint8_t deleted;
+ struct lstindex *next;
+ struct lstindex *previous;
+};
+
+#define LIST_DEFAULT (1 << 0)
+#define LIST_NOT_INDEXED (1 << 1)
+
+void lstinit(struct list *list, int first_index, uint8_t flags);
+int lstadd(struct list *list, void *item, long *item_index);
+int lstget(struct list *list, long index, void **item);
+int lstremove(struct list *list, long index, int destroy);
+int lstremovec(struct list *list, long index, int destroy, bool compact);
+int lstfirst(struct list *list);
+int lstlast(struct list *list);
+int lstnext(struct list *list, long index);
+void lstdestroy(struct list *list, int items);
+
+#endif /* _LIST_H */
diff --git a/libiot/sys/mkfile b/libiot/sys/mkfile
new file mode 100644
index 0000000..6eafd7a
--- /dev/null
+++ b/libiot/sys/mkfile
@@ -0,0 +1,5 @@
+OFILES=\
+ $OFILES\
+ sys/panic.$O\
+ sys/mutex.$O\
+ sys/list.$O\
diff --git a/libiot/sys/mutex.c b/libiot/sys/mutex.c
new file mode 100644
index 0000000..6c63fe3
--- /dev/null
+++ b/libiot/sys/mutex.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS mutex api implementation over FreeRTOS
+ *
+ */
+
+//#include "sdkconfig.h"
+#include <FreeRTOS.h>
+#include "../freertos/adds.h"
+
+//#include "esp_attr.h"
+
+#include "mutex.h"
+#include "panic.h"
+
+int mtx_inited(struct mtx *mutex) {
+ return (mutex->lock != 0);
+}
+
+void mtx_init(struct mtx *mutex, const char *name, const char *type, int opts) {
+ mutex->opts = opts;
+
+ if (mutex->opts == MTX_DEF) {
+ mutex->lock = xSemaphoreCreateBinary();
+ } else if (opts == MTX_RECURSE) {
+ mutex->lock = xSemaphoreCreateRecursiveMutex();
+ } else {
+ return;
+ }
+
+ if (mutex->lock) {
+#if 0 //{}
+ if (xPortInIsrContext()) {
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ xSemaphoreGiveFromISR( mutex->lock, &xHigherPriorityTaskWoken);
+ portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
+ } else {
+#endif
+ if (mutex->opts == MTX_DEF) {
+ xSemaphoreGive( mutex->lock );
+ }
+//{} }
+ }
+}
+
+void mtx_lock(struct mtx *mutex) {
+#if 0 //{}
+ if (xPortInIsrContext()) {
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ xSemaphoreTakeFromISR( mutex->lock, &xHigherPriorityTaskWoken );
+ portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
+ } else {
+#endif
+ if (mutex->opts == MTX_DEF) {
+ xSemaphoreTake( mutex->lock, portMAX_DELAY );
+ } else if (mutex->opts == MTX_RECURSE) {
+ xSemaphoreTakeRecursive( mutex->lock, portMAX_DELAY );
+ }
+//{} }
+}
+
+int mtx_trylock(struct mtx *mutex) {
+ if (mutex->opts == MTX_DEF) {
+ if (xSemaphoreTake( mutex->lock, 0 ) == pdTRUE) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (mutex->opts == MTX_RECURSE) {
+ if (xSemaphoreTakeRecursive( mutex->lock, 0 ) == pdTRUE) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+void mtx_unlock(struct mtx *mutex) {
+#if 0 //{}
+ if (xPortInIsrContext()) {
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ xSemaphoreGiveFromISR( mutex->lock, &xHigherPriorityTaskWoken );
+ portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
+ } else {
+#endif
+ if (mutex->opts == MTX_DEF) {
+ xSemaphoreGive( mutex->lock );
+ } else if (mutex->opts == MTX_RECURSE) {
+ xSemaphoreGiveRecursive( mutex->lock );
+ }
+//{} }
+}
+
+void mtx_destroy(struct mtx *mutex) {
+ if (!mutex->lock) return;
+
+ if (mutex->opts == MTX_DEF) {
+#if 0 //{}
+ if (xPortInIsrContext()) {
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ xSemaphoreGiveFromISR( mutex->lock, &xHigherPriorityTaskWoken );
+ portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
+ } else {
+#endif
+ xSemaphoreGive( mutex->lock );
+//{} }
+
+ vSemaphoreDelete( mutex->lock );
+ } else if (mutex->opts == MTX_RECURSE) {
+ vSemaphoreDelete( mutex->lock );
+ }
+
+ mutex->lock = 0;
+}
diff --git a/libiot/sys/mutex.h b/libiot/sys/mutex.h
new file mode 100644
index 0000000..c2084e9
--- /dev/null
+++ b/libiot/sys/mutex.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS mutex api implementation over FreeRTOS
+ *
+ */
+
+#ifndef _MUTEX_H
+#define _MUTEX_H
+
+//#include "sdkconfig.h"
+
+#include <FreeRTOS.h>
+#include <semphr.h>
+
+struct mtx {
+ SemaphoreHandle_t lock;
+ int opts;
+};
+
+#define MTX_DEF 0
+#define MTX_RECURSE 1
+
+int mtx_inited(struct mtx *mutex);
+void mtx_init(struct mtx *mutex, const char *name, const char *type, int opts);
+void mtx_lock(struct mtx *mutex);
+int mtx_trylock(struct mtx *mutex);
+void mtx_unlock(struct mtx *mutex);
+void mtx_destroy(struct mtx *mutex);
+
+#endif /* _MUTEX_H */
diff --git a/libiot/sys/panic.c b/libiot/sys/panic.c
new file mode 100644
index 0000000..a314455
--- /dev/null
+++ b/libiot/sys/panic.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS panic
+ *
+ */
+
+#include <stdio.h>
+
+void panic(char *str) {
+ printf("%s\n", str);
+ for(;;) {
+ }
+}
diff --git a/libiot/sys/panic.h b/libiot/sys/panic.h
new file mode 100644
index 0000000..8791dd1
--- /dev/null
+++ b/libiot/sys/panic.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS panic
+ *
+ */
+
+#ifndef _SYS_PANIC_H_
+#define _SYS_PANIC_H_
+
+void panic(char *str);
+
+#endif /* !_SYS_PANIC_H_ */
diff --git a/libiot/vfs/fat.c b/libiot/vfs/fat.c
new file mode 100644
index 0000000..d612277
--- /dev/null
+++ b/libiot/vfs/fat.c
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS fat vfs
+ *
+ */
+
+#include "sdkconfig.h"
+
+#if (CONFIG_SD_CARD_MMC || CONFIG_SD_CARD_SPI) && CONFIG_LUA_RTOS_USE_FAT
+
+#include <stdio.h>
+
+#include "esp_vfs.h"
+
+#include "esp_err.h"
+#include "esp_log.h"
+#include "esp_vfs_fat.h"
+#include "driver/sdmmc_host.h"
+#include "driver/sdspi_host.h"
+#include "sdmmc_cmd.h"
+#include "driver/sdmmc_defs.h"
+
+#include <drivers/gpio.h>
+#include <drivers/spi.h>
+#include <drivers/power_bus.h>
+
+#include <sys/driver.h>
+#include <sys/mount.h>
+#include <sys/syslog.h>
+
+#include <sys/vfs/vfs.h>
+
+int vfs_fat_mount(const char *target) {
+#if CONFIG_SD_CARD_SPI
+ spi_bus_t *spi_bus = get_spi_info();
+
+ #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ // Lock resources
+ if (spi_lock_bus_resources(CONFIG_LUA_RTOS_SD_SPI, DRIVER_ALL_FLAGS)) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_SD_CS, DRIVER_ALL_FLAGS, "SD Card - CS")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+
+ sdmmc_host_t host = SDSPI_HOST_DEFAULT();
+ sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
+
+ #if (CONFIG_LUA_RTOS_SD_SPI == 2)
+ host.slot = HSPI_HOST;
+
+ slot_config.gpio_miso = CONFIG_LUA_RTOS_SPI2_MISO;
+ slot_config.gpio_mosi = CONFIG_LUA_RTOS_SPI2_MOSI;
+ slot_config.gpio_sck = CONFIG_LUA_RTOS_SPI2_CLK;
+ slot_config.gpio_cs = CONFIG_LUA_RTOS_SD_CS;
+ #endif // (CONFIG_LUA_RTOS_SD_SPI == 2)
+
+ #if (CONFIG_LUA_RTOS_SD_SPI == 3)
+ host.slot = VSPI_HOST;
+
+ slot_config.gpio_miso = CONFIG_LUA_RTOS_SPI3_MISO;
+ slot_config.gpio_mosi = CONFIG_LUA_RTOS_SPI3_MOSI;
+ slot_config.gpio_sck = CONFIG_LUA_RTOS_SPI3_CLK;
+ slot_config.gpio_cs = CONFIG_LUA_RTOS_SD_CS;
+ #endif // (CONFIG_LUA_RTOS_SD_SPI == 3)
+
+ spi_bus[spi_idx(CONFIG_LUA_RTOS_SD_SPI)].setup |= SPI_DMA_SETUP;
+#endif // CONFIG_SD_CARD_SPI
+
+#if CONFIG_SD_CARD_MMC
+ sdmmc_host_t host = SDMMC_HOST_DEFAULT();
+ sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
+
+ slot_config.gpio_cd = CONFIG_LUA_RTOS_MCC_CD;
+ slot_config.gpio_wp = CONFIG_LUA_RTOS_MCC_WP;
+
+ #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ // Lock resources
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 15, DRIVER_ALL_FLAGS, "SD Card - CMD")) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 14, DRIVER_ALL_FLAGS, "SD Card - CLK")) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 2, DRIVER_ALL_FLAGS, "SD Card - DAT0")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+
+ gpio_pin_pullup(2);
+ gpio_pin_pullup(14);
+ gpio_pin_pullup(15);
+
+ #if CONFIG_LUA_RTOS_MCC_1_LINE
+ host.flags = SDMMC_HOST_FLAG_1BIT;
+ slot_config.width = 1;
+ #endif // CONFIG_LUA_RTOS_MCC_1_LINE
+
+ #if CONFIG_LUA_RTOS_MCC_4_LINE
+ host.flags = SDMMC_HOST_FLAG_4BIT;
+ slot_config.width = 4;
+
+ gpio_pin_pullup(4);
+ gpio_pin_pullup(12);
+ gpio_pin_pullup(13);
+
+ #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 4, DRIVER_ALL_FLAGS, "SD Card - DAT1")) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 12, DRIVER_ALL_FLAGS, "SD Card - DAT2")) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 13, DRIVER_ALL_FLAGS, "SD Card - DAT3")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ #endif // CONFIG_LUA_RTOS_MCC_4_LINE
+
+ #if CONFIG_LUA_RTOS_MCC_CD != -1
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_CD, DRIVER_ALL_FLAGS, "SD Card - CD")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_MCC_CD
+
+ #if CONFIG_LUA_RTOS_MCC_WP != -1
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_WP, DRIVER_ALL_FLAGS, "SD Card - WP")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_MCC_WP
+#endif // CONFIG_SD_CARD_MMC
+
+#if CONFIG_LUA_RTOS_SD_CONNECTED_TO_POWER_BUS
+ pwbus_on();
+#endif
+
+ esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ .format_if_mount_failed = false,
+ .max_files = 5
+ };
+
+ #if CONFIG_SD_CARD_SPI
+ syslog(LOG_INFO, "sd is at spi%d, cs=%s%d",
+ CONFIG_LUA_RTOS_SD_SPI,
+ gpio_portname(CONFIG_LUA_RTOS_SD_CS), gpio_name(CONFIG_LUA_RTOS_SD_CS)
+ );
+ #endif
+
+ #if CONFIG_SD_CARD_MMC
+ syslog(LOG_INFO, "sd is at mmc0");
+ #endif
+
+ sdmmc_card_t* card;
+ esp_err_t ret = esp_vfs_fat_sdmmc_mount("/fat", &host, &slot_config, &mount_config, &card);
+ if (ret != ESP_OK) {
+ syslog(LOG_INFO, "fat can't mounted (error %d)", ret);
+ vfs_fat_umount(target);
+ return -1;
+ }
+
+ syslog(LOG_INFO, "sd name %s", card->cid.name);
+ syslog(LOG_INFO, "sd type %s", (card->ocr & SD_OCR_SDHC_CAP)?"SDHC/SDXC":"SDSC");
+ syslog(LOG_INFO, "sd working at %s", (card->csd.tr_speed > 25000000)?"high speed":"default speed");
+ syslog(LOG_INFO, "sd size %.2f GB",
+ ((((double)card->csd.capacity) * ((double)card->csd.sector_size)) / 1073741824.0)
+ );
+
+ syslog(LOG_INFO, "sd CSD ver=%d, sector_size=%d, capacity=%d read_bl_len=%d",
+ card->csd.csd_ver,
+ card->csd.sector_size, card->csd.capacity, card->csd.read_block_len
+ );
+
+ syslog(LOG_INFO, "sd SCR sd_spec=%d, bus_width=%d", card->scr.sd_spec, card->scr.bus_width);
+
+ syslog(LOG_INFO, "fat mounted on %s", target);
+
+ return 0;
+}
+
+int vfs_fat_umount(const char *target) {
+ // Unmount
+ esp_vfs_fat_sdmmc_unmount();
+
+#if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ #if CONFIG_SD_CARD_SPI
+ spi_bus_t *spi_bus = get_spi_info();
+
+ // Unlock resources
+ spi_unlock_bus_resources(CONFIG_LUA_RTOS_SD_SPI);
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_SD_CS);
+ spi_bus[spi_idx(CONFIG_LUA_RTOS_SD_SPI)].setup &= ~SPI_DMA_SETUP;
+ #endif // CONFIG_SD_CARD_SPI
+
+ #if CONFIG_SD_CARD_MMC
+ // Unlock resources
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 15);
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 14);
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 2);
+
+ #if CONFIG_LUA_RTOS_MCC_4_LINE
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 14);
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 12);
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 13);
+ #endif
+
+ #if CONFIG_LUA_RTOS_MCC_CD != -1
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_CD);
+ #endif
+
+ #if CONFIG_LUA_RTOS_MCC_WP != -1
+ driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_WP);
+ #endif
+ #endif // CONFIG_SD_CARD_MMC
+#endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+
+#if CONFIG_LUA_RTOS_SD_CONNECTED_TO_POWER_BUS
+ pwbus_off();
+#endif
+
+ syslog(LOG_INFO, "fat unmounted");
+
+ return 0;
+}
+
+int vfs_fat_format(const char *target) {
+ vfs_fat_umount(target);
+
+#if CONFIG_SD_CARD_SPI
+ spi_bus_t *spi_bus = get_spi_info();
+
+ #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ // Lock resources
+ if (spi_lock_bus_resources(CONFIG_LUA_RTOS_SD_SPI, DRIVER_ALL_FLAGS)) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_SD_CS, DRIVER_ALL_FLAGS, "SD Card - CS")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+
+ sdmmc_host_t host = SDSPI_HOST_DEFAULT();
+ sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
+
+ #if (CONFIG_LUA_RTOS_SD_SPI == 2)
+ host.slot = HSPI_HOST;
+
+ slot_config.gpio_miso = CONFIG_LUA_RTOS_SPI2_MISO;
+ slot_config.gpio_mosi = CONFIG_LUA_RTOS_SPI2_MOSI;
+ slot_config.gpio_sck = CONFIG_LUA_RTOS_SPI2_CLK;
+ slot_config.gpio_cs = CONFIG_LUA_RTOS_SD_CS;
+ #endif // (CONFIG_LUA_RTOS_SD_SPI == 2)
+
+ #if (CONFIG_LUA_RTOS_SD_SPI == 3)
+ host.slot = VSPI_HOST;
+
+ slot_config.gpio_miso = CONFIG_LUA_RTOS_SPI3_MISO;
+ slot_config.gpio_mosi = CONFIG_LUA_RTOS_SPI3_MOSI;
+ slot_config.gpio_sck = CONFIG_LUA_RTOS_SPI3_CLK;
+ slot_config.gpio_cs = CONFIG_LUA_RTOS_SD_CS;
+ #endif // (CONFIG_LUA_RTOS_SD_SPI == 3)
+
+ spi_bus[spi_idx(CONFIG_LUA_RTOS_SD_SPI)].setup |= SPI_DMA_SETUP;
+#endif // CONFIG_SD_CARD_SPI
+
+#if CONFIG_SD_CARD_MMC
+ sdmmc_host_t host = SDMMC_HOST_DEFAULT();
+ sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
+
+ slot_config.gpio_cd = CONFIG_LUA_RTOS_MCC_CD;
+ slot_config.gpio_wp = CONFIG_LUA_RTOS_MCC_WP;
+
+ #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ // Lock resources
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 15, DRIVER_ALL_FLAGS, "SD Card - CMD")) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 14, DRIVER_ALL_FLAGS, "SD Card - CLK")) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 2, DRIVER_ALL_FLAGS, "SD Card - DAT0")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+
+ gpio_pin_pullup(2);
+ gpio_pin_pullup(14);
+ gpio_pin_pullup(15);
+
+ #if CONFIG_LUA_RTOS_MCC_1_LINE
+ host.flags = SDMMC_HOST_FLAG_1BIT;
+ slot_config.width = 1;
+ #endif // CONFIG_LUA_RTOS_MCC_1_LINE
+
+ #if CONFIG_LUA_RTOS_MCC_4_LINE
+ host.flags = SDMMC_HOST_FLAG_4BIT;
+ slot_config.width = 4;
+
+ gpio_pin_pullup(4);
+ gpio_pin_pullup(12);
+ gpio_pin_pullup(13);
+
+ #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 4, DRIVER_ALL_FLAGS, "SD Card - DAT1")) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 12, DRIVER_ALL_FLAGS, "SD Card - DAT2")) {
+ return -1;
+ }
+
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 13, DRIVER_ALL_FLAGS, "SD Card - DAT3")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS
+ #endif // CONFIG_LUA_RTOS_MCC_4_LINE
+
+ #if CONFIG_LUA_RTOS_MCC_CD != -1
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_CD, DRIVER_ALL_FLAGS, "SD Card - CD")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_MCC_CD
+
+ #if CONFIG_LUA_RTOS_MCC_WP != -1
+ if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_WP, DRIVER_ALL_FLAGS, "SD Card - WP")) {
+ return -1;
+ }
+ #endif // CONFIG_LUA_RTOS_MCC_WP
+#endif // CONFIG_SD_CARD_MMC
+
+ esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ .format_if_mount_failed = false,
+ .max_files = 5
+ };
+
+ #if CONFIG_SD_CARD_SPI
+ syslog(LOG_INFO, "sd is at spi%d, cs=%s%d",
+ CONFIG_LUA_RTOS_SD_SPI,
+ gpio_portname(CONFIG_LUA_RTOS_SD_CS), gpio_name(CONFIG_LUA_RTOS_SD_CS)
+ );
+ #endif
+
+ #if CONFIG_SD_CARD_MMC
+ syslog(LOG_INFO, "sd is at mmc0");
+ #endif
+
+ sdmmc_card_t* card;
+ esp_err_t ret = esp_vfs_fat_sdmmc_format("/fat", &host, &slot_config, &mount_config, &card);
+ if (ret != ESP_OK) {
+ syslog(LOG_INFO, "fat can't mounted (error %d)", ret);
+ vfs_fat_umount(target);
+ return -1;
+ }
+
+ syslog(LOG_INFO, "sd name %s", card->cid.name);
+ syslog(LOG_INFO, "sd type %s", (card->ocr & SD_OCR_SDHC_CAP)?"SDHC/SDXC":"SDSC");
+ syslog(LOG_INFO, "sd working at %s", (card->csd.tr_speed > 25000000)?"high speed":"default speed");
+ syslog(LOG_INFO, "sd size %.2f GB",
+ ((((double)card->csd.capacity) * ((double)card->csd.sector_size)) / 1073741824.0)
+ );
+
+ syslog(LOG_INFO, "sd CSD ver=%d, sector_size=%d, capacity=%d read_bl_len=%d",
+ card->csd.csd_ver,
+ card->csd.sector_size, card->csd.capacity, card->csd.read_block_len
+ );
+
+ syslog(LOG_INFO, "sd SCR sd_spec=%d, bus_width=%d", card->scr.sd_spec, card->scr.bus_width);
+
+ syslog(LOG_INFO, "fat mounted on %s", target);
+
+ vfs_fat_umount(target);
+ vfs_fat_mount(target);
+
+ return 0;
+}
+
+int vfs_fat_fsstat(const char *target, u32_t *total, u32_t *used) {
+ DWORD nclst = 0;
+ FATFS* fs = NULL;
+ FRESULT err = f_getfree (target, &nclst, &fs);
+
+ if (err != FR_OK) {
+ syslog(LOG_ERR, "fat get fs info of '%s' (%i)", target, err);
+ return -1;
+ }
+
+ //fs->free_clst and nclst both are equal and give the number of free clusters
+ //fs->csize = Cluster size [sectors]
+ //fs->ssize = Sector size (512, 1024, 2048 or 4096)
+ //fs->fsize = Size of an FAT [sectors]
+ //fs->n_fatent = nclst + 2
+
+ DWORD tclst = fs->n_fatent - 2;
+
+ if (total) {
+ *total = tclst * fs->csize * fs->ssize;
+ }
+
+ if (used) {
+ *used = (tclst - nclst) * fs->csize * fs->ssize;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/libiot/vfs/include/esp_vfs.h b/libiot/vfs/include/esp_vfs.h
new file mode 100644
index 0000000..a0762d3
--- /dev/null
+++ b/libiot/vfs/include/esp_vfs.h
@@ -0,0 +1,424 @@
+// Copyright 2015-2019 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_VFS_H__
+#define __ESP_VFS_H__
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <utime.h>
+#include <FreeRTOS.h>
+#include <semphr.h>
+#include <esp_err.h>
+#include <sys/types.h>
+#include <sys/reent.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+//#include <sys/termios.h>
+//#include <../platform_include/sys/termios.h>
+//#include <sys/poll.h>
+#include <dirent.h>
+#include <string.h>
+//#include "sdkconfig.h"
+
+#include "lwip/sockets.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _SYS_TYPES_FD_SET
+#error "VFS should be used with FD_SETSIZE and FD_SET from sys/types.h"
+#endif
+
+/**
+ * Maximum number of (global) file descriptors.
+ */
+#define MAX_FDS FD_SETSIZE /* for compatibility with fd_set and select() */
+
+/**
+ * Maximum length of path prefix (not including zero terminator)
+ */
+#define ESP_VFS_PATH_MAX 15
+
+/**
+ * Default value of flags member in esp_vfs_t structure.
+ */
+#define ESP_VFS_FLAG_DEFAULT 0
+
+/**
+ * Flag which indicates that FS needs extra context pointer in syscalls.
+ */
+#define ESP_VFS_FLAG_CONTEXT_PTR 1
+
+/*
+ * @brief VFS identificator used for esp_vfs_register_with_id()
+ */
+typedef int esp_vfs_id_t;
+
+/**
+ * @brief VFS definition structure
+ *
+ * This structure should be filled with pointers to corresponding
+ * FS driver functions.
+ *
+ * VFS component will translate all FDs so that the filesystem implementation
+ * sees them starting at zero. The caller sees a global FD which is prefixed
+ * with an pre-filesystem-implementation.
+ *
+ * Some FS implementations expect some state (e.g. pointer to some structure)
+ * to be passed in as a first argument. For these implementations,
+ * populate the members of this structure which have _p suffix, set
+ * flags member to ESP_VFS_FLAG_CONTEXT_PTR and provide the context pointer
+ * to esp_vfs_register function.
+ * If the implementation doesn't use this extra argument, populate the
+ * members without _p suffix and set flags member to ESP_VFS_FLAG_DEFAULT.
+ *
+ * If the FS driver doesn't provide some of the functions, set corresponding
+ * members to NULL.
+ */
+typedef struct
+{
+ int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT */
+ union {
+ ssize_t (*write_p)(void* p, int fd, const void * data, size_t size);
+ ssize_t (*write)(int fd, const void * data, size_t size);
+ };
+ union {
+ off_t (*lseek_p)(void* p, int fd, off_t size, int mode);
+ off_t (*lseek)(int fd, off_t size, int mode);
+ };
+ union {
+ ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size);
+ ssize_t (*read)(int fd, void * dst, size_t size);
+ };
+ union {
+ int (*open_p)(void* ctx, const char * path, int flags, int mode);
+ int (*open)(const char * path, int flags, int mode);
+ };
+ union {
+ int (*close_p)(void* ctx, int fd);
+ int (*close)(int fd);
+ };
+ union {
+ int (*fstat_p)(void* ctx, int fd, struct stat * st);
+ int (*fstat)(int fd, struct stat * st);
+ };
+ union {
+ int (*stat_p)(void* ctx, const char * path, struct stat * st);
+ int (*stat)(const char * path, struct stat * st);
+ };
+ union {
+ int (*link_p)(void* ctx, const char* n1, const char* n2);
+ int (*link)(const char* n1, const char* n2);
+ };
+ union {
+ int (*unlink_p)(void* ctx, const char *path);
+ int (*unlink)(const char *path);
+ };
+ union {
+ int (*rename_p)(void* ctx, const char *src, const char *dst);
+ int (*rename)(const char *src, const char *dst);
+ };
+ union {
+ DIR* (*opendir_p)(void* ctx, const char* name);
+ DIR* (*opendir)(const char* name);
+ };
+ union {
+ struct dirent* (*readdir_p)(void* ctx, DIR* pdir);
+ struct dirent* (*readdir)(DIR* pdir);
+ };
+ union {
+ int (*readdir_r_p)(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent);
+ int (*readdir_r)(DIR* pdir, struct dirent* entry, struct dirent** out_dirent);
+ };
+ union {
+ long (*telldir_p)(void* ctx, DIR* pdir);
+ long (*telldir)(DIR* pdir);
+ };
+ union {
+ void (*seekdir_p)(void* ctx, DIR* pdir, long offset);
+ void (*seekdir)(DIR* pdir, long offset);
+ };
+ union {
+ int (*closedir_p)(void* ctx, DIR* pdir);
+ int (*closedir)(DIR* pdir);
+ };
+ union {
+ int (*mkdir_p)(void* ctx, const char* name, mode_t mode);
+ int (*mkdir)(const char* name, mode_t mode);
+ };
+ union {
+ int (*rmdir_p)(void* ctx, const char* name);
+ int (*rmdir)(const char* name);
+ };
+ union {
+ int (*fcntl_p)(void* ctx, int fd, int cmd, va_list args);
+ int (*fcntl)(int fd, int cmd, va_list args);
+ };
+ union {
+ int (*ioctl_p)(void* ctx, int fd, int cmd, va_list args);
+ int (*ioctl)(int fd, int cmd, va_list args);
+ };
+ union {
+ int (*fsync_p)(void* ctx, int fd);
+ int (*fsync)(int fd);
+ };
+ union {
+ int (*access_p)(void* ctx, const char *path, int amode);
+ int (*access)(const char *path, int amode);
+ };
+ union {
+ int (*truncate_p)(void* ctx, const char *path, off_t length);
+ int (*truncate)(const char *path, off_t length);
+ };
+ union {
+ int (*utime_p)(void* ctx, const char *path, const struct utimbuf *times);
+ int (*utime)(const char *path, const struct utimbuf *times);
+ };
+ union {
+ int (*ftruncate_p)(void* ctx, int fd, off_t length);
+ int (*ftruncate)(int fd, off_t length);
+ };
+ union {
+ int (*writev_p)(void* ctx, int fd, const struct iovec *iov, int iovcnt);
+ int (*writev)(int fd, const struct iovec *iov, int iovcnt);
+ };
+ union {
+ int (*select_p)(void* ctx, int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
+ int (*select)(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
+ };
+#ifdef CONFIG_SUPPORT_TERMIOS
+ union {
+ int (*tcsetattr_p)(void *ctx, int fd, int optional_actions, const struct termios *p);
+ int (*tcsetattr)(int fd, int optional_actions, const struct termios *p);
+ };
+ union {
+ int (*tcgetattr_p)(void *ctx, int fd, struct termios *p);
+ int (*tcgetattr)(int fd, struct termios *p);
+ };
+ union {
+ int (*tcdrain_p)(void *ctx, int fd);
+ int (*tcdrain)(int fd);
+ };
+ union {
+ int (*tcflush_p)(void *ctx, int fd, int select);
+ int (*tcflush)(int fd, int select);
+ };
+ union {
+ int (*tcflow_p)(void *ctx, int fd, int action);
+ int (*tcflow)(int fd, int action);
+ };
+ union {
+ pid_t (*tcgetsid_p)(void *ctx, int fd);
+ pid_t (*tcgetsid)(int fd);
+ };
+ union {
+ int (*tcsendbreak_p)(void *ctx, int fd, int duration);
+ int (*tcsendbreak)(int fd, int duration);
+ };
+#endif // CONFIG_SUPPORT_TERMIOS
+
+ /** start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS */
+ esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, SemaphoreHandle_t *signal_sem);
+ /** socket select function for socket FDs with the functionality of POSIX select(); this should be set only for the socket VFS */
+ int (*socket_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
+ /** called by VFS to interrupt the socket_select call when select is activated from a non-socket VFS driver; set only for the socket driver */
+ void (*stop_socket_select)();
+ /** stop_socket_select which can be called from ISR; set only for the socket driver */
+ void (*stop_socket_select_isr)(BaseType_t *woken);
+ /** end_select is called to stop the I/O multiplexing and deinitialize the environment created by start_select for the given VFS */
+ void (*end_select)();
+} esp_vfs_t;
+
+
+/**
+ * Register a virtual filesystem for given path prefix.
+ *
+ * @param base_path file path prefix associated with the filesystem.
+ * Must be a zero-terminated C string, up to ESP_VFS_PATH_MAX
+ * characters long, and at least 2 characters long.
+ * Name must start with a "/" and must not end with "/".
+ * For example, "/data" or "/dev/spi" are valid.
+ * These VFSes would then be called to handle file paths such as
+ * "/data/myfile.txt" or "/dev/spi/0".
+ * @param vfs Pointer to esp_vfs_t, a structure which maps syscalls to
+ * the filesystem driver functions. VFS component doesn't
+ * assume ownership of this pointer.
+ * @param ctx If vfs->flags has ESP_VFS_FLAG_CONTEXT_PTR set, a pointer
+ * which should be passed to VFS functions. Otherwise, NULL.
+ *
+ * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
+ * registered.
+ */
+esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx);
+
+
+/**
+ * Special case function for registering a VFS that uses a method other than
+ * open() to open new file descriptors from the interval <min_fd; max_fd).
+ *
+ * This is a special-purpose function intended for registering LWIP sockets to VFS.
+ *
+ * @param vfs Pointer to esp_vfs_t. Meaning is the same as for esp_vfs_register().
+ * @param ctx Pointer to context structure. Meaning is the same as for esp_vfs_register().
+ * @param min_fd The smallest file descriptor this VFS will use.
+ * @param max_fd Upper boundary for file descriptors this VFS will use (the biggest file descriptor plus one).
+ *
+ * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
+ * registered, ESP_ERR_INVALID_ARG if the file descriptor boundaries
+ * are incorrect.
+ */
+esp_err_t esp_vfs_register_fd_range(const esp_vfs_t *vfs, void *ctx, int min_fd, int max_fd);
+
+/**
+ * Special case function for registering a VFS that uses a method other than
+ * open() to open new file descriptors. In comparison with
+ * esp_vfs_register_fd_range, this function doesn't pre-registers an interval
+ * of file descriptors. File descriptors can be registered later, by using
+ * esp_vfs_register_fd.
+ *
+ * @param vfs Pointer to esp_vfs_t. Meaning is the same as for esp_vfs_register().
+ * @param ctx Pointer to context structure. Meaning is the same as for esp_vfs_register().
+ * @param vfs_id Here will be written the VFS ID which can be passed to
+ * esp_vfs_register_fd for registering file descriptors.
+ *
+ * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
+ * registered, ESP_ERR_INVALID_ARG if the file descriptor boundaries
+ * are incorrect.
+ */
+esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t *vfs_id);
+
+/**
+ * Unregister a virtual filesystem for given path prefix
+ *
+ * @param base_path file prefix previously used in esp_vfs_register call
+ * @return ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for given prefix
+ * hasn't been registered
+ */
+esp_err_t esp_vfs_unregister(const char* base_path);
+
+/**
+ * Special function for registering another file descriptor for a VFS registered
+ * by esp_vfs_register_with_id.
+ *
+ * @param vfs_id VFS identificator returned by esp_vfs_register_with_id.
+ * @param fd The registered file descriptor will be written to this address.
+ *
+ * @return ESP_OK if the registration is successful,
+ * ESP_ERR_NO_MEM if too many file descriptors are registered,
+ * ESP_ERR_INVALID_ARG if the arguments are incorrect.
+ */
+esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd);
+
+/**
+ * Special function for unregistering a file descriptor belonging to a VFS
+ * registered by esp_vfs_register_with_id.
+ *
+ * @param vfs_id VFS identificator returned by esp_vfs_register_with_id.
+ * @param fd File descriptor which should be unregistered.
+ *
+ * @return ESP_OK if the registration is successful,
+ * ESP_ERR_INVALID_ARG if the arguments are incorrect.
+ */
+esp_err_t esp_vfs_unregister_fd(esp_vfs_id_t vfs_id, int fd);
+
+/**
+ * These functions are to be used in newlib syscall table. They will be called by
+ * newlib when it needs to use any of the syscalls.
+ */
+/**@{*/
+ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size);
+off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode);
+ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size);
+int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode);
+int esp_vfs_close(struct _reent *r, int fd);
+int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st);
+int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st);
+int esp_vfs_link(struct _reent *r, const char* n1, const char* n2);
+int esp_vfs_unlink(struct _reent *r, const char *path);
+int esp_vfs_rename(struct _reent *r, const char *src, const char *dst);
+int esp_vfs_utime(const char *path, const struct utimbuf *times);
+/**@}*/
+
+/**
+ * @brief Synchronous I/O multiplexing which implements the functionality of POSIX select() for VFS
+ * @param nfds Specifies the range of descriptors which should be checked.
+ * The first nfds descriptors will be checked in each set.
+ * @param readfds If not NULL, then points to a descriptor set that on input
+ * specifies which descriptors should be checked for being
+ * ready to read, and on output indicates which descriptors
+ * are ready to read.
+ * @param writefds If not NULL, then points to a descriptor set that on input
+ * specifies which descriptors should be checked for being
+ * ready to write, and on output indicates which descriptors
+ * are ready to write.
+ * @param errorfds If not NULL, then points to a descriptor set that on input
+ * specifies which descriptors should be checked for error
+ * conditions, and on output indicates which descriptors
+ * have error conditions.
+ * @param timeout If not NULL, then points to timeval structure which
+ * specifies the time period after which the functions should
+ * time-out and return. If it is NULL, then the function will
+ * not time-out.
+ *
+ * @return The number of descriptors set in the descriptor sets, or -1
+ * when an error (specified by errno) have occurred.
+ */
+int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
+
+/**
+ * @brief Notification from a VFS driver about a read/write/error condition
+ *
+ * This function is called when the VFS driver detects a read/write/error
+ * condition as it was requested by the previous call to start_select.
+ *
+ * @param signal_sem semaphore handle which was passed to the driver by the start_select call
+ */
+void esp_vfs_select_triggered(SemaphoreHandle_t *signal_sem);
+
+/**
+ * @brief Notification from a VFS driver about a read/write/error condition (ISR version)
+ *
+ * This function is called when the VFS driver detects a read/write/error
+ * condition as it was requested by the previous call to start_select.
+ *
+ * @param signal_sem semaphore handle which was passed to the driver by the start_select call
+ * @param woken is set to pdTRUE if the function wakes up a task with higher priority
+ */
+void esp_vfs_select_triggered_isr(SemaphoreHandle_t *signal_sem, BaseType_t *woken);
+
+/**
+ * @brief Implements the VFS layer for synchronous I/O multiplexing by poll()
+ *
+ * The implementation is based on esp_vfs_select. The parameters and return values are compatible with POSIX poll().
+ *
+ * @param fds Pointer to the array containing file descriptors and events poll() should consider.
+ * @param nfds Number of items in the array fds.
+ * @param timeout Poll() should wait at least timeout milliseconds. If the value is 0 then it should return
+ * immediately. If the value is -1 then it should wait (block) until the event occurs.
+ *
+ * @return A positive return value indicates the number of file descriptors that have been selected. The 0
+ * return value indicates a timed-out poll. -1 is return on failure and errno is set accordingly.
+ *
+ */
+int esp_vfs_poll(struct pollfd *fds, nfds_t nfds, int timeout);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //__ESP_VFS_H__
diff --git a/libiot/vfs/include/sys/dirent.h b/libiot/vfs/include/sys/dirent.h
new file mode 100644
index 0000000..ffc764c
--- /dev/null
+++ b/libiot/vfs/include/sys/dirent.h
@@ -0,0 +1,56 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * This header file provides POSIX-compatible definitions of directory
+ * access functions and related data types.
+ * See http://pubs.opengroup.org/onlinepubs/7908799/xsh/dirent.h.html
+ * for reference.
+ */
+
+/**
+ * @brief Opaque directory structure
+ */
+typedef struct {
+ uint16_t dd_vfs_idx; /*!< VFS index, not to be used by applications */
+ uint16_t dd_rsv; /*!< field reserved for future extension */
+ /* remaining fields are defined by VFS implementation */
+} DIR;
+
+/**
+ * @brief Directory entry structure
+ */
+struct dirent {
+ int d_ino; /*!< file number */
+ uint8_t d_type; /*!< not defined in POSIX, but present in BSD and Linux */
+#define DT_UNKNOWN 0
+#define DT_REG 1
+#define DT_DIR 2
+ char d_name[256]; /*!< zero-terminated file name */
+ uint32_t d_fsize;
+};
+
+DIR* opendir(const char* name);
+struct dirent* readdir(DIR* pdir);
+long telldir(DIR* pdir);
+void seekdir(DIR* pdir, long loc);
+void rewinddir(DIR* pdir);
+int closedir(DIR* pdir);
+int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent);
+
diff --git a/libiot/vfs/lfs.c b/libiot/vfs/lfs.c
new file mode 100644
index 0000000..c008e5e
--- /dev/null
+++ b/libiot/vfs/lfs.c
@@ -0,0 +1,949 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS lfs vfs
+ *
+ */
+
+#include "sdkconfig.h"
+
+#if CONFIG_LUA_RTOS_USE_LFS
+
+#include "rom/spi_flash.h"
+#include "esp_partition.h"
+
+#include <freertos/FreeRTOS.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <dirent.h>
+
+#include <sys/stat.h>
+
+#include "esp_vfs.h"
+#include <errno.h>
+
+#include "lfs.h"
+
+#include <sys/syslog.h>
+#include <sys/mount.h>
+#include <sys/mutex.h>
+#include <sys/list.h>
+#include <sys/fcntl.h>
+#include <sys/vfs/vfs.h>
+#include <dirent.h>
+
+static int vfs_lfs_open(const char *path, int flags, int mode);
+static ssize_t vfs_lfs_write(int fd, const void *data, size_t size);
+static ssize_t vfs_lfs_read(int fd, void * dst, size_t size);
+static int vfs_lfs_fstat(int fd, struct stat * st);
+static int vfs_lfs_close(int fd);
+static off_t vfs_lfs_lseek(int fd, off_t size, int mode);
+static int vfs_lfs_access(const char *path, int amode);
+static long vfs_lfs_telldir(DIR *dirp);
+
+static struct list files;
+static lfs_t lfs;
+
+struct vfs_lfs_context {
+ uint32_t base_addr;
+ struct mtx lock;
+};
+
+/*
+ * This function translate error codes from lfs to errno error codes
+ *
+ */
+static int lfs_to_errno(int res) {
+ switch (res) {
+ case LFS_ERR_OK:
+ return 0;
+
+ case LFS_ERR_IO:
+ case LFS_ERR_CORRUPT:
+ return EIO;
+
+ case LFS_ERR_NOENT:
+ return ENOENT;
+
+ case LFS_ERR_EXIST:
+ return EEXIST;
+
+ case LFS_ERR_NOTDIR:
+ return ENOTDIR;
+
+ case LFS_ERR_ISDIR:
+ return EISDIR;
+
+ case LFS_ERR_NOTEMPTY:
+ return ENOTEMPTY;
+
+ case LFS_ERR_BADF:
+ return EBADF;
+
+ case LFS_ERR_NOMEM:
+ return ENOMEM;
+
+ case LFS_ERR_NOSPC:
+ return ENOSPC;
+
+ case LFS_ERR_INVAL:
+ return EINVAL;
+
+ default:
+ return res;
+ }
+}
+
+static int vfs_lfs_open(const char *path, int flags, int mode) {
+ int fd;
+ int result;
+
+ // Allocate new file
+ vfs_file_t *file = calloc(1, sizeof(vfs_file_t));
+ if (!file) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ file->fs_file = (void *)calloc(1, sizeof(lfs_file_t));
+ if (!file->fs_file) {
+ free(file);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ file->path = strdup(path);
+ if (!file->path) {
+ free(file->fs_file);
+ free(file);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ // Add file to file list and get the file descriptor
+ int res = lstadd(&files, file, &fd);
+ if (res) {
+ free(file->fs_file);
+ free(file->path);
+ free(file);
+ errno = res;
+ return -1;
+ }
+
+ // Translate flags to lfs flags
+ int lfs_flags = 0;
+
+ if (flags == O_APPEND)
+ lfs_flags |= LFS_O_APPEND;
+
+ if (flags == O_RDONLY)
+ lfs_flags |= LFS_O_RDONLY;
+
+ if (flags & O_WRONLY)
+ lfs_flags |= LFS_O_WRONLY;
+
+ if (flags & O_RDWR)
+ lfs_flags |= LFS_O_RDWR;
+
+ if (flags & O_EXCL)
+ lfs_flags |= LFS_O_EXCL;
+
+ if (flags & O_CREAT)
+ lfs_flags |= LFS_O_CREAT;
+
+ if (flags & O_TRUNC)
+ lfs_flags |= LFS_O_TRUNC;
+
+ if (*path == '/') {
+ path++;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ if ((result = lfs_file_open(&lfs, file->fs_file, path, lfs_flags)) < 0) {
+ errno = lfs_to_errno(result);
+ lstremove(&files, fd, 0);
+
+ free(file->fs_file);
+ free(file->path);
+ free(file);
+
+ mtx_unlock(&ctx->lock);
+
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return fd;
+}
+
+static ssize_t vfs_lfs_write(int fd, const void *data, size_t size) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ // Write to file
+ result = lfs_file_write(&lfs, file->fs_file, (void *)data, size);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return result;
+}
+
+static ssize_t vfs_lfs_read(int fd, void *dst, size_t size) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ // Read from file
+ result = lfs_file_read(&lfs, file->fs_file, dst, size);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return result;
+}
+
+static int vfs_lfs_fstat(int fd, struct stat *st) {
+ vfs_file_t *file;
+ struct lfs_info info;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ // Init stats
+ memset(st, 0, sizeof(struct stat));
+
+ // Set block size for this file system
+ st->st_blksize = lfs.cfg->block_size;
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ // Get the file stats
+ result = lfs_stat(&lfs, file->path, &info);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ st->st_size = info.size;
+ st->st_mode = ((info.type==LFS_TYPE_REG)?S_IFREG:S_IFDIR);
+
+ return 0;
+}
+
+static int vfs_lfs_close(int fd) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ // Close file
+ result = lfs_file_close(&lfs, file->fs_file);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ // Remove file from file list
+ lstremove(&files, fd, 0);
+
+ free(file->fs_file);
+ free(file->path);
+ free(file);
+
+ mtx_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static off_t vfs_lfs_lseek(int fd, off_t size, int mode) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ int whence = LFS_SEEK_CUR;
+
+ switch (mode) {
+ case SEEK_SET:
+ whence = LFS_SEEK_SET;
+ break;
+ case SEEK_CUR:
+ whence = LFS_SEEK_CUR;
+ break;
+ case SEEK_END:
+ whence = LFS_SEEK_END;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ result = lfs_file_seek(&lfs, file->fs_file, size, whence);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return result;
+}
+
+static int vfs_lfs_stat(const char *path, struct stat *st) {
+ struct lfs_info info;
+ int result;
+
+ // Init stats
+ memset(st, 0, sizeof(struct stat));
+
+ // Set block size for this file system
+ st->st_blksize = lfs.cfg->block_size;
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ // Get the file stats
+ result = lfs_stat(&lfs, path, &info);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ st->st_size = info.size;
+ st->st_mode = ((info.type==LFS_TYPE_REG)?S_IFREG:S_IFDIR);
+
+ return 0;
+}
+
+static int vfs_lfs_access(const char *path, int amode) {
+#if 0
+ struct stat s;
+
+ if (vfs_lfs_stat(path, &s) < 0) {
+ return -1;
+ }
+
+ if (s.st_mode != S_IFREG) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return 0;
+#endif
+ return 0;
+
+}
+
+static int vfs_lfs_unlink(const char *path) {
+ struct lfs_info info;
+ int result;
+
+ // Sanity checks
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ // Is not a directory
+ result = lfs_stat(&lfs, path, &info);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ if (info.type == LFS_TYPE_DIR) {
+ mtx_unlock(&ctx->lock);
+ errno = EPERM;
+ return -1;
+ }
+
+ // Unlink
+ if ((result = lfs_remove(&lfs, path)) < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static int vfs_lfs_rename(const char *src, const char *dst) {
+ int result;
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ if ((result = lfs_rename(&lfs, src, dst)) < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static DIR* vfs_lfs_opendir(const char *name) {
+ int result;
+
+ vfs_dir_t *dir;
+
+ dir = vfs_allocate_dir("lfs", name);
+ if (!dir) {
+ return NULL;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ // Open directory
+ if ((result = lfs_dir_open(&lfs, dir->fs_dir, name)) < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ vfs_free_dir(dir);
+
+ return NULL;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return (DIR *)dir;
+}
+
+static int vfs_lfs_rmdir(const char *name) {
+ struct lfs_info info;
+ int result;
+
+ // Sanity checks
+ if (strcmp(name,"/") == 0) {
+ errno = EBUSY;
+ return -1;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ // Is a directory
+ result = lfs_stat(&lfs, name, &info);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ if (info.type != LFS_TYPE_DIR) {
+ mtx_unlock(&ctx->lock);
+ errno = ENOTDIR;
+ return -1;
+ }
+
+ // Unlink
+ if ((result = lfs_remove(&lfs, name)) < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static struct dirent *vfs_lfs_readdir(DIR *pdir) {
+ vfs_dir_t *dir = (vfs_dir_t *)pdir;
+ struct dirent *ent = &dir->ent;
+ int result;
+
+ // Clear current entry
+ memset(ent, 0, sizeof(struct dirent));
+
+ // If there are mount points to read, read them first
+ if (dir->mount) {
+ struct dirent *ment = mount_readdir((DIR *)dir);
+ if (ment) {
+ return ment;
+ }
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+again:
+ // Read next directory entry
+ if ((result = lfs_dir_read(&lfs, ((vfs_dir_t *)pdir)->fs_dir, (struct lfs_info *)dir->fs_info)) < 0) {
+ errno = lfs_to_errno(result);
+ } else if (result > 0) {
+ if ((strcmp(((struct lfs_info *)dir->fs_info)->name,".") == 0) || (strcmp(((struct lfs_info *)dir->fs_info)->name,"..") == 0)) {
+ goto again;
+ }
+
+ ent->d_type = (((struct lfs_info *)dir->fs_info)->type == LFS_TYPE_REG)?DT_REG:DT_DIR;
+ ent->d_fsize = ((struct lfs_info *)dir->fs_info)->size;
+ strlcpy(ent->d_name, ((struct lfs_info *)dir->fs_info)->name, MAXNAMLEN);
+
+ mtx_unlock(&ctx->lock);
+
+ return ent;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return NULL;
+}
+
+static long vfs_lfs_telldir(DIR *dirp) {
+ vfs_dir_t *dir = (vfs_dir_t *)dirp;
+
+ lfs_soff_t offset;
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ offset = lfs_dir_tell(&lfs, dir->fs_dir);
+ if (offset < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = EBADF;
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return (long)offset;
+}
+
+static int vfs_piffs_closedir(DIR *pdir) {
+ vfs_dir_t *dir = ((vfs_dir_t *)pdir);
+ int result;
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ if ((result = lfs_dir_close(&lfs, dir->fs_dir)) < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ vfs_free_dir(dir);
+
+ mtx_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static int vfs_lfs_mkdir(const char *path, mode_t mode) {
+ int result;
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ if ((result = lfs_mkdir(&lfs, path)) < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static int vfs_lfs_fsync(int fd) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ result = lfs_file_sync(&lfs, file->fs_file);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static int vfs_lfs_ftruncate(int fd, off_t length) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context;
+
+ mtx_lock(&ctx->lock);
+
+ result = lfs_file_truncate(&lfs, file->fs_file, length);
+ if (result < 0) {
+ mtx_unlock(&ctx->lock);
+ errno = lfs_to_errno(result);
+ return -1;
+ }
+
+ mtx_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static int lfs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) {
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)c->context;
+
+ if (spi_flash_read(ctx->base_addr + (block * c->block_size) + off, buffer, size) != 0) {
+ return LFS_ERR_IO;
+ }
+
+ return 0;
+}
+
+static int lfs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) {
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)c->context;
+
+ if (spi_flash_write(ctx->base_addr + (block * c->block_size) + off, buffer, size) != 0) {
+ return LFS_ERR_IO;
+ }
+
+ return 0;
+}
+
+static int lfs_erase(const struct lfs_config *c, lfs_block_t block) {
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)c->context;
+
+ if (spi_flash_erase_sector((ctx->base_addr + (block * c->block_size)) >> 12) != 0) {
+ return LFS_ERR_IO;
+ }
+
+ return 0;
+}
+
+static int lfs_sync(const struct lfs_config *c) {
+ return 0;
+}
+
+static struct lfs_config *lfs_config() {
+ // Find a partition
+ uint32_t base_address = 0;
+ uint32_t fs_size = 0;
+
+ const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, LUA_RTOS_LFS_PART, NULL);
+
+ if (!partition) {
+ syslog(LOG_ERR, "lfs can't find a valid partition");
+ return NULL;
+ } else {
+ base_address = partition->address;
+ fs_size = partition->size;
+ }
+
+ // Allocate file system configuration data
+ struct lfs_config *cfg = calloc(1, sizeof(struct lfs_config));
+ if (!cfg) {
+ syslog(LOG_ERR, "lfs not enough memory");
+ return NULL;
+ }
+
+ struct vfs_lfs_context *ctx = calloc(1, sizeof(struct vfs_lfs_context));
+ if (!ctx) {
+ free(cfg);
+
+ syslog(LOG_ERR, "lfs not enough memory");
+ return NULL;
+ }
+
+ // Configure the file system
+ cfg->read = lfs_read;
+ cfg->prog = lfs_prog;
+ cfg->erase = lfs_erase;
+ cfg->sync = lfs_sync;
+
+ cfg->block_size = CONFIG_LUA_RTOS_LFS_BLOCK_SIZE;
+ cfg->read_size = CONFIG_LUA_RTOS_LFS_READ_SIZE;
+ cfg->prog_size = CONFIG_LUA_RTOS_LFS_PROG_SIZE;
+ cfg->block_count = fs_size / cfg->block_size;
+ cfg->lookahead = cfg->block_count;
+
+ cfg->context = ctx;
+ ctx->base_addr = base_address;
+
+ return cfg;
+}
+
+int vfs_lfs_mount(const char *target) {
+ esp_vfs_t vfs = {
+ .flags = ESP_VFS_FLAG_DEFAULT,
+ .write = &vfs_lfs_write,
+ .open = &vfs_lfs_open,
+ .fstat = &vfs_lfs_fstat,
+ .close = &vfs_lfs_close,
+ .read = &vfs_lfs_read,
+ .lseek = &vfs_lfs_lseek,
+ .stat = &vfs_lfs_stat,
+ .link = NULL,
+ .unlink = &vfs_lfs_unlink,
+ .rename = &vfs_lfs_rename,
+ .mkdir = &vfs_lfs_mkdir,
+ .opendir = &vfs_lfs_opendir,
+ .readdir = &vfs_lfs_readdir,
+ .closedir = &vfs_piffs_closedir,
+ .rmdir = &vfs_lfs_rmdir,
+ .fsync = &vfs_lfs_fsync,
+ .access = &vfs_lfs_access,
+ .ftruncate = &vfs_lfs_ftruncate,
+ .telldir = &vfs_lfs_telldir,
+ };
+
+ // Get configuration
+ struct lfs_config *cfg = lfs_config();
+ if (!cfg) {
+ return -1;
+ }
+
+ struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)(cfg->context);
+
+ mtx_init(&ctx->lock, NULL, NULL, 0);
+
+ syslog(LOG_INFO,
+ "lfs start address at 0x%x, size %d Kb",
+ ctx->base_addr, (cfg->block_count * cfg->block_size) / 1024);
+
+ syslog(LOG_INFO, "lfs %d blocks, %d bytes/block, %d bytes/read, %d bytes/write",cfg->block_count,cfg->block_size, cfg->read_size, cfg->prog_size);
+
+ // Mount file system
+ int err = lfs_mount(&lfs, cfg);
+ if (err < 0) {
+ syslog(LOG_INFO, "lfs formatting ...");
+
+ int block;
+ for(block = 0;block < cfg->block_count;block++) {
+ lfs_erase(cfg, block);
+ }
+
+ lfs_format(&lfs, cfg);
+ err = lfs_mount(&lfs, cfg);
+ }
+
+ if (err == LFS_ERR_OK) {
+ lstinit(&files, 0, LIST_DEFAULT);
+
+ // Register the file system
+ ESP_ERROR_CHECK(esp_vfs_register("/lfs", &vfs, NULL));
+
+ syslog(LOG_INFO, "lfs mounted on %s", target);
+
+ return 0;
+ } else {
+ free(cfg);
+ free(ctx);
+
+ syslog(LOG_INFO, "lfs mount error");
+ }
+
+ return -1;
+}
+
+int vfs_lfs_umount(const char *target) {
+ // Unmount
+ lfs_umount(&lfs);
+
+ // Free resources
+ if (lfs.cfg) {
+ if (lfs.cfg->context) {
+ mtx_destroy(&((struct vfs_lfs_context *)lfs.cfg->context)->lock);
+ free(lfs.cfg->context);
+ }
+
+ free((struct lfs_config *)lfs.cfg);
+ }
+
+ lstdestroy(&files, 1);
+
+ // Unregister vfs
+ esp_vfs_unregister("/lfs");
+
+ syslog(LOG_INFO, "lfs unmounted");
+
+ return 0;
+}
+
+int vfs_lfs_format(const char *target) {
+ // Unmount first
+ vfs_lfs_umount(target);
+
+ // Get configuration
+ struct lfs_config *cfg = lfs_config();
+ if (!cfg) {
+ return -1;
+ }
+
+ // Format
+ int block;
+ for(block = 0;block < cfg->block_count;block++) {
+ lfs_erase(cfg, block);
+ }
+
+ int err = lfs_format(&lfs, cfg);
+
+ // Free resources
+ if (lfs.cfg) {
+ if (lfs.cfg->context) {
+ free(lfs.cfg->context);
+ }
+
+ free((struct lfs_config *)lfs.cfg);
+ }
+
+ if (err == LFS_ERR_OK) {
+ // Mount again
+ vfs_lfs_mount(target);
+ }
+
+ return -1;
+}
+
+int vfs_lfs_fsstat(const char *target, u32_t *total, u32_t *used) {
+
+ int err = lfs_info(&lfs, total, used);
+ if (err != 0) {
+ syslog(LOG_ERR, "lfs get fs info of '%s' (%i)", target, err);
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/libiot/vfs/mkfile b/libiot/vfs/mkfile
new file mode 100644
index 0000000..600ebc7
--- /dev/null
+++ b/libiot/vfs/mkfile
@@ -0,0 +1,7 @@
+OFILES=\
+ $OFILES\
+ vfs/spiffs.$O\
+
+CFLAGS=\
+ $CFLAGS\
+ -Ivfs/include\
diff --git a/libiot/vfs/ramfs.c b/libiot/vfs/ramfs.c
new file mode 100644
index 0000000..270209c
--- /dev/null
+++ b/libiot/vfs/ramfs.c
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS, RAM file system
+ *
+ */
+
+#include "sdkconfig.h"
+
+#if CONFIG_LUA_RTOS_USE_RAM_FS
+
+#include "esp_partition.h"
+
+#include <freertos/FreeRTOS.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <dirent.h>
+
+#include <sys/stat.h>
+
+#include "esp_vfs.h"
+
+#include <errno.h>
+
+#include "ramfs.h"
+
+#include <sys/syslog.h>
+#include <sys/mount.h>
+#include <sys/mutex.h>
+#include <sys/list.h>
+#include <sys/fcntl.h>
+#include <sys/vfs/vfs.h>
+#include <dirent.h>
+
+static int vfs_ramfs_open(const char *path, int flags, int mode);
+static ssize_t vfs_ramfs_write(int fd, const void *data, size_t size);
+static ssize_t vfs_ramfs_read(int fd, void * dst, size_t size);
+static int vfs_ramfs_fstat(int fd, struct stat * st);
+static int vfs_ramfs_close(int fd);
+static off_t vfs_ramfs_lseek(int fd, off_t size, int mode);
+static int vfs_ramfs_access(const char *path, int amode);
+
+static struct list files;
+static ramfs_t fs;
+
+/*
+ * This function translate file system errors to POSIX errors
+ *
+ */
+static int ramfs_to_errno(int res) {
+ switch (res) {
+ case RAMFS_ERR_OK:
+ return 0;
+ case RAMFS_ERR_NOMEM:
+ return ENOMEM;
+ case RAMFS_ERR_NOENT:
+ return ENOENT;
+ case RAMFS_ERR_EXIST:
+ return EEXIST;
+ case RAMFS_ERR_NOTDIR:
+ return ENOTDIR;
+ case RAMFS_ERR_BADF:
+ return EBADF;
+ case RAMFS_ERR_ACCESS:
+ return EACCES;
+ case RAMFS_ERR_NOSPC:
+ return ENOSPC;
+ case RAMFS_ERR_INVAL:
+ return EINVAL;
+ case RAMFS_ERR_ISDIR:
+ return EISDIR;
+ case RAMFS_ERR_NOTEMPTY:
+ return ENOTEMPTY;
+ case RAMFS_ERR_BUSY:
+ return EBUSY;
+ case RAMFS_ERR_PERM:
+ return EPERM;
+ case RAMFS_ERR_NAMETOOLONG:
+ return ENAMETOOLONG;
+ }
+
+ return ENOTSUP;
+}
+
+static int vfs_ramfs_open(const char *path, int flags, int mode) {
+ int fd;
+ int result;
+
+ // Allocate new file
+ vfs_file_t *file = calloc(1, sizeof(vfs_file_t));
+ if (!file) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ file->fs_file = (void *)calloc(1, sizeof(ramfs_file_t));
+ if (!file->fs_file) {
+ free(file);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ // Add file to file list and get the file descriptor
+ int res = lstadd(&files, file, &fd);
+ if (res) {
+ free(file->fs_file);
+ free(file);
+ errno = res;
+ return -1;
+ }
+
+ // Translate POSIX flags to file system flags
+ int ramfs_flags = 0;
+
+ // Access mode
+ int access_mode = flags & O_ACCMODE;
+
+ if (access_mode == O_RDONLY) {
+ ramfs_flags |= RAMFS_O_RDONLY;
+ } else if (access_mode == O_WRONLY) {
+ ramfs_flags |= RAMFS_O_WRONLY;
+ } else if (access_mode == O_RDWR) {
+ ramfs_flags |= RAMFS_O_RDWR;
+ }
+
+ // File status
+ if (flags & O_CREAT) {
+ ramfs_flags |= RAMFS_O_CREAT;
+ }
+
+ if (flags & O_EXCL) {
+ ramfs_flags |= RAMFS_O_EXCL;
+ }
+
+ if (flags & O_TRUNC) {
+ ramfs_flags |= RAMFS_O_TRUNC;
+ }
+
+ if (flags & O_APPEND) {
+ ramfs_flags |= RAMFS_O_APPEND;
+ }
+
+ if ((result = ramfs_file_open(&fs, file->fs_file, path, ramfs_flags)) != RAMFS_ERR_OK) {
+ errno = ramfs_to_errno(result);
+ lstremove(&files, fd, 0);
+
+ free(file->fs_file);
+ free(file);
+
+ return -1;
+ }
+
+ return fd;
+}
+
+static ssize_t vfs_ramfs_write(int fd, const void *data, size_t size) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ // Write to file
+ result = ramfs_file_write(&fs, file->fs_file, (void *)data, size);
+ if (result < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return result;
+}
+
+static ssize_t vfs_ramfs_read(int fd, void *dst, size_t size) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ // Read from file
+ result = ramfs_file_read(&fs, file->fs_file, dst, size);
+ if (result < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return result;
+}
+
+static int vfs_ramfs_fstat(int fd, struct stat *st) {
+ vfs_file_t *file;
+ ramfs_info_t info;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ // Init stats
+ memset(st, 0, sizeof(struct stat));
+
+ // Get the file stats
+ result = ramfs_file_stat(&fs, file->fs_file, &info);
+ if (result < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ st->st_size = info.size;
+ st->st_mode = ((info.type==RAMFS_FILE)?S_IFREG:S_IFDIR);
+ st->st_blksize = fs.block_size;
+
+ return 0;
+}
+
+static int vfs_ramfs_close(int fd) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ // Close file
+ result = ramfs_file_close(&fs, file->fs_file);
+ if (result < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ // Remove file from file list
+ lstremove(&files, fd, 0);
+
+ free(file->fs_file);
+ free(file);
+
+ return 0;
+}
+
+static off_t vfs_ramfs_lseek(int fd, off_t size, int mode) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ // Convert POSIX whence to file system whence
+ int whence = RAMFS_SEEK_CUR;
+
+ switch (mode) {
+ case SEEK_SET:
+ whence = RAMFS_SEEK_SET;
+ break;
+ case SEEK_CUR:
+ whence = RAMFS_SEEK_CUR;
+ break;
+ case SEEK_END:
+ whence = RAMFS_SEEK_END;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ result = ramfs_file_seek(&fs, file->fs_file, size, whence);
+ if (result < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return result;
+}
+
+static int vfs_ramfs_stat(const char *path, struct stat *st) {
+ ramfs_info_t info;
+ int result;
+
+ // Init stats
+ memset(st, 0, sizeof(struct stat));
+
+ // Get the file stats
+ result = ramfs_stat(&fs, path, &info);
+ if (result < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ st->st_size = info.size;
+ st->st_mode = ((info.type==RAMFS_FILE)?S_IFREG:S_IFDIR);
+ st->st_blksize = fs.block_size;
+
+ return 0;
+}
+
+static int vfs_ramfs_access(const char *path, int amode) {
+#if 0
+ struct stat s;
+
+ if (vfs_ramfs_stat(path, &s) < 0) {
+ return -1;
+ }
+
+ if (s.st_mode != S_IFREG) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return 0;
+#endif
+ return 0;
+
+}
+
+static int vfs_ramfs_unlink(const char *path) {
+ int result;
+
+ if ((result = ramfs_unlink(&fs, path)) < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int vfs_ramfs_rename(const char *src, const char *dst) {
+ int result;
+
+ if ((result = ramfs_rename(&fs, src, dst)) < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return 0;
+}
+
+static DIR* vfs_ramfs_opendir(const char *name) {
+ int result;
+
+ vfs_dir_t *dir = vfs_allocate_dir("ramfs", name);
+ if (!dir) {
+ return NULL;
+ }
+
+ // Open directory
+ if ((result = ramfs_dir_open(&fs, dir->fs_dir, name)) < 0) {
+ errno = ramfs_to_errno(result);
+ vfs_free_dir(dir);
+
+ return NULL;
+ }
+
+ return (DIR *)dir;
+}
+
+static int vfs_ramfs_rmdir(const char *name) {
+ int result;
+
+ if ((result = ramfs_rmdir(&fs, name)) < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct dirent *vfs_ramfs_readdir(DIR *pdir) {
+ vfs_dir_t *dir = (vfs_dir_t *)pdir;
+ struct dirent *ent = &dir->ent;
+ int result;
+
+ // Clear current entry
+ memset(ent, 0, sizeof(struct dirent));
+
+ // If there are mount points to read, read them first
+ if (dir->mount) {
+ struct dirent *ment = mount_readdir((DIR *)dir);
+ if (ment) {
+ return ment;
+ }
+ }
+
+ // Read next directory entry
+ if ((result = ramfs_dir_read(&fs, ((vfs_dir_t *)pdir)->fs_dir, (ramfs_info_t *)dir->fs_info)) == RAMFS_ERR_OK) {
+ ent->d_type = (((ramfs_info_t *)dir->fs_info)->type == RAMFS_FILE)?DT_REG:DT_DIR;
+ ent->d_fsize = ((ramfs_info_t *)dir->fs_info)->size;
+ strlcpy(ent->d_name, ((ramfs_info_t *)dir->fs_info)->name, MAXNAMLEN);
+
+ return ent;
+ }
+
+ if (result != RAMFS_ERR_NOENT) {
+ errno = ramfs_to_errno(result);
+ }
+
+ return NULL;
+}
+
+static long vfs_ramfs_telldir(DIR *dirp) {
+ vfs_dir_t *dir = (vfs_dir_t *)dirp;
+ ramfs_off_t offset;
+
+ offset = ramfs_telldir(&fs, dir->fs_dir);
+ if (offset < 0) {
+ errno = ramfs_to_errno(offset);
+ return -1;
+ }
+
+ return (long)offset;
+}
+
+static int vfs_ramfs_closedir(DIR *pdir) {
+ vfs_dir_t *dir = ((vfs_dir_t *)pdir);
+ int result;
+
+ if ((result = ramfs_dir_close(&fs, dir->fs_dir)) < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ vfs_free_dir(dir);
+
+ return 0;
+}
+
+static int vfs_ramfs_mkdir(const char *path, mode_t mode) {
+ int result;
+
+ if ((result = ramfs_mkdir(&fs, path)) < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int vfs_ramfs_fsync(int fd) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ result = ramfs_file_sync(&fs, file->fs_file);
+ if (result < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int vfs_ramfs_ftruncate(int fd, off_t length) {
+ vfs_file_t *file;
+ int result;
+
+ // Get file from file list
+ result = lstget(&files, fd, (void **) &file);
+ if (result) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if ((result = ramfs_file_truncate(&fs, file->fs_file, length)) != RAMFS_ERR_OK) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int vfs_ramfs_truncate(const char *path, off_t length) {
+ ramfs_file_t file;
+ int result;
+
+ if ((result = ramfs_file_open(&fs, &file, path, RAMFS_O_RDWR)) != RAMFS_ERR_OK) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ if ((result = ramfs_file_truncate(&fs, &file, length)) != RAMFS_ERR_OK) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ result = ramfs_file_close(&fs, &file);
+ if (result < 0) {
+ errno = ramfs_to_errno(result);
+ return -1;
+ }
+
+ return 0;
+}
+
+int vfs_ramfs_mount(const char *target) {
+ esp_vfs_t vfs = {
+ .flags = ESP_VFS_FLAG_DEFAULT,
+ .write = &vfs_ramfs_write,
+ .open = &vfs_ramfs_open,
+ .fstat = &vfs_ramfs_fstat,
+ .close = &vfs_ramfs_close,
+ .read = &vfs_ramfs_read,
+ .lseek = &vfs_ramfs_lseek,
+ .stat = &vfs_ramfs_stat,
+ .link = NULL,
+ .unlink = &vfs_ramfs_unlink,
+ .rename = &vfs_ramfs_rename,
+ .mkdir = &vfs_ramfs_mkdir,
+ .opendir = &vfs_ramfs_opendir,
+ .readdir = &vfs_ramfs_readdir,
+ .closedir = &vfs_ramfs_closedir,
+ .rmdir = &vfs_ramfs_rmdir,
+ .fsync = &vfs_ramfs_fsync,
+ .access = &vfs_ramfs_access,
+ .telldir = &vfs_ramfs_telldir,
+ .truncate = &vfs_ramfs_truncate,
+ .ftruncate = &vfs_ramfs_ftruncate,
+ };
+
+ int res;
+ ramfs_config_t config;
+
+ config.size = CONFIG_LUA_RTOS_RAM_FS_SIZE;
+ config.block_size = CONFIG_LUA_RTOS_RAM_FS_BLOCK_SIZE;
+
+ syslog(LOG_INFO, "ramfs size %d Kb, block size %d bytes", config.size / 1024, config.block_size);
+
+ if ((res = ramfs_mount(&fs, &config)) == RAMFS_ERR_OK) {
+ lstinit(&files, 0, LIST_DEFAULT);
+
+ // Register the file system
+ ESP_ERROR_CHECK(esp_vfs_register("/ramfs", &vfs, NULL));
+
+ syslog(LOG_INFO, "ramfs mounted on %s", target);
+
+ return 0;
+ } else {
+ syslog(LOG_INFO, "ramfs mount error");
+ }
+
+ return -1;
+}
+
+int vfs_ramfs_umount(const char *target) {
+ ramfs_umount(&fs);
+ esp_vfs_unregister("/ramfs");
+
+ syslog(LOG_INFO, "ramfs unmounted");
+
+ return 0;
+}
+
+int vfs_ramfs_format(const char *target) {
+ vfs_ramfs_umount(target);
+ vfs_ramfs_mount(target);
+
+ return 0;
+}
+
+int vfs_ramfs_fsstat(const char *target, u32_t *total, u32_t *used) {
+
+ if (total) {
+ *total = fs.size;
+ }
+
+ if (used) {
+ *used = fs.current_size;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/libiot/vfs/spiffs.c b/libiot/vfs/spiffs.c
new file mode 100644
index 0000000..cdd445a
--- /dev/null
+++ b/libiot/vfs/spiffs.c
@@ -0,0 +1,1423 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS spiffs vfs
+ *
+ */
+
+//#include "sdkconfig.h"
+
+//#if CONFIG_LUA_RTOS_USE_SPIFFS
+
+//#include "esp_partition.h"
+
+#include <FreeRTOS.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include <sys/stat.h>
+
+#include "esp_vfs.h"
+//#include "esp_attr.h"
+#include <errno.h>
+
+#include "../spiffs/spiffs.h"
+#include "../spiffs/k210_spiffs.h"
+#include "../spiffs/spiffs_nucleus.h"
+//#include <sys/syslog.h>
+//#include <sys/mount.h>
+#include "../sys/mutex.h"
+#include "../sys/list.h"
+#include <sys/fcntl.h>
+#include "vfs.h"
+#include <dirent.h>
+
+#include "../hal/w25qxx.h"
+
+#define syslog(a, ...) printf(__VA_ARGS__)
+
+static int vfs_spiffs_open(const char *path, int flags, int mode);
+static ssize_t vfs_spiffs_write(int fdi, const void *data, size_t size);
+static ssize_t vfs_spiffs_read(int fdi, void * dst, size_t size);
+static int vfs_spiffs_fstat(int fdi, struct stat * st);
+static int vfs_spiffs_close(int fdi);
+static off_t vfs_spiffs_lseek(int fdi, off_t size, int mode);
+static int vfs_spiffs_access(const char *path, int amode);
+
+#if !defined(max)
+#define max(A,B) ( (A) > (B) ? (A):(B))
+#endif
+
+#if !defined(min)
+#define min(A,B) ( (A) < (B) ? (A):(B))
+#endif
+
+#define VFS_SPIFFS_FLAGS_READONLY (1 << 0)
+
+static spiffs fs;
+static struct list files;
+
+static u8_t *my_spiffs_work_buf = NULL;
+static u8_t *my_spiffs_fds = NULL;
+static u8_t *my_spiffs_cache = NULL;
+
+static struct mtx vfs_mtx;
+static struct mtx ll_mtx;
+
+
+typedef struct fd_itm fd_itm;
+
+struct fd_itm {
+ long fd;
+ fd_itm* next;
+};
+
+fd_itm* fds_root = NULL;
+fd_itm** fds_root_last = &fds_root;
+int fds_cnt = 0;
+
+
+static int new_fd_itm(long fd){
+ fd_itm *f = malloc(sizeof(fd_itm));
+
+ f->next = NULL;
+ f->fd = fd;
+
+ *fds_root_last = f;
+ fds_root_last = &f->next;
+
+ fds_cnt++;
+
+ return fds_cnt - 1;
+}
+
+static long del_fd_itm(int fdi){
+ long fd = -1;
+ int i;
+ fd_itm *f = fds_root;
+ fd_itm **pref = &fds_root;
+
+ if(fdi < 0 || fdi >= fds_cnt || f == NULL)
+ return -1;
+
+ for(i = 0; i < fdi; i++){
+ pref = &f->next;
+ f = f->next;
+ }
+ *pref = f->next;
+
+ fd = f->fd;
+
+ free(f);
+
+ fds_cnt--;
+
+ return fd;
+}
+
+static long get_fd_itm(int fdi){
+ long fd = -1;
+ int i;
+ fd_itm *f = fds_root;
+
+ if(fdi < 0 || fdi >= fds_cnt || f == NULL)
+ return -1;
+
+ for(i = 0; i < fdi; i++){
+ f = f->next;
+ }
+ fd = f->fd;
+
+ return fd;
+}
+
+
+
+static void dir_path(char *npath, uint8_t base) {
+ int len = strlen(npath);
+
+ if (base) {
+ char *c;
+
+ c = &npath[len - 1];
+ while (c >= npath) {
+ if (*c == '/') {
+ break;
+ }
+
+ len--;
+ c--;
+ }
+ }
+
+ if (len > 1) {
+ if (npath[len - 1] == '.') {
+ npath[len - 1] = '\0';
+
+ if (npath[len - 2] == '/') {
+ npath[len - 2] = '\0';
+ }
+ } else {
+ if (npath[len - 1] == '/') {
+ npath[len - 1] = '\0';
+ }
+ }
+ } else {
+ if ((npath[len - 1] == '/') || (npath[len - 1] == '.')) {
+ npath[len - 1] = '\0';
+ }
+ }
+
+ strlcat(npath, "/.", PATH_MAX);
+}
+
+static void check_path(const char *path, uint8_t *base_is_dir,
+ uint8_t *full_is_dir, uint8_t *is_file, int *filenum) {
+ char bpath[PATH_MAX + 1]; // Base path
+ char fpath[PATH_MAX + 1]; // Full path
+ struct spiffs_dirent e;
+ spiffs_DIR d;
+ int file_num = 0;
+
+ *filenum = 0;
+ *base_is_dir = 0;
+ *full_is_dir = 0;
+ *is_file = 0;
+
+ // Get base directory name
+ strlcpy(bpath, path, PATH_MAX);
+ dir_path(bpath, 1);
+
+ // Get full directory name
+ strlcpy(fpath, path, PATH_MAX);
+ dir_path(fpath, 0);
+
+ SPIFFS_opendir(&fs, "/", &d);
+ while (SPIFFS_readdir(&d, &e)) {
+ if (!strcmp(bpath, (const char *) e.name)) {
+ *base_is_dir = 1;
+ }
+
+ if (!strcmp(fpath, (const char *) e.name)) {
+ *full_is_dir = 1;
+ }
+
+ if (!strcmp(path, (const char *) e.name)) {
+ *is_file = 1;
+ }
+
+ if (!strncmp(fpath, (const char *) e.name, min(strlen((char * )e.name), strlen(fpath) - 1))) {
+ if (strlen((const char *) e.name) >= strlen(fpath) && strcmp(fpath, (const char *) e.name)) {
+ file_num++;
+ }
+ }
+ }
+ SPIFFS_closedir(&d);
+
+ *filenum = file_num;
+}
+
+/*
+ * This function translate error codes from SPIFFS to errno error codes
+ *
+ */
+static int spiffs_result(int res) {
+ switch(res) {
+ case SPIFFS_OK :
+ case SPIFFS_ERR_END_OF_OBJECT:
+ return 0;
+ case SPIFFS_ERR_NOT_WRITABLE:
+ case SPIFFS_ERR_NOT_READABLE:
+ return EACCES;
+ case SPIFFS_ERR_NOT_MOUNTED :
+ 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 :
+ case SPIFFS_ERR_CONFLICTING_NAME:
+ case SPIFFS_ERR_NOT_A_FILE :
+ case SPIFFS_ERR_DELETED :
+ case SPIFFS_ERR_FILE_DELETED :
+ return ENOENT;
+ case SPIFFS_ERR_NAME_TOO_LONG :
+ return ENAMETOOLONG;
+ case SPIFFS_ERR_RO_NOT_IMPL :
+ case SPIFFS_ERR_RO_ABORTED_OPERATION :
+ return EROFS;
+ default:
+ return EIO;
+ }
+
+ return ENOTSUP;
+}
+
+static int vfs_spiffs_traverse(const char *pathp, int *pis_dir, int *filenum, int *valid_prefix) {
+ char path[PATH_MAX + 1];
+ char current[PATH_MAX + 3]; // Current path
+ char *dir; // Current directory
+ spiffs_stat stat;
+ int res;
+
+ int is_dir = 0;
+
+ if (pis_dir) {
+ *pis_dir = 0;
+ }
+
+ if (filenum) {
+ *filenum = 0;
+ }
+
+ if (valid_prefix) {
+ *valid_prefix = 1;
+ }
+
+ if (strlen(pathp) > PATH_MAX - 3) {
+ return ENAMETOOLONG;
+ }
+
+ // Copy original path, because the strtok function changes the original string
+ strlcpy(path, pathp, PATH_MAX);
+
+ // Current directory is empty
+ strcpy(current, "");
+
+ // Get the first directory in path
+ dir = strtok((char *)path, "/");
+ while (dir) {
+ // Append current directory to the current path
+ strncat(current, "/", PATH_MAX);
+ strncat(current, dir, PATH_MAX);
+
+ // To check if the current path is a directory
+ // we must append /.
+ strncat(current, "/.", PATH_MAX);
+
+ res = SPIFFS_stat(&fs, current, &stat);
+
+ // Remove /. from the current path to check if the current path
+ // corresponds to a file in case that is required later
+ *(current + strlen(current) - 2) = '\0';
+
+ // Get next directory in path
+ dir = strtok(NULL, "/");
+
+ if (res != SPIFFS_OK) {
+ // Current path is not a directory, then check if it is a file
+ res = SPIFFS_stat(&fs, current, &stat);
+ if (res != SPIFFS_OK) {
+ // Current path is not a directory, and it is not a file.
+ if (dir) {
+ if (valid_prefix) {
+ *valid_prefix = 0;
+ }
+ }
+
+ // Error: A component in pathname does not exist
+ return ENOENT;
+ } else {
+ // Current path is a file
+ if (dir) {
+ // There are more directories in path to check.
+
+ if (valid_prefix) {
+ *valid_prefix = 0;
+ }
+
+ // Error: a component used as a directory in pathname is not,
+ // in fact, a directory
+ return ENOTDIR;
+ }
+
+ is_dir = 0;
+ }
+ } else {
+ // Current path is a directory
+ is_dir = 1;
+ }
+ }
+
+ if (is_dir && filenum) {
+ // Count files in directory
+ struct spiffs_dirent e;
+ spiffs_DIR d;
+
+ // Get full directory name
+ char fpath[PATH_MAX + 1];
+
+ strlcpy(fpath, pathp, PATH_MAX);
+ dir_path(fpath, 0);
+
+ SPIFFS_opendir(&fs, "/", &d);
+ while (SPIFFS_readdir(&d, &e)) {
+ if (!strncmp(fpath, (const char *) e.name, min(strlen((char * )e.name), strlen(fpath) - 1))) {
+ if (strlen((const char *) e.name) >= strlen(fpath) && strcmp(fpath, (const char *) e.name)) {
+ *filenum = *filenum + 1;
+ }
+ }
+ }
+ SPIFFS_closedir(&d);
+ }
+
+ if (pis_dir) {
+ *pis_dir = (is_dir || (strcmp(pathp, "/") == 0));
+ }
+
+ return EEXIST;
+}
+
+static int vfs_spiffs_open(const char *path, int flags, int mode) {
+ char npath[PATH_MAX + 1];
+ long fd;
+ int result = 0;
+
+ // Allocate new file
+ vfs_file_t *file = calloc(1, sizeof(vfs_file_t));
+ if (!file) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ file->fs_file = (void *)calloc(1, sizeof(spiffs_file));
+ if (!file->fs_file) {
+ free(file);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ // Add file to file list. List index is file descriptor.
+ int res = lstadd(&files, file, &fd);
+ if (res) {
+ free(file->fs_file);
+ free(file);
+ errno = res;
+ return -1;
+ }
+
+ // Open file
+ spiffs_flags spiffs_flgs = 0;
+
+ // Translate flags to SPIFFS flags
+ if (flags == O_RDONLY)
+ spiffs_flgs |= SPIFFS_RDONLY;
+
+ if (flags & O_WRONLY)
+ spiffs_flgs |= SPIFFS_WRONLY;
+
+ if (flags & O_RDWR)
+ spiffs_flgs = SPIFFS_RDWR;
+
+ if (flags & O_EXCL)
+ spiffs_flgs |= SPIFFS_EXCL;
+
+ if (flags & O_CREAT)
+ spiffs_flgs |= SPIFFS_CREAT;
+
+ if (flags & O_TRUNC)
+ spiffs_flgs |= SPIFFS_TRUNC;
+
+ // Check path
+ uint8_t base_is_dir = 0;
+ uint8_t full_is_dir = 0;
+ uint8_t is_file = 0;
+ int file_num = 0;
+
+ mtx_lock(&vfs_mtx);
+
+ check_path(path, &base_is_dir, &full_is_dir, &is_file, &file_num);
+
+ if (full_is_dir) {
+ // We want to open a directory.
+ // If in flags are set some write access mode this is an error, because we only
+ // can open a directory in read mode.
+ if (spiffs_flgs & (SPIFFS_WRONLY | SPIFFS_CREAT | SPIFFS_TRUNC)) {
+ lstremove(&files, fd, 0);
+ free(file->fs_file);
+ free(file);
+ mtx_unlock(&vfs_mtx);
+ errno = EISDIR;
+ return -1;
+ }
+
+ // Open the directory
+ strlcpy(npath, path, PATH_MAX);
+ dir_path((char *) npath, 0);
+
+ // Open SPIFFS file
+ *((spiffs_file *)file->fs_file) = SPIFFS_open(&fs, npath, SPIFFS_RDONLY, 0);
+ if (*((spiffs_file *)file->fs_file) < 0) {
+ result = spiffs_result(fs.err_code);
+ }
+
+ file->is_dir = 1;
+ } else {
+ if (!base_is_dir) {
+ // If base path is not a directory we return an error
+ lstremove(&files, fd, 0);
+ free(file->fs_file);
+ free(file);
+ mtx_unlock(&vfs_mtx);
+ errno = ENOENT;
+ return -1;
+ } else {
+ // Open SPIFFS file
+ *((spiffs_file *)file->fs_file) = SPIFFS_open(&fs, path, spiffs_flgs, 0);
+ if (*((spiffs_file *)file->fs_file) < 0) {
+ result = spiffs_result(fs.err_code);
+ }
+ }
+ }
+
+ if (result != 0) {
+ lstremove(&files, fd, 0);
+ free(file->fs_file);
+ free(file);
+ mtx_unlock(&vfs_mtx);
+ errno = result;
+ return -1;
+ }
+
+ int fdi = new_fd_itm(fd);
+
+ mtx_unlock(&vfs_mtx);
+
+ return fdi; //fd;
+}
+
+static ssize_t vfs_spiffs_write(int fdi, const void *data, size_t size) {
+ vfs_file_t *file;
+ int res;
+ long fd = get_fd_itm(fdi);
+
+ res = lstget(&files, fd, (void **) &file);
+ if (res || file->is_dir) {
+ errno = EBADF;
+ return -1;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ // Write SPIFFS file
+ res = SPIFFS_write(&fs, *((spiffs_file *)file->fs_file), (void *) data, size);
+ if (res >= 0) {
+ mtx_unlock(&vfs_mtx);
+ return res;
+ } else {
+ res = spiffs_result(fs.err_code);
+ if (res != 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return -1;
+}
+
+static ssize_t vfs_spiffs_read(int fdi, void * dst, size_t size) {
+ vfs_file_t *file;
+ int res;
+ long fd = get_fd_itm(fdi);
+
+ res = lstget(&files, fd, (void **) &file);
+ if (res || file->is_dir) {
+ errno = EBADF;
+ return -1;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ // Read SPIFFS file
+ res = SPIFFS_read(&fs, *((spiffs_file *)file->fs_file), dst, size);
+ if (res >= 0) {
+ mtx_unlock(&vfs_mtx);
+ return res;
+ } else {
+ res = spiffs_result(fs.err_code);
+ if (res != 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ // EOF
+ mtx_unlock(&vfs_mtx);
+ return 0;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return -1;
+}
+
+static int vfs_spiffs_fstat(int fdi, struct stat * st) {
+ vfs_file_t *file;
+ spiffs_stat stat;
+ int res;
+ long fd = get_fd_itm(fdi);
+
+ res = lstget(&files, fd, (void **) &file);
+ if (res) {
+ errno = EBADF;
+ return -1;
+ }
+
+ // We have not time in SPIFFS
+ st->st_mtime = 0;
+ st->st_atime = 0;
+ st->st_ctime = 0;
+
+ // Set block size for this file system
+ st->st_blksize = w25qxx_FLASH_PAGE_SIZE; //{} CONFIG_LUA_RTOS_SPIFFS_LOG_PAGE_SIZE;
+
+ // First test if it's a directory entry
+ if (file->is_dir) {
+ st->st_mode = S_IFDIR;
+ st->st_size = 0;
+ return 0;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ // If is not a directory get file statistics
+ res = SPIFFS_fstat(&fs, *((spiffs_file *)file->fs_file), &stat);
+ if (res == SPIFFS_OK) {
+ st->st_size = stat.size;
+ } else {
+ st->st_size = 0;
+ res = spiffs_result(fs.err_code);
+ }
+
+ st->st_mode = S_IFREG;
+
+ if (res < 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static int vfs_spiffs_close(int fdi) {
+ vfs_file_t *file;
+ int res;
+ long fd = del_fd_itm(fdi);
+
+ res = lstget(&files, fd, (void **) &file);
+ if (res) {
+ errno = EBADF;
+ return -1;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ res = SPIFFS_close(&fs, *((spiffs_file *)file->fs_file));
+ if (res) {
+ res = spiffs_result(fs.err_code);
+ }
+
+ if (res < 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ lstremove(&files, fd, 0);
+
+ free(file->fs_file);
+ free(file);
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static off_t vfs_spiffs_lseek(int fdi, off_t size, int mode) {
+ vfs_file_t *file;
+ int res;
+ long fd = get_fd_itm(fdi);
+
+ res = lstget(&files, fd, (void **) &file);
+ if (res) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (file->is_dir) {
+ errno = EBADF;
+ return -1;
+ }
+
+ int whence = SPIFFS_SEEK_CUR;
+
+ switch (mode) {
+ case SEEK_SET:
+ whence = SPIFFS_SEEK_SET;
+ break;
+ case SEEK_CUR:
+ whence = SPIFFS_SEEK_CUR;
+ break;
+ case SEEK_END:
+ whence = SPIFFS_SEEK_END;
+ break;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ res = SPIFFS_lseek(&fs, *((spiffs_file *)file->fs_file), size, whence);
+ if (res < 0) {
+ res = spiffs_result(fs.err_code);
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return res;
+}
+
+static int vfs_spiffs_stat(const char * path, struct stat * st) {
+ int fd;
+ int res;
+
+ mtx_lock(&vfs_mtx);
+
+ fd = vfs_spiffs_open(path, 0, 0);
+ if (fd < 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return -1;
+ }
+
+ res = vfs_spiffs_fstat(fd, st);
+ if (fd < 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return -1;
+ }
+
+ vfs_spiffs_close(fd);
+
+ mtx_unlock(&vfs_mtx);
+
+ return res;
+}
+
+static int vfs_spiffs_access(const char *path, int amode) {
+ struct stat s;
+
+ mtx_lock(&vfs_mtx);
+
+ if (vfs_spiffs_stat(path, &s) < 0) {
+ mtx_unlock(&vfs_mtx);
+ return -1;
+ }
+
+ if (s.st_mode != S_IFREG) {
+ mtx_unlock(&vfs_mtx);
+ errno = EACCES;
+ return -1;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static int vfs_spiffs_unlink(const char *path) {
+ int is_dir;
+
+ mtx_lock(&vfs_mtx);
+
+ int res = vfs_spiffs_traverse(path, &is_dir, NULL, NULL);
+ if ((res != 0) && (res != EEXIST)) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ if (is_dir) {
+ mtx_unlock(&vfs_mtx);
+ errno = EPERM;
+ return -1;
+ }
+
+ res = SPIFFS_remove(&fs, path);
+ if (res) {
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return -1;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static int vfs_spiffs_rename(const char *src, const char *dst) {
+ int src_is_dir;
+ int dst_is_dir;
+ int dst_files;
+
+ mtx_lock(&vfs_mtx);
+
+ int res = vfs_spiffs_traverse(src, &src_is_dir, NULL, NULL);
+ if ((res != 0) && (res != EEXIST) && (res != ENOENT)) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ res = vfs_spiffs_traverse(dst, &dst_is_dir, &dst_files, NULL);
+ if ((res != 0) && (res != EEXIST) && (res != ENOENT)) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ if (dst_is_dir && !src_is_dir) {
+ mtx_unlock(&vfs_mtx);
+ errno = EISDIR;
+ return -1;
+ }
+
+ if (dst_is_dir && (dst_files > 0)) {
+ mtx_unlock(&vfs_mtx);
+ errno = ENOTEMPTY;
+ return -1;
+ }
+
+ char dpath[PATH_MAX + 1];
+ char *csrc;
+ char *cname;
+
+ if (src_is_dir) {
+ // We need to rename all tree
+ struct spiffs_dirent e;
+ spiffs_DIR d;
+
+ SPIFFS_opendir(&fs, "/", &d);
+ while (SPIFFS_readdir(&d, &e)) {
+ if (!strncmp(src, (const char *) e.name, strlen(src)) && e.name[strlen(src)] == '/') {
+ strlcpy(dpath, dst, PATH_MAX);
+ csrc = (char *) src;
+ cname = (char *) e.name;
+
+ while (*csrc && *cname && (*csrc == *cname)) {
+ ++csrc;
+ ++cname;
+ }
+
+ strlcat(dpath, cname, PATH_MAX);
+
+ if (SPIFFS_rename(&fs, (char *) e.name, dpath) != SPIFFS_OK) {
+ if (fs.err_code != SPIFFS_ERR_CONFLICTING_NAME) {
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return -1;
+ } else {
+ // This only happens when e.name and dpath are directories. In this case
+ // remove e.name
+ if (SPIFFS_remove(&fs, (char *) e.name) != SPIFFS_OK) {
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return -1;
+ }
+ }
+ }
+ }
+ }
+ SPIFFS_closedir(&d);
+ } else {
+ if (SPIFFS_rename(&fs, src, dst) < 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return -1;
+ }
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static DIR* vfs_spiffs_opendir(const char* name) {
+ int is_dir;
+
+ vfs_dir_t *dir = vfs_allocate_dir("spiffs", name);
+ if (!dir) {
+ return NULL;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ int res = vfs_spiffs_traverse(name, &is_dir, NULL, NULL);
+ if ((res != 0) && (res != EEXIST)) {
+ vfs_free_dir(dir);
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return NULL;
+ }
+
+ if ((res == EEXIST) && !is_dir) {
+ vfs_free_dir(dir);
+ mtx_unlock(&vfs_mtx);
+ errno = ENOTDIR;
+ return NULL;
+ }
+
+ if (!is_dir) {
+ vfs_free_dir(dir);
+ mtx_unlock(&vfs_mtx);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (!SPIFFS_opendir(&fs, name, (spiffs_DIR *)dir->fs_dir)) {
+ vfs_free_dir(dir);
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return NULL;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return (DIR *) dir;
+}
+
+static int vfs_spiffs_rmdir(const char *path) {
+ int is_dir;
+ int filenum;
+
+ if (strcmp(path,"/") == 0) {
+ errno = EBUSY;
+ return -1;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ int res = vfs_spiffs_traverse(path, &is_dir, &filenum, NULL);
+ if ((res != 0) && (res != EEXIST)) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ if (!is_dir) {
+ mtx_unlock(&vfs_mtx);
+ errno = ENOTDIR;
+ return -1;
+ }
+
+ if (filenum > 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = ENOTEMPTY;
+ return -1;
+ }
+
+ // Remove directory
+ char npath[PATH_MAX + 1];
+
+ strlcpy(npath, path, PATH_MAX);
+ dir_path(npath, 0);
+
+ res = SPIFFS_remove(&fs, npath);
+ if (res) {
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return -1;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static struct dirent* vfs_spiffs_readdir(DIR* pdir) {
+ int res = 0, len = 0, entries = 0;
+ vfs_dir_t *dir = (vfs_dir_t *) pdir;
+
+ struct dirent *ent = &dir->ent;
+
+ char *fn;
+
+ // Clear the current dirent
+ memset(ent, 0, sizeof(struct dirent));
+
+ // If there are mount points to read, read them first
+ if (dir->mount) {
+ struct dirent *ment = mount_readdir((DIR *)dir);
+ if (ment) {
+ return ment;
+ }
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ // Search for next entry
+ for (;;) {
+ // Read directory
+ dir->fs_info = (void *)SPIFFS_readdir((spiffs_DIR *)dir->fs_dir, (struct spiffs_dirent *)dir->fs_info);
+ if (!dir->fs_info) {
+ if (fs.err_code != SPIFFS_VIS_END) {
+ res = spiffs_result(fs.err_code);
+ errno = res;
+ }
+
+ break;
+ }
+
+ // Break condition
+ if (((struct spiffs_dirent *)(dir->fs_info))->name[0] == 0)
+ break;
+
+ // Get name and length
+ fn = (char *)(((struct spiffs_dirent *)(dir->fs_info))->name);
+ len = strlen(fn);
+
+ // Get entry type and size
+ ent->d_type = DT_REG;
+
+ if (len >= 2) {
+ if (fn[len - 1] == '.') {
+ if (fn[len - 2] == '/') {
+ ent->d_type = DT_DIR;
+
+ fn[len - 2] = '\0';
+
+ len = strlen(fn);
+
+ // Skip root dir
+ if (len == 0) {
+ continue;
+ }
+ }
+ }
+ }
+
+ // Skip entries not belonged to path
+ if (strncmp(fn, dir->path, strlen(dir->path)) != 0) {
+ continue;
+ }
+
+ if (strlen(dir->path) > 1) {
+ if (*(fn + strlen(dir->path)) != '/') {
+ continue;
+ }
+ }
+
+ // Skip root directory
+ fn = fn + strlen(dir->path);
+ len = strlen(fn);
+ if (len == 0) {
+ continue;
+ }
+
+ // Skip initial /
+ if (len > 1) {
+ if (*fn == '/') {
+ fn = fn + 1;
+ len--;
+ }
+ }
+
+ // Skip subdirectories
+ if (strchr(fn, '/')) {
+ continue;
+ }
+
+ ent->d_fsize = ((struct spiffs_dirent *)(dir->fs_info))->size;
+
+ strlcpy(ent->d_name, fn, MAXNAMLEN);
+
+ entries++;
+ dir->offset++;
+
+ break;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ if (entries > 0) {
+ return ent;
+ } else {
+ return NULL;
+ }
+}
+
+static long vfs_spiffs_telldir(DIR *dirp) {
+ vfs_dir_t *dir = (vfs_dir_t *)dirp;
+
+ if (dir->offset < 0) {
+ errno = EBADF;
+ return -1;
+ }
+
+ return (long)dir->offset;
+}
+
+static int vfs_spiffs_closedir(DIR* pdir) {
+ vfs_dir_t *dir = (vfs_dir_t *) pdir;
+ int res;
+
+ if (!pdir) {
+ errno = EBADF;
+ return -1;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ if ((res = SPIFFS_closedir((spiffs_DIR *)dir->fs_dir)) < 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = spiffs_result(fs.err_code);
+ return -1;
+ }
+
+ vfs_free_dir(dir);
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static int vfs_spiffs_mkdir(const char *path, mode_t mode) {
+ char npath[PATH_MAX + 1];
+ int res;
+ int valid_prefix;
+
+ mtx_lock(&vfs_mtx);
+
+ res = vfs_spiffs_traverse(path, NULL, NULL, &valid_prefix);
+ if ((res != 0) && ((res != ENOENT) || (!valid_prefix))) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ // Create directory
+ strlcpy(npath, path, PATH_MAX);
+ dir_path(npath, 0);
+
+ spiffs_file fd = SPIFFS_open(&fs, npath, SPIFFS_CREAT | SPIFFS_RDWR, 0);
+ if (fd < 0) {
+ mtx_unlock(&vfs_mtx);
+ res = spiffs_result(fs.err_code);
+ errno = res;
+ return -1;
+ }
+
+ if (SPIFFS_close(&fs, fd) < 0) {
+ mtx_unlock(&vfs_mtx);
+ res = spiffs_result(fs.err_code);
+ errno = res;
+ return -1;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static int vfs_spiffs_fsync(int fdi) {
+ vfs_file_t *file;
+ int res;
+ long fd = get_fd_itm(fdi);
+
+ res = lstget(&files, fd, (void **) &file);
+ if (res) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (file->is_dir) {
+ errno = EBADF;
+ return -1;
+ }
+
+ mtx_lock(&vfs_mtx);
+
+ res = SPIFFS_fflush(&fs, *((spiffs_file *)file->fs_file));
+ if (res >= 0) {
+ mtx_unlock(&vfs_mtx);
+ return res;
+ } else {
+ res = spiffs_result(fs.err_code);
+ if (res != 0) {
+ mtx_unlock(&vfs_mtx);
+ errno = res;
+ return -1;
+ }
+
+ mtx_unlock(&vfs_mtx);
+ errno = EIO;
+ return -1;
+ }
+
+ mtx_unlock(&vfs_mtx);
+
+ return 0;
+}
+
+static void vfs_spiffs_free_resources() {
+ if (my_spiffs_work_buf) free(my_spiffs_work_buf);
+ if (my_spiffs_fds) free(my_spiffs_fds);
+ if (my_spiffs_cache) free(my_spiffs_cache);
+
+ mtx_destroy(&vfs_mtx);
+ mtx_destroy(&ll_mtx);
+}
+
+int vfs_spiffs_mount(const char *target) {
+ esp_vfs_t vfs = {
+ .flags = ESP_VFS_FLAG_DEFAULT,
+ .write = &vfs_spiffs_write,
+ .open = &vfs_spiffs_open,
+ .fstat = &vfs_spiffs_fstat,
+ .close = &vfs_spiffs_close,
+ .read = &vfs_spiffs_read,
+ .lseek = &vfs_spiffs_lseek,
+ .stat = &vfs_spiffs_stat,
+ .link = NULL,
+ .unlink = &vfs_spiffs_unlink,
+ .rename = &vfs_spiffs_rename,
+ .mkdir = &vfs_spiffs_mkdir,
+ .opendir = &vfs_spiffs_opendir,
+ .readdir = &vfs_spiffs_readdir,
+ .closedir = &vfs_spiffs_closedir,
+ .rmdir = &vfs_spiffs_rmdir,
+ .fsync = &vfs_spiffs_fsync,
+ .access = &vfs_spiffs_access,
+ .telldir = &vfs_spiffs_telldir,
+ };
+
+ // Mount spiffs file system
+ spiffs_config cfg;
+ int res = 0;
+ int retries = 0;
+
+ // Find partition
+ const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, LUA_RTOS_SPIFFS_PART, NULL);
+
+ if (!partition) {
+ vfs_spiffs_free_resources();
+ syslog(LOG_ERR, "spiffs can't find a valid partition");
+ return -1;
+ } else {
+ cfg.phys_addr = partition->address;
+ cfg.phys_size = partition->size;
+ }
+
+ cfg.phys_erase_block = w25qxx_FLASH_SECTOR_SIZE; //{} CONFIG_LUA_RTOS_SPIFFS_ERASE_SIZE;
+ cfg.log_page_size = w25qxx_FLASH_PAGE_SIZE; //{} CONFIG_LUA_RTOS_SPIFFS_LOG_PAGE_SIZE;
+ cfg.log_block_size = w25qxx_FLASH_SECTOR_SIZE; //{} CONFIG_LUA_RTOS_SPIFFS_LOG_BLOCK_SIZE;
+
+ if (partition) {
+ syslog(LOG_INFO,
+ "spiffs start address at 0x%x, size %d Kb",
+ cfg.phys_addr, cfg.phys_size / 1024);
+ } else {
+ syslog(LOG_INFO, "spiffs start address at 0x%x, size %d Kb",
+ cfg.phys_addr, cfg.phys_size / 1024);
+ }
+
+ cfg.hal_read_f = (spiffs_read) low_spiffs_read;
+ cfg.hal_write_f = (spiffs_write) low_spiffs_write;
+ cfg.hal_erase_f = (spiffs_erase) low_spiffs_erase;
+
+ my_spiffs_work_buf = malloc(cfg.log_page_size * 2);
+ if (!my_spiffs_work_buf) {
+ vfs_spiffs_free_resources();
+ syslog(LOG_ERR, "spiffs can't allocate memory for file system");
+ return -1;
+ }
+
+ int fds_len = sizeof(spiffs_fd) * 5;
+ my_spiffs_fds = malloc(fds_len);
+ if (!my_spiffs_fds) {
+ vfs_spiffs_free_resources();
+ syslog(LOG_ERR, "spiffs can't allocate memory for file system");
+ return -1;
+ }
+
+ int cache_len = cfg.log_page_size * 5;
+ my_spiffs_cache = malloc(cache_len);
+ if (!my_spiffs_cache) {
+ vfs_spiffs_free_resources();
+ syslog(LOG_ERR, "spiffs can't allocate memory for file system");
+ return -1;
+ }
+
+ // Init mutex
+ mtx_init(&vfs_mtx, NULL, NULL, MTX_RECURSE);
+
+ mtx_init(&ll_mtx, NULL, NULL, 0);
+ fs.user_data = &ll_mtx;
+
+ while (retries < 2) {
+ res = SPIFFS_mount(&fs, &cfg, my_spiffs_work_buf, my_spiffs_fds,
+ fds_len, my_spiffs_cache, cache_len, NULL);
+
+ if (res < 0) {
+ if (fs.err_code == SPIFFS_ERR_NOT_A_FS) {
+ syslog(LOG_ERR, "spiffs no file system detected, formating...");
+ SPIFFS_unmount(&fs);
+ res = SPIFFS_format(&fs);
+ if (res < 0) {
+ vfs_spiffs_free_resources();
+ syslog(LOG_ERR, "spiffs format error");
+ return -1;
+ }
+ } else {
+ vfs_spiffs_free_resources();
+ syslog(LOG_ERR, "spiff can't mount file system (%s)",
+ strerror(spiffs_result(fs.err_code)));
+ return -1;
+ }
+ } else {
+ break;
+ }
+
+ retries++;
+ }
+
+ if (retries > 0) {
+ syslog(LOG_INFO, "spiffs creating root folder");
+
+ // Create the root folder
+ spiffs_file fd = SPIFFS_open(&fs, "/.", SPIFFS_CREAT | SPIFFS_RDWR, 0);
+ if (fd < 0) {
+ vfs_spiffs_umount(target);
+ syslog(LOG_ERR, "spiffs can't create root folder (%s)",
+ strerror(spiffs_result(fs.err_code)));
+ return -1;
+ }
+
+ if (SPIFFS_close(&fs, fd) < 0) {
+ vfs_spiffs_umount(target);
+ syslog(LOG_ERR, "spiffs can't create root folder (%s)",
+ strerror(spiffs_result(fs.err_code)));
+ return -1;
+ }
+ }
+
+ lstinit(&files, 0, LIST_DEFAULT);
+
+ ESP_ERROR_CHECK(esp_vfs_register("/spiffs", &vfs, NULL));
+
+ syslog(LOG_INFO, "spiffs mounted on %s", target);
+
+ return 0;
+}
+
+int vfs_spiffs_umount(const char *target) {
+ esp_vfs_unregister("/spiffs");
+ SPIFFS_unmount(&fs);
+
+ vfs_spiffs_free_resources();
+
+ syslog(LOG_INFO, "spiffs unmounted");
+
+ return 0;
+}
+
+int vfs_spiffs_format(const char *target) {
+ vfs_spiffs_umount(target);
+
+ mtx_init(&ll_mtx, NULL, NULL, 0);
+
+ int res = SPIFFS_format(&fs);
+ if (res < 0) {
+ syslog(LOG_ERR, "spiffs format error");
+ return -1;
+ }
+
+ mtx_destroy(&ll_mtx);
+
+ vfs_spiffs_mount(target);
+
+ syslog(LOG_INFO, "spiffs creating root folder");
+
+ // Create the root folder
+ spiffs_file fd = SPIFFS_open(&fs, "/.", SPIFFS_CREAT | SPIFFS_RDWR, 0);
+ if (fd < 0) {
+ vfs_spiffs_umount(target);
+ syslog(LOG_ERR, "spiffs can't create root folder (%s)",
+ strerror(spiffs_result(fs.err_code)));
+ return -1;
+ }
+
+ if (SPIFFS_close(&fs, fd) < 0) {
+ vfs_spiffs_umount(target);
+ syslog(LOG_ERR, "spiffs can't create root folder (%s)",
+ strerror(spiffs_result(fs.err_code)));
+ return -1;
+ }
+
+ return 0;
+}
+
+int vfs_spiffs_fsstat(const char *target, u32_t *total, u32_t *used) {
+
+ if (SPIFFS_info(&fs, total, used) != SPIFFS_OK) {
+ syslog(LOG_ERR, "spiffs get fs info of '%s' (%s)", target,
+ strerror(spiffs_result(fs.err_code)));
+ return -1;
+ }
+
+ return 0;
+}
+
+//#endif
diff --git a/libiot/vfs/vfs.h b/libiot/vfs/vfs.h
new file mode 100644
index 0000000..eb743fd
--- /dev/null
+++ b/libiot/vfs/vfs.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L.
+ * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ * * The WHITECAT logotype cannot be changed, you can remove it, but you
+ * cannot change it in any way. The WHITECAT logotype is:
+ *
+ * /\ /\
+ * / \_____/ \
+ * /_____________\
+ * W H I T E C A T
+ *
+ * * Redistributions in binary form must retain all copyright notices printed
+ * to any local or remote output device. This include any reference to
+ * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
+ * appear in the future.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Lua RTOS common vfs functions
+ *
+ */
+
+#include "esp_vfs.h"
+
+#include <stdarg.h>
+#include <unistd.h>
+
+//#include <sys/mount.h>
+
+#define LUA_RTOS_SPIFFS_PART 0x40
+#define LUA_RTOS_LFS_PART 0x41
+
+typedef struct {
+ void *fs_file;
+ char *path;
+ uint8_t is_dir;
+} vfs_file_t;
+
+typedef struct {
+ DIR dir;
+ long offset;
+ void *fs_dir;
+ void *fs_info;
+ char *path;
+ struct dirent ent;
+ const struct mount_pt *mount;
+} vfs_dir_t;
+
+typedef struct {
+ int flags; // FD flags
+} vfs_fd_local_storage_t;
+
+// Return if there are available bytes for read from the file descriptor.
+// This function is blocking.
+typedef int(*vfs_has_bytes)(int, int);
+
+// Return the bytes available for write to the file descriptor.
+// This function is non blocking.
+typedef int(*vfs_free_bytes)(int);
+
+// Get one byte from the file descriptor.
+// This functions is blocking.
+typedef int(*vfs_get_byte)(int,char *);
+
+// Put one byte to the file descriptor.
+// This function is blocking.
+typedef void(*vfs_put_byte)(int,char *);
+
+/*
+int vfs_fat_mount(const char *target);
+int vfs_fat_umount(const char *target);
+int vfs_fat_format(const char *target);
+int vfs_fat_fsstat(const char *target, u32_t *total, u32_t *used);
+*/
+
+int vfs_spiffs_mount(const char *target);
+int vfs_spiffs_umount(const char *target);
+int vfs_spiffs_format(const char *target);
+int vfs_spiffs_fsstat(const char *target, u32_t *total, u32_t *used);
+
+/*
+int vfs_lfs_mount(const char *target);
+int vfs_lfs_umount(const char *target);
+int vfs_lfs_format(const char *target);
+int vfs_lfs_fsstat(const char *target, u32_t *total, u32_t *used);
+
+int vfs_ramfs_mount(const char *target);
+int vfs_ramfs_umount(const char *target);
+int vfs_ramfs_format(const char *target);
+int vfs_ramfs_fsstat(const char *target, u32_t *total, u32_t *used);
+
+int vfs_romfs_mount(const char *target);
+int vfs_romfs_umount(const char *target);
+int vfs_romfs_fsstat(const char *target, u32_t *total, u32_t *used);
+
+int vfs_generic_fcntl(vfs_fd_local_storage_t *local_storage, int fd, int cmd, va_list args);
+ssize_t vfs_generic_read(vfs_fd_local_storage_t *local_storage, vfs_has_bytes has_bytes, vfs_get_byte get, int fd, void * dst, size_t size);
+ssize_t vfs_generic_write(vfs_fd_local_storage_t *local_storage, vfs_put_byte put, int fd, const void *data, size_t size);
+ssize_t vfs_generic_writev(vfs_fd_local_storage_t *local_storage, vfs_put_byte put, int fd, const struct iovec *iov, int iovcnt);
+int vfs_generic_select(vfs_fd_local_storage_t *local_storage, vfs_has_bytes has_bytes, vfs_free_bytes free, int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
+
+vfs_dir_t *vfs_allocate_dir(const char *vfs, const char *name);
+void vfs_free_dir(vfs_dir_t *dir);
+vfs_fd_local_storage_t *vfs_create_fd_local_storage(int num);
+*/