// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) */ #include #include #include #include #include #include #include #include "chan_user.h" #include #include void generic_close(int fd, void *unused) { close(fd); } int generic_read(int fd, __u8 *c_out, void *unused) { int n; CATCH_EINTR(n = read(fd, c_out, sizeof(*c_out))); if (n > 0) return n; else if (n == 0) return -EIO; else if (errno == EAGAIN) return 0; return -errno; } /* XXX Trivial wrapper around write */ int generic_write(int fd, const __u8 *buf, size_t n, void *unused) { int written = 0; int err; /* The FD may be in blocking mode, as such, need to retry short writes, * they may have been interrupted by a signal. */ do { errno = 0; err = write(fd, buf + written, n - written); if (err > 0) { written += err; continue; } } while (err < 0 && errno == EINTR); if (written > 0) return written; else if (errno == EAGAIN) return 0; else if (err == 0) return -EIO; return -errno; } int generic_window_size(int fd, void *unused, unsigned short *rows_out, unsigned short *cols_out) { struct winsize size; int ret; if (ioctl(fd, TIOCGWINSZ, &size) < 0) return -errno; ret = ((*rows_out != size.ws_row) || (*cols_out != size.ws_col)); *rows_out = size.ws_row; *cols_out = size.ws_col; return ret; } void generic_free(void *data) { kfree(data); } int generic_console_write(int fd, const char *buf, int n) { sigset_t old, no_sigio; struct termios save, new; int err; if (isatty(fd)) { sigemptyset(&no_sigio); sigaddset(&no_sigio, SIGIO); if (sigprocmask(SIG_BLOCK, &no_sigio, &old)) goto error; CATCH_EINTR(err = tcgetattr(fd, &save)); if (err) goto error; new = save; /* * The terminal becomes a bit less raw, to handle \n also as * "Carriage Return", not only as "New Line". Otherwise, the new * line won't start at the first column. */ new.c_oflag |= OPOST; CATCH_EINTR(err = tcsetattr(fd, TCSAFLUSH, &new)); if (err) goto error; } err = generic_write(fd, buf, n, NULL); /* * Restore raw mode, in any case; we *must* ignore any error apart * EINTR, except for debug. */ if (isatty(fd)) { CATCH_EINTR(tcsetattr(fd, TCSAFLUSH, &save)); sigprocmask(SIG_SETMASK, &old, NULL); } return err; error: return -errno; } /* * UML SIGWINCH handling * * The point of this is to handle SIGWINCH on consoles which have host * ttys and relay them inside UML to whatever might be running on the * console and cares about the window size (since SIGWINCH notifies * about terminal size changes). * * So, we have a separate thread for each host tty attached to a UML * device (side-issue - I'm annoyed that one thread can't have * multiple controlling ttys for the purpose of handling SIGWINCH, but * I imagine there are other reasons that doesn't make any sense). * * SIGWINCH can't be received synchronously, so you have to set up to * receive it as a signal. That being the case, if you are going to * wait for it, it is convenient to sit in sigsuspend() and wait for * the signal to bounce you out of it (see below for how we make sure * to exit only on SIGWINCH). */ static void winch_handler(int sig) { } struct winch_data { int pty_fd; int pipe_fd; }; static __noreturn int winch_thread(void *arg) { struct winch_data *data = arg; sigset_t sigs; int pty_fd, pipe_fd; int count; char c = 1; os_set_pdeathsig(); pty_fd = data->pty_fd; pipe_fd = data->pipe_fd; count = write(pipe_fd, &c, sizeof(c)); if (count != sizeof(c)) os_info("winch_thread : failed to write synchronization byte, err = %d\n", -count); /* * We are not using SIG_IGN on purpose, so don't fix it as I thought to * do! If using SIG_IGN, the sigsuspend() call below would not stop on * SIGWINCH. */ signal(SIGWINCH, winch_handler); sigfillset(&sigs); /* Block all signals possible. */ if (sigprocmask(SIG_SETMASK, &sigs, NULL) < 0) { os_info("winch_thread : sigprocmask failed, errno = %d\n", errno); goto wait_kill; } /* In sigsuspend(), block anything else than SIGWINCH. */ sigdelset(&sigs, SIGWINCH); if (setsid() < 0) { os_info("winch_thread : setsid failed, errno = %d\n", errno); goto wait_kill; } if (ioctl(pty_fd, TIOCSCTTY, 0) < 0) { os_info("winch_thread : TIOCSCTTY failed on " "fd %d err = %d\n", pty_fd, errno); goto wait_kill; } if (tcsetpgrp(pty_fd, os_getpid()) < 0) { os_info("winch_thread : tcsetpgrp failed on fd %d err = %d\n", pty_fd, errno); goto wait_kill; } /* * These are synchronization calls between various UML threads on the * host - since they are not different kernel threads, we cannot use * kernel semaphores. We don't use SysV semaphores because they are * persistent. */ count = read(pipe_fd, &c, sizeof(c)); if (count != sizeof(c)) os_info("winch_thread : failed to read synchronization byte, err = %d\n", errno); while(1) { /* * This will be interrupted by SIGWINCH only, since * other signals are blocked. */ sigsuspend(&sigs); count = write(pipe_fd, &c, sizeof(c)); if (count != sizeof(c)) os_info("winch_thread : write failed, err = %d\n", errno); } wait_kill: c = 2; count = write(pipe_fd, &c, sizeof(c)); while (1) pause(); } static int winch_tramp(int fd, struct tty_port *port, int *fd_out, unsigned long *stack_out) { struct winch_data data; int fds[2], n, err, pid; char c; err = os_pipe(fds, 1, 1); if (err < 0) { printk(UM_KERN_ERR "winch_tramp : os_pipe failed, err = %d\n", -err); goto out; } data = ((struct winch_data) { .pty_fd = fd, .pipe_fd = fds[1] } ); /* * CLONE_FILES so this thread doesn't hold open files which are open * now, but later closed in a different thread. This is a * problem with /dev/net/tun, which if held open by this * thread, prevents the TUN/TAP device from being reused. */ pid = run_helper_thread(winch_thread, &data, CLONE_FILES, stack_out); if (pid < 0) { err = pid; printk(UM_KERN_ERR "fork of winch_thread failed - errno = %d\n", -err); goto out_close; } *fd_out = fds[0]; n = read(fds[0], &c, sizeof(c)); if (n != sizeof(c)) { printk(UM_KERN_ERR "winch_tramp : failed to read " "synchronization byte\n"); printk(UM_KERN_ERR "read failed, err = %d\n", errno); printk(UM_KERN_ERR "fd %d will not support SIGWINCH\n", fd); err = -EINVAL; goto out_close; } err = os_set_fd_block(*fd_out, 0); if (err) { printk(UM_KERN_ERR "winch_tramp: failed to set thread_fd " "non-blocking.\n"); goto out_close; } return pid; out_close: close(fds[1]); close(fds[0]); out: return err; } void register_winch(int fd, struct tty_port *port) { unsigned long stack; int pid, thread, count, thread_fd = -1; char c = 1; if (!isatty(fd)) return; pid = tcgetpgrp(fd); if (is_skas_winch(pid, fd, port)) { register_winch_irq(-1, fd, -1, port, 0); return; } if (pid == -1) { thread = winch_tramp(fd, port, &thread_fd, &stack); if (thread < 0) return; register_winch_irq(thread_fd, fd, thread, port, stack); count = write(thread_fd, &c, sizeof(c)); if (count != sizeof(c)) printk(UM_KERN_ERR "register_winch : failed to write " "synchronization byte, err = %d\n", errno); } }