|
| 1 | +#include "AcodeAlpine.h" |
| 2 | +#include <stdio.h> |
| 3 | +#include <stdlib.h> |
| 4 | +#include "kernel/errno.h" |
| 5 | +#include "kernel/init.h" |
| 6 | +#include "kernel/signal.h" |
| 7 | +#include "kernel/task.h" |
| 8 | +#include "fs/devices.h" |
| 9 | +#include "fs/tty.h" |
| 10 | +#include "AlpineSpawn.h" |
| 11 | + |
| 12 | +// The guest side is a regular /dev/pts slave, so shells get job control, |
| 13 | +// termios and SIGWINCH. The host side is this driver instead of a pty master, |
| 14 | +// which keeps terminal I/O out of emulated sockets and polling. |
| 15 | +struct AlpineTerminal { |
| 16 | + struct tty *tty; |
| 17 | + int rows; |
| 18 | + int cols; |
| 19 | + void *context; |
| 20 | + AlpineTerminalCallbacks callbacks; |
| 21 | + lock_t lock; |
| 22 | + cond_t drained; |
| 23 | + bool closed; |
| 24 | +}; |
| 25 | + |
| 26 | +static int terminal_write(struct tty *tty, const void *data, size_t length, bool blocking); |
| 27 | +static void terminal_cleanup(struct tty *tty); |
| 28 | +static int attach_terminal(void *argument); |
| 29 | + |
| 30 | +static const struct tty_driver_ops terminal_ops = { |
| 31 | + .write = terminal_write, |
| 32 | + .cleanup = terminal_cleanup, |
| 33 | +}; |
| 34 | +// pty_open_fake fills in the slot table and major number. |
| 35 | +static struct tty_driver terminal_driver = {.ops = &terminal_ops}; |
| 36 | + |
| 37 | +int alpine_terminal_start(const char *command, const char *environment, int rows, int cols, |
| 38 | + void *context, AlpineTerminalCallbacks callbacks, AlpineTerminal **handle) { |
| 39 | + *handle = NULL; |
| 40 | + AlpineTerminal *terminal = calloc(1, sizeof(AlpineTerminal)); |
| 41 | + if (terminal == NULL) { callbacks.release(context); return _ENOMEM; } |
| 42 | + *terminal = (AlpineTerminal) {.rows = rows, .cols = cols, .context = context, .callbacks = callbacks}; |
| 43 | + lock_init(&terminal->lock); |
| 44 | + cond_init(&terminal->drained); |
| 45 | + int pid = alpine_spawn(command, environment, attach_terminal, terminal); |
| 46 | + if (terminal->tty == NULL) { |
| 47 | + callbacks.release(context); |
| 48 | + cond_destroy(&terminal->drained); |
| 49 | + free(terminal); |
| 50 | + } else if (pid < 0) { |
| 51 | + alpine_terminal_close(terminal); |
| 52 | + } else { |
| 53 | + *handle = terminal; |
| 54 | + } |
| 55 | + return pid; |
| 56 | +} |
| 57 | + |
| 58 | +long alpine_terminal_input(AlpineTerminal *terminal, const char *data, size_t length) { |
| 59 | + return tty_input(terminal->tty, data, length, false); |
| 60 | +} |
| 61 | + |
| 62 | +void alpine_terminal_resize(AlpineTerminal *terminal, int rows, int cols) { |
| 63 | + struct tty *tty = terminal->tty; |
| 64 | + lock(&tty->lock); |
| 65 | + tty->winsize = (struct winsize_) {.row = rows, .col = cols}; |
| 66 | + pid_t_ foreground = tty->fg_group; |
| 67 | + unlock(&tty->lock); |
| 68 | + if (foreground != 0) send_group_signal(foreground, SIGWINCH_, SIGINFO_NIL); |
| 69 | +} |
| 70 | + |
| 71 | +void alpine_terminal_drained(AlpineTerminal *terminal) { |
| 72 | + lock(&terminal->lock); |
| 73 | + notify(&terminal->drained); |
| 74 | + unlock(&terminal->lock); |
| 75 | +} |
| 76 | + |
| 77 | +void alpine_terminal_close(AlpineTerminal *terminal) { |
| 78 | + lock(&terminal->lock); |
| 79 | + terminal->closed = true; |
| 80 | + notify(&terminal->drained); |
| 81 | + unlock(&terminal->lock); |
| 82 | + struct tty *tty = terminal->tty; |
| 83 | + lock(&tty->lock); |
| 84 | + pid_t_ session = tty->session; |
| 85 | + pid_t_ foreground = tty->fg_group; |
| 86 | + tty_hangup(tty); |
| 87 | + unlock(&tty->lock); |
| 88 | + // Matches Linux when the master side of a pty goes away. |
| 89 | + if (foreground != 0) send_group_signal(foreground, SIGHUP_, SIGINFO_NIL); |
| 90 | + if (session != 0 && session != foreground) send_group_signal(session, SIGHUP_, SIGINFO_NIL); |
| 91 | + lock(&ttys_lock); |
| 92 | + tty_release(tty); |
| 93 | + unlock(&ttys_lock); |
| 94 | +} |
| 95 | + |
| 96 | +// Runs on guest threads. The tty lock may be held (echo), so it must not re-enter the tty. |
| 97 | +// A blocking writer waits like on a full pipe while the client is behind. wait_for |
| 98 | +// returns _EINTR for pending guest signals, so Ctrl-C and SIGKILL still get through, |
| 99 | +// and nothing has been written yet when it does. |
| 100 | +static int terminal_write(struct tty *tty, const void *data, size_t length, bool blocking) { |
| 101 | + AlpineTerminal *terminal = tty->data; |
| 102 | + if (blocking) { |
| 103 | + int result = 0; |
| 104 | + lock(&terminal->lock); |
| 105 | + while (result == 0 && !terminal->closed && terminal->callbacks.congested(terminal->context)) { |
| 106 | + struct timespec recheck = {.tv_nsec = 100 * 1000 * 1000}; |
| 107 | + result = wait_for(&terminal->drained, &terminal->lock, &recheck); |
| 108 | + if (result == _ETIMEDOUT) result = 0; |
| 109 | + } |
| 110 | + unlock(&terminal->lock); |
| 111 | + if (result < 0) return result; |
| 112 | + } |
| 113 | + terminal->callbacks.output(terminal->context, data, length); |
| 114 | + return 0; |
| 115 | +} |
| 116 | + |
| 117 | +// Runs once the host and every guest descriptor have released the tty. |
| 118 | +static void terminal_cleanup(struct tty *tty) { |
| 119 | + AlpineTerminal *terminal = tty->data; |
| 120 | + terminal->callbacks.release(terminal->context); |
| 121 | + cond_destroy(&terminal->drained); |
| 122 | + free(terminal); |
| 123 | +} |
| 124 | + |
| 125 | +// Runs as the new session leader, so opening the slave makes it the controlling tty. |
| 126 | +static int attach_terminal(void *argument) { |
| 127 | + AlpineTerminal *terminal = argument; |
| 128 | + struct tty *tty = pty_open_fake(&terminal_driver); |
| 129 | + if (IS_ERR(tty)) return (int) PTR_ERR(tty); |
| 130 | + tty->data = terminal; |
| 131 | + tty->winsize = (struct winsize_) {.row = terminal->rows, .col = terminal->cols}; |
| 132 | + terminal->tty = tty; |
| 133 | + char path[32]; |
| 134 | + snprintf(path, sizeof(path), "/dev/pts/%d", tty->num); |
| 135 | + return create_stdio(path, TTY_PSEUDO_SLAVE_MAJOR, tty->num); |
| 136 | +} |
0 commit comments