| /* | |
| * Present a block device as a raw image through FUSE | |
| * | |
| * Copyright (c) 2020, 2025 Hanna Czenczek <hreitz@redhat.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; under version 2 or later of the License. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, see <http://www.gnu.org/licenses/>. | |
| */ | |
| #define FUSE_USE_VERSION 31 | |
| #include "qemu/osdep.h" | |
| #include "qemu/memalign.h" | |
| #include "qemu/aio.h" | |
| #include "block/block_int-common.h" | |
| #include "block/export.h" | |
| #include "block/fuse.h" | |
| #include "block/qapi.h" | |
| #include "qapi/error.h" | |
| #include "qapi/qapi-commands-block.h" | |
| #include "qemu/coroutine.h" | |
| #include "qemu/error-report.h" | |
| #include "qemu/main-loop.h" | |
| #include "system/block-backend.h" | |
| #include "system/iothread.h" | |
| #include <fuse.h> | |
| #include <fuse_lowlevel.h> | |
| #include "standard-headers/linux/fuse.h" | |
| #include <sys/ioctl.h> | |
| #if defined(CONFIG_FALLOCATE_ZERO_RANGE) | |
| #include <linux/falloc.h> | |
| #endif | |
| #ifdef __linux__ | |
| #include <linux/fs.h> | |
| #endif | |
| /* Prevent overly long bounce buffer allocations */ | |
| #define FUSE_MAX_READ_BYTES (MIN(BDRV_REQUEST_MAX_BYTES, 1 * 1024 * 1024)) | |
| #define FUSE_MAX_WRITE_BYTES (64 * 1024) | |
| typedef struct FuseRequestInHeader { | |
| struct fuse_in_header common; | |
| /* All supported requests */ | |
| union { | |
| /* | |
| * When using_old_fuse_init_in() is true, then the smaller older struct | |
| * is used by the kernel. The flags2 member and other new members must | |
| * be treated as absent then. | |
| */ | |
| struct fuse_init_in init; | |
| struct fuse_open_in open; | |
| struct fuse_setattr_in setattr; | |
| struct fuse_read_in read; | |
| struct fuse_write_in write; | |
| struct fuse_fallocate_in fallocate; | |
| #ifdef CONFIG_FUSE_LSEEK | |
| struct fuse_lseek_in lseek; | |
| #endif | |
| }; | |
| } FuseRequestInHeader; | |
| typedef struct FuseRequestOutHeader { | |
| struct fuse_out_header common; | |
| /* All supported requests */ | |
| union { | |
| struct fuse_init_out init; | |
| struct fuse_statfs_out statfs; | |
| struct fuse_open_out open; | |
| struct fuse_attr_out attr; | |
| struct fuse_write_out write; | |
| #ifdef CONFIG_FUSE_LSEEK | |
| struct fuse_lseek_out lseek; | |
| #endif | |
| }; | |
| } FuseRequestOutHeader; | |
| typedef union FuseRequestInHeaderBuf { | |
| struct FuseRequestInHeader structured; | |
| struct { | |
| /* | |
| * Part of the request header that is filled for write requests | |
| * (Needed because we want the data to go into a different buffer, to | |
| * avoid having to use a bounce buffer) | |
| */ | |
| char head[sizeof(struct fuse_in_header) + | |
| sizeof(struct fuse_write_in)]; | |
| /* | |
| * Rest of the request header for requests that have a longer header | |
| * than write requests | |
| */ | |
| char tail[sizeof(FuseRequestInHeader) - | |
| (sizeof(struct fuse_in_header) + | |
| sizeof(struct fuse_write_in))]; | |
| }; | |
| } FuseRequestInHeaderBuf; | |
| QEMU_BUILD_BUG_ON(sizeof(FuseRequestInHeaderBuf) != | |
| sizeof(FuseRequestInHeader)); | |
| QEMU_BUILD_BUG_ON(sizeof(((FuseRequestInHeaderBuf *)0)->head) + | |
| sizeof(((FuseRequestInHeaderBuf *)0)->tail) != | |
| sizeof(FuseRequestInHeader)); | |
| typedef struct FuseExport FuseExport; | |
| /* | |
| * One FUSE "queue", representing one FUSE FD from which requests are fetched | |
| * and processed. Each queue is tied to an AioContext. | |
| */ | |
| typedef struct FuseQueue { | |
| FuseExport *exp; | |
| AioContext *ctx; | |
| int fuse_fd; | |
| /* | |
| * Cached buffer to receive the data of WRITE requests. Cached because: | |
| * To read requests, we put a FuseRequestInHeaderBuf (FRIHB) object on the | |
| * stack, and a (WRITE data) buffer on the heap. We pass FRIHB.head and the | |
| * data buffer to readv(). This way, for WRITE requests, we get exactly | |
| * their data in the data buffer and can avoid bounce buffering. | |
| * However, for non-WRITE requests, some of the header may end up in the | |
| * data buffer, so we will need to copy that back into the FRIHB object, and | |
| * then we don't need the heap buffer anymore. That is why we cache it, so | |
| * we can trivially reuse it between non-WRITE requests. | |
| * | |
| * Note that these data buffers and thus req_write_data_cached are allocated | |
| * via blk_blockalign() and thus need to be freed via qemu_vfree(). | |
| */ | |
| void *req_write_data_cached; | |
| } FuseQueue; | |
| struct FuseExport { | |
| BlockExport common; | |
| struct fuse_session *fuse_session; | |
| unsigned int in_flight; /* atomic */ | |
| bool mounted, fd_handler_set_up; | |
| /* | |
| * Set when there was an unrecoverable error and no requests should be read | |
| * from the device anymore (basically only in case of something we would | |
| * consider a kernel bug). Access atomically. | |
| */ | |
| bool halted; | |
| int num_queues; | |
| FuseQueue *queues; | |
| /* | |
| * True if this export should follow the generic export's AioContext. | |
| * Will be false if the queues' AioContexts have been explicitly set by the | |
| * user, i.e. are expected to stay in those contexts. | |
| * (I.e. is always false if there is more than one queue.) | |
| */ | |
| bool follow_aio_context; | |
| char *mountpoint; | |
| bool writable; | |
| bool growable; | |
| /* Whether allow_other was used as a mount option or not */ | |
| bool allow_other; | |
| /* All atomic */ | |
| mode_t st_mode; | |
| uid_t st_uid; | |
| gid_t st_gid; | |
| }; | |
| /* | |
| * Verify that the size of FuseRequestInHeaderBuf.head plus the data | |
| * buffer are big enough to be accepted by the FUSE kernel driver. | |
| */ | |
| QEMU_BUILD_BUG_ON(sizeof(((FuseRequestInHeaderBuf *)0)->head) + | |
| FUSE_MAX_WRITE_BYTES < | |
| FUSE_MIN_READ_BUFFER); | |
| static GHashTable *exports; | |
| static void fuse_export_shutdown(BlockExport *exp); | |
| static void fuse_export_delete(BlockExport *exp); | |
| static void fuse_export_halt(FuseExport *exp); | |
| static void init_exports_table(void); | |
| static int mount_fuse_export(FuseExport *exp, Error **errp); | |
| static int clone_fuse_fd(int fd, Error **errp); | |
| static bool is_regular_file(const char *path, Error **errp); | |
| static void read_from_fuse_fd(void *opaque); | |
| static void coroutine_fn | |
| fuse_co_process_request(FuseQueue *q, const FuseRequestInHeader *in_hdr, | |
| const void *data_buffer); | |
| static int fuse_write_err(int fd, const struct fuse_in_header *in_hdr, int err); | |
| static void fuse_inc_in_flight(FuseExport *exp) | |
| { | |
| if (qatomic_fetch_inc(&exp->in_flight) == 0) { | |
| /* Prevent export from being deleted */ | |
| blk_exp_ref(&exp->common); | |
| } | |
| } | |
| static void fuse_dec_in_flight(FuseExport *exp) | |
| { | |
| if (qatomic_fetch_dec(&exp->in_flight) == 1) { | |
| /* Wake AIO_WAIT_WHILE() */ | |
| aio_wait_kick(); | |
| /* Now the export can be deleted */ | |
| blk_exp_unref(&exp->common); | |
| } | |
| } | |
| /** | |
| * Attach FUSE FD read handler. | |
| */ | |
| static void fuse_attach_handlers(FuseExport *exp) | |
| { | |
| if (qatomic_read(&exp->halted)) { | |
| return; | |
| } | |
| for (int i = 0; i < exp->num_queues; i++) { | |
| aio_set_fd_handler(exp->queues[i].ctx, exp->queues[i].fuse_fd, | |
| read_from_fuse_fd, NULL, NULL, NULL, | |
| &exp->queues[i]); | |
| } | |
| exp->fd_handler_set_up = true; | |
| } | |
| /** | |
| * Detach FUSE FD read handler. | |
| */ | |
| static void fuse_detach_handlers(FuseExport *exp) | |
| { | |
| for (int i = 0; i < exp->num_queues; i++) { | |
| aio_set_fd_handler(exp->queues[i].ctx, exp->queues[i].fuse_fd, | |
| NULL, NULL, NULL, NULL, NULL); | |
| } | |
| exp->fd_handler_set_up = false; | |
| } | |
| static void fuse_export_drained_begin(void *opaque) | |
| { | |
| fuse_detach_handlers(opaque); | |
| } | |
| static void fuse_export_drained_end(void *opaque) | |
| { | |
| FuseExport *exp = opaque; | |
| /* Refresh AioContext in case it changed */ | |
| exp->common.ctx = blk_get_aio_context(exp->common.blk); | |
| if (exp->follow_aio_context) { | |
| assert(exp->num_queues == 1); | |
| exp->queues[0].ctx = exp->common.ctx; | |
| } | |
| fuse_attach_handlers(exp); | |
| } | |
| static bool fuse_export_drained_poll(void *opaque) | |
| { | |
| FuseExport *exp = opaque; | |
| return qatomic_read(&exp->in_flight) > 0; | |
| } | |
| static const BlockDevOps fuse_export_blk_dev_ops = { | |
| .drained_begin = fuse_export_drained_begin, | |
| .drained_end = fuse_export_drained_end, | |
| .drained_poll = fuse_export_drained_poll, | |
| }; | |
| static int fuse_export_create(BlockExport *blk_exp, | |
| BlockExportOptions *blk_exp_args, | |
| AioContext *const *multithread, | |
| size_t mt_count, | |
| Error **errp) | |
| { | |
| ERRP_GUARD(); /* ensure clean-up even with error_fatal */ | |
| FuseExport *exp = container_of(blk_exp, FuseExport, common); | |
| BlockExportOptionsFuse *args = &blk_exp_args->u.fuse; | |
| uint32_t st_mode; | |
| int ret; | |
| assert(blk_exp_args->type == BLOCK_EXPORT_TYPE_FUSE); | |
| if (multithread) { | |
| /* Guaranteed by common export code */ | |
| assert(mt_count >= 1); | |
| exp->follow_aio_context = false; | |
| exp->num_queues = mt_count; | |
| exp->queues = g_new(FuseQueue, mt_count); | |
| for (size_t i = 0; i < mt_count; i++) { | |
| exp->queues[i] = (FuseQueue) { | |
| .exp = exp, | |
| .ctx = multithread[i], | |
| .fuse_fd = -1, | |
| }; | |
| } | |
| } else { | |
| /* Guaranteed by common export code */ | |
| assert(mt_count == 0); | |
| exp->follow_aio_context = true; | |
| exp->num_queues = 1; | |
| exp->queues = g_new(FuseQueue, 1); | |
| exp->queues[0] = (FuseQueue) { | |
| .exp = exp, | |
| .ctx = exp->common.ctx, | |
| .fuse_fd = -1, | |
| }; | |
| } | |
| /* For growable and writable exports, take the RESIZE permission */ | |
| if (args->growable || blk_exp_args->writable) { | |
| uint64_t blk_perm, blk_shared_perm; | |
| blk_get_perm(exp->common.blk, &blk_perm, &blk_shared_perm); | |
| ret = blk_set_perm(exp->common.blk, blk_perm | BLK_PERM_RESIZE, | |
| blk_shared_perm, errp); | |
| if (ret < 0) { | |
| goto fail; | |
| } | |
| } | |
| blk_set_dev_ops(exp->common.blk, &fuse_export_blk_dev_ops, exp); | |
| /* | |
| * We handle draining ourselves using an in-flight counter and by disabling | |
| * the FUSE fd handler. Do not queue BlockBackend requests, they need to | |
| * complete so the in-flight counter reaches zero. | |
| */ | |
| blk_set_disable_request_queuing(exp->common.blk, true); | |
| init_exports_table(); | |
| /* | |
| * It is important to do this check before calling is_regular_file() -- | |
| * that function will do a stat(), which we would have to handle if we | |
| * already exported something on @mountpoint. But we cannot, because | |
| * we are currently caught up here. | |
| * (Note that ideally we would want to resolve relative paths here, | |
| * but bdrv_make_absolute_filename() might do the wrong thing for | |
| * paths that contain colons, and realpath() would resolve symlinks, | |
| * which we do not want: The mount point is not going to be the | |
| * symlink's destination, but the link itself.) | |
| * So this will not catch all potential clashes, but hopefully at | |
| * least the most common one of specifying exactly the same path | |
| * string twice. | |
| */ | |
| if (g_hash_table_contains(exports, args->mountpoint)) { | |
| error_setg(errp, "There already is a FUSE export on '%s'", | |
| args->mountpoint); | |
| ret = -EEXIST; | |
| goto fail; | |
| } | |
| if (!is_regular_file(args->mountpoint, errp)) { | |
| ret = -EINVAL; | |
| goto fail; | |
| } | |
| exp->mountpoint = g_strdup(args->mountpoint); | |
| exp->writable = blk_exp_args->writable; | |
| exp->growable = args->growable; | |
| /* set default */ | |
| if (!args->has_allow_other) { | |
| args->allow_other = FUSE_EXPORT_ALLOW_OTHER_AUTO; | |
| } | |
| st_mode = S_IFREG | S_IRUSR; | |
| if (exp->writable) { | |
| st_mode |= S_IWUSR; | |
| } | |
| qatomic_set(&exp->st_mode, st_mode); | |
| qatomic_set(&exp->st_uid, getuid()); | |
| qatomic_set(&exp->st_gid, getgid()); | |
| if (args->allow_other == FUSE_EXPORT_ALLOW_OTHER_AUTO) { | |
| /* Try allow_other == true first, ignore errors */ | |
| exp->allow_other = true; | |
| ret = mount_fuse_export(exp, NULL); | |
| if (ret < 0) { | |
| exp->allow_other = false; | |
| ret = mount_fuse_export(exp, errp); | |
| } | |
| } else { | |
| exp->allow_other = args->allow_other == FUSE_EXPORT_ALLOW_OTHER_ON; | |
| ret = mount_fuse_export(exp, errp); | |
| } | |
| if (ret < 0) { | |
| goto fail; | |
| } | |
| g_hash_table_insert(exports, g_strdup(exp->mountpoint), NULL); | |
| assert(exp->num_queues >= 1); | |
| exp->queues[0].fuse_fd = fuse_session_fd(exp->fuse_session); | |
| ret = qemu_fcntl_addfl(exp->queues[0].fuse_fd, O_NONBLOCK); | |
| if (ret < 0) { | |
| error_setg_errno(errp, -ret, "Failed to make FUSE FD non-blocking"); | |
| goto fail; | |
| } | |
| for (int i = 1; i < exp->num_queues; i++) { | |
| int fd = clone_fuse_fd(exp->queues[0].fuse_fd, errp); | |
| if (fd < 0) { | |
| ret = fd; | |
| goto fail; | |
| } | |
| exp->queues[i].fuse_fd = fd; | |
| } | |
| fuse_attach_handlers(exp); | |
| return 0; | |
| fail: | |
| fuse_export_shutdown(blk_exp); | |
| fuse_export_delete(blk_exp); | |
| return ret; | |
| } | |
| /** | |
| * Allocates the global @exports hash table. | |
| */ | |
| static void init_exports_table(void) | |
| { | |
| if (exports) { | |
| return; | |
| } | |
| exports = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); | |
| } | |
| /** | |
| * Create exp->fuse_session and mount it. Expects exp->mountpoint, | |
| * exp->writable, and exp->allow_other to be set as intended for the mount. | |
| */ | |
| static int mount_fuse_export(FuseExport *exp, Error **errp) | |
| { | |
| const char *fuse_argv[4]; | |
| char *mount_opts; | |
| struct fuse_args fuse_args; | |
| int ret; | |
| /* | |
| * We just create the session for mounting/unmounting, no need to provide | |
| * any operations. However, since libfuse commit 52a633a5d, we have to | |
| * provide some op struct and cannot just pass NULL (even though the commit | |
| * message ("allow passing ops as NULL") seems to imply the exact opposite, | |
| * as does the comment added to fuse_session_new_fn() ("To create a no-op | |
| * session just for mounting pass op as NULL."). | |
| * This is how said libfuse commit implements a no-op session internally, so | |
| * do it the same way. | |
| */ | |
| static const struct fuse_lowlevel_ops null_ops = { 0 }; | |
| /* | |
| * Note that these mount options differ from what we would pass to a direct | |
| * mount() call: | |
| * - nosuid, nodev, and noatime are not understood by the kernel; libfuse | |
| * uses those options to construct the mount flags (MS_*) | |
| * - The FUSE kernel driver requires additional options (fd, rootmode, | |
| * user_id, group_id); these will be set by libfuse. | |
| * Note that max_read is set here, while max_write is set via the FUSE INIT | |
| * operation. | |
| */ | |
| mount_opts = g_strdup_printf("%s,nosuid,nodev,noatime,max_read=%zu," | |
| "default_permissions%s", | |
| exp->writable ? "rw" : "ro", | |
| FUSE_MAX_READ_BYTES, | |
| exp->allow_other ? ",allow_other" : ""); | |
| fuse_argv[0] = ""; /* Dummy program name */ | |
| fuse_argv[1] = "-o"; | |
| fuse_argv[2] = mount_opts; | |
| fuse_argv[3] = NULL; | |
| fuse_args = (struct fuse_args)FUSE_ARGS_INIT(3, (char **)fuse_argv); | |
| exp->fuse_session = fuse_session_new(&fuse_args, &null_ops, | |
| sizeof(null_ops), NULL); | |
| g_free(mount_opts); | |
| if (!exp->fuse_session) { | |
| error_setg(errp, "Failed to set up FUSE session"); | |
| return -EIO; | |
| } | |
| ret = fuse_session_mount(exp->fuse_session, exp->mountpoint); | |
| if (ret < 0) { | |
| error_setg(errp, "Failed to mount FUSE session to export"); | |
| ret = -EIO; | |
| goto fail; | |
| } | |
| exp->mounted = true; | |
| return 0; | |
| fail: | |
| fuse_session_destroy(exp->fuse_session); | |
| exp->fuse_session = NULL; | |
| return ret; | |
| } | |
| /** | |
| * Allocate a buffer to receive WRITE data, or take the cached one. | |
| */ | |
| static void *get_write_data_buffer(FuseQueue *q) | |
| { | |
| if (q->req_write_data_cached) { | |
| void *cached = q->req_write_data_cached; | |
| q->req_write_data_cached = NULL; | |
| return cached; | |
| } else { | |
| return blk_blockalign(q->exp->common.blk, FUSE_MAX_WRITE_BYTES); | |
| } | |
| } | |
| /** | |
| * Release a WRITE data buffer, possibly reusing it for a subsequent request. | |
| */ | |
| static void release_write_data_buffer(FuseQueue *q, void **buffer) | |
| { | |
| if (!*buffer) { | |
| return; | |
| } | |
| if (!q->req_write_data_cached) { | |
| q->req_write_data_cached = *buffer; | |
| } else { | |
| qemu_vfree(*buffer); | |
| } | |
| *buffer = NULL; | |
| } | |
| /** | |
| * Return the length of the specific operation's own in_header. | |
| * Return -ENOSYS if the operation is not supported. | |
| */ | |
| static ssize_t req_op_hdr_len(const FuseRequestInHeader *in_hdr) | |
| { | |
| switch (in_hdr->common.opcode) { | |
| case FUSE_INIT: | |
| return sizeof(in_hdr->init); | |
| case FUSE_OPEN: | |
| return sizeof(in_hdr->open); | |
| case FUSE_SETATTR: | |
| return sizeof(in_hdr->setattr); | |
| case FUSE_READ: | |
| return sizeof(in_hdr->read); | |
| case FUSE_WRITE: | |
| return sizeof(in_hdr->write); | |
| case FUSE_FALLOCATE: | |
| return sizeof(in_hdr->fallocate); | |
| #ifdef CONFIG_FUSE_LSEEK | |
| case FUSE_LSEEK: | |
| return sizeof(in_hdr->lseek); | |
| #endif | |
| case FUSE_DESTROY: | |
| case FUSE_STATFS: | |
| case FUSE_RELEASE: | |
| case FUSE_LOOKUP: | |
| case FUSE_FORGET: | |
| case FUSE_BATCH_FORGET: | |
| case FUSE_GETATTR: | |
| case FUSE_FSYNC: | |
| case FUSE_FLUSH: | |
| /* These requests don't have their own header or we don't care */ | |
| return 0; | |
| default: | |
| return -ENOSYS; | |
| } | |
| } | |
| /** | |
| * Clone the given /dev/fuse file descriptor, yielding a second FD from which | |
| * requests can be pulled for the associated filesystem. Returns an FD on | |
| * success, and -errno on error. | |
| */ | |
| static int clone_fuse_fd(int fd, Error **errp) | |
| { | |
| uint32_t src_fd = fd; | |
| int new_fd; | |
| int ret; | |
| /* | |
| * The name "/dev/fuse" is fixed, see libfuse's lib/fuse_loop_mt.c | |
| * (fuse_clone_chan()). | |
| */ | |
| new_fd = open("/dev/fuse", O_RDWR | O_CLOEXEC | O_NONBLOCK); | |
| if (new_fd < 0) { | |
| ret = -errno; | |
| error_setg_errno(errp, errno, "Failed to open /dev/fuse"); | |
| return ret; | |
| } | |
| ret = ioctl(new_fd, FUSE_DEV_IOC_CLONE, &src_fd); | |
| if (ret < 0) { | |
| ret = -errno; | |
| error_setg_errno(errp, errno, "Failed to clone FUSE FD"); | |
| close(new_fd); | |
| return ret; | |
| } | |
| return new_fd; | |
| } | |
| /** | |
| * Check whether the smaller older fuse_init_in structure from before protocol | |
| * version 7.36 is used. The flags2 member and other new members must be treated | |
| * as absent then. | |
| */ | |
| static bool using_old_fuse_init_in(const struct fuse_init_in *in) | |
| { | |
| return in->major < 7 || (in->major == 7 && in->minor < 36); | |
| } | |
| /** | |
| * Try to read a single request from the FUSE FD. | |
| * Takes a FuseQueue pointer in `opaque`. | |
| * | |
| * Assumes the export's in-flight counter has already been incremented. | |
| * | |
| * If a request is available, process it. | |
| */ | |
| static void coroutine_fn co_read_from_fuse_fd(void *opaque) | |
| { | |
| FuseQueue *q = opaque; | |
| int fuse_fd = q->fuse_fd; | |
| FuseExport *exp = q->exp; | |
| ssize_t ret; | |
| FuseRequestInHeaderBuf in_hdr_buf; | |
| const FuseRequestInHeader *in_hdr; | |
| void *data_buffer = NULL; | |
| struct iovec iov[2]; | |
| ssize_t op_hdr_len; | |
| if (unlikely(qatomic_read(&exp->halted))) { | |
| goto no_request; | |
| } | |
| data_buffer = get_write_data_buffer(q); | |
| /* Construct the I/O vector to hold the FUSE request */ | |
| iov[0] = (struct iovec) { &in_hdr_buf.head, sizeof(in_hdr_buf.head) }; | |
| iov[1] = (struct iovec) { data_buffer, FUSE_MAX_WRITE_BYTES }; | |
| ret = RETRY_ON_EINTR(readv(fuse_fd, iov, ARRAY_SIZE(iov))); | |
| if (ret < 0 && errno == EAGAIN) { | |
| /* No request available */ | |
| goto no_request; | |
| } else if (unlikely(ret < 0)) { | |
| error_report("Failed to read from FUSE device: %s", strerror(errno)); | |
| goto no_request; | |
| } | |
| if (unlikely(ret < sizeof(in_hdr->common))) { | |
| error_report("Incomplete read from FUSE device, expected at least %zu " | |
| "bytes, read %zi bytes; cannot trust subsequent " | |
| "requests, halting the export", | |
| sizeof(in_hdr->common), ret); | |
| fuse_export_halt(exp); | |
| goto no_request; | |
| } | |
| in_hdr = &in_hdr_buf.structured; | |
| if (unlikely(ret != in_hdr->common.len)) { | |
| error_report("Number of bytes read from FUSE device does not match " | |
| "request size, expected %" PRIu32 " bytes, read %zi " | |
| "bytes; cannot trust subsequent requests, halting the " | |
| "export", | |
| in_hdr->common.len, ret); | |
| fuse_export_halt(exp); | |
| goto no_request; | |
| } | |
| op_hdr_len = req_op_hdr_len(in_hdr); | |
| if (op_hdr_len < 0) { | |
| fuse_write_err(fuse_fd, &in_hdr->common, op_hdr_len); | |
| goto no_request; | |
| } | |
| /* | |
| * If the request is of type FUSE_INIT, need to check the version to | |
| * actually determine the length of the fuse_init_in structure used by the | |
| * kernel. In protocol version 7.36, the structure was extended. | |
| */ | |
| if (in_hdr->common.opcode == FUSE_INIT) { | |
| /* Length of the fuse_init_in structure before 7.36. */ | |
| size_t old_init_hdr_len = 16; | |
| /* | |
| * Expect at least the size of the smaller older structure to ensure the | |
| * version can be checked. | |
| */ | |
| if (unlikely(ret < sizeof(in_hdr->common) + old_init_hdr_len)) { | |
| error_report("FUSE_INIT request truncated, read only %zi bytes", | |
| ret); | |
| fuse_write_err(fuse_fd, &in_hdr->common, -EINVAL); | |
| goto no_request; | |
| } | |
| if (using_old_fuse_init_in(&in_hdr->init)) { | |
| op_hdr_len = old_init_hdr_len; | |
| } | |
| } | |
| if (unlikely(ret < sizeof(in_hdr->common) + op_hdr_len)) { | |
| error_report("FUSE request truncated, expected %zu bytes, read %zi " | |
| "bytes", | |
| sizeof(in_hdr->common) + op_hdr_len, ret); | |
| fuse_write_err(fuse_fd, &in_hdr->common, -EINVAL); | |
| goto no_request; | |
| } | |
| /* | |
| * Only WRITE uses the write data buffer, so for non-WRITE requests longer | |
| * than .head, we need to copy any data that spilled into data_buffer into | |
| * .tail. Then we can release the write data buffer. | |
| */ | |
| if (in_hdr->common.opcode != FUSE_WRITE) { | |
| if (ret > sizeof(in_hdr_buf.head)) { | |
| size_t len; | |
| /* Limit size to prevent overflow */ | |
| len = MIN(ret - sizeof(in_hdr_buf.head), sizeof(in_hdr_buf.tail)); | |
| memcpy(in_hdr_buf.tail, data_buffer, len); | |
| } | |
| release_write_data_buffer(q, &data_buffer); | |
| } | |
| fuse_co_process_request(q, in_hdr, data_buffer); | |
| no_request: | |
| release_write_data_buffer(q, &data_buffer); | |
| fuse_dec_in_flight(exp); | |
| } | |
| /** | |
| * Try to read and process a single request from the FUSE FD. | |
| * (To be used as a handler for when the FUSE FD becomes readable.) | |
| * Takes a FuseQueue pointer in `opaque`. | |
| */ | |
| static void read_from_fuse_fd(void *opaque) | |
| { | |
| FuseQueue *q = opaque; | |
| Coroutine *co; | |
| co = qemu_coroutine_create(co_read_from_fuse_fd, q); | |
| /* Decremented by co_read_from_fuse_fd() */ | |
| fuse_inc_in_flight(q->exp); | |
| qemu_coroutine_enter(co); | |
| } | |
| static void fuse_export_shutdown(BlockExport *blk_exp) | |
| { | |
| FuseExport *exp = container_of(blk_exp, FuseExport, common); | |
| if (exp->fd_handler_set_up) { | |
| fuse_detach_handlers(exp); | |
| } | |
| if (exp->mountpoint) { | |
| /* | |
| * Safe to drop now, because we will not handle any requests for this | |
| * export anymore anyway (at least not from the main thread). | |
| */ | |
| g_hash_table_remove(exports, exp->mountpoint); | |
| } | |
| } | |
| static void fuse_export_delete(BlockExport *blk_exp) | |
| { | |
| FuseExport *exp = container_of(blk_exp, FuseExport, common); | |
| for (int i = 0; i < exp->num_queues; i++) { | |
| FuseQueue *q = &exp->queues[i]; | |
| /* Queue 0's FD belongs to the FUSE session */ | |
| if (i > 0 && q->fuse_fd >= 0) { | |
| close(q->fuse_fd); | |
| } | |
| qemu_vfree(q->req_write_data_cached); | |
| } | |
| g_free(exp->queues); | |
| if (exp->fuse_session) { | |
| if (exp->mounted) { | |
| fuse_session_unmount(exp->fuse_session); | |
| } | |
| fuse_session_destroy(exp->fuse_session); | |
| } | |
| g_free(exp->mountpoint); | |
| } | |
| /** | |
| * Halt the export: Detach FD handlers, and set exp->halted to true, preventing | |
| * fuse_attach_handlers() from re-attaching them, therefore stopping all further | |
| * request processing. | |
| * | |
| * Call this function when an unrecoverable error happens that makes processing | |
| * all future requests unreliable. | |
| */ | |
| static void fuse_export_halt(FuseExport *exp) | |
| { | |
| qatomic_set(&exp->halted, true); | |
| fuse_detach_handlers(exp); | |
| } | |
| /** | |
| * Check whether @path points to a regular file. If not, put an | |
| * appropriate message into *errp. | |
| */ | |
| static bool is_regular_file(const char *path, Error **errp) | |
| { | |
| struct stat statbuf; | |
| int ret; | |
| ret = stat(path, &statbuf); | |
| if (ret < 0) { | |
| error_setg_errno(errp, errno, "Failed to stat '%s'", path); | |
| return false; | |
| } | |
| if (!S_ISREG(statbuf.st_mode)) { | |
| error_setg(errp, "'%s' is not a regular file", path); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Process FUSE INIT. | |
| * Return the number of bytes written to *out on success, and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_init(FuseExport *exp, struct fuse_init_out *out, | |
| const struct fuse_init_in *in) | |
| { | |
| uint32_t supported_flags = FUSE_ASYNC_READ | FUSE_ASYNC_DIO; | |
| uint32_t flags2 = 0; | |
| if (!exp->growable) { | |
| /* | |
| * Back when libfuse was used, it would always set this flag and thus | |
| * the kernel did not execute a truncate itself and passed along O_TRUNC | |
| * to user space. Continue setting the flag for backwards compatibility | |
| * when the export is not growable to avoid issues with O_TRUNC, i.e. | |
| * blockdev-based exports running into ENOTSUP and file-based exports | |
| * with growable=off to be truncated and then stuck with size 0. | |
| */ | |
| supported_flags = FUSE_ATOMIC_O_TRUNC; | |
| } | |
| if (in->major != 7) { | |
| error_report("FUSE major version mismatch: We have 7, but kernel has %" | |
| PRIu32, in->major); | |
| return -EINVAL; | |
| } | |
| /* 2007's 7.9 added fuse_attr.blksize; working around that would be hard */ | |
| if (in->minor < 9) { | |
| error_report("FUSE minor version too old: 9 required, but kernel has %" | |
| PRIu32, in->minor); | |
| return -EINVAL; | |
| } | |
| if (!using_old_fuse_init_in(in)) { | |
| /* The flags2 flags must be shifted down by 32 bits. */ | |
| const uint32_t supported_flags2 = FUSE_DIRECT_IO_ALLOW_MMAP >> 32; | |
| /* flags2 is only considered if FUSE_INIT_EXT is set. */ | |
| supported_flags = supported_flags | FUSE_INIT_EXT; | |
| flags2 = in->flags2 & supported_flags2; | |
| } | |
| *out = (struct fuse_init_out) { | |
| .major = 7, | |
| .minor = MIN(FUSE_KERNEL_MINOR_VERSION, in->minor), | |
| .max_readahead = in->max_readahead, | |
| .max_write = FUSE_MAX_WRITE_BYTES, | |
| .flags = in->flags & supported_flags, | |
| .flags2 = flags2, | |
| /* libfuse maximum: 2^16 - 1 */ | |
| .max_background = UINT16_MAX, | |
| /* libfuse default: max_background * 3 / 4 */ | |
| .congestion_threshold = (int)UINT16_MAX * 3 / 4, | |
| /* libfuse default: 1 */ | |
| .time_gran = 1, | |
| /* | |
| * probably unneeded without FUSE_MAX_PAGES, but this would be the | |
| * libfuse default | |
| */ | |
| .max_pages = DIV_ROUND_UP(FUSE_MAX_WRITE_BYTES, | |
| qemu_real_host_page_size()), | |
| /* Only needed for mappings (i.e. DAX) */ | |
| .map_alignment = 0, | |
| }; | |
| /* | |
| * Before 7.23, fuse_init_out is shorter. | |
| * Drop the tail (time_gran, max_pages, map_alignment). | |
| */ | |
| return out->minor >= 23 ? sizeof(*out) : FUSE_COMPAT_22_INIT_OUT_SIZE; | |
| } | |
| /** | |
| * Return some filesystem information, just to not break e.g. `df`. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_statfs(FuseExport *exp, struct fuse_statfs_out *out) | |
| { | |
| BlockDriverState *root_bs; | |
| uint32_t opt_transfer = 512; | |
| root_bs = blk_bs(exp->common.blk); | |
| if (root_bs) { | |
| opt_transfer = root_bs->bl.opt_transfer; | |
| if (!opt_transfer) { | |
| opt_transfer = root_bs->bl.request_alignment; | |
| } | |
| opt_transfer = MAX(opt_transfer, 512); | |
| } | |
| *out = (struct fuse_statfs_out) { | |
| /* These are the fields libfuse sets by default */ | |
| .st = { | |
| .namelen = 255, | |
| .bsize = opt_transfer, | |
| }, | |
| }; | |
| return sizeof(*out); | |
| } | |
| /** | |
| * Let clients get file attributes (i.e., stat() the file). | |
| * Return the number of bytes written to *out on success, and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_getattr(FuseExport *exp, struct fuse_attr_out *out) | |
| { | |
| int64_t length, allocated_blocks; | |
| time_t now = time(NULL); | |
| length = blk_co_getlength(exp->common.blk); | |
| if (length < 0) { | |
| return length; | |
| } | |
| allocated_blocks = bdrv_co_get_allocated_file_size(blk_bs(exp->common.blk)); | |
| if (allocated_blocks <= 0) { | |
| allocated_blocks = DIV_ROUND_UP(length, 512); | |
| } else { | |
| allocated_blocks = DIV_ROUND_UP(allocated_blocks, 512); | |
| } | |
| *out = (struct fuse_attr_out) { | |
| .attr_valid = 1, | |
| .attr = { | |
| .ino = 1, | |
| .mode = qatomic_read(&exp->st_mode), | |
| .nlink = 1, | |
| .uid = qatomic_read(&exp->st_uid), | |
| .gid = qatomic_read(&exp->st_gid), | |
| .size = length, | |
| .blksize = blk_bs(exp->common.blk)->bl.request_alignment, | |
| .blocks = allocated_blocks, | |
| .atime = now, | |
| .mtime = now, | |
| .ctime = now, | |
| }, | |
| }; | |
| return sizeof(*out); | |
| } | |
| static int coroutine_fn GRAPH_RDLOCK | |
| fuse_co_do_truncate(const FuseExport *exp, int64_t size, bool req_zero_write, | |
| PreallocMode prealloc) | |
| { | |
| BdrvRequestFlags truncate_flags = 0; | |
| if (req_zero_write) { | |
| truncate_flags |= BDRV_REQ_ZERO_WRITE; | |
| } | |
| return blk_co_truncate(exp->common.blk, size, true, prealloc, | |
| truncate_flags, NULL); | |
| } | |
| /** | |
| * Let clients set file attributes. Only resizing and changing | |
| * permissions (st_mode, st_uid, st_gid) is allowed. | |
| * Changing permissions is only allowed as far as it will actually | |
| * permit access: Read-only exports cannot be given +w, and exports | |
| * without allow_other cannot be given a different UID or GID, and | |
| * they cannot be given non-owner access. | |
| * Return the number of bytes written to *out on success, and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_setattr(FuseExport *exp, struct fuse_attr_out *out, uint32_t to_set, | |
| uint64_t size, uint32_t mode, uint32_t uid, uint32_t gid) | |
| { | |
| int supported_attrs; | |
| int ret; | |
| /* SIZE and MODE are actually supported, the others can be safely ignored */ | |
| supported_attrs = FATTR_SIZE | FATTR_MODE | | |
| FATTR_FH | FATTR_LOCKOWNER | FATTR_KILL_SUIDGID; | |
| if (exp->allow_other) { | |
| supported_attrs |= FATTR_UID | FATTR_GID; | |
| } | |
| if (to_set & ~supported_attrs) { | |
| return -ENOTSUP; | |
| } | |
| /* Do some argument checks first before committing to anything */ | |
| if (to_set & FATTR_MODE) { | |
| /* | |
| * Without allow_other, non-owners can never access the export, so do | |
| * not allow setting permissions for them | |
| */ | |
| if (!exp->allow_other && (mode & (S_IRWXG | S_IRWXO)) != 0) { | |
| return -EPERM; | |
| } | |
| /* +w for read-only exports makes no sense, disallow it */ | |
| if (!exp->writable && (mode & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0) { | |
| return -EROFS; | |
| } | |
| } | |
| if (to_set & FATTR_SIZE) { | |
| if (!exp->writable) { | |
| return -EACCES; | |
| } | |
| ret = fuse_co_do_truncate(exp, size, true, PREALLOC_MODE_OFF); | |
| if (ret < 0) { | |
| return ret; | |
| } | |
| } | |
| if (to_set & FATTR_MODE) { | |
| /* Ignore FUSE-supplied file type, only change the mode */ | |
| qatomic_set(&exp->st_mode, (mode & 07777) | S_IFREG); | |
| } | |
| if (to_set & FATTR_UID) { | |
| qatomic_set(&exp->st_uid, uid); | |
| } | |
| if (to_set & FATTR_GID) { | |
| qatomic_set(&exp->st_gid, gid); | |
| } | |
| return fuse_co_getattr(exp, out); | |
| } | |
| /** | |
| * Open an inode. We only have a single inode in our exported filesystem, so we | |
| * just acknowledge the request. | |
| * Return the number of bytes written to *out on success, and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_open(FuseExport *exp, struct fuse_open_out *out) | |
| { | |
| *out = (struct fuse_open_out) { | |
| .open_flags = FOPEN_DIRECT_IO | FOPEN_PARALLEL_DIRECT_WRITES, | |
| }; | |
| return sizeof(*out); | |
| } | |
| /** | |
| * Handle client reads from the exported image. Allocates *bufptr and reads | |
| * data from the block device into that buffer. | |
| * Returns the buffer (read) size on success, and -errno on error. | |
| * Note: If the returned size is 0, *bufptr will be set to NULL. | |
| * After use, *bufptr must be freed via qemu_vfree(). | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_read(FuseExport *exp, void **bufptr, uint64_t offset, uint32_t size) | |
| { | |
| int64_t blk_len; | |
| void *buf; | |
| int ret; | |
| /* Limited by max_read, should not happen */ | |
| if (size > FUSE_MAX_READ_BYTES) { | |
| return -EINVAL; | |
| } | |
| /** | |
| * Clients will expect short reads at EOF, so we have to limit | |
| * offset+size to the image length. | |
| */ | |
| blk_len = blk_co_getlength(exp->common.blk); | |
| if (blk_len < 0) { | |
| return blk_len; | |
| } | |
| if (offset >= blk_len) { | |
| /* Explicitly set to NULL because we return success here */ | |
| *bufptr = NULL; | |
| return 0; | |
| } | |
| if (offset + size > blk_len) { | |
| size = blk_len - offset; | |
| } | |
| buf = qemu_try_blockalign(blk_bs(exp->common.blk), size); | |
| if (!buf) { | |
| return -ENOMEM; | |
| } | |
| ret = blk_co_pread(exp->common.blk, offset, size, buf, 0); | |
| if (ret < 0) { | |
| qemu_vfree(buf); | |
| return ret; | |
| } | |
| *bufptr = buf; | |
| return size; | |
| } | |
| /** | |
| * Handle client writes to the exported image. @buf has the data to be written. | |
| * Return the number of bytes written to *out on success, and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_write(FuseExport *exp, struct fuse_write_out *out, | |
| uint64_t offset, uint32_t size, const void *buf) | |
| { | |
| int64_t blk_len; | |
| int ret; | |
| QEMU_BUILD_BUG_ON(FUSE_MAX_WRITE_BYTES > BDRV_REQUEST_MAX_BYTES); | |
| /* Limited by max_write, should not happen */ | |
| if (size > FUSE_MAX_WRITE_BYTES) { | |
| return -EINVAL; | |
| } | |
| if (!exp->writable) { | |
| return -EACCES; | |
| } | |
| /** | |
| * Clients will expect short writes at EOF, so we have to limit | |
| * offset+size to the image length. | |
| */ | |
| blk_len = blk_co_getlength(exp->common.blk); | |
| if (blk_len < 0) { | |
| return blk_len; | |
| } | |
| if (offset >= blk_len && !exp->growable) { | |
| *out = (struct fuse_write_out) { | |
| .size = 0, | |
| }; | |
| return sizeof(*out); | |
| } | |
| if (offset + size < offset) { | |
| return -EINVAL; | |
| } else if (offset + size > blk_len) { | |
| if (exp->growable) { | |
| ret = fuse_co_do_truncate(exp, offset + size, true, | |
| PREALLOC_MODE_OFF); | |
| if (ret < 0) { | |
| return ret; | |
| } | |
| } else { | |
| size = blk_len - offset; | |
| } | |
| } | |
| ret = blk_co_pwrite(exp->common.blk, offset, size, buf, 0); | |
| if (ret < 0) { | |
| return ret; | |
| } | |
| *out = (struct fuse_write_out) { | |
| .size = size, | |
| }; | |
| return sizeof(*out); | |
| } | |
| /** | |
| * Let clients perform various fallocate() operations. | |
| * Return 0 on success (no 'out' object), and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_fallocate(FuseExport *exp, | |
| uint64_t offset, uint64_t length, uint32_t mode) | |
| { | |
| int64_t blk_len; | |
| int ret; | |
| if (!exp->writable) { | |
| return -EACCES; | |
| } | |
| blk_len = blk_co_getlength(exp->common.blk); | |
| if (blk_len < 0) { | |
| return blk_len; | |
| } | |
| #ifdef CONFIG_FALLOCATE_PUNCH_HOLE | |
| if (mode & FALLOC_FL_KEEP_SIZE) { | |
| length = MIN(length, blk_len - offset); | |
| } | |
| #endif /* CONFIG_FALLOCATE_PUNCH_HOLE */ | |
| if (!mode) { | |
| /* We can only fallocate at the EOF with a truncate */ | |
| if (offset < blk_len) { | |
| return -EOPNOTSUPP; | |
| } | |
| if (offset > blk_len) { | |
| /* No preallocation needed here */ | |
| ret = fuse_co_do_truncate(exp, offset, true, PREALLOC_MODE_OFF); | |
| if (ret < 0) { | |
| return ret; | |
| } | |
| } | |
| ret = fuse_co_do_truncate(exp, offset + length, true, | |
| PREALLOC_MODE_FALLOC); | |
| } | |
| #ifdef CONFIG_FALLOCATE_PUNCH_HOLE | |
| else if (mode & FALLOC_FL_PUNCH_HOLE) { | |
| if (!(mode & FALLOC_FL_KEEP_SIZE)) { | |
| return -EINVAL; | |
| } | |
| do { | |
| int size = MIN(length, BDRV_REQUEST_MAX_BYTES); | |
| ret = blk_co_pwrite_zeroes(exp->common.blk, offset, size, | |
| BDRV_REQ_MAY_UNMAP | | |
| BDRV_REQ_NO_FALLBACK); | |
| if (ret == -ENOTSUP) { | |
| /* | |
| * fallocate() specifies to return EOPNOTSUPP for unsupported | |
| * operations | |
| */ | |
| ret = -EOPNOTSUPP; | |
| } | |
| offset += size; | |
| length -= size; | |
| } while (ret == 0 && length > 0); | |
| } | |
| #endif /* CONFIG_FALLOCATE_PUNCH_HOLE */ | |
| #ifdef CONFIG_FALLOCATE_ZERO_RANGE | |
| else if (mode & FALLOC_FL_ZERO_RANGE) { | |
| if (!(mode & FALLOC_FL_KEEP_SIZE) && offset + length > blk_len) { | |
| /* No need for zeroes, we are going to write them ourselves */ | |
| ret = fuse_co_do_truncate(exp, offset + length, false, | |
| PREALLOC_MODE_OFF); | |
| if (ret < 0) { | |
| return ret; | |
| } | |
| } | |
| do { | |
| int size = MIN(length, BDRV_REQUEST_MAX_BYTES); | |
| ret = blk_co_pwrite_zeroes(exp->common.blk, | |
| offset, size, 0); | |
| offset += size; | |
| length -= size; | |
| } while (ret == 0 && length > 0); | |
| } | |
| #endif /* CONFIG_FALLOCATE_ZERO_RANGE */ | |
| else { | |
| ret = -EOPNOTSUPP; | |
| } | |
| return ret < 0 ? ret : 0; | |
| } | |
| /** | |
| * Let clients fsync the exported image. | |
| * Return 0 on success (no 'out' object), and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK fuse_co_fsync(FuseExport *exp) | |
| { | |
| return blk_co_flush(exp->common.blk); | |
| } | |
| /** | |
| * Called before an FD to the exported image is closed. (libfuse | |
| * notes this to be a way to return last-minute errors.) | |
| * Return 0 on success (no 'out' object), and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK fuse_co_flush(FuseExport *exp) | |
| { | |
| return blk_co_flush(exp->common.blk); | |
| } | |
| #ifdef CONFIG_FUSE_LSEEK | |
| /** | |
| * Let clients inquire allocation status. | |
| * Return the number of bytes written to *out on success, and -errno on error. | |
| */ | |
| static ssize_t coroutine_fn GRAPH_RDLOCK | |
| fuse_co_lseek(FuseExport *exp, struct fuse_lseek_out *out, | |
| uint64_t offset, uint32_t whence) | |
| { | |
| if (whence != SEEK_HOLE && whence != SEEK_DATA) { | |
| return -EINVAL; | |
| } | |
| while (true) { | |
| int64_t pnum; | |
| int ret; | |
| ret = bdrv_co_block_status_above(blk_bs(exp->common.blk), NULL, | |
| offset, INT64_MAX, &pnum, NULL, NULL); | |
| if (ret < 0) { | |
| return ret; | |
| } | |
| if (!pnum && (ret & BDRV_BLOCK_EOF)) { | |
| int64_t blk_len; | |
| /* | |
| * If blk_getlength() rounds (e.g. by sectors), then the | |
| * export length will be rounded, too. However, | |
| * bdrv_block_status_above() may return EOF at unaligned | |
| * offsets. We must not let this become visible and thus | |
| * always simulate a hole between @offset (the real EOF) | |
| * and @blk_len (the client-visible EOF). | |
| */ | |
| blk_len = blk_co_getlength(exp->common.blk); | |
| if (blk_len < 0) { | |
| return blk_len; | |
| } | |
| if (offset > blk_len || whence == SEEK_DATA) { | |
| return -ENXIO; | |
| } | |
| *out = (struct fuse_lseek_out) { | |
| .offset = offset, | |
| }; | |
| return sizeof(*out); | |
| } | |
| if (ret & BDRV_BLOCK_DATA) { | |
| if (whence == SEEK_DATA) { | |
| *out = (struct fuse_lseek_out) { | |
| .offset = offset, | |
| }; | |
| return sizeof(*out); | |
| } | |
| } else { | |
| if (whence == SEEK_HOLE) { | |
| *out = (struct fuse_lseek_out) { | |
| .offset = offset, | |
| }; | |
| return sizeof(*out); | |
| } | |
| } | |
| /* Safety check against infinite loops */ | |
| if (!pnum) { | |
| return -ENXIO; | |
| } | |
| offset += pnum; | |
| } | |
| } | |
| #endif | |
| /** | |
| * Write a FUSE response to the given @fd. | |
| * | |
| * Effectively, writes out_hdr->common.len bytes of the buffer that is *out_hdr. | |
| * | |
| * @fd: FUSE file descriptor | |
| * @out_hdr: Request response header and request-specific response data | |
| */ | |
| static int fuse_write_response(int fd, FuseRequestOutHeader *out_hdr) | |
| { | |
| size_t to_write = out_hdr->common.len; | |
| ssize_t ret; | |
| /* Must at least write fuse_out_header */ | |
| assert(to_write >= sizeof(out_hdr->common)); | |
| ret = RETRY_ON_EINTR(write(fd, out_hdr, to_write)); | |
| if (ret < 0) { | |
| ret = -errno; | |
| error_report("Failed to write to FUSE device: %s", strerror(-ret)); | |
| return ret; | |
| } | |
| /* Short writes are unexpected, treat them as errors */ | |
| if (ret != to_write) { | |
| error_report("Short write to FUSE device, wrote %zi of %zu bytes", | |
| ret, to_write); | |
| return -EIO; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Write a FUSE error response to @fd. | |
| * | |
| * @fd: FUSE file descriptor | |
| * @in_hdr: Incoming request header to which to respond | |
| * @err: Error code (-errno, must be negative!) | |
| */ | |
| static int fuse_write_err(int fd, const struct fuse_in_header *in_hdr, int err) | |
| { | |
| FuseRequestOutHeader out_hdr = { | |
| .common = { | |
| .len = sizeof(out_hdr.common), | |
| /* FUSE expects negative error values */ | |
| .error = err, | |
| .unique = in_hdr->unique, | |
| }, | |
| }; | |
| return fuse_write_response(fd, &out_hdr); | |
| } | |
| /** | |
| * Write a FUSE response to the given @fd, using separate buffers for the | |
| * response header and data. | |
| * | |
| * In contrast to fuse_write_response(), this function cannot return a full | |
| * FuseRequestOutHeader (i.e. including request-specific response structs), | |
| * but only FuseRequestOutHeader.common. The remaining data must be in | |
| * *buf. | |
| * | |
| * (Total length must be set in out_hdr->len.) | |
| * | |
| * @fd: FUSE file descriptor | |
| * @out_hdr: Request response header | |
| * @buf: Pointer to response data | |
| */ | |
| static int fuse_write_buf_response(int fd, | |
| const struct fuse_out_header *out_hdr, | |
| const void *buf) | |
| { | |
| size_t to_write = out_hdr->len; | |
| struct iovec iov[2] = { | |
| { (void *)out_hdr, sizeof(*out_hdr) }, | |
| { (void *)buf, to_write - sizeof(*out_hdr) }, | |
| }; | |
| ssize_t ret; | |
| /* *buf length must not be negative */ | |
| assert(to_write >= sizeof(*out_hdr)); | |
| ret = RETRY_ON_EINTR(writev(fd, iov, ARRAY_SIZE(iov))); | |
| if (ret < 0) { | |
| ret = -errno; | |
| error_report("Failed to write to FUSE device: %s", strerror(-ret)); | |
| return ret; | |
| } | |
| /* Short writes are unexpected, treat them as errors */ | |
| if (ret != to_write) { | |
| error_report("Short write to FUSE device, wrote %zi of %zu bytes", | |
| ret, to_write); | |
| return -EIO; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Process a FUSE request, incl. writing the response. | |
| */ | |
| static void coroutine_fn | |
| fuse_co_process_request(FuseQueue *q, const FuseRequestInHeader *in_hdr, | |
| const void *data_buffer) | |
| { | |
| FuseRequestOutHeader out_hdr; | |
| FuseExport *exp = q->exp; | |
| /* For read requests: Data to be returned */ | |
| void *out_data_buffer = NULL; | |
| ssize_t ret; | |
| GRAPH_RDLOCK_GUARD(); | |
| switch (in_hdr->common.opcode) { | |
| case FUSE_INIT: | |
| ret = fuse_co_init(exp, &out_hdr.init, &in_hdr->init); | |
| break; | |
| case FUSE_DESTROY: | |
| ret = 0; | |
| break; | |
| case FUSE_STATFS: | |
| ret = fuse_co_statfs(exp, &out_hdr.statfs); | |
| break; | |
| case FUSE_OPEN: | |
| ret = fuse_co_open(exp, &out_hdr.open); | |
| break; | |
| case FUSE_RELEASE: | |
| ret = 0; | |
| break; | |
| case FUSE_LOOKUP: | |
| ret = -ENOENT; /* There is no node but the root node */ | |
| break; | |
| case FUSE_FORGET: | |
| case FUSE_BATCH_FORGET: | |
| /* These have no response, and there is nothing we need to do */ | |
| return; | |
| case FUSE_GETATTR: | |
| ret = fuse_co_getattr(exp, &out_hdr.attr); | |
| break; | |
| case FUSE_SETATTR: { | |
| const struct fuse_setattr_in *in = &in_hdr->setattr; | |
| ret = fuse_co_setattr(exp, &out_hdr.attr, | |
| in->valid, in->size, in->mode, in->uid, in->gid); | |
| break; | |
| } | |
| case FUSE_READ: { | |
| const struct fuse_read_in *in = &in_hdr->read; | |
| ret = fuse_co_read(exp, &out_data_buffer, in->offset, in->size); | |
| break; | |
| } | |
| case FUSE_WRITE: { | |
| const struct fuse_write_in *in = &in_hdr->write; | |
| uint32_t req_len = in_hdr->common.len; | |
| if (unlikely(req_len < sizeof(in_hdr->common) + sizeof(*in) + | |
| in->size)) { | |
| warn_report("FUSE WRITE truncated; received %zu bytes of %" PRIu32, | |
| req_len - sizeof(in_hdr->common) - sizeof(*in), | |
| in->size); | |
| ret = -EINVAL; | |
| break; | |
| } | |
| /* | |
| * co_read_from_fuse_fd() has checked that in_hdr->len matches the | |
| * number of bytes read, which cannot exceed the max_write value we set | |
| * (FUSE_MAX_WRITE_BYTES). So we know that FUSE_MAX_WRITE_BYTES >= | |
| * in_hdr->len >= in->size + X, so this assertion must hold. | |
| */ | |
| assert(in->size <= FUSE_MAX_WRITE_BYTES); | |
| ret = fuse_co_write(exp, &out_hdr.write, | |
| in->offset, in->size, data_buffer); | |
| break; | |
| } | |
| case FUSE_FALLOCATE: { | |
| const struct fuse_fallocate_in *in = &in_hdr->fallocate; | |
| ret = fuse_co_fallocate(exp, in->offset, in->length, in->mode); | |
| break; | |
| } | |
| case FUSE_FSYNC: | |
| ret = fuse_co_fsync(exp); | |
| break; | |
| case FUSE_FLUSH: | |
| ret = fuse_co_flush(exp); | |
| break; | |
| #ifdef CONFIG_FUSE_LSEEK | |
| case FUSE_LSEEK: { | |
| const struct fuse_lseek_in *in = &in_hdr->lseek; | |
| ret = fuse_co_lseek(exp, &out_hdr.lseek, in->offset, in->whence); | |
| break; | |
| } | |
| #endif | |
| default: | |
| ret = -ENOSYS; | |
| } | |
| if (ret >= 0) { | |
| out_hdr.common = (struct fuse_out_header) { | |
| .len = sizeof(out_hdr.common) + ret, | |
| .unique = in_hdr->common.unique, | |
| }; | |
| } else { | |
| /* fuse_read() must not return a buffer in case of error */ | |
| assert(out_data_buffer == NULL); | |
| out_hdr.common = (struct fuse_out_header) { | |
| .len = sizeof(out_hdr.common), | |
| /* FUSE expects negative errno values */ | |
| .error = ret, | |
| .unique = in_hdr->common.unique, | |
| }; | |
| } | |
| if (out_data_buffer) { | |
| fuse_write_buf_response(q->fuse_fd, &out_hdr.common, out_data_buffer); | |
| qemu_vfree(out_data_buffer); | |
| } else { | |
| fuse_write_response(q->fuse_fd, &out_hdr); | |
| } | |
| } | |
| const BlockExportDriver blk_exp_fuse = { | |
| .type = BLOCK_EXPORT_TYPE_FUSE, | |
| .instance_size = sizeof(FuseExport), | |
| .create = fuse_export_create, | |
| .delete = fuse_export_delete, | |
| .request_shutdown = fuse_export_shutdown, | |
| }; |