11 #include <sys/ioctl.h>
14 #include <sys/types.h>
20 fail_io (const char *msg, ...)
21 __attribute__ ((noreturn))
22 __attribute__ ((format (printf, 1, 2)));
24 /* Prints MSG, formatting as with printf(),
25 plus an error message based on errno,
28 fail_io (const char *msg, ...)
33 vfprintf (stderr, msg, args);
37 fprintf (stderr, ": %s", strerror (errno));
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. */
46 make_noncanon (int fd, int vmin, int vtime)
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");
61 /* Make FD non-blocking if NONBLOCKING is true,
62 or blocking if NONBLOCKING is false. */
64 make_nonblocking (int fd, bool nonblocking)
66 int flags = fcntl (fd, F_GETFL);
73 if (fcntl (fd, F_SETFL, flags) < 0)
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
83 handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call)
91 /* Slave side of pty has been closed. */
113 /* Copies data from stdin to PTY and from PTY to stdout until no
114 more data can be read or written. */
116 relay (int pty, int dead_child_fd)
125 struct pipe pipes[2];
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);
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);
139 memset (pipes, 0, sizeof pipes);
140 pipes[0].in = STDIN_FILENO;
143 pipes[1].out = STDOUT_FILENO;
145 while (pipes[0].in != -1 || pipes[1].in != -1)
147 fd_set read_fds, write_fds;
152 FD_ZERO (&write_fds);
153 for (i = 0; i < 2; i++)
155 struct pipe *p = &pipes[i];
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)
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);
168 FD_SET (dead_child_fd, &read_fds);
172 retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL);
174 while (retval < 0 && errno == EINTR);
178 if (FD_ISSET (dead_child_fd, &read_fds))
180 /* Child died. Do final relaying. */
181 struct pipe *p = &pipes[1];
184 make_nonblocking (STDOUT_FILENO, false);
192 n = write (p->out, p->buf + p->ofs, p->size);
196 fail_io ("zero-length write");
202 p->size = n = read (p->in, p->buf, sizeof p->buf);
208 for (i = 0; i < 2; i++)
210 struct pipe *p = &pipes[i];
211 if (p->in != -1 && FD_ISSET (p->in, &read_fds))
213 ssize_t n = read (p->in, p->buf + p->ofs + p->size,
214 sizeof p->buf - p->ofs - p->size);
219 if (p->size == BUFSIZ && p->ofs != 0)
221 memmove (p->buf, p->buf + p->ofs, p->size);
225 else if (!handle_error (n, &p->in, p->in == pty, "read"))
228 if (p->out != -1 && FD_ISSET (p->out, &write_fds))
230 ssize_t n = write (p->out, p->buf + p->ofs, p->size);
238 else if (!handle_error (n, &p->out, p->out == pty, "write"))
245 static int dead_child_fd;
248 sigchld_handler (int signo __attribute__ ((unused)))
250 if (write (dead_child_fd, "", 1) < 0)
255 main (int argc __attribute__ ((unused)), char *argv[])
262 struct itimerval zero_itimerval, old_itimerval;
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");
273 /* Open master side of pty and get ready to open slave. */
274 master = open ("/dev/ptmx", O_RDWR | O_NOCTTY);
276 fail_io ("open \"/dev/ptmx\"");
277 if (grantpt (master) < 0)
279 if (unlockpt (master) < 0)
280 fail_io ("unlockpt");
282 /* Open slave side of pty. */
283 name = ptsname (master);
286 slave = open (name, O_RDWR);
288 fail_io ("open \"%s\"", name);
290 /* System V implementations need STREAMS configuration for the
292 if (isastream (slave))
294 if (ioctl (slave, I_PUSH, "ptem") < 0
295 || ioctl (slave, I_PUSH, "ldterm") < 0)
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)
304 dead_child_fd = pipe_fds[1];
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");
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");
325 /* Running in parent process. */
328 relay (master, pipe_fds[0]);
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)
335 if (WIFEXITED (status))
336 return WEXITSTATUS (status);
337 else if (WIFSIGNALED (status))
338 raise (WTERMSIG (status));
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)
349 if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0
350 || close (slave) < 0 || close (master) < 0)
352 execvp (argv[1], argv + 1);