|
@@ -11,8 +11,9 @@
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
*/
|
|
|
|
|
|
-//#define LOG_LOCAL_LEVEL 4
|
|
|
-//#define CONFIG_LITTLEFS_FOR_IDF_3_2 /* For old IDF - like in release 1.0.4 */
|
|
|
+//#define LOG_LOCAL_LEVEL 5
|
|
|
+//#define CONFIG_LITTLEFS_FOR_IDF_3_2 /* For old IDF - like in release 1.0.4 */
|
|
|
+#define CONFIG_LITTLEFS_SPIFFS_COMPAT 0 /* Use 1 for better drop-in replacement of SPIFFS */
|
|
|
|
|
|
#include "esp_log.h"
|
|
|
#include "esp_spi_flash.h"
|
|
@@ -37,6 +38,7 @@
|
|
|
#include "esp_littlefs.h"
|
|
|
#include "littlefs_api.h"
|
|
|
|
|
|
+
|
|
|
static const char TAG[] = "esp_littlefs";
|
|
|
|
|
|
#define CONFIG_LITTLEFS_BLOCK_SIZE 4096 /* ESP32 can only operate at 4kb */
|
|
@@ -64,7 +66,6 @@ static const char TAG[] = "esp_littlefs";
|
|
|
#define CONFIG_LITTLEFS_MTIME_USE_SECONDS 1
|
|
|
#endif
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
* @brief littlefs DIR structure
|
|
|
*/
|
|
@@ -79,6 +80,8 @@ typedef struct {
|
|
|
static int vfs_littlefs_open(void* ctx, const char * path, int flags, int mode);
|
|
|
static ssize_t vfs_littlefs_write(void* ctx, int fd, const void * data, size_t size);
|
|
|
static ssize_t vfs_littlefs_read(void* ctx, int fd, void * dst, size_t size);
|
|
|
+//static ssize_t vfs_littlefs_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset);
|
|
|
+//static ssize_t vfs_littlefs_pread(void *ctx, int fd, void *dst, size_t size, off_t offset);
|
|
|
static int vfs_littlefs_close(void* ctx, int fd);
|
|
|
static off_t vfs_littlefs_lseek(void* ctx, int fd, off_t offset, int mode);
|
|
|
static int vfs_littlefs_stat(void* ctx, const char * path, struct stat * st);
|
|
@@ -115,6 +118,11 @@ static time_t vfs_littlefs_get_mtime(esp_littlefs_t *efs, const char *path);
|
|
|
static int vfs_littlefs_fstat(void* ctx, int fd, struct stat * st);
|
|
|
#endif
|
|
|
|
|
|
+#if CONFIG_LITTLEFS_SPIFFS_COMPAT
|
|
|
+static void mkdirs(esp_littlefs_t * efs, const char *dir);
|
|
|
+static void rmdirs(esp_littlefs_t * efs, const char *dir);
|
|
|
+#endif // CONFIG_LITTLEFS_SPIFFS_COMPAT
|
|
|
+
|
|
|
static int sem_take(esp_littlefs_t *efs);
|
|
|
static int sem_give(esp_littlefs_t *efs);
|
|
|
|
|
@@ -171,8 +179,10 @@ esp_err_t esp_vfs_littlefs_register(const esp_vfs_littlefs_conf_t * conf)
|
|
|
const esp_vfs_t vfs = {
|
|
|
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
|
|
|
.write_p = &vfs_littlefs_write,
|
|
|
+// .pwrite_p = &vfs_littlefs_pwrite,
|
|
|
.lseek_p = &vfs_littlefs_lseek,
|
|
|
.read_p = &vfs_littlefs_read,
|
|
|
+// .pread_p = &vfs_littlefs_pread,
|
|
|
.open_p = &vfs_littlefs_open,
|
|
|
.close_p = &vfs_littlefs_close,
|
|
|
#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH
|
|
@@ -730,6 +740,8 @@ static int esp_littlefs_allocate_fd(esp_littlefs_t *efs, vfs_littlefs_file_t **
|
|
|
*/
|
|
|
(*file)->path = (char*)(*file) + sizeof(**file);
|
|
|
#endif
|
|
|
+
|
|
|
+ (*file)->open_count = 1;
|
|
|
|
|
|
/* Now find a free place in cache */
|
|
|
for(i=0; i < efs->cache_size; i++) {
|
|
@@ -885,33 +897,54 @@ static int vfs_littlefs_open(void* ctx, const char * path, int flags, int mode)
|
|
|
|
|
|
/* Get a FD */
|
|
|
sem_take(efs);
|
|
|
- fd = esp_littlefs_allocate_fd(efs, &file
|
|
|
+
|
|
|
+ if((fd = esp_littlefs_get_fd_by_name(efs, path)) >= 0) {
|
|
|
+ /* FD is already open, increase the reference counter*/
|
|
|
+ efs->cache[fd]->open_count++;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ /* Need to allocate a new FD */
|
|
|
+ fd = esp_littlefs_allocate_fd(efs, &file
|
|
|
#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH
|
|
|
- , path_len
|
|
|
+ , path_len
|
|
|
#endif
|
|
|
- );
|
|
|
- if(fd < 0) {
|
|
|
- errno = -fd;
|
|
|
- sem_give(efs);
|
|
|
- ESP_LOGV(TAG, "Error obtaining FD");
|
|
|
- return LFS_ERR_INVAL;
|
|
|
- }
|
|
|
- /* Open File */
|
|
|
- res = lfs_file_open(efs->fs, &file->file, path, lfs_flags);
|
|
|
+ );
|
|
|
|
|
|
- if( res < 0 ) {
|
|
|
- errno = -res;
|
|
|
- esp_littlefs_free_fd(efs, fd);
|
|
|
- sem_give(efs);
|
|
|
- ESP_LOGV(TAG, "Failed to open file. Error %s (%d)",
|
|
|
- esp_littlefs_errno(res), res);
|
|
|
- return LFS_ERR_INVAL;
|
|
|
- }
|
|
|
+ if(fd < 0) {
|
|
|
+ errno = -fd;
|
|
|
+ sem_give(efs);
|
|
|
+ ESP_LOGV(TAG, "Error obtaining FD");
|
|
|
+ return LFS_ERR_INVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+#if CONFIG_LITTLEFS_SPIFFS_COMPAT
|
|
|
+ /* Create all parent directories (if necessary) */
|
|
|
+ ESP_LOGV(TAG, "LITTLEFS_SPIFFS_COMPAT attempting to create all directories for %s", path);
|
|
|
+ mkdirs(efs, path);
|
|
|
+#endif // CONFIG_LITTLEFS_SPIFFS_COMPAT
|
|
|
+
|
|
|
+ /* Open File */
|
|
|
+ res = lfs_file_open(efs->fs, &file->file, path, lfs_flags);
|
|
|
|
|
|
- file->hash = compute_hash(path);
|
|
|
+ if( res < 0 ) {
|
|
|
+ errno = -res;
|
|
|
+ esp_littlefs_free_fd(efs, fd);
|
|
|
+ sem_give(efs);
|
|
|
+#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH
|
|
|
+ ESP_LOGV(TAG, "Failed to open file %s. Error %s (%d)",
|
|
|
+ path, esp_littlefs_errno(res), res);
|
|
|
+#else
|
|
|
+ ESP_LOGV(TAG, "Failed to open file. Error %s (%d)",
|
|
|
+ esp_littlefs_errno(res), res);
|
|
|
+#endif
|
|
|
+ return LFS_ERR_INVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ file->hash = compute_hash(path);
|
|
|
#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH
|
|
|
- memcpy(file->path, path, path_len);
|
|
|
+ memcpy(file->path, path, path_len);
|
|
|
#endif
|
|
|
+ }
|
|
|
|
|
|
#if CONFIG_LITTLEFS_USE_MTIME
|
|
|
if (lfs_flags != LFS_O_RDONLY) {
|
|
@@ -986,33 +1019,159 @@ static ssize_t vfs_littlefs_read(void* ctx, int fd, void * dst, size_t size) {
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
+#if 0 //disabled
|
|
|
+
|
|
|
+static ssize_t vfs_littlefs_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset)
|
|
|
+{
|
|
|
+ esp_littlefs_t *efs = (esp_littlefs_t *)ctx;
|
|
|
+ ssize_t res, save_res;
|
|
|
+ vfs_littlefs_file_t *file = NULL;
|
|
|
+
|
|
|
+ sem_take(efs);
|
|
|
+ if ((uint32_t)fd > efs->cache_size)
|
|
|
+ {
|
|
|
+ sem_give(efs);
|
|
|
+ ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size);
|
|
|
+ return LFS_ERR_BADF;
|
|
|
+ }
|
|
|
+ file = efs->cache[fd];
|
|
|
+
|
|
|
+ off_t old_offset = lfs_file_seek(efs->fs, &file->file, 0, SEEK_CUR);
|
|
|
+ if (old_offset < (off_t)0)
|
|
|
+ {
|
|
|
+ res = old_offset;
|
|
|
+ goto exit;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set to wanted position. */
|
|
|
+ res = lfs_file_seek(efs->fs, &file->file, offset, SEEK_SET);
|
|
|
+ if (res < (off_t)0)
|
|
|
+ goto exit;
|
|
|
+
|
|
|
+ /* Write out the data. */
|
|
|
+ res = lfs_file_write(efs->fs, &file->file, src, size);
|
|
|
+
|
|
|
+ /* Now we have to restore the position. If this fails we have to
|
|
|
+ return this as an error. But if the writing also failed we
|
|
|
+ return writing error. */
|
|
|
+ save_res = lfs_file_seek(efs->fs, &file->file, old_offset, SEEK_SET);
|
|
|
+ if (res >= (ssize_t)0 && save_res < (off_t)0)
|
|
|
+ {
|
|
|
+ res = save_res;
|
|
|
+ }
|
|
|
+ sem_give(efs);
|
|
|
+
|
|
|
+exit:
|
|
|
+ if (res < 0)
|
|
|
+ {
|
|
|
+ errno = -res;
|
|
|
+#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH
|
|
|
+ ESP_LOGV(TAG, "Failed to write FD %d; path \"%s\". Error %s (%d)",
|
|
|
+ fd, file->path, esp_littlefs_errno(res), res);
|
|
|
+#else
|
|
|
+ ESP_LOGV(TAG, "Failed to write FD %d. Error %s (%d)",
|
|
|
+ fd, esp_littlefs_errno(res), res);
|
|
|
+#endif
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t vfs_littlefs_pread(void *ctx, int fd, void *dst, size_t size, off_t offset)
|
|
|
+{
|
|
|
+ esp_littlefs_t *efs = (esp_littlefs_t *)ctx;
|
|
|
+ ssize_t res, save_res;
|
|
|
+ vfs_littlefs_file_t *file = NULL;
|
|
|
+
|
|
|
+ sem_take(efs);
|
|
|
+ if ((uint32_t)fd > efs->cache_size)
|
|
|
+ {
|
|
|
+ sem_give(efs);
|
|
|
+ ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size);
|
|
|
+ return LFS_ERR_BADF;
|
|
|
+ }
|
|
|
+ file = efs->cache[fd];
|
|
|
+
|
|
|
+ off_t old_offset = lfs_file_seek(efs->fs, &file->file, 0, SEEK_CUR);
|
|
|
+ if (old_offset < (off_t)0)
|
|
|
+ {
|
|
|
+ res = old_offset;
|
|
|
+ goto exit;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set to wanted position. */
|
|
|
+ res = lfs_file_seek(efs->fs, &file->file, offset, SEEK_SET);
|
|
|
+ if (res < (off_t)0)
|
|
|
+ goto exit;
|
|
|
+
|
|
|
+ /* Read the data. */
|
|
|
+ res = lfs_file_read(efs->fs, &file->file, dst, size);
|
|
|
+
|
|
|
+ /* Now we have to restore the position. If this fails we have to
|
|
|
+ return this as an error. But if the reading also failed we
|
|
|
+ return reading error. */
|
|
|
+ save_res = lfs_file_seek(efs->fs, &file->file, old_offset, SEEK_SET);
|
|
|
+ if (res >= (ssize_t)0 && save_res < (off_t)0)
|
|
|
+ {
|
|
|
+ res = save_res;
|
|
|
+ }
|
|
|
+ sem_give(efs);
|
|
|
+
|
|
|
+exit:
|
|
|
+ if (res < 0)
|
|
|
+ {
|
|
|
+ errno = -res;
|
|
|
+#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH
|
|
|
+ ESP_LOGV(TAG, "Failed to read file \"%s\". Error %s (%d)",
|
|
|
+ file->path, esp_littlefs_errno(res), res);
|
|
|
+#else
|
|
|
+ ESP_LOGV(TAG, "Failed to read FD %d. Error %s (%d)",
|
|
|
+ fd, esp_littlefs_errno(res), res);
|
|
|
+#endif
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+#endif //disabled
|
|
|
+
|
|
|
static int vfs_littlefs_close(void* ctx, int fd) {
|
|
|
// TODO update mtime on close? SPIFFS doesn't do this
|
|
|
esp_littlefs_t * efs = (esp_littlefs_t *)ctx;
|
|
|
- int res;
|
|
|
+ int res = ESP_OK;
|
|
|
vfs_littlefs_file_t *file = NULL;
|
|
|
|
|
|
sem_take(efs);
|
|
|
+
|
|
|
if((uint32_t)fd > efs->cache_size) {
|
|
|
sem_give(efs);
|
|
|
ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size);
|
|
|
return LFS_ERR_BADF;
|
|
|
}
|
|
|
file = efs->cache[fd];
|
|
|
- res = lfs_file_close(efs->fs, &file->file);
|
|
|
- if(res < 0){
|
|
|
- errno = -res;
|
|
|
- sem_give(efs);
|
|
|
+ assert(file->open_count > 0);
|
|
|
+ file->open_count--;
|
|
|
+
|
|
|
+ if(file->open_count == 0) {
|
|
|
+ /* Actually close the file and release the descriptor */
|
|
|
+ res = lfs_file_close(efs->fs, &file->file);
|
|
|
+ if(res < 0){
|
|
|
+ errno = -res;
|
|
|
+ sem_give(efs);
|
|
|
#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH
|
|
|
- ESP_LOGV(TAG, "Failed to close file \"%s\". Error %s (%d)",
|
|
|
- file->path, esp_littlefs_errno(res), res);
|
|
|
+ ESP_LOGV(TAG, "Failed to close file \"%s\". Error %s (%d)",
|
|
|
+ file->path, esp_littlefs_errno(res), res);
|
|
|
#else
|
|
|
- ESP_LOGV(TAG, "Failed to close Fd %d. Error %s (%d)",
|
|
|
- fd, esp_littlefs_errno(res), res);
|
|
|
+ ESP_LOGV(TAG, "Failed to close Fd %d. Error %s (%d)",
|
|
|
+ fd, esp_littlefs_errno(res), res);
|
|
|
#endif
|
|
|
- return res;
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ esp_littlefs_free_fd(efs, fd);
|
|
|
}
|
|
|
- esp_littlefs_free_fd(efs, fd);
|
|
|
+
|
|
|
sem_give(efs);
|
|
|
return res;
|
|
|
}
|
|
@@ -1194,6 +1353,11 @@ static int vfs_littlefs_unlink(void* ctx, const char *path) {
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
+#if CONFIG_LITTLEFS_SPIFFS_COMPAT
|
|
|
+ /* Attempt to delete all parent directories that are empty */
|
|
|
+ rmdirs(efs, path);
|
|
|
+#endif // CONFIG_LITTLEFS_SPIFFS_COMPAT
|
|
|
+
|
|
|
sem_give(efs);
|
|
|
|
|
|
return 0;
|
|
@@ -1426,7 +1590,7 @@ static int vfs_littlefs_rmdir(void* ctx, const char* name) {
|
|
|
}
|
|
|
|
|
|
/* Unlink the dir */
|
|
|
- res = lfs_remove(efs->fs, name);
|
|
|
+ res = lfs_remove(efs->fs, name);
|
|
|
sem_give(efs);
|
|
|
if ( res < 0) {
|
|
|
errno = -res;
|
|
@@ -1511,3 +1675,54 @@ static time_t vfs_littlefs_get_mtime(esp_littlefs_t *efs, const char *path)
|
|
|
return t;
|
|
|
}
|
|
|
#endif //CONFIG_LITTLEFS_USE_MTIME
|
|
|
+
|
|
|
+#if CONFIG_LITTLEFS_SPIFFS_COMPAT
|
|
|
+/**
|
|
|
+ * @brief Recursively make all parent directories for a file.
|
|
|
+ * @param[in] dir Path of directories to make up to. The last element
|
|
|
+ * of the path is assumed to be the file and IS NOT created.
|
|
|
+ * e.g.
|
|
|
+ * "foo/bar/baz"
|
|
|
+ * will create directories "foo" and "bar"
|
|
|
+ */
|
|
|
+static void mkdirs(esp_littlefs_t * efs, const char *dir) {
|
|
|
+ char tmp[CONFIG_LITTLEFS_OBJ_NAME_LEN];
|
|
|
+ char *p = NULL;
|
|
|
+
|
|
|
+ strlcpy(tmp, dir, sizeof(tmp));
|
|
|
+ for(p = tmp + 1; *p; p++) {
|
|
|
+ if(*p == '/') {
|
|
|
+ *p = '\0';
|
|
|
+ vfs_littlefs_mkdir((void*)efs, tmp, S_IRWXU);
|
|
|
+ *p = '/';
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief Recursively attempt to delete all empty directories for a file.
|
|
|
+ * @param[in] dir Path of directories to delete. The last element of the path
|
|
|
+ * is assumed to be the file and IS NOT deleted.
|
|
|
+ * e.g.
|
|
|
+ * "foo/bar/baz"
|
|
|
+ * will attempt to delete directories (in order):
|
|
|
+ * 1. "foo/bar/baz"
|
|
|
+ * 2. "foo/bar"
|
|
|
+ * 3. "foo"
|
|
|
+ */
|
|
|
+
|
|
|
+static void rmdirs(esp_littlefs_t * efs, const char *dir) {
|
|
|
+ char tmp[CONFIG_LITTLEFS_OBJ_NAME_LEN];
|
|
|
+ char *p = NULL;
|
|
|
+
|
|
|
+ strlcpy(tmp, dir, sizeof(tmp));
|
|
|
+ for(p = tmp + strlen(tmp) - 1; p != tmp; p--) {
|
|
|
+ if(*p == '/') {
|
|
|
+ *p = '\0';
|
|
|
+ vfs_littlefs_rmdir((void*)efs, tmp);
|
|
|
+ *p = '/';
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#endif // CONFIG_LITTLEFS_SPIFFS_COMPAT
|