| // SPDX-License-Identifier: GPL-2.0 |
| #ifndef NO_BCACHEFS_FS |
| |
| #include "bcachefs.h" |
| #include "printbuf.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> |
| |
| 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; |
| |
| fd_install(fd, file); |
| get_task_struct(thr->task); |
| wake_up_process(thr->task); |
| 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 */ |