Instead of relaying on stddef.h being included by other headers let's include it explicitly, following a report of it being missing for Ubuntu 22.04 (glibc 2.35) Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
644 lines
14 KiB
C
644 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 <stddef.h>
|
|
#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);
|
|
}
|