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 Sets *FD to -1 if the fd is no longer readable or writable. */
82 handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call)
90 /* Slave side of pty has been closed. */
109 /* Copies data from stdin to PTY and from PTY to stdout until no
110 more data can be read or written. */
112 relay (int pty, int dead_child_fd)
121 struct pipe pipes[2];
123 /* Make PTY, stdin, and stdout non-blocking. */
124 make_nonblocking (pty, true);
125 make_nonblocking (STDIN_FILENO, true);
126 make_nonblocking (STDOUT_FILENO, true);
128 /* Configure noncanonical mode on PTY and stdin to avoid
129 waiting for end-of-line. We want to minimize context
130 switching on PTY (for efficiency) and minimize latency on
131 stdin to avoid a laggy user experience. */
132 make_noncanon (pty, 16, 1);
133 make_noncanon (STDIN_FILENO, 1, 0);
135 memset (pipes, 0, sizeof pipes);
136 pipes[0].in = STDIN_FILENO;
139 pipes[1].out = STDOUT_FILENO;
141 while (pipes[1].in != -1)
143 fd_set read_fds, write_fds;
148 FD_ZERO (&write_fds);
149 for (i = 0; i < 2; i++)
151 struct pipe *p = &pipes[i];
153 /* Don't do anything with the stdin->pty pipe until we
154 have some data for the pty->stdout pipe. If we get
155 too eager, Bochs will throw away our input. */
156 if (i == 0 && !pipes[1].active)
159 if (p->in != -1 && p->size + p->ofs < sizeof p->buf)
160 FD_SET (p->in, &read_fds);
161 if (p->out != -1 && p->size > 0)
162 FD_SET (p->out, &write_fds);
164 FD_SET (dead_child_fd, &read_fds);
168 retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL);
170 while (retval < 0 && errno == EINTR);
174 if (FD_ISSET (dead_child_fd, &read_fds))
177 for (i = 0; i < 2; i++)
179 struct pipe *p = &pipes[i];
180 if (p->in != -1 && FD_ISSET (p->in, &read_fds))
182 ssize_t n = read (p->in, p->buf + p->ofs + p->size,
183 sizeof p->buf - p->ofs - p->size);
188 if (p->size == BUFSIZ && p->ofs != 0)
190 memmove (p->buf, p->buf + p->ofs, p->size);
195 handle_error (n, &p->in, p->in == pty, "read");
197 if (p->out != -1 && FD_ISSET (p->out, &write_fds))
199 ssize_t n = write (p->out, p->buf + p->ofs, p->size);
208 handle_error (n, &p->out, p->out == pty, "write");
213 if (pipes[1].out == -1)
216 make_nonblocking (STDOUT_FILENO, false);
219 struct pipe *p = &pipes[1];
225 n = write (p->out, p->buf + p->ofs, p->size);
229 fail_io ("zero-length write");
235 p->size = n = read (p->in, p->buf, sizeof p->buf);
241 static int dead_child_fd;
244 sigchld_handler (int signo __attribute__ ((unused)))
246 if (write (dead_child_fd, "", 1) < 0)
251 main (int argc __attribute__ ((unused)), char *argv[])
258 struct itimerval zero_itimerval, old_itimerval;
263 "usage: squish-pty COMMAND [ARG]...\n"
264 "Squishes both stdin and stdout into a single pseudoterminal,\n"
265 "which is passed as stdout to run the specified COMMAND.\n");
269 /* Open master side of pty and get ready to open slave. */
270 master = open ("/dev/ptmx", O_RDWR | O_NOCTTY);
272 fail_io ("open \"/dev/ptmx\"");
273 if (grantpt (master) < 0)
275 if (unlockpt (master) < 0)
276 fail_io ("unlockpt");
278 /* Open slave side of pty. */
279 name = ptsname (master);
282 slave = open (name, O_RDWR);
284 fail_io ("open \"%s\"", name);
286 /* System V implementations need STREAMS configuration for the
288 if (isastream (slave))
290 if (ioctl (slave, I_PUSH, "ptem") < 0
291 || ioctl (slave, I_PUSH, "ldterm") < 0)
295 /* Arrange to get notified when a child dies, by writing a byte
296 to a pipe fd. We really want to use pselect() and
297 sigprocmask(), but Solaris 2.7 doesn't have it. */
298 if (pipe (pipe_fds) < 0)
300 dead_child_fd = pipe_fds[1];
302 memset (&sa, 0, sizeof sa);
303 sa.sa_handler = sigchld_handler;
304 sigemptyset (&sa.sa_mask);
305 sa.sa_flags = SA_RESTART;
306 if (sigaction (SIGCHLD, &sa, NULL) < 0)
307 fail_io ("sigaction");
309 /* Save the virtual interval timer, which might have been set
310 by the process that ran us. It really should be applied to
311 our child process. */
312 memset (&zero_itimerval, 0, sizeof zero_itimerval);
313 if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, &old_itimerval) < 0)
314 fail_io ("setitimer");
321 /* Running in parent process. */
324 relay (master, pipe_fds[0]);
326 /* If the subprocess has died, die in the same fashion.
327 In particular, dying from SIGVTALRM tells the pintos
328 script that we ran out of CPU time. */
329 if (waitpid (pid, &status, WNOHANG) > 0)
331 if (WIFEXITED (status))
332 return WEXITSTATUS (status);
333 else if (WIFSIGNALED (status))
334 raise (WTERMSIG (status));
340 /* Running in child process. */
341 if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0)
342 fail_io ("setitimer");
343 if (dup2 (slave, STDOUT_FILENO) < 0)
345 if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0
346 || close (slave) < 0 || close (master) < 0)
348 execvp (argv[1], argv + 1);