native_posix: Added support to access flash via FUSE
Added support to access flash device partitions from host through FUSE. Signed-off-by: Jan Van Winkel <jan.van_winkel@dxplore.eu>
This commit is contained in:
parent
3815ae6f7f
commit
a90c000790
|
@ -320,6 +320,7 @@
|
|||
/subsys/debug/ @nashif
|
||||
/subsys/fs/ @nashif
|
||||
/subsys/fs/fcb/ @nvlsianpu
|
||||
/subsys/fs/fuse_fs_access.c @vanwinkeljan
|
||||
/subsys/fs/nvs/ @Laczen
|
||||
/subsys/logging/ @nordic-krch
|
||||
/subsys/mgmt/ @carlescufi @nvlsianpu
|
||||
|
|
|
@ -554,6 +554,9 @@ The following peripherals are currently provided with this board:
|
|||
configuration. In case the file does not exists the driver will take care of
|
||||
creating the file, else the existing file is used.
|
||||
|
||||
The flash content can be accessed from the host system, as explained in the
|
||||
`Host based flash access`_ section.
|
||||
|
||||
UART
|
||||
****
|
||||
|
||||
|
@ -626,3 +629,38 @@ development by integrating more seamlessly with the host operating system:
|
|||
A backend/"bottom" for Zephyr's CTF tracing subsystem which writes the tracing
|
||||
data to a file in the host filesystem.
|
||||
More information can be found in :ref:`Common Tracing Format <ctf>`
|
||||
|
||||
Host based flash access
|
||||
***********************
|
||||
|
||||
If a flash device is present, the file system partitions on the flash
|
||||
device can be exposed through the host file system by enabling
|
||||
:option:`CONFIG_FUSE_FS_ACCESS`. This option enables a FUSE
|
||||
(File system in User space) layer that maps the Zephyr file system calls to
|
||||
the required UNIX file system calls, and provides access to the flash file
|
||||
system partitions with normal operating system commands such as ``cd``,
|
||||
``ls`` and ``mkdir``.
|
||||
|
||||
By default the partitions are exposed through the directory *flash* in the
|
||||
current working directory. This directory can be changed via the command line
|
||||
option *--flash-mount*. As this directory operates as a mount point for FUSE
|
||||
you have to ensure that it exists before starting the native POSIX board.
|
||||
|
||||
On exit, the native POSIX board application will take care of unmounting the
|
||||
directory. In the unfortunate case that the native POSIX board application
|
||||
crashes, you can cleanup the stale mount point by using the program
|
||||
``fusermount``::
|
||||
|
||||
$ fusermount -u flash
|
||||
|
||||
Note that this feature requires a 32-bit version of the FUSE library, with a
|
||||
minimal version of 2.6, on the host system and ``pkg-config`` settings to
|
||||
correctly pickup the FUSE install path and compiler flags.
|
||||
|
||||
On a Ubuntu 18.04 host system, for example, install the ``pkg-config`` and
|
||||
``libfuse-dev:i386`` packages, and configure the pkg-config search path with
|
||||
these commands::
|
||||
|
||||
$ sudo apt-get install pkg-config libfuse-dev:i386
|
||||
$ export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig
|
||||
|
||||
|
|
|
@ -19,3 +19,13 @@ endif()
|
|||
|
||||
add_subdirectory_ifdef(CONFIG_FCB ./fcb)
|
||||
add_subdirectory_ifdef(CONFIG_NVS ./nvs)
|
||||
|
||||
if(CONFIG_FUSE_FS_ACCESS)
|
||||
zephyr_library_named(FS_FUSE)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_search_module(FUSE REQUIRED fuse)
|
||||
zephyr_include_directories(${FUSE_INCLUDE_DIR})
|
||||
zephyr_link_libraries(${FUSE_LIBRARIES})
|
||||
zephyr_library_compile_definitions(_FILE_OFFSET_BITS=64)
|
||||
zephyr_library_sources(fuse_fs_access.c)
|
||||
endif()
|
||||
|
|
|
@ -51,6 +51,12 @@ config FILE_SYSTEM_SHELL
|
|||
This shell provides basic browsing of the contents of the
|
||||
file system.
|
||||
|
||||
config FUSE_FS_ACCESS
|
||||
bool "Enable FUSE based access to file system partitions"
|
||||
depends on ARCH_POSIX
|
||||
help
|
||||
Expose file system partitions to the host system through FUSE.
|
||||
|
||||
menu "FatFs Settings"
|
||||
visible if FAT_FILESYSTEM_ELM
|
||||
|
||||
|
|
536
subsys/fs/fuse_fs_access.c
Normal file
536
subsys/fs/fuse_fs_access.c
Normal file
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
* 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.h>
|
||||
#include <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 u8_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];
|
||||
|
||||
sprintf(dir_path, "%s", path);
|
||||
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);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
sprintf(mount_path, "%s/", path);
|
||||
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);
|
||||
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);
|
||||
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;
|
||||
|
||||
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);
|
Loading…
Reference in a new issue