| /* |
| * Copyright (c) 2019 Jan Van Winkel <jan.van_winkel@dxplore.eu> |
| * 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 <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <pthread.h> |
| #include <semaphore.h> |
| #include <fuse.h> |
| #include <libgen.h> |
| #include <linux/limits.h> |
| #include <unistd.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <nsi_tracing.h> |
| #include <nsi_utils.h> |
| #include <nsi_errno.h> |
| #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); |
| } |