/* * 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 #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); }