Split the fuse FS driver into 2 parts: A top built in the embedded side, with the embedded libC, and a bottom built in the runner side with the host libC. The error returns are converted to match the host libC. Also, before the host FUSE thread, which is asynchronous to Zephyr was calling directly into the Zephyr filesystem code, which resulted quite often if catastrophic failures or corruption of the Zephyr state. This is now fixed by having the FUSE thread queue requests to a Zephyr thread, which will be handled in the embedded side in a coherent way. This adds a slightly noticeable overhead, but the performance is still acceptable. Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
643 lines
14 KiB
C
643 lines
14 KiB
C
/*
|
|
* 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);
|
|
}
|