// SPDX-License-Identifier: GPL-2.0 #ifndef NO_BCACHEFS_FS #include "bcachefs.h" #include "thread_with_file.h" #include #include #include #include #include #include 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: 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 */