// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) */ #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sched.h> #include <signal.h> #include <termios.h> #include <sys/ioctl.h> #include "chan_user.h" #include <os.h> #include <um_malloc.h> void generic_close(int fd, void *unused) { close(fd); } int generic_read(int fd, char *c_out, void *unused) { int n; 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 char *buf, int n, void *unused) { int err; err = write(fd, buf, n); if (err > 0) return err; 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 int winch_thread(void *arg) { struct winch_data *data = arg; sigset_t sigs; int pty_fd, pipe_fd; int count; char c = 1; pty_fd = data->pty_fd; pipe_fd = data->pipe_fd; count = write(pipe_fd, &c, sizeof(c)); if (count != sizeof(c)) printk(UM_KERN_ERR "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) { printk(UM_KERN_ERR "winch_thread : sigprocmask failed, " "errno = %d\n", errno); exit(1); } /* In sigsuspend(), block anything else than SIGWINCH. */ sigdelset(&sigs, SIGWINCH); if (setsid() < 0) { printk(UM_KERN_ERR "winch_thread : setsid failed, errno = %d\n", errno); exit(1); } if (ioctl(pty_fd, TIOCSCTTY, 0) < 0) { printk(UM_KERN_ERR "winch_thread : TIOCSCTTY failed on " "fd %d err = %d\n", pty_fd, errno); exit(1); } if (tcsetpgrp(pty_fd, os_getpid()) < 0) { printk(UM_KERN_ERR "winch_thread : tcsetpgrp failed on " "fd %d err = %d\n", pty_fd, errno); exit(1); } /* * 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)) printk(UM_KERN_ERR "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)) printk(UM_KERN_ERR "winch_thread : write failed, " "err = %d\n", errno); } } 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); } }