diff --git a/mongoose.c b/mongoose.c index 9c086aa1..b1fd2dbb 100644 --- a/mongoose.c +++ b/mongoose.c @@ -1555,7 +1555,6 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) { // Received headers - check authentication and possibly start uploads/ota struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash); - if (mg_match(hm->uri, mg_str("/api/hi"), NULL) || mg_match(hm->uri, mg_str("/api/logout"), NULL)) { // Do nothing, handle them MG_EV_HTTP_MSG. We bypass auth for those @@ -1628,7 +1627,7 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) { break; } } - if (ch == NULL) mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found"); + if (ch == NULL) mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found\n"); mg_log_http_req(c, hm); } } @@ -2855,6 +2854,7 @@ size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt, + struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { struct mg_fd *fd = (struct mg_fd *) mg_calloc(1, sizeof(*fd)); if (fd != NULL) { @@ -2899,6 +2899,7 @@ bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, char tmp[MG_PATH_MAX], rnd[10]; size_t path_len = mg_snprintf(tmp, sizeof(tmp), "%s..%s", path, mg_random_str(rnd, sizeof(rnd))); + fs->rm(tmp); if (path_len < sizeof(tmp) && (fd = mg_fs_open(fs, tmp, MG_FS_WRITE | MG_FS_EXCL)) != NULL) { result = fs->wr(fd->fd, buf, len) == len; @@ -2906,8 +2907,11 @@ bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, if (result) { fs->rm(path); result = fs->mv(tmp, path); + MG_DEBUG(("mv %s -> %s result=%d", tmp, path, result)); } fs->rm(tmp); + } else { + MG_ERROR(("mg_fs_open(%s) failed, path_len=%zu", tmp, path_len)); } return result; } @@ -3209,6 +3213,10 @@ struct mg_fs mg_fs_packed = { #define MG_STAT_FUNC stat #endif +#if MG_ENABLE_LFS +#include +#endif + static int p_stat(const char *path, size_t *size, time_t *mtime) { #if !defined(S_ISDIR) MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); @@ -3360,7 +3368,6 @@ struct dirent *readdir(DIR *d) { static void p_list(const char *dir, void (*fn)(const char *, void *), void *userdata) { -#if MG_ENABLE_DIRLIST struct dirent *dp; DIR *dirp; if ((dirp = (opendir(dir))) == NULL) return; @@ -3369,9 +3376,6 @@ static void p_list(const char *dir, void (*fn)(const char *, void *), fn(dp->d_name, userdata); } closedir(dirp); -#else - (void) dir, (void) fn, (void) userdata; -#endif } static void *p_open(const char *path, int flags) { @@ -6362,18 +6366,17 @@ bool mg_l2_pppoe_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, -#if MG_ENABLE_LFS -#include -#include -#include -#include -#include +#if MG_ENABLE_LFS #ifndef LFS_USE_RAM #define LFS_USE_RAM 0 // If 1, use RAM FS. If 0, use flash #endif +#ifndef MG_LFS_SIZE +#define MG_LFS_SIZE (64 * 1024) +#endif + #ifndef MG_LFS_BUF_SIZE #define MG_LFS_BUF_SIZE 64 // Buffer size used for reads, writes, and cache #endif @@ -6386,19 +6389,6 @@ bool mg_l2_pppoe_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto, #define MG_LFS_BLOCK_SIZE 8192 #endif -#ifndef DT_DIR -#define DT_DIR 4 -#endif -#ifndef DT_REG -#define DT_REG 8 -#endif - -typedef struct mg_lfs_fd DIR; -struct dirent { - char d_name[LFS_NAME_MAX + 1]; - unsigned char d_type; -}; - struct mg_lfs_fd { struct mg_lfs_fd *next; bool isopen; @@ -6468,29 +6458,50 @@ static int lfs_driver_sync(const struct lfs_config *cfg) { } bool mg_lfs_init(size_t size) { - int result = 0; + //bool ok = false; + MG_DEBUG(("size: %zu", size)); if (s_lfs_ready) return true; - if (size == 0) return false; + if (size == 0) { + MG_ERROR(("invalidsize = 0")); + } else { #if LFS_USE_RAM - s_cfg.block_size = MG_LFS_BLOCK_SIZE; - if (s_ram_fs == NULL) s_ram_fs = (uint8_t *) mg_calloc(1, size); - s_fs = s_ram_fs; + s_cfg.block_size = MG_LFS_BLOCK_SIZE; + if (s_ram_fs == NULL) s_ram_fs = (uint8_t *) mg_calloc(1, size); + s_fs = s_ram_fs; #else - if (mg_flash == NULL || mg_flash->secsz == 0 || mg_flash->size < size) return false; - if (mg_flash->write_fn == NULL) return false; - s_cfg.block_size = mg_flash->secsz; - s_fs = (uint8_t *) mg_flash->start + mg_flash->size - size; + { + // Some flash attributes may be initialised on ota_begin + int lev = mg_log_level; + mg_log_level = MG_LL_NONE; + mg_ota_begin(1); + mg_ota_end(); + mg_log_set(lev); + } + if (mg_flash == NULL || mg_flash->secsz == 0 || mg_flash->size < size) { + MG_ERROR(("mg_flash %p secsz %zu flash_size %zu < size %zu", mg_flash, mg_flash->secsz, mg_flash->size, size)); + } else if (mg_flash->write_fn == NULL) { + MG_ERROR(("flash write_fn == NULL")); + } else { + s_cfg.block_size = mg_flash->secsz; + s_fs = (uint8_t *) mg_flash->start + mg_flash->size - size; + } #endif - if (s_fs == NULL || s_cfg.block_size == 0 || size % s_cfg.block_size != 0) { - return false; + if (s_fs == NULL || s_cfg.block_size == 0 || size % s_cfg.block_size != 0) { + MG_ERROR(("s_fs %p blk_size %zu size %zu", s_fs, s_cfg.block_size)); + } else { + s_cfg.block_count = (lfs_size_t) (size / s_cfg.block_size); + if (s_cfg.block_count == 0) { + MG_ERROR(("blk_count == 0")); + } else { + if (lfs_mount(&s_lfs, &s_cfg) != 0) lfs_format(&s_lfs, &s_cfg); + if (lfs_mount(&s_lfs, &s_cfg) != 0) { + MG_ERROR(("lfs_mount")); + } else { + s_lfs_ready = true; + } + } + } } - s_cfg.block_count = (lfs_size_t) (size / s_cfg.block_size); - if (s_cfg.block_count == 0) return false; - if (lfs_mount(&s_lfs, &s_cfg) != 0) { - lfs_format(&s_lfs, &s_cfg); - if (lfs_mount(&s_lfs, &s_cfg) != 0) result = -1; - } - s_lfs_ready = result == 0; return s_lfs_ready; } @@ -6504,6 +6515,7 @@ static struct mg_lfs_fd *find_fd(int fd) { static struct mg_lfs_fd *open_fd(void) { struct mg_lfs_fd *f = NULL; + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); if (s_lfs_ready) { f = (struct mg_lfs_fd *) mg_calloc(1, sizeof(*f)); if (f != NULL) { @@ -6531,6 +6543,7 @@ int _open(const char *path, int flags, mode_t mode) { int err, lfs_flags = 0, fd = -1; struct mg_lfs_fd *f = open_fd(); (void) mode; + MG_DEBUG(("%s -> %p, lfs_ready %d", path, f, s_lfs_ready)); if (f == NULL) return -1; fd = f->fd; if ((flags & 3) == O_RDONLY) lfs_flags |= LFS_O_RDONLY; @@ -6572,11 +6585,21 @@ int _lseek(int fd, int offset, int whence) { } int _rename(const char *oldname, const char *newname) { + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); return s_lfs_ready ? lfs_rename(&s_lfs, oldname, newname) : -1; } -int _unlink_r(void *r, const char *a) { - (void) r; +// Some libc rename() implementations have no atomic rename syscall and emulate +// it as link() + unlink() instead of calling _rename() directly. LittleFS has +// no real hard links, but forwarding to _rename() gives the same end result +// (oldname becomes newname), which is all mg_file_write()'s atomic-write +// pattern (write to a temp file, then rename it into place) needs +int _link(const char *oldname, const char *newname) { + return _rename(oldname, newname); +} + +int _unlink(const char *a) { + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); return s_lfs_ready ? lfs_remove(&s_lfs, a) : -1; } @@ -6585,7 +6608,7 @@ DIR *opendir(const char *name) { if (f == NULL) return NULL; if (lfs_dir_open(&s_lfs, &f->dir, name) != 0) { close_fd(f); - return NULL; + f = NULL; } return (DIR *) f; } @@ -6612,7 +6635,9 @@ struct dirent *readdir(DIR *dir) { int _stat(const char *path, struct stat *st) { struct lfs_info info; + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); if (!s_lfs_ready || lfs_stat(&s_lfs, path, &info) != 0) return -1; + memset(st, 0, sizeof(*st)); st->st_mode = info.type == LFS_TYPE_DIR ? S_IFDIR : S_IFREG; st->st_size = info.size; return 0; @@ -6626,9 +6651,9 @@ int _fstat(int fd, struct stat *st) { int mkdir(const char *path, mode_t mode) { (void) mode; + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); return s_lfs_ready ? lfs_mkdir(&s_lfs, path) : -1; } - #endif #ifdef MG_ENABLE_LINES diff --git a/mongoose.h b/mongoose.h index 99e4e992..f1d02ed3 100644 --- a/mongoose.h +++ b/mongoose.h @@ -103,8 +103,9 @@ extern "C" { #include #include +#ifndef MG_PATH_MAX #define MG_PATH_MAX 100 -#define MG_ENABLE_DIRLIST 0 +#endif #endif @@ -127,8 +128,9 @@ extern "C" { #include #include +#ifndef MG_PATH_MAX #define MG_PATH_MAX 100 -#define MG_ENABLE_DIRLIST 0 +#endif #endif @@ -158,10 +160,6 @@ extern "C" { #define MG_PATH_MAX 100 #endif -#ifndef MG_ENABLE_DIRLIST -#define MG_ENABLE_DIRLIST 0 -#endif - #ifndef MG_ENABLE_TCPIP #define MG_ENABLE_TCPIP 1 // Enable built-in TCP/IP stack @@ -1364,7 +1362,7 @@ struct timeval { #endif #ifndef MG_ENABLE_DIRLIST -#define MG_ENABLE_DIRLIST 0 +#define MG_ENABLE_DIRLIST MG_ENABLE_LFS #endif #ifndef MG_ENABLE_CUSTOM_RANDOM @@ -1416,7 +1414,7 @@ struct timeval { #endif #ifndef MG_ENABLE_POSIX_FS -#define MG_ENABLE_POSIX_FS 0 +#define MG_ENABLE_POSIX_FS MG_ENABLE_LFS #endif #ifndef MG_INVALID_SOCKET @@ -1929,6 +1927,7 @@ void mg_timer_poll(struct mg_timer **head, uint64_t new_ms); + // Flags returned by mg_fs.st() and passed to mg_fs.open(). enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4, MG_FS_EXCL = 8 }; @@ -1966,6 +1965,7 @@ void mg_fs_close(struct mg_fd *fd); // Sequential directory iterator. Call repeatedly with the same buf/len; // each call fills buf with the next entry name. Returns false when done. +// Note: make sure buf[0] == 0 before the first call bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len); // Reads the entire file into a heap-allocated buffer. The returned mg_str is @@ -2005,6 +2005,26 @@ struct mg_str mg_unpacked(const char *path); extern const struct mg_mem_file mg_packed_files[]; +#if MG_ENABLE_LFS +#include +#include + +#ifndef DT_DIR +#define DT_DIR 4 +#endif +#ifndef DT_REG +#define DT_REG 8 +#endif + +struct dirent { + char d_name[LFS_NAME_MAX + 1]; + unsigned char d_type; +}; + +typedef struct mg_lfs_fd DIR; +#endif + + diff --git a/src/arch_armcgt.h b/src/arch_armcgt.h index 100d5da3..a582aa1d 100644 --- a/src/arch_armcgt.h +++ b/src/arch_armcgt.h @@ -12,7 +12,8 @@ #include #include +#ifndef MG_PATH_MAX #define MG_PATH_MAX 100 -#define MG_ENABLE_DIRLIST 0 +#endif #endif diff --git a/src/arch_armgcc.h b/src/arch_armgcc.h index cd2ffee4..d9aff5e5 100644 --- a/src/arch_armgcc.h +++ b/src/arch_armgcc.h @@ -18,7 +18,8 @@ #include #include +#ifndef MG_PATH_MAX #define MG_PATH_MAX 100 -#define MG_ENABLE_DIRLIST 0 +#endif #endif diff --git a/src/arch_cube.h b/src/arch_cube.h index 5b9f9250..6a69701e 100644 --- a/src/arch_cube.h +++ b/src/arch_cube.h @@ -25,10 +25,6 @@ #define MG_PATH_MAX 100 #endif -#ifndef MG_ENABLE_DIRLIST -#define MG_ENABLE_DIRLIST 0 -#endif - #ifndef MG_ENABLE_TCPIP #define MG_ENABLE_TCPIP 1 // Enable built-in TCP/IP stack diff --git a/src/config.h b/src/config.h index 3681f524..f45b7b4b 100644 --- a/src/config.h +++ b/src/config.h @@ -83,7 +83,7 @@ #endif #ifndef MG_ENABLE_DIRLIST -#define MG_ENABLE_DIRLIST 0 +#define MG_ENABLE_DIRLIST MG_ENABLE_LFS #endif #ifndef MG_ENABLE_CUSTOM_RANDOM @@ -135,7 +135,7 @@ #endif #ifndef MG_ENABLE_POSIX_FS -#define MG_ENABLE_POSIX_FS 0 +#define MG_ENABLE_POSIX_FS MG_ENABLE_LFS #endif #ifndef MG_INVALID_SOCKET diff --git a/src/dash.c b/src/dash.c index cb7cc83c..d17a66e1 100644 --- a/src/dash.c +++ b/src/dash.c @@ -580,7 +580,6 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) { // Received headers - check authentication and possibly start uploads/ota struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_dash_user *u = mg_dash_authenticate(c, hm, dash); - if (mg_match(hm->uri, mg_str("/api/hi"), NULL) || mg_match(hm->uri, mg_str("/api/logout"), NULL)) { // Do nothing, handle them MG_EV_HTTP_MSG. We bypass auth for those @@ -653,7 +652,7 @@ void mg_dash_ev_handler(struct mg_connection *c, int ev, void *ev_data) { break; } } - if (ch == NULL) mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found"); + if (ch == NULL) mg_http_reply(c, 404, MG_JSON_HEADERS, "Not Found\n"); mg_log_http_req(c, hm); } } diff --git a/src/fs.c b/src/fs.c index 6df00c4a..7d67d102 100644 --- a/src/fs.c +++ b/src/fs.c @@ -1,4 +1,5 @@ #include "fs.h" +#include "log.h" #include "printf.h" #include "str.h" #include "util.h" @@ -47,6 +48,7 @@ bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, char tmp[MG_PATH_MAX], rnd[10]; size_t path_len = mg_snprintf(tmp, sizeof(tmp), "%s..%s", path, mg_random_str(rnd, sizeof(rnd))); + fs->rm(tmp); if (path_len < sizeof(tmp) && (fd = mg_fs_open(fs, tmp, MG_FS_WRITE | MG_FS_EXCL)) != NULL) { result = fs->wr(fd->fd, buf, len) == len; @@ -54,8 +56,11 @@ bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, if (result) { fs->rm(path); result = fs->mv(tmp, path); + MG_DEBUG(("mv %s -> %s result=%d", tmp, path, result)); } fs->rm(tmp); + } else { + MG_ERROR(("mg_fs_open(%s) failed, path_len=%zu", tmp, path_len)); } return result; } diff --git a/src/fs.h b/src/fs.h index 326e86f3..da4ff591 100644 --- a/src/fs.h +++ b/src/fs.h @@ -2,6 +2,7 @@ #include "arch.h" #include "config.h" +#include "lfs.h" // Flags returned by mg_fs.st() and passed to mg_fs.open(). enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4, MG_FS_EXCL = 8 }; @@ -40,6 +41,7 @@ void mg_fs_close(struct mg_fd *fd); // Sequential directory iterator. Call repeatedly with the same buf/len; // each call fills buf with the next entry name. Returns false when done. +// Note: make sure buf[0] == 0 before the first call bool mg_fs_ls(struct mg_fs *fs, const char *path, char *buf, size_t len); // Reads the entire file into a heap-allocated buffer. The returned mg_str is diff --git a/src/fs_posix.c b/src/fs_posix.c index 064dc381..8a2e58a6 100644 --- a/src/fs_posix.c +++ b/src/fs_posix.c @@ -10,6 +10,10 @@ #define MG_STAT_FUNC stat #endif +#if MG_ENABLE_LFS +#include +#endif + static int p_stat(const char *path, size_t *size, time_t *mtime) { #if !defined(S_ISDIR) MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); @@ -161,7 +165,6 @@ struct dirent *readdir(DIR *d) { static void p_list(const char *dir, void (*fn)(const char *, void *), void *userdata) { -#if MG_ENABLE_DIRLIST struct dirent *dp; DIR *dirp; if ((dirp = (opendir(dir))) == NULL) return; @@ -170,9 +173,6 @@ static void p_list(const char *dir, void (*fn)(const char *, void *), fn(dp->d_name, userdata); } closedir(dirp); -#else - (void) dir, (void) fn, (void) userdata; -#endif } static void *p_open(const char *path, int flags) { diff --git a/src/lfs.c b/src/lfs.c index e4b2c44f..28dc6d65 100644 --- a/src/lfs.c +++ b/src/lfs.c @@ -3,19 +3,18 @@ #include "flash.h" #include "util.h" +#include "lfs.h" #if MG_ENABLE_LFS -#include -#include -#include -#include -#include - #ifndef LFS_USE_RAM #define LFS_USE_RAM 0 // If 1, use RAM FS. If 0, use flash #endif +#ifndef MG_LFS_SIZE +#define MG_LFS_SIZE (64 * 1024) +#endif + #ifndef MG_LFS_BUF_SIZE #define MG_LFS_BUF_SIZE 64 // Buffer size used for reads, writes, and cache #endif @@ -28,19 +27,6 @@ #define MG_LFS_BLOCK_SIZE 8192 #endif -#ifndef DT_DIR -#define DT_DIR 4 -#endif -#ifndef DT_REG -#define DT_REG 8 -#endif - -typedef struct mg_lfs_fd DIR; -struct dirent { - char d_name[LFS_NAME_MAX + 1]; - unsigned char d_type; -}; - struct mg_lfs_fd { struct mg_lfs_fd *next; bool isopen; @@ -110,29 +96,50 @@ static int lfs_driver_sync(const struct lfs_config *cfg) { } bool mg_lfs_init(size_t size) { - int result = 0; + //bool ok = false; + MG_DEBUG(("size: %zu", size)); if (s_lfs_ready) return true; - if (size == 0) return false; + if (size == 0) { + MG_ERROR(("invalidsize = 0")); + } else { #if LFS_USE_RAM - s_cfg.block_size = MG_LFS_BLOCK_SIZE; - if (s_ram_fs == NULL) s_ram_fs = (uint8_t *) mg_calloc(1, size); - s_fs = s_ram_fs; + s_cfg.block_size = MG_LFS_BLOCK_SIZE; + if (s_ram_fs == NULL) s_ram_fs = (uint8_t *) mg_calloc(1, size); + s_fs = s_ram_fs; #else - if (mg_flash == NULL || mg_flash->secsz == 0 || mg_flash->size < size) return false; - if (mg_flash->write_fn == NULL) return false; - s_cfg.block_size = mg_flash->secsz; - s_fs = (uint8_t *) mg_flash->start + mg_flash->size - size; + { + // Some flash attributes may be initialised on ota_begin + int lev = mg_log_level; + mg_log_level = MG_LL_NONE; + mg_ota_begin(1); + mg_ota_end(); + mg_log_set(lev); + } + if (mg_flash == NULL || mg_flash->secsz == 0 || mg_flash->size < size) { + MG_ERROR(("mg_flash %p secsz %zu flash_size %zu < size %zu", mg_flash, mg_flash->secsz, mg_flash->size, size)); + } else if (mg_flash->write_fn == NULL) { + MG_ERROR(("flash write_fn == NULL")); + } else { + s_cfg.block_size = mg_flash->secsz; + s_fs = (uint8_t *) mg_flash->start + mg_flash->size - size; + } #endif - if (s_fs == NULL || s_cfg.block_size == 0 || size % s_cfg.block_size != 0) { - return false; + if (s_fs == NULL || s_cfg.block_size == 0 || size % s_cfg.block_size != 0) { + MG_ERROR(("s_fs %p blk_size %zu size %zu", s_fs, s_cfg.block_size)); + } else { + s_cfg.block_count = (lfs_size_t) (size / s_cfg.block_size); + if (s_cfg.block_count == 0) { + MG_ERROR(("blk_count == 0")); + } else { + if (lfs_mount(&s_lfs, &s_cfg) != 0) lfs_format(&s_lfs, &s_cfg); + if (lfs_mount(&s_lfs, &s_cfg) != 0) { + MG_ERROR(("lfs_mount")); + } else { + s_lfs_ready = true; + } + } + } } - s_cfg.block_count = (lfs_size_t) (size / s_cfg.block_size); - if (s_cfg.block_count == 0) return false; - if (lfs_mount(&s_lfs, &s_cfg) != 0) { - lfs_format(&s_lfs, &s_cfg); - if (lfs_mount(&s_lfs, &s_cfg) != 0) result = -1; - } - s_lfs_ready = result == 0; return s_lfs_ready; } @@ -146,6 +153,7 @@ static struct mg_lfs_fd *find_fd(int fd) { static struct mg_lfs_fd *open_fd(void) { struct mg_lfs_fd *f = NULL; + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); if (s_lfs_ready) { f = (struct mg_lfs_fd *) mg_calloc(1, sizeof(*f)); if (f != NULL) { @@ -173,6 +181,7 @@ int _open(const char *path, int flags, mode_t mode) { int err, lfs_flags = 0, fd = -1; struct mg_lfs_fd *f = open_fd(); (void) mode; + MG_DEBUG(("%s -> %p, lfs_ready %d", path, f, s_lfs_ready)); if (f == NULL) return -1; fd = f->fd; if ((flags & 3) == O_RDONLY) lfs_flags |= LFS_O_RDONLY; @@ -214,11 +223,21 @@ int _lseek(int fd, int offset, int whence) { } int _rename(const char *oldname, const char *newname) { + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); return s_lfs_ready ? lfs_rename(&s_lfs, oldname, newname) : -1; } -int _unlink_r(void *r, const char *a) { - (void) r; +// Some libc rename() implementations have no atomic rename syscall and emulate +// it as link() + unlink() instead of calling _rename() directly. LittleFS has +// no real hard links, but forwarding to _rename() gives the same end result +// (oldname becomes newname), which is all mg_file_write()'s atomic-write +// pattern (write to a temp file, then rename it into place) needs +int _link(const char *oldname, const char *newname) { + return _rename(oldname, newname); +} + +int _unlink(const char *a) { + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); return s_lfs_ready ? lfs_remove(&s_lfs, a) : -1; } @@ -227,7 +246,7 @@ DIR *opendir(const char *name) { if (f == NULL) return NULL; if (lfs_dir_open(&s_lfs, &f->dir, name) != 0) { close_fd(f); - return NULL; + f = NULL; } return (DIR *) f; } @@ -254,7 +273,9 @@ struct dirent *readdir(DIR *dir) { int _stat(const char *path, struct stat *st) { struct lfs_info info; + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); if (!s_lfs_ready || lfs_stat(&s_lfs, path, &info) != 0) return -1; + memset(st, 0, sizeof(*st)); st->st_mode = info.type == LFS_TYPE_DIR ? S_IFDIR : S_IFREG; st->st_size = info.size; return 0; @@ -268,7 +289,7 @@ int _fstat(int fd, struct stat *st) { int mkdir(const char *path, mode_t mode) { (void) mode; + if (s_lfs_ready == false) mg_lfs_init(MG_LFS_SIZE); return s_lfs_ready ? lfs_mkdir(&s_lfs, path) : -1; } - #endif diff --git a/src/lfs.h b/src/lfs.h new file mode 100644 index 00000000..6c29b3f8 --- /dev/null +++ b/src/lfs.h @@ -0,0 +1,20 @@ +#pragma once + +#if MG_ENABLE_LFS +#include +#include + +#ifndef DT_DIR +#define DT_DIR 4 +#endif +#ifndef DT_REG +#define DT_REG 8 +#endif + +struct dirent { + char d_name[LFS_NAME_MAX + 1]; + unsigned char d_type; +}; + +typedef struct mg_lfs_fd DIR; +#endif diff --git a/test/Makefile b/test/Makefile index 94800a37..92d5c83f 100644 --- a/test/Makefile +++ b/test/Makefile @@ -106,7 +106,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c) cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ + cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/lfs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ # Check that all external (exported) symbols have "mg_" prefix mg_prefix: mongoose.c mongoose.h diff --git a/tutorials/device-dashboard/full/dashboard.c b/tutorials/device-dashboard/full/dashboard.c index fff14bca..eabbf6be 100644 --- a/tutorials/device-dashboard/full/dashboard.c +++ b/tutorials/device-dashboard/full/dashboard.c @@ -150,7 +150,7 @@ static bool graph1_fn(enum mg_dash_op op, struct mg_dash_user *u) { return true; } static struct mg_field_set set_graph1 = {"graph1", fields_graph1, graph1_fn, - NULL, NULL}; + NULL, NULL}; #define NUM_POINTS_GRAPH2 100 // How many graph2 data points to send static char s_graph2_data[NUM_POINTS_GRAPH2 * 4 + 2 + 1]; @@ -218,6 +218,7 @@ void mg_dash_init(struct mg_mgr *mgr) { #if MG_ARCH == MG_ARCH_UNIX // Create demo files in the upload directory + mkdir("/tmp", 0755); mkdir("/tmp/dashboard", 0755); mg_file_printf(&mg_fs_posix, "/tmp/dashboard/device-config.json", "{\"model\":\"example\",\"fw\":\"1.0\"}"); diff --git a/tutorials/device-dashboard/full/dashboard.html b/tutorials/device-dashboard/full/dashboard.html index 3fb3562b..faa67da1 100644 --- a/tutorials/device-dashboard/full/dashboard.html +++ b/tutorials/device-dashboard/full/dashboard.html @@ -778,15 +778,22 @@ a, a:visited, a:link { text-decoration: none; }
-

The two default files are fake - you can delete them

-

However the manager works - you can upload new files. They - are uploaded to /tmp/ directory on the device. - This requires stdio (fopen/fread/...) implemeneted.

-

The easiest way to implement it is to use a filesystem like - LittleFS on your internal or external flash - and retarget the standard library to use it.

-

See this example on how LittleFS can be implemented on - Nucleo-H563ZI internal flash partition.

+

This file manager uses /tmp/dashboard/ directory. + It works on any platform that supports + standard file IO: fopen, fread, fwrite, fclose. + This includes Linux/Mac/Windows.

+

+ Mongoose provides a built-in LittleFS integration that + makes stadard file IO work on embedded systems like STM32. + For that, add "#define MG_ENABLE_LFS 1" to your + mongoose_config.h +

+

See + this ready-to-build example + which implements LittleFS on Nucleo-H563ZI internal flash. You + can upload, delete and serve files on STM32 - which is + a perfect use case for confuration files, keeping and viewing logs, etc. +

diff --git a/tutorials/stm32/nucleo-h563zi/minimal/Makefile b/tutorials/stm32/nucleo-h563zi/minimal/Makefile index 9045ed37..8d543933 100644 --- a/tutorials/stm32/nucleo-h563zi/minimal/Makefile +++ b/tutorials/stm32/nucleo-h563zi/minimal/Makefile @@ -1,23 +1,26 @@ # Copyright (c) 2026 Cesanta Software Limited # Environment setup: https://mongoose.ws/docs/getting-started/build-environment/ +MGDIR = ../../../.. + CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion CFLAGS += -g3 -Os -ffunction-sections -fdata-sections -CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_h5/Include -Imongoose +CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include -Icmsis_h5/Include -Imongoose CFLAGS += -mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard $(CFLAGS_EXTRA) LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map SOURCES = main.c hal.c +SOURCES += littlefs/lfs.c littlefs/lfs_util.c SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h563xx.s -SOURCES += mongoose/mongoose.c +SOURCES += $(MGDIR)/mongoose.c + +# Disabled for littlefs/lfs.c +CFLAGS += -Wno-conversion -Wno-shadow all build example: firmware.bin -mongoose/mongoose.c mongoose/mongoose.h: - cp ../../../../mongoose.[ch] mongoose/ - -firmware.elf: cmsis_core cmsis_h5 hal.h link.ld Makefile $(SOURCES) mongoose/mongoose.h +firmware.elf: cmsis_core cmsis_h5 littlefs hal.h link.ld Makefile $(SOURCES) $(MGDIR)/mongoose.h arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@ firmware.bin: firmware.elf @@ -42,6 +45,10 @@ cmsis_core: cmsis_h5: git clone -q -c advice.detachedHead=false --depth 1 -b v1.6.0 https://github.com/STMicroelectronics/cmsis-device-h5 $@ +littlefs/lfs.c littlefs/lfs_util.c: littlefs +littlefs: + git clone -q -c advice.detachedHead=false --depth 1 -b v2.11.3 https://github.com/littlefs-project/littlefs $@ + clean: rm -rf firmware.* cmsis_* mongoose/mongoose.* diff --git a/tutorials/stm32/nucleo-h563zi/minimal/main.c b/tutorials/stm32/nucleo-h563zi/minimal/main.c index cea2a7a6..b94e82a2 100644 --- a/tutorials/stm32/nucleo-h563zi/minimal/main.c +++ b/tutorials/stm32/nucleo-h563zi/minimal/main.c @@ -45,8 +45,20 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else if (mg_match(hm->uri, mg_str("/files/#"), NULL)) { + struct mg_http_serve_opts opts = { + .root_dir = "/files/=/fs/", + .allow_delete = true, + .allow_upload = true, + }; + mg_http_serve_dir(c, hm, &opts); } else { - mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", hal_get_tick()); + size_t u = hal_ram_used(), t = u + hal_ram_free(); + mg_http_reply(c, 200, "", + "Hi from Mongoose!\n" + "Tick %llu\n" + "RAM usage: %zu / %zu (%zu %%)\n", + hal_get_tick(), u, t, (size_t) (100 * u / t)); } } } @@ -67,6 +79,14 @@ int main(void) { mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + // Create directory for Web file manager. Will be mapped to /files/ URL + mkdir("/fs", 0755); + MG_INFO(("File manager operations:")); + MG_INFO(("Upload: curl IP/files/NAME --data-binary @FILE")); + MG_INFO(("Delete: curl IP/files/NAME -X DELETE")); + MG_INFO(("Show: curl IP/files/NAME")); + MG_INFO(("List: curl IP/files/")); + for (;;) { mg_mgr_poll(&mgr, 0); blink_task(); diff --git a/tutorials/stm32/nucleo-h563zi/minimal/mongoose/mongoose_config.h b/tutorials/stm32/nucleo-h563zi/minimal/mongoose_config.h similarity index 81% rename from tutorials/stm32/nucleo-h563zi/minimal/mongoose/mongoose_config.h rename to tutorials/stm32/nucleo-h563zi/minimal/mongoose_config.h index cdc861c4..d1a9dff6 100644 --- a/tutorials/stm32/nucleo-h563zi/minimal/mongoose/mongoose_config.h +++ b/tutorials/stm32/nucleo-h563zi/minimal/mongoose_config.h @@ -12,6 +12,11 @@ #define MG_ENABLE_PACKED_FS 1 #define MG_ENABLE_DRIVER_STM32H 1 +#define MG_ENABLE_LFS 1 // Enable POSIX filesystem on interfal flash using LFS +#define MG_LFS_SIZE (1024 * 128) // Must be multiple of H5 8Kb sectors +#define MG_OTA MG_OTA_STM32H5 // MG_OTA required for LFS, for flash usage +#define MG_ENABLE_LINES 1 + // #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 4 // #define MG_TCPIP_PHY_ADDR 0 // PHY address diff --git a/tutorials/stm32/nucleo-h563zi/minimal/sys/dirent.h b/tutorials/stm32/nucleo-h563zi/minimal/sys/dirent.h new file mode 100644 index 00000000..e69de29b