diff options
author | Jens Axboe <axboe@kernel.dk> | 2025-04-29 15:36:54 -0600 |
---|---|---|
committer | Jens Axboe <axboe@kernel.dk> | 2025-04-30 07:14:02 -0600 |
commit | 2e7136592b64216af1c397e3ea26811a4ed103d5 (patch) | |
tree | 383c00e6990c9a4eaa10b2521b6096391ff4cb02 | |
parent | 12d7275e254f89ba3f2d500e5ffb1d3ae2aaaf8b (diff) | |
download | linux-block-io_uring-handle.tar.gz |
io_uring: add support for IORING_OP_OPEN_BY_HANDLEio_uring-handle
Add support for opening files with a struct file_handle, similarly to
what open_by_handle_at() supports.
Signed-off-by: Jens Axboe <axboe@kernel.dk>
-rw-r--r-- | include/uapi/linux/io_uring.h | 8 | ||||
-rw-r--r-- | io_uring/opdef.c | 13 | ||||
-rw-r--r-- | io_uring/openclose.c | 152 | ||||
-rw-r--r-- | io_uring/openclose.h | 14 |
4 files changed, 187 insertions, 0 deletions
diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index 130f3bc71a6911..e819bdcc60eef1 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -285,6 +285,7 @@ enum io_uring_op { IORING_OP_READV_FIXED, IORING_OP_WRITEV_FIXED, IORING_OP_PIPE, + IORING_OP_OPEN_BY_HANDLE, /* this goes last, obviously */ IORING_OP_LAST, @@ -447,6 +448,13 @@ enum io_uring_msg_ring_flags { #define IORING_NOP_FIXED_BUFFER (1U << 3) /* + * IORING_OP_OPEN_BY_HANDLE flags + * + * IORING_OPEN_HANDLE_FIXED Use a fixed/registered file + */ +#define IORING_OPEN_HANDLE_FIXED (1U << 0) + +/* * IO completion data structure (Completion Queue Entry) */ struct io_uring_cqe { diff --git a/io_uring/opdef.c b/io_uring/opdef.c index db36433c2294ef..91b6567b6d4f60 100644 --- a/io_uring/opdef.c +++ b/io_uring/opdef.c @@ -573,6 +573,16 @@ const struct io_issue_def io_issue_defs[] = { .prep = io_pipe_prep, .issue = io_pipe, }, + [IORING_OP_OPEN_BY_HANDLE] = { + .ioprio = 1, + .async_size = sizeof(struct io_open_handle_async), +#if defined(CONFIG_FHANDLE) + .prep = io_open_by_handle_prep, + .issue = io_open_by_handle, +#else + .prep = io_eopnotsupp_prep, +#endif + }, }; const struct io_cold_def io_cold_defs[] = { @@ -822,6 +832,9 @@ const struct io_cold_def io_cold_defs[] = { [IORING_OP_PIPE] = { .name = "PIPE", }, + [IORING_OP_OPEN_BY_HANDLE] = { + .name = "OPEN_BY_HANDLE", + }, }; const char *io_uring_get_opcode(u8 opcode) diff --git a/io_uring/openclose.c b/io_uring/openclose.c index 4dd46116345783..6233352f72976e 100644 --- a/io_uring/openclose.c +++ b/io_uring/openclose.c @@ -8,6 +8,7 @@ #include <linux/namei.h> #include <linux/pipe_fs_i.h> #include <linux/watch_queue.h> +#include <linux/fs_struct.h> #include <linux/io_uring.h> #include <uapi/linux/io_uring.h> @@ -38,6 +39,15 @@ struct io_fixed_install { unsigned int o_flags; }; +struct io_open_handle { + struct file *file; + int dirfd; + int open_flags; + int flags; + u32 file_slot; + struct handle_to_path_ctx ctx; +}; + static bool io_openat_force_async(struct io_open *open) { /* @@ -435,3 +445,145 @@ int io_pipe(struct io_kiocb *req, unsigned int issue_flags) fput(files[1]); return ret; } + +#if defined(CONFIG_FHANDLE) + +int io_open_by_handle_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) +{ + struct io_open_handle *h = io_kiocb_to_cmd(req, struct io_open_handle); + struct io_open_handle_async *ah; + struct file_handle __user *uh; + + if (sqe->off || sqe->len) + return -EINVAL; + if (req->flags & REQ_F_FIXED_FILE) + return -EBADF; + + memset(&h->ctx, 0, sizeof(h->ctx)); + + h->dirfd = READ_ONCE(sqe->fd); + h->open_flags = READ_ONCE(sqe->open_flags); + h->flags = READ_ONCE(sqe->ioprio); + if (h->flags & ~IORING_OPEN_HANDLE_FIXED) + return -EINVAL; + + h->file_slot = READ_ONCE(sqe->file_index); + if (h->file_slot && !(h->flags & IORING_OPEN_HANDLE_FIXED)) + return -EINVAL; + + ah = io_uring_alloc_async_data(NULL, req); + if (!ah) + return -ENOMEM; + + uh = u64_to_user_ptr(READ_ONCE(sqe->addr)); + if (get_user(ah->handle.handle_bytes, &uh->handle_bytes) || + get_user(ah->handle.handle_type, &uh->handle_type)) + return -EFAULT; + + if (!ah->handle.handle_bytes || ah->handle.handle_bytes > MAX_HANDLE_SZ) + return -EINVAL; + if (ah->handle.handle_type < 0 || + FILEID_USER_FLAGS(ah->handle.handle_type) & ~FILEID_VALID_USER_FLAGS) + return -EINVAL; + + if (copy_from_user(&ah->handle.f_handle, &uh->f_handle, ah->handle.handle_bytes)) + return -EFAULT; + + if (ah->handle.handle_type & FILEID_IS_CONNECTABLE) { + h->ctx.fh_flags |= EXPORT_FH_CONNECTABLE; + h->ctx.flags |= HANDLE_CHECK_SUBTREE; + } + if (ah->handle.handle_type & FILEID_IS_DIR) + h->ctx.fh_flags |= EXPORT_FH_DIR_ONLY; + ah->handle.handle_type &= ~FILEID_USER_FLAGS_MASK; + return 0; +} + +static int __io_open_by_handle(struct io_kiocb *req, unsigned int issue_flags, + const struct export_operations *eops) +{ + struct io_open_handle *h = io_kiocb_to_cmd(req, struct io_open_handle); + struct io_open_handle_async *ah = req->async_data; + struct path path __free(path_put) = { }; + struct file *file; + int ret, fd; + + ret = do_handle_to_path(&ah->handle, &path, &h->ctx); + path_put(&h->ctx.root); + if (ret < 0) + return ret; + + if (!(h->flags & IORING_OPEN_HANDLE_FIXED)) { + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + return fd; + } + + if (eops->open) { + if (issue_flags & IO_URING_F_NONBLOCK) { + file = ERR_PTR(-EAGAIN); + goto err; + } + file = eops->open(&path, h->open_flags); + } else { + struct open_flags op; + struct open_how how = build_open_how(h->open_flags, 0); + + ret = build_open_flags(&how, &op); + if (ret) + return ret; + if (issue_flags & IO_URING_F_NONBLOCK) { + op.lookup_flags |= LOOKUP_CACHED; + op.open_flag |= O_NONBLOCK; + } + file = do_file_open_root(&path, "", &op); + } + if (IS_ERR(file)) { +err: + if (!(h->flags & IORING_OPEN_HANDLE_FIXED)) + put_unused_fd(fd); + return PTR_ERR(file); + } + + if (!(h->flags & IORING_OPEN_HANDLE_FIXED)) { + fd_install(fd, file); + return fd; + } + + return io_fixed_fd_install(req, issue_flags, file, h->file_slot); +} + +int io_open_by_handle(struct io_kiocb *req, unsigned int issue_flags) +{ + struct io_open_handle *h = io_kiocb_to_cmd(req, struct io_open_handle); + const struct export_operations *eops; + int ret; + + ret = get_path_from_fd(h->dirfd, &h->ctx.root); + if (ret < 0) + goto err; + + eops = h->ctx.root.mnt->mnt_sb->s_export_op; + if (eops && eops->permission) + ret = eops->permission(&h->ctx, h->open_flags); + else + ret = may_decode_fh(&h->ctx, h->open_flags); + if (ret) { + path_put(&h->ctx.root); + goto err; + } + + ret = __io_open_by_handle(req, issue_flags, eops); + io_req_set_res(req, ret, 0); + if (ret < 0) { +err: + req_set_fail(req); + return ret; + } + + kfree(req->async_data); + req->async_data = NULL; + req->flags &= ~REQ_F_ASYNC_DATA; + return IOU_OK; +} +#endif /* CONFIG_FHANDLE */ diff --git a/io_uring/openclose.h b/io_uring/openclose.h index 4ca2a9935abc9c..97a0958b6769e8 100644 --- a/io_uring/openclose.h +++ b/io_uring/openclose.h @@ -1,5 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 +#include <linux/fs.h> +#include <linux/exportfs.h> + +struct io_open_handle_async { + union { + struct file_handle handle; + char pad[sizeof(struct file_handle) + + MAX_HANDLE_SZ]; + }; +}; + int __io_close_fixed(struct io_ring_ctx *ctx, unsigned int issue_flags, unsigned int offset); @@ -16,5 +27,8 @@ int io_close(struct io_kiocb *req, unsigned int issue_flags); int io_pipe_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); int io_pipe(struct io_kiocb *req, unsigned int issue_flags); +int io_open_by_handle_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); +int io_open_by_handle(struct io_kiocb *req, unsigned int issue_flags); + int io_install_fixed_fd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); int io_install_fixed_fd(struct io_kiocb *req, unsigned int issue_flags); |