blob: 1e10738cd6aa42d79673f883b3953894eb9cb091 [file] [log] [blame]
/*
* Copyright (c) 2025 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <errno.h>
#include "virtiofs.h"
#include <zephyr/drivers/virtio.h>
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
/*
* Currently we are using only one request queue, so we don't have to initialize queues
* after that one
*/
#define QUEUE_COUNT (REQUEST_QUEUE + 1)
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, QUEUE_COUNT, 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
*/
}