From 02b18136e2cd8ec1a1d7cb73e6bb2dd244ad2519 Mon Sep 17 00:00:00 2001 From: Jakub Michalski Date: Wed, 9 Apr 2025 10:21:14 +0200 Subject: [PATCH] fs: add virtiofs operations This commit adds virtiofs functions implementing fuse operations required to enable zephyr filesystem support Signed-off-by: Jakub Michalski --- subsys/fs/CMakeLists.txt | 4 +- subsys/fs/Kconfig | 1 + subsys/fs/virtiofs/CMakeLists.txt | 13 + subsys/fs/virtiofs/Kconfig | 52 ++ subsys/fs/virtiofs/virtiofs.c | 778 ++++++++++++++++++++++++++++++ subsys/fs/virtiofs/virtiofs.h | 49 ++ 6 files changed, 896 insertions(+), 1 deletion(-) create mode 100644 subsys/fs/virtiofs/CMakeLists.txt create mode 100644 subsys/fs/virtiofs/Kconfig create mode 100644 subsys/fs/virtiofs/virtiofs.c create mode 100644 subsys/fs/virtiofs/virtiofs.h diff --git a/subsys/fs/CMakeLists.txt b/subsys/fs/CMakeLists.txt index ef37efffadd..512389eef76 100644 --- a/subsys/fs/CMakeLists.txt +++ b/subsys/fs/CMakeLists.txt @@ -18,13 +18,15 @@ if(CONFIG_FILE_SYSTEM_LIB_LINK) add_subdirectory_ifdef(CONFIG_FILE_SYSTEM_EXT2 ext2) add_subdirectory_ifdef(CONFIG_FUSE_CLIENT fuse_client) + add_subdirectory_ifdef(CONFIG_FILE_SYSTEM_VIRTIOFS virtiofs) zephyr_library_link_libraries(FS) target_link_libraries_ifdef(CONFIG_FAT_FILESYSTEM_ELM FS INTERFACE ELMFAT) target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_LITTLEFS FS INTERFACE LITTLEFS) target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_EXT2 FS INTERFACE EXT2) - target_link_libraries_ifdef(CONFIG_FUSE_CLIENT FS INTERFACE FUSE_CLIENT) + target_link_libraries_ifdef(CONFIG_FUSE_CLIENT FS INTERFACE FUSE_CLIENT) + target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_VIRTIOFS FS INTERFACE VIRTIOFS) endif() add_subdirectory_ifdef(CONFIG_FCB ./fcb) diff --git a/subsys/fs/Kconfig b/subsys/fs/Kconfig index 4710434925b..709bb91bb5e 100644 --- a/subsys/fs/Kconfig +++ b/subsys/fs/Kconfig @@ -120,6 +120,7 @@ rsource "Kconfig.fatfs" rsource "Kconfig.littlefs" rsource "ext2/Kconfig" rsource "fuse_client/Kconfig" +rsource "virtiofs/Kconfig" endif # FILE_SYSTEM_LIB_LINK diff --git a/subsys/fs/virtiofs/CMakeLists.txt b/subsys/fs/virtiofs/CMakeLists.txt new file mode 100644 index 00000000000..d107ac82f8e --- /dev/null +++ b/subsys/fs/virtiofs/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2025 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +add_library(VIRTIOFS INTERFACE) +target_include_directories(VIRTIOFS INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +zephyr_library() +zephyr_library_sources( + virtiofs.c + virtiofs_zfs.c +) + +zephyr_library_link_libraries(VIRTIOFS) diff --git a/subsys/fs/virtiofs/Kconfig b/subsys/fs/virtiofs/Kconfig new file mode 100644 index 00000000000..fed3f482478 --- /dev/null +++ b/subsys/fs/virtiofs/Kconfig @@ -0,0 +1,52 @@ +# Copyright (c) 2025 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +config FILE_SYSTEM_VIRTIOFS + bool "Virtiofs file system support" + depends on FILE_SYSTEM + select FUSE_CLIENT + help + Enable virtiofs file system support. + +config VIRTIOFS_DEBUG + bool "Print virtiofs verbose debug information" + help + Enables printing of virtiofs verbose debug information + +config VIRTIOFS_MAX_FILES + int "Virtiofs max open files" + default 1024 + help + Virtiofs max simultaneously open files + +config VIRTIOFS_MAX_VQUEUE_SIZE + int "Virtiofs max virtqueue size" + default 1024 + help + Maximum size of virtqueue + +config VIRTIOFS_NO_NOTIFICATION_QUEUE_SLOT + bool "Omit notification queue (idx 1) and assume that idx 1 is the first request queue" + default y + help + According to virtio specification v1.3 section 5.11.2 queue at idx 1 is notification queue and + request queues start at idx 2, however on qemu+virtiofsd thats not true and idx 1 is + the first request queue + +config VIRTIOFS_VIRTIOFSD_UNLINK_QUIRK + bool "Fix unlink() with some virtiofsd versions" + default y + help + Some virtiofsd versions (at least Debian 1:7.2+dfsg-7+deb12u7) + will fail with EIO on unlink if the file wasn't looked up before + +config VIRTIOFS_CREATE_MODE_VALUE + int "Virtiofs mode value used during file/directory creation" + default 438 #0666 + help + During creation of file or directory we have to set mode, this config allows + configuring that value. This determines access permissions for created files and directories. + +module = VIRTIOFS +module-str = virtiofs +source "subsys/logging/Kconfig.template.log_config" diff --git a/subsys/fs/virtiofs/virtiofs.c b/subsys/fs/virtiofs/virtiofs.c new file mode 100644 index 00000000000..ad594ed0c64 --- /dev/null +++ b/subsys/fs/virtiofs/virtiofs.c @@ -0,0 +1,778 @@ +/* + * Copyright (c) 2025 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "virtiofs.h" +#include + +LOG_MODULE_REGISTER(virtiofs, CONFIG_VIRTIOFS_LOG_LEVEL); + +/* + * According to 5.11.2 of virtio specification v1.3 the virtiofs queues are indexed as + * follows: + * - idx 0 - hiprio + * - idx 1 - notification queue + * - idx 2..n - request queues + * notification queue is available only if VIRTIO_FS_F_NOTIFICATION is present and + * there is no mention that in its absence the request queues will be shifted and start + * at idx 1, so the request queues shall start at idx 2. However in case of qemu+virtiofsd + * who don't support VIRTIO_FS_F_NOTIFICATION, the last available queue is at idx 1 and + * virtio_fs_config.num_request_queues states that there is a single request queue present + * which must be the one at idx 1 + */ +#ifdef CONFIG_VIRTIOFS_NO_NOTIFICATION_QUEUE_SLOT +#define REQUEST_QUEUE 1 +#else +#define REQUEST_QUEUE 2 +#endif + + +struct virtio_fs_config { + char tag[36]; + uint32_t num_request_queues; +}; + +static int virtiofs_validate_response( + const struct fuse_out_header *header, uint32_t opcode, uint32_t used_len, + uint32_t expected_len) +{ + if (used_len < sizeof(*header)) { + LOG_ERR("used length is smaller than size of fuse_out_header"); + return -EIO; + } + + if (header->error != 0) { + LOG_ERR( + "%s error %d (%s)", + fuse_opcode_to_string(opcode), + -header->error, + strerror(-header->error) + ); + return header->error; + } + + if (expected_len != -1 && header->len != expected_len) { + LOG_ERR( + "%s return message has invalid length (0x%x), expected 0x%x", + fuse_opcode_to_string(opcode), + header->len, + expected_len + ); + return -EIO; + } + + return 0; +} + +struct recv_cb_param { + struct k_sem sem; + uint32_t used_len; +}; + +void virtiofs_recv_cb(void *opaque, uint32_t used_len) +{ + struct recv_cb_param *arg = opaque; + + arg->used_len = used_len; + k_sem_give(&arg->sem); +} + +static uint32_t virtiofs_send_receive( + const struct device *dev, uint16_t virtq, struct virtq_buf *bufs, + uint16_t bufs_size, uint16_t device_readable) +{ + struct virtq *virtqueue = virtio_get_virtqueue(dev, virtq); + struct recv_cb_param cb_arg; + + k_sem_init(&cb_arg.sem, 0, 1); + + virtq_add_buffer_chain( + virtqueue, bufs, bufs_size, device_readable, virtiofs_recv_cb, &cb_arg, + K_FOREVER + ); + virtio_notify_virtqueue(dev, virtq); + + k_sem_take(&cb_arg.sem, K_FOREVER); + + return cb_arg.used_len; +} + +static uint16_t virtiofs_queue_enum_cb(uint16_t queue_idx, uint16_t max_size, void *unused) +{ + if (queue_idx == REQUEST_QUEUE) { + return MIN(CONFIG_VIRTIOFS_MAX_VQUEUE_SIZE, max_size); + } else { + return 0; + } +} + +int virtiofs_init(const struct device *dev, struct fuse_init_out *response) +{ + struct virtio_fs_config *fs_config = virtio_get_device_specific_config(dev); + struct fuse_init_req req; + int ret = 0; + + if (!fs_config) { + LOG_ERR("no virtio_fs_config present"); + return -ENXIO; + } + if (fs_config->num_request_queues < 1) { + /* this shouldn't ever happen */ + LOG_ERR("no request queue present"); + return -ENODEV; + } + + ret = virtio_commit_feature_bits(dev); + if (ret != 0) { + return ret; + } + + ret = virtio_init_virtqueues(dev, REQUEST_QUEUE, virtiofs_queue_enum_cb, NULL); + if (ret != 0) { + LOG_ERR("failed to initialize fs virtqueues"); + return ret; + } + + virtio_finalize_init(dev); + + fuse_create_init_req(&req); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.init_in) }, + { .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.init_out) } + }; + + LOG_INF("sending FUSE_INIT, unique=%" PRIu64, req.in_header.unique); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1); + + LOG_INF("received FUSE_INIT response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response( + &req.out_header, FUSE_INIT, used_len, buf[1].len + ); + + if (valid_ret != 0) { + return valid_ret; + } + + if (req.init_out.major != FUSE_MAJOR_VERSION) { + LOG_ERR( + "FUSE_INIT major version mismatch (%d), version %d is supported", + req.init_out.major, + FUSE_MAJOR_VERSION + ); + return -ENOTSUP; + } + + if (req.init_out.minor < FUSE_MINOR_VERSION) { + LOG_ERR( + "FUSE_INIT minor version is too low (%d), version %d is supported", + req.init_out.minor, + FUSE_MINOR_VERSION + ); + return -ENOTSUP; + } + + *response = req.init_out; + +#ifdef CONFIG_VIRTIOFS_DEBUG + fuse_dump_init_req_out(&req.init_out); +#endif + + return 0; +} + +/** + * @brief lookups object in the virtiofs filesystem + * + * @param dev virtio device its used on + * @param inode inode to start from + * @param path path to object we are looking for + * @param response virtiofs response for object + * @param parent_inode will be set to immediate parent inode of object that we are looking for. + * If immediate parent doesn't exist it will be set to 0. If not 0 it has to be FUSE_FORGET by + * caller. Can be NULL. + * @return 0 or error code on failure + */ +int virtiofs_lookup( + const struct device *dev, uint64_t inode, const char *path, struct fuse_entry_out *response, + uint64_t *parent_inode) +{ + uint32_t path_len = strlen(path) + 1; + const char *curr = path; + uint32_t curr_len = 0; + uint64_t curr_inode = inode; + struct fuse_lookup_req req; + + /* + * we have to split path and lookup it dir by dir, because FUSE_LOOKUP doesn't work with + * full paths like abc/xyz/file. We have to lookup abc, then lookup xyz with abc's inode + * as a base and then lookup file with xyz's inode as a base + */ + while (curr < path + path_len) { + curr_len = 0; + for (const char *c = curr; c < path + path_len - 1 && *c != '/'; c++) { + curr_len++; + } + + fuse_create_lookup_req(&req, curr_inode, curr_len + 1); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(struct fuse_in_header) }, + { .addr = (void *)curr, .len = curr_len }, + /* + * despite length being part of in_header this still has to be null + * terminated + */ + { .addr = "", .len = 1}, + { .addr = &req.out_header, + .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out) } + }; + + LOG_INF( + "sending FUSE_LOOKUP for \"%s\", nodeid=%" PRIu64 ", unique=%" PRIu64, + curr, curr_inode, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 4, 3); + + LOG_INF("received FUSE_LOOKUP response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response( + &req.out_header, FUSE_LOOKUP, used_len, + sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out) + ); + + if (parent_inode) { + *parent_inode = curr_inode; + } + + *response = req.entry_out; + if (valid_ret != 0) { + if (parent_inode && (curr + curr_len + 1 != path + path_len)) { + /* there is no immediate parent */ + if (*parent_inode != inode) { + virtiofs_forget(dev, *parent_inode, 1); + } + *parent_inode = 0; + } + return valid_ret; + } + +#ifdef CONFIG_VIRTIOFS_DEBUG + fuse_dump_entry_out(&req.entry_out); +#endif + bool is_curr_parent = true; + + for (const char *c = curr; c < path + path_len; c++) { + if (*c == '/') { + is_curr_parent = false; + } + } + + /* + * unless its inode param passed to this function or a parent of object we + * are looking for, curr_inode won't be used anymore so we can forget it + */ + if (curr_inode != inode && (!parent_inode || !is_curr_parent)) { + virtiofs_forget(dev, curr_inode, 1); + } + + curr_inode = req.entry_out.nodeid; + curr += curr_len + 1; + } + + return 0; +} + +int virtiofs_open( + const struct device *dev, uint64_t inode, uint32_t flags, struct fuse_open_out *response, + enum fuse_object_type type) +{ + struct fuse_open_req req; + + fuse_create_open_req(&req, inode, flags, type); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = req.in_header.len }, + { .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.open_out) } + }; + + LOG_INF( + "sending %s, nodeid=%" PRIu64 ", flags=0%" PRIo32 ", unique=%" PRIu64, + type == FUSE_DIR ? "FUSE_OPENDIR" : "FUSE_OPEN", inode, flags, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1); + + LOG_INF( + "received %s response, unique=%" PRIu64, + type == FUSE_DIR ? "FUSE_OPENDIR" : "FUSE_OPEN", req.out_header.unique + ); + + int valid_ret = virtiofs_validate_response( + &req.out_header, type == FUSE_DIR ? FUSE_OPENDIR : FUSE_OPEN, used_len, buf[1].len + ); + + if (valid_ret != 0) { + return valid_ret; + } + + *response = req.open_out; + +#ifdef CONFIG_VIRTIOFS_DEBUG + fuse_dump_open_req_out(&req.open_out); +#endif + + return 0; +} + +int virtiofs_read( + const struct device *dev, uint64_t inode, uint64_t fh, + uint64_t offset, uint32_t size, uint8_t *readbuf) +{ + struct fuse_read_req req; + + fuse_create_read_req(&req, inode, fh, offset, size, FUSE_FILE); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = req.in_header.len }, + { .addr = &req.out_header, .len = sizeof(struct fuse_out_header) }, + { .addr = readbuf, .len = size } + }; + + LOG_INF( + "sending FUSE_READ, nodeid=%" PRIu64 ", fh=%" PRIu64 ", offset=%" PRIu64 + ", size=%" PRIu32 ", unique=%" PRIu64, + inode, fh, offset, size, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 1); + + LOG_INF("received FUSE_READ response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response(&req.out_header, FUSE_READ, used_len, -1); + + if (valid_ret != 0) { + return valid_ret; + } + + return req.out_header.len - sizeof(req.out_header); +} + +int virtiofs_release(const struct device *dev, uint64_t inode, uint64_t fh, + enum fuse_object_type type) +{ + struct fuse_release_req req; + + fuse_create_release_req(&req, inode, fh, type); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = req.in_header.len }, + { .addr = &req.out_header, .len = sizeof(req.out_header) } + }; + + LOG_INF( + "sending %s, inode=%" PRIu64 ", fh=%" PRIu64 ", unique=%" PRIu64, + type == FUSE_DIR ? "FUSE_RELEASEDIR" : "FUSE_RELEASE", inode, fh, + req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1); + + LOG_INF( + "received %s response, unique=%" PRIu64, + type == FUSE_DIR ? "FUSE_RELEASEDIR" : "FUSE_RELEASE", req.out_header.unique + ); + + return virtiofs_validate_response( + &req.out_header, type == FUSE_DIR ? FUSE_RELEASEDIR : FUSE_RELEASE, used_len, -1 + ); +} + +int virtiofs_destroy(const struct device *dev) +{ + struct fuse_destroy_req req; + + fuse_create_destroy_req(&req); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) }, + { .addr = &req.out_header, .len = sizeof(req.out_header) } + }; + + LOG_INF("sending FUSE_DESTROY, unique=%" PRIu64, req.in_header.unique); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1); + + LOG_INF("received FUSE_DESTROY response, unique=%" PRIu64, req.in_header.unique); + + return virtiofs_validate_response(&req.out_header, FUSE_DESTROY, used_len, -1); +} + +int virtiofs_create( + const struct device *dev, uint64_t inode, const char *fname, uint32_t flags, + uint32_t mode, struct fuse_create_out *response) +{ + uint32_t fname_len = strlen(fname) + 1; + struct fuse_create_req req; + + fuse_create_create_req(&req, inode, fname_len, flags, mode); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.create_in) }, + { .addr = (void *)fname, .len = fname_len }, + { .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.create_out) } + }; + + LOG_INF( + "sending FUSE_CREATE for \"%s\", nodeid=%" PRIu64 ", flags=0%" PRIo32 + ", unique=%" PRIu64, + fname, inode, flags, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 2); + + LOG_INF("received FUSE_CREATE response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response( + &req.out_header, FUSE_CREATE, used_len, buf[2].len + ); + + if (valid_ret != 0) { + return valid_ret; + } + + *response = req.create_out; + +#ifdef CONFIG_VIRTIOFS_DEBUG + fuse_dump_create_req_out(&req.create_out); +#endif + + return 0; +} + +int virtiofs_write( + const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset, + uint32_t size, const uint8_t *write_buf) +{ + struct fuse_write_req req; + + fuse_create_write_req(&req, inode, fh, offset, size); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.write_in) }, + { .addr = (void *)write_buf, .len = size }, + { .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.write_out) } + }; + + LOG_INF( + "sending FUSE_WRITE, nodeid=%" PRIu64", fh=%" PRIu64 ", offset=%" PRIu64 + ", size=%" PRIu32 ", unique=%" PRIu64, + inode, fh, offset, size, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 2); + + LOG_INF("received FUSE_WRITE response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response( + &req.out_header, FUSE_WRITE, used_len, buf[2].len + ); + + if (valid_ret != 0) { + return valid_ret; + } + +#ifdef CONFIG_VIRTIOFS_DEBUG + fuse_dump_write_out(&req.write_out); +#endif + + return req.write_out.size; +} + +int virtiofs_lseek( + const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset, + uint32_t whence, struct fuse_lseek_out *response) +{ + struct fuse_lseek_req req; + + fuse_create_lseek_req(&req, inode, fh, offset, whence); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = req.in_header.len }, + { .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.lseek_out) } + }; + + LOG_INF( + "sending FUSE_LSEEK, nodeid=%" PRIu64 ", fh=%" PRIu64 ", offset=%" PRIu64 + ", whence=%" PRIu32 ", unique=%" PRIu64, + inode, fh, offset, whence, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1); + + LOG_INF("received FUSE_LSEEK response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response( + &req.out_header, FUSE_LSEEK, used_len, buf[1].len + ); + + if (valid_ret != 0) { + return valid_ret; + } + + *response = req.lseek_out; + +#ifdef CONFIG_VIRTIOFS_DEBUG + fuse_dump_lseek_out(&req.lseek_out); +#endif + + return 0; +} + +int virtiofs_setattr( + const struct device *dev, uint64_t inode, struct fuse_setattr_in *in, + struct fuse_attr_out *response) +{ + struct fuse_setattr_req req; + + fuse_create_setattr_req(&req, inode); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) }, + { .addr = in, .len = sizeof(*in) }, + { .addr = &req.out_header, .len = sizeof(req.out_header) }, + { .addr = response, .len = sizeof(*response) }, + }; + + LOG_INF("sending FUSE_SETATTR, unique=%" PRIu64, req.in_header.unique); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 4, 2); + + LOG_INF("received FUSE_SETATTR response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response( + &req.out_header, FUSE_SETATTR, used_len, sizeof(req.out_header) + sizeof(*response) + ); + + if (valid_ret != 0) { + return valid_ret; + } + +#ifdef CONFIG_VIRTIOFS_DEBUG + fuse_dump_attr_out(response); +#endif + + return 0; +} + +int virtiofs_fsync(const struct device *dev, uint64_t inode, uint64_t fh) +{ + struct fuse_fsync_req req; + + fuse_create_fsync_req(&req, inode, fh); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, + .len = sizeof(req.in_header) + sizeof(req.fsync_in) }, + { .addr = &req.out_header, .len = sizeof(req.out_header) } + }; + + LOG_INF( + "sending FUSE_FSYNC, nodeid=%" PRIu64 ", fh=%" PRIu64 ", unique=%" PRIu64, + inode, fh, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1); + + LOG_INF("received FUSE_FSYNC response, unique=%" PRIu64, req.out_header.unique); + + return virtiofs_validate_response( + &req.out_header, FUSE_FSYNC, used_len, sizeof(req.out_header) + ); +} + +int virtiofs_mkdir(const struct device *dev, uint64_t inode, const char *dirname, uint32_t mode) +{ + struct fuse_mkdir_req req; + uint32_t dirname_len = strlen(dirname) + 1; + + fuse_create_mkdir_req(&req, inode, dirname_len, mode); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.mkdir_in) }, + { .addr = (void *)dirname, .len = dirname_len }, + { .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.entry_out) } + }; + + LOG_INF( + "sending FUSE_MKDIR %s, inode=%" PRIu64 ", unique=%" PRIu64, + dirname, inode, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 2); + + LOG_INF("received FUSE_MKDIR response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response( + &req.out_header, FUSE_MKDIR, used_len, buf[2].len + ); + + if (valid_ret != 0) { + return valid_ret; + } + + return 0; +} + +int virtiofs_unlink(const struct device *dev, const char *fname, enum fuse_object_type type) +{ + struct fuse_unlink_req req; + uint32_t fname_len = strlen(fname) + 1; + + fuse_create_unlink_req(&req, fname_len, type); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) }, + { .addr = (void *)fname, .len = fname_len }, + { .addr = &req.out_header, .len = sizeof(req.out_header) } + }; + + LOG_INF( + "sending %s for %s, unique=%" PRIu64, + type == FUSE_DIR ? "FUSE_RMDIR" : "FUSE_UNLINK", fname, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 2); + + LOG_INF( + "received %s response, unique=%" PRIu64, + type == FUSE_DIR ? "FUSE_RMDIR" : "FUSE_UNLINK", req.out_header.unique + ); + + return virtiofs_validate_response( + &req.out_header, type == FUSE_DIR ? FUSE_RMDIR : FUSE_UNLINK, used_len, + sizeof(req.out_header) + ); +} + +int virtiofs_rename( + const struct device *dev, uint64_t old_dir_inode, const char *old_name, + uint64_t new_dir_inode, const char *new_name) +{ + struct fuse_rename_req req; + uint32_t old_len = strlen(old_name) + 1; + uint32_t new_len = strlen(new_name) + 1; + + fuse_create_rename_req(&req, old_dir_inode, old_len, new_dir_inode, new_len); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.rename_in) }, + { .addr = (void *)old_name, .len = old_len }, + { .addr = (void *)new_name, .len = new_len }, + { .addr = &req.out_header, .len = sizeof(req.out_header) } + }; + + LOG_INF( + "sending FUSE_RENAME %s to %s, unique=%" PRIu64, + old_name, new_name, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 4, 3); + + LOG_INF("received FUSE_RENAME response, unique=%" PRIu64, req.out_header.unique); + + return virtiofs_validate_response( + &req.out_header, FUSE_RENAME, used_len, sizeof(req.out_header) + ); +} + +int virtiofs_statfs(const struct device *dev, struct fuse_kstatfs *response) +{ + struct fuse_kstatfs_req req; + + fuse_fill_header(&req.in_header, sizeof(req.in_header), FUSE_STATFS, FUSE_ROOT_INODE); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = sizeof(req.in_header) }, + { .addr = &req.out_header, + .len = sizeof(req.out_header) + sizeof(req.kstatfs_out) } + }; + + LOG_INF("sending FUSE_STATFS, unique=%" PRIu64, req.in_header.unique); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1); + + LOG_INF("received FUSE_STATFS response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response( + &req.out_header, FUSE_STATFS, used_len, buf[1].len + ); + + if (valid_ret != 0) { + return valid_ret; + } + +#ifdef CONFIG_VIRTIOFS_DEBUG + fuse_dump_kstafs(&req.kstatfs_out); +#endif + + *response = req.kstatfs_out; + + return 0; +} + +int virtiofs_readdir( + const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset, + uint8_t *dirent_buf, uint32_t dirent_size, uint8_t *name_buf, uint32_t name_size) +{ + struct fuse_read_req req; + + fuse_create_read_req(&req, inode, fh, offset, dirent_size + name_size, FUSE_DIR); + + struct virtq_buf buf[] = { + { .addr = &req.in_header, .len = req.in_header.len }, + { .addr = &req.out_header, .len = sizeof(struct fuse_out_header) }, + { .addr = dirent_buf, .len = dirent_size }, + { .addr = name_buf, .len = name_size } + }; + + LOG_INF( + "sending FUSE_READDIR, nodeid=%" PRIu64 ", fh=%" PRIu64 ", offset=%" PRIu64 + ", size=%" PRIu32 ", unique=%" PRIu64, + inode, fh, offset, dirent_size + name_size, req.in_header.unique + ); + uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 4, 1); + + LOG_INF("received FUSE_READDIR response, unique=%" PRIu64, req.out_header.unique); + + int valid_ret = virtiofs_validate_response(&req.out_header, FUSE_READDIR, used_len, -1); + + if (valid_ret != 0) { + return valid_ret; + } + + return req.out_header.len - sizeof(req.out_header); +} + +void virtiofs_forget(const struct device *dev, uint64_t inode, uint64_t nlookup) +{ + if (inode == FUSE_ROOT_INODE) { + return; + } + + struct fuse_forget_req req; + + fuse_fill_header(&req.in_header, sizeof(req.in_header), FUSE_FORGET, inode); + req.forget_in.nlookup = nlookup; /* refcount will be decreased by this value */ + + struct virtq_buf buf[] = { + { .addr = &req, .len = sizeof(req.in_header) + sizeof(req.forget_in) } + }; + + LOG_INF( + "sending FUSE_FORGET nodeid=%" PRIu64 ", nlookup=%" PRIu64 ", unique=%" PRIu64, + inode, nlookup, req.in_header.unique + ); + virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 1, 1); + LOG_INF("received FUSE_FORGET response, unique=%" PRIu64, req.in_header.unique); + + /* + * In comparison to other fuse operations this one doesn't return fuse_out_header, + * despite virtio spec v1.3 5.11.6.1 saying that out header is common to all + * types of fuse requests (comment in include/uapi/linux/fuse.h states otherwise that + * FUSE_FORGET has no reply), so there is no error code to return + */ +} diff --git a/subsys/fs/virtiofs/virtiofs.h b/subsys/fs/virtiofs/virtiofs.h new file mode 100644 index 00000000000..fc5e863833d --- /dev/null +++ b/subsys/fs/virtiofs/virtiofs.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_FS_VIRTIOFS_VIRTIOFS_H_ +#define ZEPHYR_SUBSYS_FS_VIRTIOFS_VIRTIOFS_H_ +#include +#include <../fuse_client/fuse_client.h> + +int virtiofs_init(const struct device *dev, struct fuse_init_out *response); +int virtiofs_lookup( + const struct device *dev, uint64_t inode, const char *name, struct fuse_entry_out *response, + uint64_t *parent_inode); +int virtiofs_open( + const struct device *dev, uint64_t inode, uint32_t flags, struct fuse_open_out *response, + enum fuse_object_type type); +int virtiofs_read( + const struct device *dev, uint64_t inode, uint64_t fh, + uint64_t offset, uint32_t size, uint8_t *buf); +int virtiofs_release(const struct device *dev, uint64_t inode, uint64_t fh, + enum fuse_object_type type); +int virtiofs_destroy(const struct device *dev); +int virtiofs_create( + const struct device *dev, uint64_t inode, const char *fname, uint32_t flags, + uint32_t mode, struct fuse_create_out *response); +int virtiofs_write( + const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset, + uint32_t size, const uint8_t *write_buf); +int virtiofs_lseek( + const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset, + uint32_t whence, struct fuse_lseek_out *response); +int virtiofs_setattr( + const struct device *dev, uint64_t inode, struct fuse_setattr_in *in, + struct fuse_attr_out *response); +int virtiofs_fsync(const struct device *dev, uint64_t inode, uint64_t fh); +int virtiofs_mkdir(const struct device *dev, uint64_t inode, const char *dirname, uint32_t mode); +int virtiofs_unlink(const struct device *dev, const char *fname, enum fuse_object_type type); +int virtiofs_rename( + const struct device *dev, uint64_t old_dir_inode, const char *old_name, + uint64_t new_dir_inode, const char *new_name); +int virtiofs_statfs(const struct device *dev, struct fuse_kstatfs *response); +int virtiofs_readdir( + const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset, + uint8_t *dirent_buf, uint32_t dirent_size, uint8_t *name_buf, uint32_t name_size); +void virtiofs_forget(const struct device *dev, uint64_t inode, uint64_t nlookup); + +#endif /* ZEPHYR_SUBSYS_FS_VIRTIOFS_VIRTIOFS_H_ */