|  | #include <linux/device.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/miscdevice.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/anon_inodes.h> | 
|  | #include <linux/ktime.h> | 
|  | #include <linux/hrtimer.h> | 
|  | #include <linux/poll.h> | 
|  |  | 
|  | #include <linux/io_uring/cmd.h> | 
|  | #include <linux/io_uring_types.h> | 
|  | #include <uapi/linux/io_uring/mock_file.h> | 
|  |  | 
|  | struct io_mock_iocb { | 
|  | struct kiocb		*iocb; | 
|  | struct hrtimer		timer; | 
|  | int			res; | 
|  | }; | 
|  |  | 
|  | struct io_mock_file { | 
|  | size_t			size; | 
|  | u64			rw_delay_ns; | 
|  | bool			pollable; | 
|  | struct wait_queue_head	poll_wq; | 
|  | }; | 
|  |  | 
|  | #define IO_VALID_COPY_CMD_FLAGS		IORING_MOCK_COPY_FROM | 
|  |  | 
|  | static int io_copy_regbuf(struct iov_iter *reg_iter, void __user *ubuf) | 
|  | { | 
|  | size_t ret, copied = 0; | 
|  | size_t buflen = PAGE_SIZE; | 
|  | void *tmp_buf; | 
|  |  | 
|  | tmp_buf = kzalloc(buflen, GFP_KERNEL); | 
|  | if (!tmp_buf) | 
|  | return -ENOMEM; | 
|  |  | 
|  | while (iov_iter_count(reg_iter)) { | 
|  | size_t len = min(iov_iter_count(reg_iter), buflen); | 
|  |  | 
|  | if (iov_iter_rw(reg_iter) == ITER_SOURCE) { | 
|  | ret = copy_from_iter(tmp_buf, len, reg_iter); | 
|  | if (ret <= 0) | 
|  | break; | 
|  | if (copy_to_user(ubuf, tmp_buf, ret)) | 
|  | break; | 
|  | } else { | 
|  | if (copy_from_user(tmp_buf, ubuf, len)) | 
|  | break; | 
|  | ret = copy_to_iter(tmp_buf, len, reg_iter); | 
|  | if (ret <= 0) | 
|  | break; | 
|  | } | 
|  | ubuf += ret; | 
|  | copied += ret; | 
|  | } | 
|  |  | 
|  | kfree(tmp_buf); | 
|  | return copied; | 
|  | } | 
|  |  | 
|  | static int io_cmd_copy_regbuf(struct io_uring_cmd *cmd, unsigned int issue_flags) | 
|  | { | 
|  | const struct io_uring_sqe *sqe = cmd->sqe; | 
|  | const struct iovec __user *iovec; | 
|  | unsigned flags, iovec_len; | 
|  | struct iov_iter iter; | 
|  | void __user *ubuf; | 
|  | int dir, ret; | 
|  |  | 
|  | ubuf = u64_to_user_ptr(READ_ONCE(sqe->addr3)); | 
|  | iovec = u64_to_user_ptr(READ_ONCE(sqe->addr)); | 
|  | iovec_len = READ_ONCE(sqe->len); | 
|  | flags = READ_ONCE(sqe->file_index); | 
|  |  | 
|  | if (unlikely(sqe->ioprio || sqe->__pad1)) | 
|  | return -EINVAL; | 
|  | if (flags & ~IO_VALID_COPY_CMD_FLAGS) | 
|  | return -EINVAL; | 
|  |  | 
|  | dir = (flags & IORING_MOCK_COPY_FROM) ? ITER_SOURCE : ITER_DEST; | 
|  | ret = io_uring_cmd_import_fixed_vec(cmd, iovec, iovec_len, dir, &iter, | 
|  | issue_flags); | 
|  | if (ret) | 
|  | return ret; | 
|  | ret = io_copy_regbuf(&iter, ubuf); | 
|  | return ret ? ret : -EFAULT; | 
|  | } | 
|  |  | 
|  | static int io_mock_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) | 
|  | { | 
|  | switch (cmd->cmd_op) { | 
|  | case IORING_MOCK_CMD_COPY_REGBUF: | 
|  | return io_cmd_copy_regbuf(cmd, issue_flags); | 
|  | } | 
|  | return -ENOTSUPP; | 
|  | } | 
|  |  | 
|  | static enum hrtimer_restart io_mock_rw_timer_expired(struct hrtimer *timer) | 
|  | { | 
|  | struct io_mock_iocb *mio = container_of(timer, struct io_mock_iocb, timer); | 
|  | struct kiocb *iocb = mio->iocb; | 
|  |  | 
|  | WRITE_ONCE(iocb->private, NULL); | 
|  | iocb->ki_complete(iocb, mio->res); | 
|  | kfree(mio); | 
|  | return HRTIMER_NORESTART; | 
|  | } | 
|  |  | 
|  | static ssize_t io_mock_delay_rw(struct kiocb *iocb, size_t len) | 
|  | { | 
|  | struct io_mock_file *mf = iocb->ki_filp->private_data; | 
|  | struct io_mock_iocb *mio; | 
|  |  | 
|  | mio = kzalloc(sizeof(*mio), GFP_KERNEL); | 
|  | if (!mio) | 
|  | return -ENOMEM; | 
|  |  | 
|  | mio->iocb = iocb; | 
|  | mio->res = len; | 
|  | hrtimer_setup(&mio->timer, io_mock_rw_timer_expired, | 
|  | CLOCK_MONOTONIC, HRTIMER_MODE_REL); | 
|  | hrtimer_start(&mio->timer, ns_to_ktime(mf->rw_delay_ns), | 
|  | HRTIMER_MODE_REL); | 
|  | return -EIOCBQUEUED; | 
|  | } | 
|  |  | 
|  | static ssize_t io_mock_read_iter(struct kiocb *iocb, struct iov_iter *to) | 
|  | { | 
|  | struct io_mock_file *mf = iocb->ki_filp->private_data; | 
|  | size_t len = iov_iter_count(to); | 
|  | size_t nr_zeroed; | 
|  |  | 
|  | if (iocb->ki_pos + len > mf->size) | 
|  | return -EINVAL; | 
|  | nr_zeroed = iov_iter_zero(len, to); | 
|  | if (!mf->rw_delay_ns || nr_zeroed != len) | 
|  | return nr_zeroed; | 
|  |  | 
|  | return io_mock_delay_rw(iocb, len); | 
|  | } | 
|  |  | 
|  | static ssize_t io_mock_write_iter(struct kiocb *iocb, struct iov_iter *from) | 
|  | { | 
|  | struct io_mock_file *mf = iocb->ki_filp->private_data; | 
|  | size_t len = iov_iter_count(from); | 
|  |  | 
|  | if (iocb->ki_pos + len > mf->size) | 
|  | return -EINVAL; | 
|  | if (!mf->rw_delay_ns) { | 
|  | iov_iter_advance(from, len); | 
|  | return len; | 
|  | } | 
|  |  | 
|  | return io_mock_delay_rw(iocb, len); | 
|  | } | 
|  |  | 
|  | static loff_t io_mock_llseek(struct file *file, loff_t offset, int whence) | 
|  | { | 
|  | struct io_mock_file *mf = file->private_data; | 
|  |  | 
|  | return fixed_size_llseek(file, offset, whence, mf->size); | 
|  | } | 
|  |  | 
|  | static __poll_t io_mock_poll(struct file *file, struct poll_table_struct *pt) | 
|  | { | 
|  | struct io_mock_file *mf = file->private_data; | 
|  | __poll_t mask = 0; | 
|  |  | 
|  | poll_wait(file, &mf->poll_wq, pt); | 
|  |  | 
|  | mask |= EPOLLOUT | EPOLLWRNORM; | 
|  | mask |= EPOLLIN | EPOLLRDNORM; | 
|  | return mask; | 
|  | } | 
|  |  | 
|  | static int io_mock_release(struct inode *inode, struct file *file) | 
|  | { | 
|  | struct io_mock_file *mf = file->private_data; | 
|  |  | 
|  | kfree(mf); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct file_operations io_mock_fops = { | 
|  | .owner		= THIS_MODULE, | 
|  | .release	= io_mock_release, | 
|  | .uring_cmd	= io_mock_cmd, | 
|  | .read_iter	= io_mock_read_iter, | 
|  | .write_iter	= io_mock_write_iter, | 
|  | .llseek		= io_mock_llseek, | 
|  | }; | 
|  |  | 
|  | static const struct file_operations io_mock_poll_fops = { | 
|  | .owner		= THIS_MODULE, | 
|  | .release	= io_mock_release, | 
|  | .uring_cmd	= io_mock_cmd, | 
|  | .read_iter	= io_mock_read_iter, | 
|  | .write_iter	= io_mock_write_iter, | 
|  | .llseek		= io_mock_llseek, | 
|  | .poll		= io_mock_poll, | 
|  | }; | 
|  |  | 
|  | #define IO_VALID_CREATE_FLAGS (IORING_MOCK_CREATE_F_SUPPORT_NOWAIT | \ | 
|  | IORING_MOCK_CREATE_F_POLL) | 
|  |  | 
|  | static int io_create_mock_file(struct io_uring_cmd *cmd, unsigned int issue_flags) | 
|  | { | 
|  | const struct file_operations *fops = &io_mock_fops; | 
|  | const struct io_uring_sqe *sqe = cmd->sqe; | 
|  | struct io_uring_mock_create mc, __user *uarg; | 
|  | struct io_mock_file *mf = NULL; | 
|  | struct file *file = NULL; | 
|  | size_t uarg_size; | 
|  | int fd = -1, ret; | 
|  |  | 
|  | /* | 
|  | * It's a testing only driver that allows exercising edge cases | 
|  | * that wouldn't be possible to hit otherwise. | 
|  | */ | 
|  | add_taint(TAINT_TEST, LOCKDEP_STILL_OK); | 
|  |  | 
|  | uarg = u64_to_user_ptr(READ_ONCE(sqe->addr)); | 
|  | uarg_size = READ_ONCE(sqe->len); | 
|  |  | 
|  | if (sqe->ioprio || sqe->__pad1 || sqe->addr3 || sqe->file_index) | 
|  | return -EINVAL; | 
|  | if (uarg_size != sizeof(mc)) | 
|  | return -EINVAL; | 
|  |  | 
|  | memset(&mc, 0, sizeof(mc)); | 
|  | if (copy_from_user(&mc, uarg, uarg_size)) | 
|  | return -EFAULT; | 
|  | if (!mem_is_zero(mc.__resv, sizeof(mc.__resv))) | 
|  | return -EINVAL; | 
|  | if (mc.flags & ~IO_VALID_CREATE_FLAGS) | 
|  | return -EINVAL; | 
|  | if (mc.file_size > SZ_1G) | 
|  | return -EINVAL; | 
|  | if (mc.rw_delay_ns > NSEC_PER_SEC) | 
|  | return -EINVAL; | 
|  |  | 
|  | mf = kzalloc(sizeof(*mf), GFP_KERNEL_ACCOUNT); | 
|  | if (!mf) | 
|  | return -ENOMEM; | 
|  |  | 
|  | ret = fd = get_unused_fd_flags(O_RDWR | O_CLOEXEC); | 
|  | if (fd < 0) | 
|  | goto fail; | 
|  |  | 
|  | init_waitqueue_head(&mf->poll_wq); | 
|  | mf->size = mc.file_size; | 
|  | mf->rw_delay_ns = mc.rw_delay_ns; | 
|  | if (mc.flags & IORING_MOCK_CREATE_F_POLL) { | 
|  | fops = &io_mock_poll_fops; | 
|  | mf->pollable = true; | 
|  | } | 
|  |  | 
|  | file = anon_inode_create_getfile("[io_uring_mock]", fops, | 
|  | mf, O_RDWR | O_CLOEXEC, NULL); | 
|  | if (IS_ERR(file)) { | 
|  | ret = PTR_ERR(file); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | file->f_mode |= FMODE_READ | FMODE_CAN_READ | | 
|  | FMODE_WRITE | FMODE_CAN_WRITE | | 
|  | FMODE_LSEEK; | 
|  | if (mc.flags & IORING_MOCK_CREATE_F_SUPPORT_NOWAIT) | 
|  | file->f_mode |= FMODE_NOWAIT; | 
|  |  | 
|  | mc.out_fd = fd; | 
|  | if (copy_to_user(uarg, &mc, uarg_size)) { | 
|  | fput(file); | 
|  | ret = -EFAULT; | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | fd_install(fd, file); | 
|  | return 0; | 
|  | fail: | 
|  | if (fd >= 0) | 
|  | put_unused_fd(fd); | 
|  | kfree(mf); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int io_probe_mock(struct io_uring_cmd *cmd) | 
|  | { | 
|  | const struct io_uring_sqe *sqe = cmd->sqe; | 
|  | struct io_uring_mock_probe mp, __user *uarg; | 
|  | size_t uarg_size; | 
|  |  | 
|  | uarg = u64_to_user_ptr(READ_ONCE(sqe->addr)); | 
|  | uarg_size = READ_ONCE(sqe->len); | 
|  |  | 
|  | if (sqe->ioprio || sqe->__pad1 || sqe->addr3 || sqe->file_index || | 
|  | uarg_size != sizeof(mp)) | 
|  | return -EINVAL; | 
|  |  | 
|  | memset(&mp, 0, sizeof(mp)); | 
|  | if (copy_from_user(&mp, uarg, uarg_size)) | 
|  | return -EFAULT; | 
|  | if (!mem_is_zero(&mp, sizeof(mp))) | 
|  | return -EINVAL; | 
|  |  | 
|  | mp.features = IORING_MOCK_FEAT_END; | 
|  |  | 
|  | if (copy_to_user(uarg, &mp, uarg_size)) | 
|  | return -EFAULT; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int iou_mock_mgr_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) | 
|  | { | 
|  | if (!capable(CAP_SYS_ADMIN)) | 
|  | return -EPERM; | 
|  |  | 
|  | switch (cmd->cmd_op) { | 
|  | case IORING_MOCK_MGR_CMD_PROBE: | 
|  | return io_probe_mock(cmd); | 
|  | case IORING_MOCK_MGR_CMD_CREATE: | 
|  | return io_create_mock_file(cmd, issue_flags); | 
|  | } | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  |  | 
|  | static const struct file_operations iou_mock_dev_fops = { | 
|  | .owner		= THIS_MODULE, | 
|  | .uring_cmd	= iou_mock_mgr_cmd, | 
|  | }; | 
|  |  | 
|  | static struct miscdevice iou_mock_miscdev = { | 
|  | .minor			= MISC_DYNAMIC_MINOR, | 
|  | .name			= "io_uring_mock", | 
|  | .fops			= &iou_mock_dev_fops, | 
|  | }; | 
|  |  | 
|  | static int __init io_mock_init(void) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = misc_register(&iou_mock_miscdev); | 
|  | if (ret < 0) { | 
|  | pr_err("Could not initialize io_uring mock device\n"); | 
|  | return ret; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void __exit io_mock_exit(void) | 
|  | { | 
|  | misc_deregister(&iou_mock_miscdev); | 
|  | } | 
|  |  | 
|  | module_init(io_mock_init) | 
|  | module_exit(io_mock_exit) | 
|  |  | 
|  | MODULE_AUTHOR("Pavel Begunkov <asml.silence@gmail.com>"); | 
|  | MODULE_DESCRIPTION("io_uring mock file"); | 
|  | MODULE_LICENSE("GPL"); |