diff --git a/subsys/fs/CMakeLists.txt b/subsys/fs/CMakeLists.txt index 15f97649a13..48df605ae01 100644 --- a/subsys/fs/CMakeLists.txt +++ b/subsys/fs/CMakeLists.txt @@ -36,9 +36,12 @@ if(CONFIG_FUSE_FS_ACCESS) zephyr_include_directories(${FUSE_INCLUDE_DIRS}) if (CONFIG_NATIVE_LIBRARY) target_link_options(native_simulator INTERFACE "-l${FUSE_LIBRARIES}") + target_sources(native_simulator INTERFACE fuse_fs_access_bottom.c) + target_compile_options(native_simulator INTERFACE "-D_FILE_OFFSET_BITS=64") else() zephyr_link_libraries(${FUSE_LIBRARIES}) + zephyr_library_sources(fuse_fs_access_bottom.c) + zephyr_library_compile_definitions(_FILE_OFFSET_BITS=64) endif() - zephyr_library_compile_definitions(_FILE_OFFSET_BITS=64) zephyr_library_sources(fuse_fs_access.c) endif() diff --git a/subsys/fs/Kconfig b/subsys/fs/Kconfig index bf6933f13aa..4dde4b27100 100644 --- a/subsys/fs/Kconfig +++ b/subsys/fs/Kconfig @@ -95,6 +95,7 @@ config FILE_SYSTEM_MKFS config FUSE_FS_ACCESS bool "FUSE based access to file system partitions" depends on ARCH_POSIX + select NATIVE_USE_NSI_ERRNO help Expose file system partitions to the host system through FUSE. diff --git a/subsys/fs/fuse_fs_access.c b/subsys/fs/fuse_fs_access.c index 9ecc2eddb4b..e4c26511c64 100644 --- a/subsys/fs/fuse_fs_access.c +++ b/subsys/fs/fuse_fs_access.c @@ -1,46 +1,24 @@ /* * Copyright (c) 2019 Jan Van Winkel + * Copyright (c) 2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ -#define FUSE_USE_VERSION 26 - -#undef _XOPEN_SOURCE -#define _XOPEN_SOURCE 700 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include #include +#include #include "cmdline.h" #include "soc.h" -#define S_IRWX_DIR (0775) -#define S_IRW_FILE (0664) +#include "fuse_fs_access_bottom.h" #define NUMBER_OF_OPEN_FILES 128 -#define INVALID_FILE_HANDLE (NUMBER_OF_OPEN_FILES + 1) - -#define DIR_END '\0' static struct fs_file_t files[NUMBER_OF_OPEN_FILES]; static uint8_t file_handles[NUMBER_OF_OPEN_FILES]; -static pthread_t fuse_thread; - static const char default_fuse_mountpoint[] = "flash"; static const char *fuse_mountpoint; @@ -66,427 +44,221 @@ static void release_file_handle(size_t handle) } } -static bool is_mount_point(const char *path) -{ - char dir_path[PATH_MAX]; - size_t len; - - len = strlen(path); - if (len >= sizeof(dir_path)) { - return false; - } - - memcpy(dir_path, path, len); - dir_path[len] = '\0'; - return strcmp(dirname(dir_path), "/") == 0; -} - -static int fuse_fs_access_getattr(const char *path, struct stat *stat) +static int ffa_stat_top(const char *path, struct ffa_dirent *entry_bottom) { struct fs_dirent entry; int err; - stat->st_dev = 0; - stat->st_ino = 0; - stat->st_nlink = 0; - stat->st_uid = getuid(); - stat->st_gid = getgid(); - stat->st_rdev = 0; - stat->st_blksize = 0; - stat->st_blocks = 0; - stat->st_atime = 0; - stat->st_mtime = 0; - stat->st_ctime = 0; - - if ((strcmp(path, "/") == 0) || is_mount_point(path)) { - if (strstr(path, "/.") != NULL) { - return -ENOENT; - } - stat->st_mode = S_IFDIR | S_IRWX_DIR; - stat->st_size = 0; - return 0; - } - err = fs_stat(path, &entry); + if (err != 0) { - return err; + return nsi_errno_to_mid(-err); } + entry_bottom->size = entry.size; + if (entry.type == FS_DIR_ENTRY_DIR) { - stat->st_mode = S_IFDIR | S_IRWX_DIR; - stat->st_size = 0; + entry_bottom->is_directory = true; } else { - stat->st_mode = S_IFREG | S_IRW_FILE; - stat->st_size = entry.size; + entry_bottom->is_directory = false; } return 0; } -static int fuse_fs_access_readmount(void *buf, fuse_fill_dir_t filler) +static int ffa_readmount_top(int *mnt_nbr, const char **mnt_name) { - int mnt_nbr = 0; - const char *mnt_name; - struct stat stat; - int err; + int err = fs_readmount(mnt_nbr, mnt_name); - stat.st_dev = 0; - stat.st_ino = 0; - stat.st_nlink = 0; - stat.st_uid = getuid(); - stat.st_gid = getgid(); - stat.st_rdev = 0; - stat.st_atime = 0; - stat.st_mtime = 0; - stat.st_ctime = 0; - stat.st_mode = S_IFDIR | S_IRWX_DIR; - stat.st_size = 0; - stat.st_blksize = 0; - stat.st_blocks = 0; - - filler(buf, ".", &stat, 0); - filler(buf, "..", NULL, 0); - - do { - err = fs_readmount(&mnt_nbr, &mnt_name); - if (err < 0) { - break; - } - - filler(buf, &mnt_name[1], &stat, 0); - - } while (true); - - if (err == -ENOENT) { - err = 0; - } - - return err; + return nsi_errno_to_mid(-err); } -static int fuse_fs_access_readdir(const char *path, void *buf, - fuse_fill_dir_t filler, off_t off, - struct fuse_file_info *fi) -{ +/* Status shared between readdir_* calls */ +static struct { struct fs_dir_t dir; struct fs_dirent entry; +} readdir_status; + +static int ffa_readdir_start(const char *path) +{ int err; - struct stat stat; - ARG_UNUSED(off); - ARG_UNUSED(fi); - - if (strcmp(path, "/") == 0) { - return fuse_fs_access_readmount(buf, filler); - } - - fs_dir_t_init(&dir); - - if (is_mount_point(path)) { - /* File system API expects trailing slash for a mount point - * directory but FUSE strips the trailing slashes from - * directory names so add it back. - */ - char mount_path[PATH_MAX] = {0}; - size_t len = strlen(path); - - if (len >= (PATH_MAX - 2)) { - return -ENOMEM; - } - - memcpy(mount_path, path, len); - mount_path[len] = '/'; - err = fs_opendir(&dir, mount_path); - } else { - err = fs_opendir(&dir, path); - } - - if (err) { - return -ENOEXEC; - } - - stat.st_dev = 0; - stat.st_ino = 0; - stat.st_nlink = 0; - stat.st_uid = getuid(); - stat.st_gid = getgid(); - stat.st_rdev = 0; - stat.st_atime = 0; - stat.st_mtime = 0; - stat.st_ctime = 0; - stat.st_mode = S_IFDIR | S_IRWX_DIR; - stat.st_size = 0; - stat.st_blksize = 0; - stat.st_blocks = 0; - - filler(buf, ".", &stat, 0); - filler(buf, "..", &stat, 0); - - do { - err = fs_readdir(&dir, &entry); - if (err) { - break; - } - - if (entry.name[0] == DIR_END) { - break; - } - - if (entry.type == FS_DIR_ENTRY_DIR) { - stat.st_mode = S_IFDIR | S_IRWX_DIR; - stat.st_size = 0; - } else { - stat.st_mode = S_IFREG | S_IRW_FILE; - stat.st_size = entry.size; - } - - if (filler(buf, entry.name, &stat, 0)) { - break; - } - - } while (1); - - fs_closedir(&dir); - - return err; + fs_dir_t_init(&readdir_status.dir); + err = fs_opendir(&readdir_status.dir, path); + return nsi_errno_to_mid(-err); } -static int fuse_fs_access_create(const char *path, mode_t mode, - struct fuse_file_info *fi) +static int ffa_readdir_read_next(struct ffa_dirent *entry_bottom) +{ + int err; + + err = fs_readdir(&readdir_status.dir, &readdir_status.entry); + + if (err) { + return nsi_errno_to_mid(-err); + } + entry_bottom->name = readdir_status.entry.name; + entry_bottom->size = readdir_status.entry.size; + + if (readdir_status.entry.type == FS_DIR_ENTRY_DIR) { + entry_bottom->is_directory = true; + } else { + entry_bottom->is_directory = false; + } + + return 0; +} + +static void ffa_readdir_end(void) +{ + (void)fs_closedir(&readdir_status.dir); +} + +static int ffa_create_top(const char *path, uint64_t *fh) { int err; ssize_t handle; - ARG_UNUSED(mode); - - if (is_mount_point(path)) { - return -ENOENT; - } - handle = get_new_file_handle(); if (handle < 0) { - return handle; + return nsi_errno_to_mid(-handle); } - fi->fh = handle; + *fh = handle; err = fs_open(&files[handle], path, FS_O_CREATE | FS_O_WRITE); if (err != 0) { release_file_handle(handle); - fi->fh = INVALID_FILE_HANDLE; - return err; + *fh = INVALID_FILE_HANDLE; + return nsi_errno_to_mid(-err); } return 0; } -static int fuse_fs_access_open(const char *path, struct fuse_file_info *fi) +static int ffa_release_top(uint64_t fh) { - return fuse_fs_access_create(path, 0, fi); -} + fs_close(&files[fh]); -static int fuse_fs_access_release(const char *path, struct fuse_file_info *fi) -{ - ARG_UNUSED(path); - - if (fi->fh == INVALID_FILE_HANDLE) { - return -EINVAL; - } - - fs_close(&files[fi->fh]); - - release_file_handle(fi->fh); + release_file_handle(fh); return 0; } -static int fuse_fs_access_read(const char *path, char *buf, size_t size, - off_t off, struct fuse_file_info *fi) +static int ffa_read_top(uint64_t fh, char *buf, size_t size, off_t off) { int err; - ARG_UNUSED(path); + err = fs_seek(&files[fh], off, FS_SEEK_SET); - if (fi->fh == INVALID_FILE_HANDLE) { - return -EINVAL; + if (err == 0) { + err = fs_read(&files[fh], buf, size); } - err = fs_seek(&files[fi->fh], off, FS_SEEK_SET); - if (err != 0) { - return err; - } - - err = fs_read(&files[fi->fh], buf, size); - - return err; + return nsi_errno_to_mid(-err); } -static int fuse_fs_access_write(const char *path, const char *buf, size_t size, - off_t off, struct fuse_file_info *fi) +static int ffa_write_top(uint64_t fh, const char *buf, size_t size, off_t off) { int err; - ARG_UNUSED(path); + err = fs_seek(&files[fh], off, FS_SEEK_SET); - if (fi->fh == INVALID_FILE_HANDLE) { - return -EINVAL; + if (err == 0) { + err = fs_write(&files[fh], buf, size); } - err = fs_seek(&files[fi->fh], off, FS_SEEK_SET); - if (err != 0) { - return err; - } - - err = fs_write(&files[fi->fh], buf, size); - - return err; + return nsi_errno_to_mid(-err); } -static int fuse_fs_access_ftruncate(const char *path, off_t size, - struct fuse_file_info *fi) +static int ffa_ftruncate_top(uint64_t fh, off_t size) { - int err; + int err = fs_truncate(&files[fh], size); - ARG_UNUSED(path); - - if (fi->fh == INVALID_FILE_HANDLE) { - return -EINVAL; - } - - err = fs_truncate(&files[fi->fh], size); - - return err; + return nsi_errno_to_mid(-err); } -static int fuse_fs_access_truncate(const char *path, off_t size) +static int ffa_truncate_top(const char *path, off_t size) { int err; static struct fs_file_t file; err = fs_open(&file, path, FS_O_CREATE | FS_O_WRITE); if (err != 0) { - return err; + return nsi_errno_to_mid(-err); } err = fs_truncate(&file, size); if (err != 0) { fs_close(&file); - return err; + } else { + err = fs_close(&file); } - err = fs_close(&file); - - return err; + return nsi_errno_to_mid(-err); } -static int fuse_fs_access_mkdir(const char *path, mode_t mode) +static int ffa_mkdir_top(const char *path) { - ARG_UNUSED(mode); + int err = fs_mkdir(path); - return fs_mkdir(path); + return nsi_errno_to_mid(-err); } -static int fuse_fs_access_rmdir(const char *path) +static int ffa_unlink_top(const char *path) { - return fs_unlink(path); + int err = fs_unlink(path); + + return nsi_errno_to_mid(-err); } -static int fuse_fs_access_unlink(const char *path) -{ - return fs_unlink(path); -} - -static int fuse_fs_access_statfs(const char *path, struct statvfs *buf) -{ - ARG_UNUSED(path); - ARG_UNUSED(buf); - return 0; -} - -static int fuse_fs_access_utimens(const char *path, const struct timespec tv[2]) -{ - /* dummy */ - ARG_UNUSED(path); - ARG_UNUSED(tv); - - return 0; -} - - -static struct fuse_operations fuse_fs_access_oper = { - .getattr = fuse_fs_access_getattr, - .readlink = NULL, - .getdir = NULL, - .mknod = NULL, - .mkdir = fuse_fs_access_mkdir, - .unlink = fuse_fs_access_unlink, - .rmdir = fuse_fs_access_rmdir, - .symlink = NULL, - .rename = NULL, - .link = NULL, - .chmod = NULL, - .chown = NULL, - .truncate = fuse_fs_access_truncate, - .utime = NULL, - .open = fuse_fs_access_open, - .read = fuse_fs_access_read, - .write = fuse_fs_access_write, - .statfs = fuse_fs_access_statfs, - .flush = NULL, - .release = fuse_fs_access_release, - .fsync = NULL, - .setxattr = NULL, - .getxattr = NULL, - .listxattr = NULL, - .removexattr = NULL, - .opendir = NULL, - .readdir = fuse_fs_access_readdir, - .releasedir = NULL, - .fsyncdir = NULL, - .init = NULL, - .destroy = NULL, - .access = NULL, - .create = fuse_fs_access_create, - .ftruncate = fuse_fs_access_ftruncate, - .fgetattr = NULL, - .lock = NULL, - .utimens = fuse_fs_access_utimens, - .bmap = NULL, - .flag_nullpath_ok = 0, - .flag_nopath = 0, - .flag_utime_omit_ok = 0, - .flag_reserved = 0, - .ioctl = NULL, - .poll = NULL, - .write_buf = NULL, - .read_buf = NULL, - .flock = NULL, - .fallocate = NULL, +struct ffa_op_callbacks op_callbacks = { + .readdir_start = ffa_readdir_start, + .readdir_read_next = ffa_readdir_read_next, + .readdir_end = ffa_readdir_end, + .stat = ffa_stat_top, + .readmount = ffa_readmount_top, + .mkdir = ffa_mkdir_top, + .create = ffa_create_top, + .release = ffa_release_top, + .read = ffa_read_top, + .write = ffa_write_top, + .ftruncate = ffa_ftruncate_top, + .truncate = ffa_truncate_top, + .unlink = ffa_unlink_top, + .rmdir = ffa_unlink_top, }; -static void *fuse_fs_access_main(void *arg) +static void fuse_top_dispath_thread(void *arg1, void *arg2, void *arg3) { - ARG_UNUSED(arg); + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); - char *argv[] = { - "", - "-f", - "-s", - (char *) fuse_mountpoint - }; - int argc = ARRAY_SIZE(argv); +#define COOLDOWN_TIME 10 + int cooldown_count = 0; - posix_print_trace("Mounting flash at %s/\n", fuse_mountpoint); - fuse_main(argc, argv, &fuse_fs_access_oper, NULL); - - pthread_exit(0); + while (true) { + if (ffa_is_op_pended()) { + ffa_run_pending_op(); + cooldown_count = COOLDOWN_TIME; + } else { + if (cooldown_count > 0) { + k_sleep(K_MSEC(1)); + cooldown_count--; + } else { + k_sleep(K_MSEC(20)); + } + } + } } +K_THREAD_DEFINE(fuse_op_handler, CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE, + fuse_top_dispath_thread, NULL, NULL, NULL, 100, 0, 0); + static void fuse_fs_access_init(void) { - int err; - struct stat st; size_t i = 0; while (i < ARRAY_SIZE(files)) { @@ -498,44 +270,16 @@ static void fuse_fs_access_init(void) fuse_mountpoint = default_fuse_mountpoint; } - if (stat(fuse_mountpoint, &st) < 0) { - if (mkdir(fuse_mountpoint, 0700) < 0) { - posix_print_error_and_exit("Failed to create" - " directory for flash mount point (%s): %s\n", - fuse_mountpoint, strerror(errno)); - } - } else if (!S_ISDIR(st.st_mode)) { - posix_print_error_and_exit("%s is not a directory\n", - fuse_mountpoint); - - } - - err = pthread_create(&fuse_thread, NULL, fuse_fs_access_main, NULL); - if (err < 0) { - posix_print_error_and_exit( - "Failed to create thread for " - "fuse_fs_access_main\n"); - } + ffsa_init_bottom(fuse_mountpoint, &op_callbacks); } -static void fuse_fs_access_exit(void) +static void fuse_fs_access_cleanup(void) { - char *full_cmd; - const char cmd[] = "fusermount -uz "; - if (fuse_mountpoint == NULL) { return; } - full_cmd = malloc(strlen(cmd) + strlen(fuse_mountpoint) + 1); - - sprintf(full_cmd, "%s%s", cmd, fuse_mountpoint); - if (system(full_cmd) < -1) { - printf("Failed to unmount fuse mount point\n"); - } - free(full_cmd); - - pthread_join(fuse_thread, NULL); + ffsa_cleanup_bottom(fuse_mountpoint); } static void fuse_fs_access_options(void) @@ -558,4 +302,4 @@ static void fuse_fs_access_options(void) NATIVE_TASK(fuse_fs_access_options, PRE_BOOT_1, 1); NATIVE_TASK(fuse_fs_access_init, PRE_BOOT_2, 1); -NATIVE_TASK(fuse_fs_access_exit, ON_EXIT, 1); +NATIVE_TASK(fuse_fs_access_cleanup, ON_EXIT, 1); diff --git a/subsys/fs/fuse_fs_access_bottom.c b/subsys/fs/fuse_fs_access_bottom.c new file mode 100644 index 00000000000..e3d3cd8b96e --- /dev/null +++ b/subsys/fs/fuse_fs_access_bottom.c @@ -0,0 +1,642 @@ +/* + * Copyright (c) 2019 Jan Van Winkel + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define FUSE_USE_VERSION 26 + +#undef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fuse_fs_access_bottom.h" + + +#define S_IRWX_DIR (0775) +#define S_IRW_FILE (0664) + +#define DIR_END '\0' + +static pthread_t fuse_thread; +static struct ffa_op_callbacks *op_callbacks; + +/* Pending operation the bottom/fuse thread is queuing into the Zephyr thread */ +struct { + int op; /* One of OP_**/ + void *args; /* Pointer to arguments structure, one of op_args_* or a simple argument */ + int ret; /* Return from the operation */ + bool pending; /* Is there a pending operation */ + sem_t op_done; /* semaphore to signal the job is done */ +} op_queue; + +#define OP_STAT offsetof(struct ffa_op_callbacks, stat) +#define OP_READMOUNT offsetof(struct ffa_op_callbacks, readmount) +#define OP_READDIR_START offsetof(struct ffa_op_callbacks, readdir_start) +#define OP_READDIR_READ_NEXT offsetof(struct ffa_op_callbacks, readdir_read_next) +#define OP_READDIR_END offsetof(struct ffa_op_callbacks, readdir_end) +#define OP_MKDIR offsetof(struct ffa_op_callbacks, mkdir) +#define OP_CREATE offsetof(struct ffa_op_callbacks, create) +#define OP_RELEASE offsetof(struct ffa_op_callbacks, release) +#define OP_READ offsetof(struct ffa_op_callbacks, read) +#define OP_WRITE offsetof(struct ffa_op_callbacks, write) +#define OP_FTRUNCATE offsetof(struct ffa_op_callbacks, ftruncate) +#define OP_TRUNCATE offsetof(struct ffa_op_callbacks, truncate) +#define OP_UNLINK offsetof(struct ffa_op_callbacks, unlink) +#define OP_RMDIR offsetof(struct ffa_op_callbacks, rmdir) + +struct op_args_truncate { + const char *path; + off_t size; +}; +struct op_args_ftruncate { + uint64_t fh; + off_t size; +}; +struct op_args_readwrite { + uint64_t fh; + char *buf; + off_t size; + off_t off; +}; +struct op_args_create { + const char *path; + uint64_t *fh_p; +}; +struct op_args_readmount { + int *mnt_nbr_p; + const char **mnt_name_p; +}; +struct op_args_stat { + const char *path; + struct ffa_dirent *entry_p; +}; + +static inline int queue_op(int op, void *args) +{ + op_queue.op = op; + op_queue.args = args; + op_queue.pending = true; + + sem_wait(&op_queue.op_done); + + return op_queue.ret; +} + +bool ffa_is_op_pended(void) +{ + return op_queue.pending; +} + +void ffa_run_pending_op(void) +{ + switch ((intptr_t)op_queue.op) { + case OP_RMDIR: + op_queue.ret = op_callbacks->rmdir((const char *)op_queue.args); + break; + case OP_UNLINK: + op_queue.ret = op_callbacks->unlink((const char *)op_queue.args); + break; + case OP_TRUNCATE: { + struct op_args_truncate *args = op_queue.args; + + op_queue.ret = op_callbacks->truncate(args->path, args->size); + break; + } + case OP_FTRUNCATE: { + struct op_args_ftruncate *args = op_queue.args; + + op_queue.ret = op_callbacks->ftruncate(args->fh, args->size); + break; + } + case OP_WRITE: { + struct op_args_readwrite *args = op_queue.args; + + op_queue.ret = op_callbacks->write(args->fh, args->buf, args->size, args->off); + break; + } + case OP_READ: { + struct op_args_readwrite *args = op_queue.args; + + op_queue.ret = op_callbacks->read(args->fh, args->buf, args->size, args->off); + break; + } + case OP_RELEASE: + op_queue.ret = op_callbacks->release(*(uint64_t *)op_queue.args); + break; + case OP_CREATE: { + struct op_args_create *args = op_queue.args; + + op_queue.ret = op_callbacks->create(args->path, args->fh_p); + break; + } + case OP_MKDIR: + op_queue.ret = op_callbacks->mkdir((const char *)op_queue.args); + break; + case OP_READDIR_END: + op_callbacks->readdir_end(); + break; + case OP_READDIR_READ_NEXT: + op_queue.ret = op_callbacks->readdir_read_next((struct ffa_dirent *)op_queue.args); + break; + case OP_READDIR_START: + op_queue.ret = op_callbacks->readdir_start((const char *)op_queue.args); + break; + case OP_READMOUNT: { + struct op_args_readmount *args = op_queue.args; + + op_queue.ret = op_callbacks->readmount(args->mnt_nbr_p, args->mnt_name_p); + break; + } + case OP_STAT: { + struct op_args_stat *args = op_queue.args; + + op_queue.ret = op_callbacks->stat(args->path, args->entry_p); + break; + } + default: + nsi_print_error_and_exit("Programming error, unknown queued operation\n"); + break; + } + op_queue.pending = false; + sem_post(&op_queue.op_done); +} + +static bool is_mount_point(const char *path) +{ + char dir_path[PATH_MAX]; + size_t len; + + len = strlen(path); + if (len >= sizeof(dir_path)) { + return false; + } + + memcpy(dir_path, path, len); + dir_path[len] = '\0'; + return strcmp(dirname(dir_path), "/") == 0; +} + +static int fuse_fs_access_getattr(const char *path, struct stat *st) +{ + struct ffa_dirent entry; + int err; + + st->st_dev = 0; + st->st_ino = 0; + st->st_nlink = 0; + st->st_uid = getuid(); + st->st_gid = getgid(); + st->st_rdev = 0; + st->st_blksize = 0; + st->st_blocks = 0; + st->st_atime = 0; + st->st_mtime = 0; + st->st_ctime = 0; + + if ((strcmp(path, "/") == 0) || is_mount_point(path)) { + if (strstr(path, "/.") != NULL) { + return -ENOENT; + } + st->st_mode = S_IFDIR | S_IRWX_DIR; + st->st_size = 0; + return 0; + } + + struct op_args_stat args; + + args.path = path; + args.entry_p = &entry; + + err = queue_op(OP_STAT, (void *)&args); + + if (err != 0) { + return -nsi_errno_from_mid(err); + } + + if (entry.is_directory) { + st->st_mode = S_IFDIR | S_IRWX_DIR; + st->st_size = 0; + } else { + st->st_mode = S_IFREG | S_IRW_FILE; + st->st_size = entry.size; + } + + return 0; +} + +static int fuse_fs_access_readmount(void *buf, fuse_fill_dir_t filler) +{ + int mnt_nbr = 0; + const char *mnt_name; + struct stat st; + int err; + + st.st_dev = 0; + st.st_ino = 0; + st.st_nlink = 0; + st.st_uid = getuid(); + st.st_gid = getgid(); + st.st_rdev = 0; + st.st_atime = 0; + st.st_mtime = 0; + st.st_ctime = 0; + st.st_mode = S_IFDIR | S_IRWX_DIR; + st.st_size = 0; + st.st_blksize = 0; + st.st_blocks = 0; + + filler(buf, ".", &st, 0); + filler(buf, "..", NULL, 0); + + do { + struct op_args_readmount args; + + args.mnt_nbr_p = &mnt_nbr; + args.mnt_name_p = &mnt_name; + + err = queue_op(OP_READMOUNT, (void *)&args); + err = -nsi_errno_from_mid(err); + + if (err < 0) { + break; + } + + filler(buf, &mnt_name[1], &st, 0); + + } while (true); + + if (err == -ENOENT) { + err = 0; + } + + return err; +} + +static int fuse_fs_access_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t off, + struct fuse_file_info *fi) +{ + NSI_ARG_UNUSED(off); + NSI_ARG_UNUSED(fi); + + struct ffa_dirent entry; + int err; + struct stat st; + + if (strcmp(path, "/") == 0) { + err = fuse_fs_access_readmount(buf, filler); + return -nsi_errno_from_mid(err); + } + + if (is_mount_point(path)) { + /* File system API expects trailing slash for a mount point + * directory but FUSE strips the trailing slashes from + * directory names so add it back. + */ + char mount_path[PATH_MAX] = {0}; + size_t len = strlen(path); + + if (len >= (PATH_MAX - 2)) { + return -ENOMEM; + } + + memcpy(mount_path, path, len); + mount_path[len] = '/'; + err = queue_op(OP_READDIR_START, (void *)mount_path); + } else { + err = queue_op(OP_READDIR_START, (void *)path); + } + + if (err) { + return -ENOEXEC; + } + + st.st_dev = 0; + st.st_ino = 0; + st.st_nlink = 0; + st.st_uid = getuid(); + st.st_gid = getgid(); + st.st_rdev = 0; + st.st_atime = 0; + st.st_mtime = 0; + st.st_ctime = 0; + st.st_mode = S_IFDIR | S_IRWX_DIR; + st.st_size = 0; + st.st_blksize = 0; + st.st_blocks = 0; + + filler(buf, ".", &st, 0); + filler(buf, "..", &st, 0); + + do { + err = queue_op(OP_READDIR_READ_NEXT, (void *)&entry); + if (err) { + break; + } + + if (entry.name[0] == DIR_END) { + break; + } + + if (entry.is_directory) { + st.st_mode = S_IFDIR | S_IRWX_DIR; + st.st_size = 0; + } else { + st.st_mode = S_IFREG | S_IRW_FILE; + st.st_size = entry.size; + } + + if (filler(buf, entry.name, &st, 0)) { + break; + } + } while (1); + + queue_op(OP_READDIR_END, NULL); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_mkdir(const char *path, mode_t mode) +{ + NSI_ARG_UNUSED(mode); + + int err = queue_op(OP_MKDIR, (void *)path); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_create(const char *path, mode_t mode, struct fuse_file_info *fi) +{ + int err; + struct op_args_create args; + + NSI_ARG_UNUSED(mode); + + if (is_mount_point(path)) { + return -ENOENT; + } + + args.path = path; + args.fh_p = &fi->fh; + + err = queue_op(OP_CREATE, (void *)&args); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_open(const char *path, struct fuse_file_info *fi) +{ + int err = fuse_fs_access_create(path, 0, fi); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_release(const char *path, struct fuse_file_info *fi) +{ + NSI_ARG_UNUSED(path); + + if (fi->fh == INVALID_FILE_HANDLE) { + return -EINVAL; + } + + (void)queue_op(OP_RELEASE, (void *)&fi->fh); + + return 0; +} + +static int fuse_fs_access_read(const char *path, char *buf, size_t size, off_t off, + struct fuse_file_info *fi) +{ + int err; + struct op_args_readwrite args; + + NSI_ARG_UNUSED(path); + + if (fi->fh == INVALID_FILE_HANDLE) { + return -EINVAL; + } + + args.fh = fi->fh; + args.buf = buf; + args.size = size; + args.off = off; + + err = queue_op(OP_READ, (void *)&args); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_write(const char *path, const char *buf, size_t size, off_t off, + struct fuse_file_info *fi) +{ + int err; + struct op_args_readwrite args; + + NSI_ARG_UNUSED(path); + + if (fi->fh == INVALID_FILE_HANDLE) { + return -EINVAL; + } + + args.fh = fi->fh; + args.buf = (char *)buf; + args.size = size; + args.off = off; + + err = queue_op(OP_WRITE, (void *)&args); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) +{ + struct op_args_ftruncate args; + int err; + + NSI_ARG_UNUSED(path); + + if (fi->fh == INVALID_FILE_HANDLE) { + return -EINVAL; + } + + args.fh = fi->fh; + args.size = size; + + err = queue_op(OP_FTRUNCATE, (void *)&args); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_truncate(const char *path, off_t size) +{ + struct op_args_truncate args; + int err; + + args.path = path; + args.size = size; + + err = queue_op(OP_TRUNCATE, (void *)&args); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_rmdir(const char *path) +{ + int err = queue_op(OP_RMDIR, (void *)path); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_unlink(const char *path) +{ + int err = queue_op(OP_UNLINK, (void *)path); + + return -nsi_errno_from_mid(err); +} + +static int fuse_fs_access_statfs(const char *path, struct statvfs *buf) +{ + NSI_ARG_UNUSED(path); + NSI_ARG_UNUSED(buf); + return 0; +} + +static int fuse_fs_access_utimens(const char *path, const struct timespec tv[2]) +{ + /* dummy */ + NSI_ARG_UNUSED(path); + NSI_ARG_UNUSED(tv); + return 0; +} + +static struct fuse_operations fuse_fs_access_oper = { + .getattr = fuse_fs_access_getattr, + .readlink = NULL, + .getdir = NULL, + .mknod = NULL, + .mkdir = fuse_fs_access_mkdir, + .unlink = fuse_fs_access_unlink, + .rmdir = fuse_fs_access_rmdir, + .symlink = NULL, + .rename = NULL, + .link = NULL, + .chmod = NULL, + .chown = NULL, + .truncate = fuse_fs_access_truncate, + .utime = NULL, + .open = fuse_fs_access_open, + .read = fuse_fs_access_read, + .write = fuse_fs_access_write, + .statfs = fuse_fs_access_statfs, + .flush = NULL, + .release = fuse_fs_access_release, + .fsync = NULL, + .setxattr = NULL, + .getxattr = NULL, + .listxattr = NULL, + .removexattr = NULL, + .opendir = NULL, + .readdir = fuse_fs_access_readdir, + .releasedir = NULL, + .fsyncdir = NULL, + .init = NULL, + .destroy = NULL, + .access = NULL, + .create = fuse_fs_access_create, + .ftruncate = fuse_fs_access_ftruncate, + .fgetattr = NULL, + .lock = NULL, + .utimens = fuse_fs_access_utimens, + .bmap = NULL, + .flag_nullpath_ok = 0, + .flag_nopath = 0, + .flag_utime_omit_ok = 0, + .flag_reserved = 0, + .ioctl = NULL, + .poll = NULL, + .write_buf = NULL, + .read_buf = NULL, + .flock = NULL, + .fallocate = NULL, +}; + +static void *ffsa_main(void *fuse_mountpoint) +{ + char *argv[] = { + "", + "-f", + "-s", + (char *)fuse_mountpoint + }; + int argc = NSI_ARRAY_SIZE(argv); + + nsi_print_trace("FUSE mounting flash in host %s/\n", (char *)fuse_mountpoint); + + fuse_main(argc, argv, &fuse_fs_access_oper, NULL); + + pthread_exit(0); + return NULL; +} + +void ffsa_init_bottom(const char *fuse_mountpoint, struct ffa_op_callbacks *op_cbs) +{ + struct stat st; + int err; + + op_callbacks = op_cbs; + + if (stat(fuse_mountpoint, &st) < 0) { + if (mkdir(fuse_mountpoint, 0700) < 0) { + nsi_print_error_and_exit("Failed to create directory for flash mount point " + "(%s): %s\n", + fuse_mountpoint, strerror(errno)); + } + } else if (!S_ISDIR(st.st_mode)) { + nsi_print_error_and_exit("%s is not a directory\n", fuse_mountpoint); + } + + err = pthread_create(&fuse_thread, NULL, ffsa_main, (void *)fuse_mountpoint); + if (err < 0) { + nsi_print_error_and_exit("Failed to create thread for fuse_fs_access_main\n"); + } + + err = sem_init(&op_queue.op_done, 0, 0); + if (err) { + nsi_print_error_and_exit("Failed to initialize semaphore\n"); + } +} + +void ffsa_cleanup_bottom(const char *fuse_mountpoint) +{ + char *full_cmd; + static const char cmd[] = "fusermount -uz "; + + full_cmd = malloc(strlen(cmd) + strlen(fuse_mountpoint) + 1); + + sprintf(full_cmd, "%s%s", cmd, fuse_mountpoint); + if (system(full_cmd) < -1) { + nsi_print_trace("Failed to unmount fuse mount point\n"); + } + free(full_cmd); + + pthread_join(fuse_thread, NULL); +} diff --git a/subsys/fs/fuse_fs_access_bottom.h b/subsys/fs/fuse_fs_access_bottom.h new file mode 100644 index 00000000000..93d198c09ea --- /dev/null +++ b/subsys/fs/fuse_fs_access_bottom.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SUBSYS_FS_FUSE_FS_ACCESS_BOTTOM_H +#define SUBSYS_FS_FUSE_FS_ACCESS_BOTTOM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define INVALID_FILE_HANDLE (INT32_MAX) + +struct ffa_dirent { + bool is_directory; + char *name; + size_t size; +}; + +struct ffa_op_callbacks { + int (*stat)(const char *path, struct ffa_dirent *entry); + int (*readmount)(int *mnt_nbr, const char **mnt_name); + int (*readdir_start)(const char *path); + int (*readdir_read_next)(struct ffa_dirent *entry); + void (*readdir_end)(void); + int (*mkdir)(const char *path); + int (*create)(const char *path, uint64_t *fh); + int (*release)(uint64_t fh); + int (*read)(uint64_t fh, char *buf, size_t size, off_t off); + int (*write)(uint64_t fh, const char *buf, size_t size, off_t off); + int (*ftruncate)(uint64_t fh, off_t size); + int (*truncate)(const char *path, off_t size); + int (*unlink)(const char *path); + int (*rmdir)(const char *path); +}; + +void ffsa_init_bottom(const char *fuse_mountpoint, struct ffa_op_callbacks *op_cbs); +void ffsa_cleanup_bottom(const char *fuse_mountpoint); + +bool ffa_is_op_pended(void); +void ffa_run_pending_op(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SUBSYS_FS_FUSE_FS_ACCESS_BOTTOM_H */