PKUOS - Pintos
Pintos source browser for PKU Operating System 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
19static void
20fail_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. */
27static void
28fail_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. */
45static void
46make_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. */
63static void
64make_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. */
81static void
82handle_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. */
111static void
112relay (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);
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
241static int dead_child_fd;
242
243static void
244sigchld_handler (int signo __attribute__ ((unused)))
245{
246 if (write (dead_child_fd, "", 1) < 0)
247 _exit (1);
248}
249
250int
251main (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}
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 ...
static char buf[BUF_SIZE]
char * name[]
Definition: insult.c:47
int printf(const char *format,...)
Writes formatted output to the console.
Definition: stdio.c:79
void exit(int status)
Definition: syscall.c:72
void close(int fd)
Definition: syscall.c:139
int open(const char *file)
Definition: syscall.c:103
int write(int fd, const void *buffer, unsigned size)
Definition: syscall.c:121
int read(int fd, void *buffer, unsigned size)
Definition: syscall.c:115
int pid_t
Process identifier.
Definition: syscall.h:8
#define EXIT_FAILURE
Unsuccessful execution.
Definition: syscall.h:20
void msg(const char *format,...)
Definition: lib.c:28
static int dead_child_fd
Definition: squish-pty.c:241
static void sigchld_handler(int signo __attribute__((unused)))
Definition: squish-pty.c:244
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
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
int main(int argc __attribute__((unused)), char *argv[])
Definition: squish-pty.c:251
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
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
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
#define va_end(LIST)
Definition: stdarg.h:10
#define va_start(LIST, ARG)
Definition: stdarg.h:9
__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
#define NULL
Definition: stddef.h:4
#define STDOUT_FILENO
Definition: stdio.h:16
#define STDIN_FILENO
Include lib/user/stdio.h or lib/kernel/stdio.h, as appropriate.
Definition: stdio.h:15
void * memset(void *dst_, int value, size_t size)
Sets the SIZE bytes in DST to VALUE.
Definition: string.c:279
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