| // SPDX-License-Identifier: GPL-2.0 | |
| #ifndef NO_BCACHEFS_FS | |
| #include "bcachefs.h" | |
| #include "thread_with_file.h" | |
| #include <linux/anon_inodes.h> | |
| #include <linux/file.h> | |
| #include <linux/kthread.h> | |
| #include <linux/pagemap.h> | |
| #include <linux/poll.h> | |
| #include <linux/sched/sysctl.h> | |
| void bch2_thread_with_file_exit(struct thread_with_file *thr) | |
| { | |
| if (thr->task) { | |
| kthread_stop(thr->task); | |
| put_task_struct(thr->task); | |
| } | |
| } | |
| int bch2_run_thread_with_file(struct thread_with_file *thr, | |
| const struct file_operations *fops, | |
| int (*fn)(void *)) | |
| { | |
| struct file *file = NULL; | |
| int ret, fd = -1; | |
| unsigned fd_flags = O_CLOEXEC; | |
| if (fops->read && fops->write) | |
| fd_flags |= O_RDWR; | |
| else if (fops->read) | |
| fd_flags |= O_RDONLY; | |
| else if (fops->write) | |
| fd_flags |= O_WRONLY; | |
| char name[TASK_COMM_LEN]; | |
| get_task_comm(name, current); | |
| thr->ret = 0; | |
| thr->task = kthread_create(fn, thr, "%s", name); | |
| ret = PTR_ERR_OR_ZERO(thr->task); | |
| if (ret) | |
| return ret; | |
| ret = get_unused_fd_flags(fd_flags); | |
| if (ret < 0) | |
| goto err; | |
| fd = ret; | |
| file = anon_inode_getfile(name, fops, thr, fd_flags); | |
| ret = PTR_ERR_OR_ZERO(file); | |
| if (ret) | |
| goto err; | |
| get_task_struct(thr->task); | |
| wake_up_process(thr->task); | |
| fd_install(fd, file); | |
| return fd; | |
| err: | |
| if (fd >= 0) | |
| put_unused_fd(fd); | |
| if (thr->task) | |
| kthread_stop(thr->task); | |
| return ret; | |
| } | |
| /* stdio_redirect */ | |
| static bool stdio_redirect_has_more_input(struct stdio_redirect *stdio, size_t seen) | |
| { | |
| return stdio->input.buf.nr > seen || stdio->done; | |
| } | |
| static bool stdio_redirect_has_input(struct stdio_redirect *stdio) | |
| { | |
| return stdio_redirect_has_more_input(stdio, 0); | |
| } | |
| static bool stdio_redirect_has_output(struct stdio_redirect *stdio) | |
| { | |
| return stdio->output.buf.nr || stdio->done; | |
| } | |
| #define STDIO_REDIRECT_BUFSIZE 4096 | |
| static bool stdio_redirect_has_input_space(struct stdio_redirect *stdio) | |
| { | |
| return stdio->input.buf.nr < STDIO_REDIRECT_BUFSIZE || stdio->done; | |
| } | |
| static bool stdio_redirect_has_output_space(struct stdio_redirect *stdio) | |
| { | |
| return stdio->output.buf.nr < STDIO_REDIRECT_BUFSIZE || stdio->done; | |
| } | |
| static void stdio_buf_init(struct stdio_buf *buf) | |
| { | |
| spin_lock_init(&buf->lock); | |
| init_waitqueue_head(&buf->wait); | |
| darray_init(&buf->buf); | |
| } | |
| /* thread_with_stdio */ | |
| static void thread_with_stdio_done(struct thread_with_stdio *thr) | |
| { | |
| thr->thr.done = true; | |
| thr->stdio.done = true; | |
| wake_up(&thr->stdio.input.wait); | |
| wake_up(&thr->stdio.output.wait); | |
| } | |
| static ssize_t thread_with_stdio_read(struct file *file, char __user *ubuf, | |
| size_t len, loff_t *ppos) | |
| { | |
| struct thread_with_stdio *thr = | |
| container_of(file->private_data, struct thread_with_stdio, thr); | |
| struct stdio_buf *buf = &thr->stdio.output; | |
| size_t copied = 0, b; | |
| int ret = 0; | |
| if (!(file->f_flags & O_NONBLOCK)) { | |
| ret = wait_event_interruptible(buf->wait, stdio_redirect_has_output(&thr->stdio)); | |
| if (ret) | |
| return ret; | |
| } else if (!stdio_redirect_has_output(&thr->stdio)) | |
| return -EAGAIN; | |
| while (len && buf->buf.nr) { | |
| if (fault_in_writeable(ubuf, len) == len) { | |
| ret = -EFAULT; | |
| break; | |
| } | |
| spin_lock_irq(&buf->lock); | |
| b = min_t(size_t, len, buf->buf.nr); | |
| if (b && !copy_to_user_nofault(ubuf, buf->buf.data, b)) { | |
| ubuf += b; | |
| len -= b; | |
| copied += b; | |
| buf->buf.nr -= b; | |
| memmove(buf->buf.data, | |
| buf->buf.data + b, | |
| buf->buf.nr); | |
| } | |
| spin_unlock_irq(&buf->lock); | |
| } | |
| return copied ?: ret; | |
| } | |
| static int thread_with_stdio_release(struct inode *inode, struct file *file) | |
| { | |
| struct thread_with_stdio *thr = | |
| container_of(file->private_data, struct thread_with_stdio, thr); | |
| thread_with_stdio_done(thr); | |
| bch2_thread_with_file_exit(&thr->thr); | |
| darray_exit(&thr->stdio.input.buf); | |
| darray_exit(&thr->stdio.output.buf); | |
| thr->ops->exit(thr); | |
| return 0; | |
| } | |
| static ssize_t thread_with_stdio_write(struct file *file, const char __user *ubuf, | |
| size_t len, loff_t *ppos) | |
| { | |
| struct thread_with_stdio *thr = | |
| container_of(file->private_data, struct thread_with_stdio, thr); | |
| struct stdio_buf *buf = &thr->stdio.input; | |
| size_t copied = 0; | |
| ssize_t ret = 0; | |
| while (len) { | |
| if (thr->thr.done) { | |
| ret = -EPIPE; | |
| break; | |
| } | |
| size_t b = len - fault_in_readable(ubuf, len); | |
| if (!b) { | |
| ret = -EFAULT; | |
| break; | |
| } | |
| spin_lock(&buf->lock); | |
| size_t makeroom = b; | |
| if (!buf->waiting_for_line || memchr(buf->buf.data, '\n', buf->buf.nr)) | |
| makeroom = min_t(ssize_t, makeroom, | |
| max_t(ssize_t, STDIO_REDIRECT_BUFSIZE - buf->buf.nr, | |
| 0)); | |
| darray_make_room_gfp(&buf->buf, makeroom, GFP_NOWAIT); | |
| b = min(len, darray_room(buf->buf)); | |
| if (b && !copy_from_user_nofault(&darray_top(buf->buf), ubuf, b)) { | |
| buf->buf.nr += b; | |
| ubuf += b; | |
| len -= b; | |
| copied += b; | |
| } | |
| spin_unlock(&buf->lock); | |
| if (b) { | |
| wake_up(&buf->wait); | |
| } else { | |
| if ((file->f_flags & O_NONBLOCK)) { | |
| ret = -EAGAIN; | |
| break; | |
| } | |
| ret = wait_event_interruptible(buf->wait, | |
| stdio_redirect_has_input_space(&thr->stdio)); | |
| if (ret) | |
| break; | |
| } | |
| } | |
| return copied ?: ret; | |
| } | |
| static __poll_t thread_with_stdio_poll(struct file *file, struct poll_table_struct *wait) | |
| { | |
| struct thread_with_stdio *thr = | |
| container_of(file->private_data, struct thread_with_stdio, thr); | |
| poll_wait(file, &thr->stdio.output.wait, wait); | |
| poll_wait(file, &thr->stdio.input.wait, wait); | |
| __poll_t mask = 0; | |
| if (stdio_redirect_has_output(&thr->stdio)) | |
| mask |= EPOLLIN; | |
| if (stdio_redirect_has_input_space(&thr->stdio)) | |
| mask |= EPOLLOUT; | |
| if (thr->thr.done) | |
| mask |= EPOLLHUP|EPOLLERR; | |
| return mask; | |
| } | |
| static __poll_t thread_with_stdout_poll(struct file *file, struct poll_table_struct *wait) | |
| { | |
| struct thread_with_stdio *thr = | |
| container_of(file->private_data, struct thread_with_stdio, thr); | |
| poll_wait(file, &thr->stdio.output.wait, wait); | |
| __poll_t mask = 0; | |
| if (stdio_redirect_has_output(&thr->stdio)) | |
| mask |= EPOLLIN; | |
| if (thr->thr.done) | |
| mask |= EPOLLHUP|EPOLLERR; | |
| return mask; | |
| } | |
| static int thread_with_stdio_flush(struct file *file, fl_owner_t id) | |
| { | |
| struct thread_with_stdio *thr = | |
| container_of(file->private_data, struct thread_with_stdio, thr); | |
| return thr->thr.ret; | |
| } | |
| static long thread_with_stdio_ioctl(struct file *file, unsigned int cmd, unsigned long p) | |
| { | |
| struct thread_with_stdio *thr = | |
| container_of(file->private_data, struct thread_with_stdio, thr); | |
| if (thr->ops->unlocked_ioctl) | |
| return thr->ops->unlocked_ioctl(thr, cmd, p); | |
| return -ENOTTY; | |
| } | |
| static const struct file_operations thread_with_stdio_fops = { | |
| .read = thread_with_stdio_read, | |
| .write = thread_with_stdio_write, | |
| .poll = thread_with_stdio_poll, | |
| .flush = thread_with_stdio_flush, | |
| .release = thread_with_stdio_release, | |
| .unlocked_ioctl = thread_with_stdio_ioctl, | |
| }; | |
| static const struct file_operations thread_with_stdout_fops = { | |
| .read = thread_with_stdio_read, | |
| .poll = thread_with_stdout_poll, | |
| .flush = thread_with_stdio_flush, | |
| .release = thread_with_stdio_release, | |
| .unlocked_ioctl = thread_with_stdio_ioctl, | |
| }; | |
| static int thread_with_stdio_fn(void *arg) | |
| { | |
| struct thread_with_stdio *thr = arg; | |
| thr->thr.ret = thr->ops->fn(thr); | |
| thread_with_stdio_done(thr); | |
| return 0; | |
| } | |
| void bch2_thread_with_stdio_init(struct thread_with_stdio *thr, | |
| const struct thread_with_stdio_ops *ops) | |
| { | |
| stdio_buf_init(&thr->stdio.input); | |
| stdio_buf_init(&thr->stdio.output); | |
| thr->ops = ops; | |
| } | |
| int __bch2_run_thread_with_stdio(struct thread_with_stdio *thr) | |
| { | |
| return bch2_run_thread_with_file(&thr->thr, &thread_with_stdio_fops, thread_with_stdio_fn); | |
| } | |
| int bch2_run_thread_with_stdio(struct thread_with_stdio *thr, | |
| const struct thread_with_stdio_ops *ops) | |
| { | |
| bch2_thread_with_stdio_init(thr, ops); | |
| return __bch2_run_thread_with_stdio(thr); | |
| } | |
| int bch2_run_thread_with_stdout(struct thread_with_stdio *thr, | |
| const struct thread_with_stdio_ops *ops) | |
| { | |
| stdio_buf_init(&thr->stdio.input); | |
| stdio_buf_init(&thr->stdio.output); | |
| thr->ops = ops; | |
| return bch2_run_thread_with_file(&thr->thr, &thread_with_stdout_fops, thread_with_stdio_fn); | |
| } | |
| EXPORT_SYMBOL_GPL(bch2_run_thread_with_stdout); | |
| int bch2_stdio_redirect_read(struct stdio_redirect *stdio, char *ubuf, size_t len) | |
| { | |
| struct stdio_buf *buf = &stdio->input; | |
| /* | |
| * we're waiting on user input (or for the file descriptor to be | |
| * closed), don't want a hung task warning: | |
| */ | |
| do { | |
| wait_event_timeout(buf->wait, stdio_redirect_has_input(stdio), | |
| sysctl_hung_task_timeout_secs * HZ / 2); | |
| } while (!stdio_redirect_has_input(stdio)); | |
| if (stdio->done) | |
| return -1; | |
| spin_lock(&buf->lock); | |
| int ret = min(len, buf->buf.nr); | |
| buf->buf.nr -= ret; | |
| memcpy(ubuf, buf->buf.data, ret); | |
| memmove(buf->buf.data, | |
| buf->buf.data + ret, | |
| buf->buf.nr); | |
| spin_unlock(&buf->lock); | |
| wake_up(&buf->wait); | |
| return ret; | |
| } | |
| int bch2_stdio_redirect_readline_timeout(struct stdio_redirect *stdio, | |
| darray_char *line, | |
| unsigned long timeout) | |
| { | |
| unsigned long until = jiffies + timeout, t; | |
| struct stdio_buf *buf = &stdio->input; | |
| size_t seen = 0; | |
| again: | |
| t = timeout != MAX_SCHEDULE_TIMEOUT | |
| ? max_t(long, until - jiffies, 0) | |
| : timeout; | |
| t = min(t, sysctl_hung_task_timeout_secs * HZ / 2); | |
| wait_event_timeout(buf->wait, stdio_redirect_has_more_input(stdio, seen), t); | |
| if (stdio->done) | |
| return -1; | |
| spin_lock(&buf->lock); | |
| seen = buf->buf.nr; | |
| char *n = memchr(buf->buf.data, '\n', seen); | |
| if (!n && timeout != MAX_SCHEDULE_TIMEOUT && time_after_eq(jiffies, until)) { | |
| spin_unlock(&buf->lock); | |
| return -ETIME; | |
| } | |
| if (!n) { | |
| buf->waiting_for_line = true; | |
| spin_unlock(&buf->lock); | |
| goto again; | |
| } | |
| size_t b = n + 1 - buf->buf.data; | |
| if (b > line->size) { | |
| spin_unlock(&buf->lock); | |
| int ret = darray_resize(line, b); | |
| if (ret) | |
| return ret; | |
| seen = 0; | |
| goto again; | |
| } | |
| buf->buf.nr -= b; | |
| memcpy(line->data, buf->buf.data, b); | |
| memmove(buf->buf.data, | |
| buf->buf.data + b, | |
| buf->buf.nr); | |
| line->nr = b; | |
| buf->waiting_for_line = false; | |
| spin_unlock(&buf->lock); | |
| wake_up(&buf->wait); | |
| return 0; | |
| } | |
| int bch2_stdio_redirect_readline(struct stdio_redirect *stdio, darray_char *line) | |
| { | |
| return bch2_stdio_redirect_readline_timeout(stdio, line, MAX_SCHEDULE_TIMEOUT); | |
| } | |
| __printf(3, 0) | |
| static ssize_t bch2_darray_vprintf(darray_char *out, gfp_t gfp, const char *fmt, va_list args) | |
| { | |
| ssize_t ret; | |
| do { | |
| va_list args2; | |
| size_t len; | |
| va_copy(args2, args); | |
| len = vsnprintf(out->data + out->nr, darray_room(*out), fmt, args2); | |
| va_end(args2); | |
| if (len + 1 <= darray_room(*out)) { | |
| out->nr += len; | |
| return len; | |
| } | |
| ret = darray_make_room_gfp(out, len + 1, gfp); | |
| } while (ret == 0); | |
| return ret; | |
| } | |
| ssize_t bch2_stdio_redirect_vprintf(struct stdio_redirect *stdio, bool nonblocking, | |
| const char *fmt, va_list args) | |
| { | |
| struct stdio_buf *buf = &stdio->output; | |
| unsigned long flags; | |
| ssize_t ret; | |
| again: | |
| if (stdio->done) | |
| return -EPIPE; | |
| spin_lock_irqsave(&buf->lock, flags); | |
| ret = bch2_darray_vprintf(&buf->buf, GFP_NOWAIT, fmt, args); | |
| spin_unlock_irqrestore(&buf->lock, flags); | |
| if (ret < 0) { | |
| if (nonblocking) | |
| return -EAGAIN; | |
| ret = wait_event_interruptible(buf->wait, | |
| stdio_redirect_has_output_space(stdio)); | |
| if (ret) | |
| return ret; | |
| goto again; | |
| } | |
| wake_up(&buf->wait); | |
| return ret; | |
| } | |
| ssize_t bch2_stdio_redirect_printf(struct stdio_redirect *stdio, bool nonblocking, | |
| const char *fmt, ...) | |
| { | |
| va_list args; | |
| ssize_t ret; | |
| va_start(args, fmt); | |
| ret = bch2_stdio_redirect_vprintf(stdio, nonblocking, fmt, args); | |
| va_end(args); | |
| return ret; | |
| } | |
| #endif /* NO_BCACHEFS_FS */ |