blob: d3df397842f9aa56db390bd304a20bfe5963eea6 [file] [log] [blame]
/*
* Copyright (c) 2019 Jan Van Winkel <jan.van_winkel@dxplore.eu>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <libgen.h>
#include <linux/limits.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/time.h>
#include <sys/types.h>
#include <zephyr/kernel.h>
#include <zephyr/fs/fs.h>
#include "cmdline.h"
#include "soc.h"
#define S_IRWX_DIR (0775)
#define S_IRW_FILE (0664)
#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;
static ssize_t get_new_file_handle(void)
{
size_t idx;
for (idx = 0; idx < ARRAY_SIZE(file_handles); ++idx) {
if (file_handles[idx] == 0) {
++file_handles[idx];
return idx;
}
}
return -ENOMEM;
}
static void release_file_handle(size_t handle)
{
if (handle < ARRAY_SIZE(file_handles)) {
--file_handles[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)
{
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;
}
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;
}
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 stat;
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_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;
}
static int fuse_fs_access_readdir(const char *path, void *buf,
fuse_fill_dir_t filler, off_t off,
struct fuse_file_info *fi)
{
struct fs_dir_t dir;
struct fs_dirent entry;
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;
}
static int fuse_fs_access_create(const char *path, mode_t mode,
struct fuse_file_info *fi)
{
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;
}
fi->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;
}
return 0;
}
static int fuse_fs_access_open(const char *path, struct fuse_file_info *fi)
{
return fuse_fs_access_create(path, 0, fi);
}
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);
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;
ARG_UNUSED(path);
if (fi->fh == INVALID_FILE_HANDLE) {
return -EINVAL;
}
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;
}
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;
ARG_UNUSED(path);
if (fi->fh == INVALID_FILE_HANDLE) {
return -EINVAL;
}
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;
}
static int fuse_fs_access_ftruncate(const char *path, off_t size,
struct fuse_file_info *fi)
{
int err;
ARG_UNUSED(path);
if (fi->fh == INVALID_FILE_HANDLE) {
return -EINVAL;
}
err = fs_truncate(&files[fi->fh], size);
return err;
}
static int fuse_fs_access_truncate(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;
}
err = fs_truncate(&file, size);
if (err != 0) {
fs_close(&file);
return err;
}
err = fs_close(&file);
return err;
}
static int fuse_fs_access_mkdir(const char *path, mode_t mode)
{
ARG_UNUSED(mode);
return fs_mkdir(path);
}
static int fuse_fs_access_rmdir(const char *path)
{
return fs_unlink(path);
}
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,
};
static void *fuse_fs_access_main(void *arg)
{
ARG_UNUSED(arg);
char *argv[] = {
"",
"-f",
"-s",
(char *) fuse_mountpoint
};
int argc = ARRAY_SIZE(argv);
posix_print_trace("Mounting flash at %s/\n", fuse_mountpoint);
fuse_main(argc, argv, &fuse_fs_access_oper, NULL);
pthread_exit(0);
}
static void fuse_fs_access_init(void)
{
int err;
struct stat st;
size_t i = 0;
while (i < ARRAY_SIZE(files)) {
fs_file_t_init(&files[i]);
++i;
}
if (fuse_mountpoint == NULL) {
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");
}
}
static void fuse_fs_access_exit(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);
}
static void fuse_fs_access_options(void)
{
static struct args_struct_t fuse_fs_access_options[] = {
{ .manual = false,
.is_mandatory = false,
.is_switch = false,
.option = "flash-mount",
.name = "path",
.type = 's',
.dest = (void *)&fuse_mountpoint,
.call_when_found = NULL,
.descript = "Path to the directory where to mount flash" },
ARG_TABLE_ENDMARKER
};
native_add_command_line_opts(fuse_fs_access_options);
}
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);