// SPDX-License-Identifier: GPL-2.0 #ifndef NO_BCACHEFS_FS #include "bcachefs.h" #include "printbuf.h" #include "thread_with_file.h" #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; } static inline bool thread_with_stdio_has_output(struct thread_with_stdio *thr) { return thr->stdio.output_buf.pos || thr->output2.nr || thr->thr.done; } static ssize_t thread_with_stdio_read(struct file *file, char __user *buf, size_t len, loff_t *ppos) { struct thread_with_stdio *thr = container_of(file->private_data, struct thread_with_stdio, thr); size_t copied = 0, b; int ret = 0; if ((file->f_flags & O_NONBLOCK) && !thread_with_stdio_has_output(thr)) return -EAGAIN; ret = wait_event_interruptible(thr->stdio.output_wait, thread_with_stdio_has_output(thr)); if (ret) return ret; if (thr->thr.done) return 0; while (len) { ret = darray_make_room(&thr->output2, thr->stdio.output_buf.pos); if (ret) break; spin_lock_irq(&thr->stdio.output_lock); b = min_t(size_t, darray_room(thr->output2), thr->stdio.output_buf.pos); memcpy(&darray_top(thr->output2), thr->stdio.output_buf.buf, b); memmove(thr->stdio.output_buf.buf, thr->stdio.output_buf.buf + b, thr->stdio.output_buf.pos - b); thr->output2.nr += b; thr->stdio.output_buf.pos -= b; spin_unlock_irq(&thr->stdio.output_lock); b = min(len, thr->output2.nr); if (!b) break; b -= copy_to_user(buf, thr->output2.data, b); if (!b) { ret = -EFAULT; break; } copied += b; buf += b; len -= b; memmove(thr->output2.data, thr->output2.data + b, thr->output2.nr - b); thr->output2.nr -= b; } 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); bch2_thread_with_file_exit(&thr->thr); printbuf_exit(&thr->stdio.input_buf); printbuf_exit(&thr->stdio.output_buf); darray_exit(&thr->output2); thr->exit(thr); return 0; } #define WRITE_BUFFER 4096 static inline bool thread_with_stdio_has_input_space(struct thread_with_stdio *thr) { return thr->stdio.input_buf.pos < WRITE_BUFFER || thr->thr.done; } 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 printbuf *buf = &thr->stdio.input_buf; 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(&thr->stdio.input_lock); if (buf->pos < WRITE_BUFFER) bch2_printbuf_make_room(buf, min(b, WRITE_BUFFER - buf->pos)); b = min(len, printbuf_remaining_size(buf)); if (b && !copy_from_user_nofault(&buf->buf[buf->pos], ubuf, b)) { ubuf += b; len -= b; copied += b; buf->pos += b; } spin_unlock(&thr->stdio.input_lock); if (b) { wake_up(&thr->stdio.input_wait); } else { if ((file->f_flags & O_NONBLOCK)) { ret = -EAGAIN; break; } ret = wait_event_interruptible(thr->stdio.input_wait, thread_with_stdio_has_input_space(thr)); 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 (thread_with_stdio_has_output(thr)) mask |= EPOLLIN; if (thread_with_stdio_has_input_space(thr)) mask |= EPOLLOUT; if (thr->thr.done) mask |= EPOLLHUP|EPOLLERR; return mask; } static const struct file_operations thread_with_stdio_fops = { .release = thread_with_stdio_release, .read = thread_with_stdio_read, .write = thread_with_stdio_write, .poll = thread_with_stdio_poll, .llseek = no_llseek, }; int bch2_run_thread_with_stdio(struct thread_with_stdio *thr, void (*exit)(struct thread_with_stdio *), int (*fn)(void *)) { thr->stdio.input_buf = PRINTBUF; thr->stdio.input_buf.atomic++; spin_lock_init(&thr->stdio.input_lock); init_waitqueue_head(&thr->stdio.input_wait); thr->stdio.output_buf = PRINTBUF; thr->stdio.output_buf.atomic++; spin_lock_init(&thr->stdio.output_lock); init_waitqueue_head(&thr->stdio.output_wait); darray_init(&thr->output2); thr->exit = exit; return bch2_run_thread_with_file(&thr->thr, &thread_with_stdio_fops, fn); } int bch2_stdio_redirect_read(struct stdio_redirect *stdio, char *buf, size_t len) { wait_event(stdio->input_wait, stdio->input_buf.pos || stdio->done); if (stdio->done) return -1; spin_lock(&stdio->input_lock); int ret = min(len, stdio->input_buf.pos); stdio->input_buf.pos -= ret; memcpy(buf, stdio->input_buf.buf, ret); memmove(stdio->input_buf.buf, stdio->input_buf.buf + ret, stdio->input_buf.pos); spin_unlock(&stdio->input_lock); wake_up(&stdio->input_wait); return ret; } int bch2_stdio_redirect_readline(struct stdio_redirect *stdio, char *buf, size_t len) { wait_event(stdio->input_wait, stdio->input_buf.pos || stdio->done); if (stdio->done) return -1; spin_lock(&stdio->input_lock); int ret = min(len, stdio->input_buf.pos); char *n = memchr(stdio->input_buf.buf, '\n', ret); if (n) ret = min(ret, n + 1 - stdio->input_buf.buf); stdio->input_buf.pos -= ret; memcpy(buf, stdio->input_buf.buf, ret); memmove(stdio->input_buf.buf, stdio->input_buf.buf + ret, stdio->input_buf.pos); spin_unlock(&stdio->input_lock); wake_up(&stdio->input_wait); return ret; } #endif /* NO_BCACHEFS_FS */