diff options
Diffstat (limited to 'utils/squish-pty.c')
| -rw-r--r-- | utils/squish-pty.c | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/utils/squish-pty.c b/utils/squish-pty.c new file mode 100644 index 0000000..c8375a5 --- /dev/null +++ b/utils/squish-pty.c | |||
| @@ -0,0 +1,355 @@ | |||
| 1 | #define _GNU_SOURCE 1 | ||
| 2 | #include <errno.h> | ||
| 3 | #include <fcntl.h> | ||
| 4 | #include <signal.h> | ||
| 5 | #include <stdarg.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | #include <stropts.h> | ||
| 11 | #include <sys/ioctl.h> | ||
| 12 | #include <sys/stat.h> | ||
| 13 | #include <sys/time.h> | ||
| 14 | #include <sys/types.h> | ||
| 15 | #include <sys/wait.h> | ||
| 16 | #include <termios.h> | ||
| 17 | #include <unistd.h> | ||
| 18 | |||
| 19 | static void | ||
| 20 | fail_io (const char *msg, ...) | ||
| 21 | __attribute__ ((noreturn)) | ||
| 22 | __attribute__ ((format (printf, 1, 2))); | ||
| 23 | |||
| 24 | /* Prints MSG, formatting as with printf(), | ||
| 25 | plus an error message based on errno, | ||
| 26 | and exits. */ | ||
| 27 | static void | ||
| 28 | fail_io (const char *msg, ...) | ||
| 29 | { | ||
| 30 | va_list args; | ||
| 31 | |||
| 32 | va_start (args, msg); | ||
| 33 | vfprintf (stderr, msg, args); | ||
| 34 | va_end (args); | ||
| 35 | |||
| 36 | if (errno != 0) | ||
| 37 | fprintf (stderr, ": %s", strerror (errno)); | ||
| 38 | putc ('\n', stderr); | ||
| 39 | exit (EXIT_FAILURE); | ||
| 40 | } | ||
| 41 | |||
| 42 | /* If FD is a terminal, configures it for noncanonical input mode | ||
| 43 | with VMIN and VTIME set as indicated. | ||
| 44 | If FD is not a terminal, has no effect. */ | ||
| 45 | static void | ||
| 46 | make_noncanon (int fd, int vmin, int vtime) | ||
| 47 | { | ||
| 48 | if (isatty (fd)) | ||
| 49 | { | ||
| 50 | struct termios termios; | ||
| 51 | if (tcgetattr (fd, &termios) < 0) | ||
| 52 | fail_io ("tcgetattr"); | ||
| 53 | termios.c_lflag &= ~(ICANON | ECHO); | ||
| 54 | termios.c_cc[VMIN] = vmin; | ||
| 55 | termios.c_cc[VTIME] = vtime; | ||
| 56 | if (tcsetattr (fd, TCSANOW, &termios) < 0) | ||
| 57 | fail_io ("tcsetattr"); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | /* Make FD non-blocking if NONBLOCKING is true, | ||
| 62 | or blocking if NONBLOCKING is false. */ | ||
| 63 | static void | ||
| 64 | make_nonblocking (int fd, bool nonblocking) | ||
| 65 | { | ||
| 66 | int flags = fcntl (fd, F_GETFL); | ||
| 67 | if (flags < 0) | ||
| 68 | fail_io ("fcntl"); | ||
| 69 | if (nonblocking) | ||
| 70 | flags |= O_NONBLOCK; | ||
| 71 | else | ||
| 72 | flags &= ~O_NONBLOCK; | ||
| 73 | if (fcntl (fd, F_SETFL, flags) < 0) | ||
| 74 | fail_io ("fcntl"); | ||
| 75 | } | ||
| 76 | |||
| 77 | /* Handle a read or write on *FD, which is the pty if FD_IS_PTY | ||
| 78 | is true, that returned end-of-file or error indication RETVAL. | ||
| 79 | The system call is named CALL, for use in error messages. | ||
| 80 | Returns true if processing may continue, false if we're all | ||
| 81 | done. */ | ||
| 82 | static bool | ||
| 83 | handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call) | ||
| 84 | { | ||
| 85 | if (fd_is_pty) | ||
| 86 | { | ||
| 87 | if (retval < 0) | ||
| 88 | { | ||
| 89 | if (errno == EIO) | ||
| 90 | { | ||
| 91 | /* Slave side of pty has been closed. */ | ||
| 92 | return false; | ||
| 93 | } | ||
| 94 | else | ||
| 95 | fail_io (call); | ||
| 96 | } | ||
| 97 | else | ||
| 98 | return true; | ||
| 99 | } | ||
| 100 | else | ||
| 101 | { | ||
| 102 | if (retval == 0) | ||
| 103 | { | ||
| 104 | close (*fd); | ||
| 105 | *fd = -1; | ||
| 106 | return true; | ||
| 107 | } | ||
| 108 | else | ||
| 109 | fail_io (call); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | /* Copies data from stdin to PTY and from PTY to stdout until no | ||
| 114 | more data can be read or written. */ | ||
| 115 | static void | ||
| 116 | relay (int pty, int dead_child_fd) | ||
| 117 | { | ||
| 118 | struct pipe | ||
| 119 | { | ||
| 120 | int in, out; | ||
| 121 | char buf[BUFSIZ]; | ||
| 122 | size_t size, ofs; | ||
| 123 | bool active; | ||
| 124 | }; | ||
| 125 | struct pipe pipes[2]; | ||
| 126 | |||
| 127 | /* Make PTY, stdin, and stdout non-blocking. */ | ||
| 128 | make_nonblocking (pty, true); | ||
| 129 | make_nonblocking (STDIN_FILENO, true); | ||
| 130 | make_nonblocking (STDOUT_FILENO, true); | ||
| 131 | |||
| 132 | /* Configure noncanonical mode on PTY and stdin to avoid | ||
| 133 | waiting for end-of-line. We want to minimize context | ||
| 134 | switching on PTY (for efficiency) and minimize latency on | ||
| 135 | stdin to avoid a laggy user experience. */ | ||
| 136 | make_noncanon (pty, 16, 1); | ||
| 137 | make_noncanon (STDIN_FILENO, 1, 0); | ||
| 138 | |||
| 139 | memset (pipes, 0, sizeof pipes); | ||
| 140 | pipes[0].in = STDIN_FILENO; | ||
| 141 | pipes[0].out = pty; | ||
| 142 | pipes[1].in = pty; | ||
| 143 | pipes[1].out = STDOUT_FILENO; | ||
| 144 | |||
| 145 | while (pipes[0].in != -1 || pipes[1].in != -1) | ||
| 146 | { | ||
| 147 | fd_set read_fds, write_fds; | ||
| 148 | int retval; | ||
| 149 | int i; | ||
| 150 | |||
| 151 | FD_ZERO (&read_fds); | ||
| 152 | FD_ZERO (&write_fds); | ||
| 153 | for (i = 0; i < 2; i++) | ||
| 154 | { | ||
| 155 | struct pipe *p = &pipes[i]; | ||
| 156 | |||
| 157 | /* Don't do anything with the stdin->pty pipe until we | ||
| 158 | have some data for the pty->stdout pipe. If we get | ||
| 159 | too eager, Bochs will throw away our input. */ | ||
| 160 | if (i == 0 && !pipes[1].active) | ||
| 161 | continue; | ||
| 162 | |||
| 163 | if (p->in != -1 && p->size + p->ofs < sizeof p->buf) | ||
| 164 | FD_SET (p->in, &read_fds); | ||
| 165 | if (p->out != -1 && p->size > 0) | ||
| 166 | FD_SET (p->out, &write_fds); | ||
| 167 | } | ||
| 168 | FD_SET (dead_child_fd, &read_fds); | ||
| 169 | |||
| 170 | do | ||
| 171 | { | ||
| 172 | retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL); | ||
| 173 | } | ||
| 174 | while (retval < 0 && errno == EINTR); | ||
| 175 | if (retval < 0) | ||
| 176 | fail_io ("select"); | ||
| 177 | |||
| 178 | if (FD_ISSET (dead_child_fd, &read_fds)) | ||
| 179 | { | ||
| 180 | /* Child died. Do final relaying. */ | ||
| 181 | struct pipe *p = &pipes[1]; | ||
| 182 | if (p->out == -1) | ||
| 183 | return; | ||
| 184 | make_nonblocking (STDOUT_FILENO, false); | ||
| 185 | for (;;) | ||
| 186 | { | ||
| 187 | ssize_t n; | ||
| 188 | |||
| 189 | /* Write buffer. */ | ||
| 190 | while (p->size > 0) | ||
| 191 | { | ||
| 192 | n = write (p->out, p->buf + p->ofs, p->size); | ||
| 193 | if (n < 0) | ||
| 194 | fail_io ("write"); | ||
| 195 | else if (n == 0) | ||
| 196 | fail_io ("zero-length write"); | ||
| 197 | p->ofs += n; | ||
| 198 | p->size -= n; | ||
| 199 | } | ||
| 200 | p->ofs = 0; | ||
| 201 | |||
| 202 | p->size = n = read (p->in, p->buf, sizeof p->buf); | ||
| 203 | if (n <= 0) | ||
| 204 | return; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | for (i = 0; i < 2; i++) | ||
| 209 | { | ||
| 210 | struct pipe *p = &pipes[i]; | ||
| 211 | if (p->in != -1 && FD_ISSET (p->in, &read_fds)) | ||
| 212 | { | ||
| 213 | ssize_t n = read (p->in, p->buf + p->ofs + p->size, | ||
| 214 | sizeof p->buf - p->ofs - p->size); | ||
| 215 | if (n > 0) | ||
| 216 | { | ||
| 217 | p->active = true; | ||
| 218 | p->size += n; | ||
| 219 | if (p->size == BUFSIZ && p->ofs != 0) | ||
| 220 | { | ||
| 221 | memmove (p->buf, p->buf + p->ofs, p->size); | ||
| 222 | p->ofs = 0; | ||
| 223 | } | ||
| 224 | } | ||
| 225 | else if (!handle_error (n, &p->in, p->in == pty, "read")) | ||
| 226 | return; | ||
| 227 | } | ||
| 228 | if (p->out != -1 && FD_ISSET (p->out, &write_fds)) | ||
| 229 | { | ||
| 230 | ssize_t n = write (p->out, p->buf + p->ofs, p->size); | ||
| 231 | if (n > 0) | ||
| 232 | { | ||
| 233 | p->ofs += n; | ||
| 234 | p->size -= n; | ||
| 235 | if (p->size == 0) | ||
| 236 | p->ofs = 0; | ||
| 237 | } | ||
| 238 | else if (!handle_error (n, &p->out, p->out == pty, "write")) | ||
| 239 | return; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | static int dead_child_fd; | ||
| 246 | |||
| 247 | static void | ||
| 248 | sigchld_handler (int signo __attribute__ ((unused))) | ||
| 249 | { | ||
| 250 | if (write (dead_child_fd, "", 1) < 0) | ||
| 251 | _exit (1); | ||
| 252 | } | ||
| 253 | |||
| 254 | int | ||
| 255 | main (int argc __attribute__ ((unused)), char *argv[]) | ||
| 256 | { | ||
| 257 | int master, slave; | ||
| 258 | char *name; | ||
| 259 | pid_t pid; | ||
| 260 | struct sigaction sa; | ||
| 261 | int pipe_fds[2]; | ||
| 262 | struct itimerval zero_itimerval, old_itimerval; | ||
| 263 | |||
| 264 | if (argc < 2) | ||
| 265 | { | ||
| 266 | fprintf (stderr, | ||
| 267 | "usage: squish-pty COMMAND [ARG]...\n" | ||
| 268 | "Squishes both stdin and stdout into a single pseudoterminal,\n" | ||
| 269 | "which is passed as stdout to run the specified COMMAND.\n"); | ||
| 270 | return EXIT_FAILURE; | ||
| 271 | } | ||
| 272 | |||
| 273 | /* Open master side of pty and get ready to open slave. */ | ||
| 274 | master = open ("/dev/ptmx", O_RDWR | O_NOCTTY); | ||
| 275 | if (master < 0) | ||
| 276 | fail_io ("open \"/dev/ptmx\""); | ||
| 277 | if (grantpt (master) < 0) | ||
| 278 | fail_io ("grantpt"); | ||
| 279 | if (unlockpt (master) < 0) | ||
| 280 | fail_io ("unlockpt"); | ||
| 281 | |||
| 282 | /* Open slave side of pty. */ | ||
| 283 | name = ptsname (master); | ||
| 284 | if (name == NULL) | ||
| 285 | fail_io ("ptsname"); | ||
| 286 | slave = open (name, O_RDWR); | ||
| 287 | if (slave < 0) | ||
| 288 | fail_io ("open \"%s\"", name); | ||
| 289 | |||
| 290 | /* System V implementations need STREAMS configuration for the | ||
| 291 | slave. */ | ||
| 292 | if (isastream (slave)) | ||
| 293 | { | ||
| 294 | if (ioctl (slave, I_PUSH, "ptem") < 0 | ||
| 295 | || ioctl (slave, I_PUSH, "ldterm") < 0) | ||
| 296 | fail_io ("ioctl"); | ||
| 297 | } | ||
| 298 | |||
| 299 | /* Arrange to get notified when a child dies, by writing a byte | ||
| 300 | to a pipe fd. We really want to use pselect() and | ||
| 301 | sigprocmask(), but Solaris 2.7 doesn't have it. */ | ||
| 302 | if (pipe (pipe_fds) < 0) | ||
| 303 | fail_io ("pipe"); | ||
| 304 | dead_child_fd = pipe_fds[1]; | ||
| 305 | |||
| 306 | memset (&sa, 0, sizeof sa); | ||
| 307 | sa.sa_handler = sigchld_handler; | ||
| 308 | sigemptyset (&sa.sa_mask); | ||
| 309 | sa.sa_flags = SA_RESTART; | ||
| 310 | if (sigaction (SIGCHLD, &sa, NULL) < 0) | ||
| 311 | fail_io ("sigaction"); | ||
| 312 | |||
| 313 | /* Save the virtual interval timer, which might have been set | ||
| 314 | by the process that ran us. It really should be applied to | ||
| 315 | our child process. */ | ||
| 316 | memset (&zero_itimerval, 0, sizeof zero_itimerval); | ||
| 317 | if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, &old_itimerval) < 0) | ||
| 318 | fail_io ("setitimer"); | ||
| 319 | |||
| 320 | pid = fork (); | ||
| 321 | if (pid < 0) | ||
| 322 | fail_io ("fork"); | ||
| 323 | else if (pid != 0) | ||
| 324 | { | ||
| 325 | /* Running in parent process. */ | ||
| 326 | int status; | ||
| 327 | close (slave); | ||
| 328 | relay (master, pipe_fds[0]); | ||
| 329 | |||
| 330 | /* If the subprocess has died, die in the same fashion. | ||
| 331 | In particular, dying from SIGVTALRM tells the pintos | ||
| 332 | script that we ran out of CPU time. */ | ||
| 333 | if (waitpid (pid, &status, WNOHANG) > 0) | ||
| 334 | { | ||
| 335 | if (WIFEXITED (status)) | ||
| 336 | return WEXITSTATUS (status); | ||
| 337 | else if (WIFSIGNALED (status)) | ||
| 338 | raise (WTERMSIG (status)); | ||
| 339 | } | ||
| 340 | return 0; | ||
| 341 | } | ||
| 342 | else | ||
| 343 | { | ||
| 344 | /* Running in child process. */ | ||
| 345 | if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0) | ||
| 346 | fail_io ("setitimer"); | ||
| 347 | if (dup2 (slave, STDOUT_FILENO) < 0) | ||
| 348 | fail_io ("dup2"); | ||
| 349 | if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0 | ||
| 350 | || close (slave) < 0 || close (master) < 0) | ||
| 351 | fail_io ("close"); | ||
| 352 | execvp (argv[1], argv + 1); | ||
| 353 | fail_io ("exec"); | ||
| 354 | } | ||
| 355 | } | ||
