CS318 - Pintos
Pintos source browser for JHU CS318 course
squish-pty.c
Go to the documentation of this file.
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);
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  Sets *FD to -1 if the fd is no longer readable or writable. */
81 static void
82 handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call)
83 {
84  if (fd_is_pty)
85  {
86  if (retval < 0)
87  {
88  if (errno == EIO)
89  {
90  /* Slave side of pty has been closed. */
91  *fd = -1;
92  }
93  else
94  fail_io ("%s", call);
95  }
96  }
97  else
98  {
99  if (retval == 0)
100  {
101  close (*fd);
102  *fd = -1;
103  }
104  else
105  fail_io ("%s", call);
106  }
107 }
108 
109 /** Copies data from stdin to PTY and from PTY to stdout until no
110  more data can be read or written. */
111 static void
112 relay (int pty, int dead_child_fd)
113 {
114  struct pipe
115  {
116  int in, out;
117  char buf[BUFSIZ];
118  size_t size, ofs;
119  bool active;
120  };
121  struct pipe pipes[2];
122 
123  /* Make PTY, stdin, and stdout non-blocking. */
124  make_nonblocking (pty, true);
127 
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);
134 
135  memset (pipes, 0, sizeof pipes);
136  pipes[0].in = STDIN_FILENO;
137  pipes[0].out = pty;
138  pipes[1].in = pty;
139  pipes[1].out = STDOUT_FILENO;
140 
141  while (pipes[1].in != -1)
142  {
143  fd_set read_fds, write_fds;
144  int retval;
145  int i;
146 
147  FD_ZERO (&read_fds);
148  FD_ZERO (&write_fds);
149  for (i = 0; i < 2; i++)
150  {
151  struct pipe *p = &pipes[i];
152 
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)
157  continue;
158 
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);
163  }
164  FD_SET (dead_child_fd, &read_fds);
165 
166  do
167  {
168  retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL);
169  }
170  while (retval < 0 && errno == EINTR);
171  if (retval < 0)
172  fail_io ("select");
173 
174  if (FD_ISSET (dead_child_fd, &read_fds))
175  break;
176 
177  for (i = 0; i < 2; i++)
178  {
179  struct pipe *p = &pipes[i];
180  if (p->in != -1 && FD_ISSET (p->in, &read_fds))
181  {
182  ssize_t n = read (p->in, p->buf + p->ofs + p->size,
183  sizeof p->buf - p->ofs - p->size);
184  if (n > 0)
185  {
186  p->active = true;
187  p->size += n;
188  if (p->size == BUFSIZ && p->ofs != 0)
189  {
190  memmove (p->buf, p->buf + p->ofs, p->size);
191  p->ofs = 0;
192  }
193  }
194  else
195  handle_error (n, &p->in, p->in == pty, "read");
196  }
197  if (p->out != -1 && FD_ISSET (p->out, &write_fds))
198  {
199  ssize_t n = write (p->out, p->buf + p->ofs, p->size);
200  if (n > 0)
201  {
202  p->ofs += n;
203  p->size -= n;
204  if (p->size == 0)
205  p->ofs = 0;
206  }
207  else
208  handle_error (n, &p->out, p->out == pty, "write");
209  }
210  }
211  }
212 
213  if (pipes[1].out == -1)
214  return;
215 
217  for (;;)
218  {
219  struct pipe *p = &pipes[1];
220  ssize_t n;
221 
222  /* Write buffer. */
223  while (p->size > 0)
224  {
225  n = write (p->out, p->buf + p->ofs, p->size);
226  if (n < 0)
227  fail_io ("write");
228  else if (n == 0)
229  fail_io ("zero-length write");
230  p->ofs += n;
231  p->size -= n;
232  }
233  p->ofs = 0;
234 
235  p->size = n = read (p->in, p->buf, sizeof p->buf);
236  if (n <= 0)
237  return;
238  }
239 }
240 
241 static int dead_child_fd;
242 
243 static void
244 sigchld_handler (int signo __attribute__ ((unused)))
245 {
246  if (write (dead_child_fd, "", 1) < 0)
247  _exit (1);
248 }
249 
250 int
251 main (int argc __attribute__ ((unused)), char *argv[])
252 {
253  int master, slave;
254  char *name;
255  pid_t pid;
256  struct sigaction sa;
257  int pipe_fds[2];
258  struct itimerval zero_itimerval, old_itimerval;
259 
260  if (argc < 2)
261  {
262  fprintf (stderr,
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");
266  return EXIT_FAILURE;
267  }
268 
269  /* Open master side of pty and get ready to open slave. */
270  master = open ("/dev/ptmx", O_RDWR | O_NOCTTY);
271  if (master < 0)
272  fail_io ("open \"/dev/ptmx\"");
273  if (grantpt (master) < 0)
274  fail_io ("grantpt");
275  if (unlockpt (master) < 0)
276  fail_io ("unlockpt");
277 
278  /* Open slave side of pty. */
279  name = ptsname (master);
280  if (name == NULL)
281  fail_io ("ptsname");
282  slave = open (name, O_RDWR);
283  if (slave < 0)
284  fail_io ("open \"%s\"", name);
285 
286  /* System V implementations need STREAMS configuration for the
287  slave. */
288  /*
289  if (isastream (slave))
290  {
291  if (ioctl (slave, I_PUSH, "ptem") < 0
292  || ioctl (slave, I_PUSH, "ldterm") < 0)
293  fail_io ("ioctl");
294  }
295  */
296 
297  /* Arrange to get notified when a child dies, by writing a byte
298  to a pipe fd. We really want to use pselect() and
299  sigprocmask(), but Solaris 2.7 doesn't have it. */
300  if (pipe (pipe_fds) < 0)
301  fail_io ("pipe");
302  dead_child_fd = pipe_fds[1];
303 
304  memset (&sa, 0, sizeof sa);
305  sa.sa_handler = sigchld_handler;
306  sigemptyset (&sa.sa_mask);
307  sa.sa_flags = SA_RESTART;
308  if (sigaction (SIGCHLD, &sa, NULL) < 0)
309  fail_io ("sigaction");
310 
311  /* Save the virtual interval timer, which might have been set
312  by the process that ran us. It really should be applied to
313  our child process. */
314  memset (&zero_itimerval, 0, sizeof zero_itimerval);
315  if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, &old_itimerval) < 0)
316  fail_io ("setitimer");
317 
318  pid = fork ();
319  if (pid < 0)
320  fail_io ("fork");
321  else if (pid != 0)
322  {
323  /* Running in parent process. */
324  int status;
325  close (slave);
326  relay (master, pipe_fds[0]);
327 
328  /* If the subprocess has died, die in the same fashion.
329  In particular, dying from SIGVTALRM tells the pintos
330  script that we ran out of CPU time. */
331  if (waitpid (pid, &status, WNOHANG) > 0)
332  {
333  if (WIFEXITED (status))
334  return WEXITSTATUS (status);
335  else if (WIFSIGNALED (status))
336  raise (WTERMSIG (status));
337  }
338  return 0;
339  }
340  else
341  {
342  /* huang@cs.jhu.edu -
343  * On recent BSD system, the first setitimer call seems to have an integer
344  * overflow bug that would cause tv_usec field of the old_itmerval to be
345  * either negative or greater than 999999, which would then call the second
346  * setitimer call to fail with EINVAL. Fix by do a sanity check first. We
347  * set the tv_usec to 999999 in both invalid conditions.
348  */
349  if (old_itimerval.it_value.tv_usec < 0 || old_itimerval.it_value.tv_usec > 999999) {
350  old_itimerval.it_value.tv_usec = 999999;
351  }
352  if (old_itimerval.it_interval.tv_usec < 0 || old_itimerval.it_interval.tv_usec > 999999) {
353  old_itimerval.it_interval.tv_usec = 999999;
354  }
355  /* Running in child process. */
356  if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0)
357  fail_io ("setitimer-child");
358  if (dup2 (slave, STDOUT_FILENO) < 0)
359  fail_io ("dup2");
360  if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0
361  || close (slave) < 0 || close (master) < 0)
362  fail_io ("close");
363  execvp (argv[1], argv + 1);
364  fail_io ("exec");
365  }
366 }
name
char * name[]
Definition: insult.c:47
make_nonblocking
static void make_nonblocking(int fd, bool nonblocking)
Make FD non-blocking if NONBLOCKING is true, or blocking if NONBLOCKING is false.
Definition: squish-pty.c:64
STDIN_FILENO
#define STDIN_FILENO
Include lib/user/stdio.h or lib/kernel/stdio.h, as appropriate.
Definition: stdio.h:15
va_end
#define va_end(LIST)
Definition: stdarg.h:10
sigchld_handler
static void sigchld_handler(int signo __attribute__((unused)))
Definition: squish-pty.c:244
STDOUT_FILENO
#define STDOUT_FILENO
Definition: stdio.h:16
va_start
#define va_start(LIST, ARG)
Definition: stdarg.h:9
dead_child_fd
static int dead_child_fd
Definition: squish-pty.c:241
NULL
#define NULL
Definition: stddef.h:4
string.h
buf
static char buf[BUF_SIZE]
Definition: child-syn-read.c:16
relay
static void relay(int pty, int dead_child_fd)
Copies data from stdin to PTY and from PTY to stdout until no more data can be read or written.
Definition: squish-pty.c:112
main
int main(int argc __attribute__((unused)), char *argv[])
Definition: squish-pty.c:251
fail_io
static void fail_io(const char *msg,...) __attribute__((noreturn)) __attribute__((format(printf
Prints MSG, formatting as with printf(), plus an error message based on errno, and exits.
Definition: squish-pty.c:28
write
int write(int fd, const void *buffer, unsigned size)
Definition: syscall.c:121
stdbool.h
memmove
void * memmove(void *dst_, const void *src_, size_t size)
Copies SIZE bytes from SRC to DST, which are allowed to overlap.
Definition: string.c:24
EXIT_FAILURE
#define EXIT_FAILURE
Unsuccessful execution.
Definition: syscall.h:20
stdarg.h
printf
int printf(const char *format,...)
Writes formatted output to the console.
Definition: stdio.c:79
open
int open(const char *file)
Definition: syscall.c:103
va_list
__builtin_va_list va_list
GCC has <stdarg.h> functionality as built-ins, so all we need is to use it.
Definition: stdarg.h:7
__attribute__
static char dst[8192] __attribute__((section(".testEndmem,\"aw\",@nobits#")))
Utility function for tests that try to break system calls by passing them data that crosses from one ...
handle_error
static void handle_error(ssize_t retval, int *fd, bool fd_is_pty, const char *call)
Handle a read or write on *FD, which is the pty if FD_IS_PTY is true, that returned end-of-file or er...
Definition: squish-pty.c:82
pid_t
int pid_t
Process identifier.
Definition: syscall.h:8
close
void close(int fd)
Definition: syscall.c:139
msg
void msg(const char *format,...)
Definition: lib.c:28
memset
void * memset(void *dst_, int value, size_t size)
Sets the SIZE bytes in DST to VALUE.
Definition: string.c:279
make_noncanon
static void make_noncanon(int fd, int vmin, int vtime)
If FD is a terminal, configures it for noncanonical input mode with VMIN and VTIME set as indicated.
Definition: squish-pty.c:46
stdlib.h
exit
void exit(int status)
Definition: syscall.c:72
read
int read(int fd, void *buffer, unsigned size)
Definition: syscall.c:115