]>
Commit | Line | Data |
---|---|---|
6954895c KZ |
1 | /* |
2 | * This is pseudo-terminal container for child process where parent creates a | |
3 | * proxy between the current std{in,out,etrr} and the child's pty. Advantages: | |
4 | * | |
5 | * - child has no access to parent's terminal (e.g. su --pty) | |
6 | * - parent can log all traffic between user and child's terminall (e.g. script(1)) | |
7 | * - it's possible to start commands on terminal although parent has no terminal | |
8 | * | |
9 | * This code is in the public domain; do with it what you wish. | |
10 | * | |
11 | * Written by Karel Zak <kzak@redhat.com> in Jul 2019 | |
12 | */ | |
13 | #include <stdio.h> | |
14 | #include <stdlib.h> | |
15 | #include <pty.h> | |
16 | #include <poll.h> | |
17 | #include <sys/signalfd.h> | |
18 | #include <paths.h> | |
19 | #include <sys/types.h> | |
20 | #include <sys/wait.h> | |
21 | ||
22 | #include "c.h" | |
23 | #include "all-io.h" | |
24 | #include "ttyutils.h" | |
25 | #include "pty-session.h" | |
4d5b2fed | 26 | #include "monotonic.h" |
6954895c KZ |
27 | #include "debug.h" |
28 | ||
29 | static UL_DEBUG_DEFINE_MASK(ulpty); | |
30 | UL_DEBUG_DEFINE_MASKNAMES(ulpty) = UL_DEBUG_EMPTY_MASKNAMES; | |
31 | ||
32 | #define ULPTY_DEBUG_INIT (1 << 1) | |
33 | #define ULPTY_DEBUG_SETUP (1 << 2) | |
34 | #define ULPTY_DEBUG_SIG (1 << 3) | |
35 | #define ULPTY_DEBUG_IO (1 << 4) | |
36 | #define ULPTY_DEBUG_DONE (1 << 5) | |
37 | #define ULPTY_DEBUG_ALL 0xFFFF | |
38 | ||
39 | #define DBG(m, x) __UL_DBG(ulpty, ULPTY_DEBUG_, m, x) | |
40 | #define ON_DBG(m, x) __UL_DBG_CALL(ulpty, ULPTY_DEBUG_, m, x) | |
41 | ||
42 | #define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpty) | |
43 | #include "debugobj.h" | |
44 | ||
45 | void ul_pty_init_debug(int mask) | |
46 | { | |
47 | if (ulpty_debug_mask) | |
48 | return; | |
49 | __UL_INIT_DEBUG_FROM_ENV(ulpty, ULPTY_DEBUG_, mask, ULPTY_DEBUG); | |
50 | } | |
51 | ||
52 | struct ul_pty *ul_new_pty(int is_stdin_tty) | |
53 | { | |
54 | struct ul_pty *pty = calloc(1, sizeof(*pty)); | |
55 | ||
56 | if (!pty) | |
57 | return NULL; | |
58 | ||
59 | DBG(SETUP, ul_debugobj(pty, "alloc handler")); | |
60 | pty->isterm = is_stdin_tty; | |
61 | pty->master = -1; | |
62 | pty->slave = -1; | |
63 | pty->sigfd = -1; | |
64 | pty->child = (pid_t) -1; | |
65 | ||
66 | return pty; | |
67 | } | |
68 | ||
ab61a038 | 69 | void ul_free_pty(struct ul_pty *pty) |
6954895c | 70 | { |
ab61a038 | 71 | free(pty); |
6954895c KZ |
72 | } |
73 | ||
ab61a038 | 74 | |
6954895c KZ |
75 | int ul_pty_get_delivered_signal(struct ul_pty *pty) |
76 | { | |
77 | assert(pty); | |
78 | return pty->delivered_signal; | |
79 | } | |
80 | ||
81 | struct ul_pty_callbacks *ul_pty_get_callbacks(struct ul_pty *pty) | |
82 | { | |
83 | assert(pty); | |
84 | return &pty->callbacks; | |
85 | } | |
86 | ||
87 | void ul_pty_set_callback_data(struct ul_pty *pty, void *data) | |
88 | { | |
89 | assert(pty); | |
90 | pty->callback_data = data; | |
91 | } | |
92 | ||
93 | void ul_pty_set_child(struct ul_pty *pty, pid_t child) | |
94 | { | |
95 | assert(pty); | |
96 | pty->child = child; | |
97 | } | |
98 | ||
4d5b2fed KZ |
99 | int ul_pty_get_childfd(struct ul_pty *pty) |
100 | { | |
101 | assert(pty); | |
102 | return pty->master; | |
103 | } | |
104 | ||
6954895c KZ |
105 | /* it's active when signals are redurected to sigfd */ |
106 | int ul_pty_is_running(struct ul_pty *pty) | |
107 | { | |
108 | assert(pty); | |
109 | return pty->sigfd >= 0; | |
110 | } | |
111 | ||
4d5b2fed KZ |
112 | void ul_pty_set_mainloop_time(struct ul_pty *pty, struct timeval *tv) |
113 | { | |
114 | assert(pty); | |
115 | if (!tv) { | |
116 | DBG(IO, ul_debugobj(pty, "mainloop time: clear")); | |
117 | timerclear(&pty->next_callback_time); | |
118 | } else { | |
119 | pty->next_callback_time.tv_sec = tv->tv_sec; | |
120 | pty->next_callback_time.tv_usec = tv->tv_usec; | |
121 | DBG(IO, ul_debugobj(pty, "mainloop time: %ld.%06ld", tv->tv_sec, tv->tv_usec)); | |
122 | } | |
123 | } | |
124 | ||
6954895c KZ |
125 | /* call me before fork() */ |
126 | int ul_pty_setup(struct ul_pty *pty) | |
127 | { | |
128 | struct termios slave_attrs; | |
129 | int rc; | |
130 | ||
ab61a038 KZ |
131 | /* save the current signals setting */ |
132 | sigprocmask(0, NULL, &pty->orgsig); | |
133 | ||
6954895c KZ |
134 | if (pty->isterm) { |
135 | DBG(SETUP, ul_debugobj(pty, "create for terminal")); | |
136 | ||
137 | /* original setting of the current terminal */ | |
138 | if (tcgetattr(STDIN_FILENO, &pty->stdin_attrs) != 0) | |
139 | return -errno; | |
140 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win); | |
141 | /* create master+slave */ | |
142 | rc = openpty(&pty->master, &pty->slave, NULL, &pty->stdin_attrs, &pty->win); | |
143 | ||
144 | /* set the current terminal to raw mode; pty_cleanup() reverses this change on exit */ | |
145 | slave_attrs = pty->stdin_attrs; | |
146 | cfmakeraw(&slave_attrs); | |
147 | slave_attrs.c_lflag &= ~ECHO; | |
148 | tcsetattr(STDIN_FILENO, TCSANOW, &slave_attrs); | |
149 | } else { | |
150 | DBG(SETUP, ul_debugobj(pty, "create for non-terminal")); | |
151 | rc = openpty(&pty->master, &pty->slave, NULL, NULL, NULL); | |
152 | ||
153 | if (!rc) { | |
154 | tcgetattr(pty->slave, &slave_attrs); | |
155 | slave_attrs.c_lflag &= ~ECHO; | |
156 | tcsetattr(pty->slave, TCSANOW, &slave_attrs); | |
157 | } | |
158 | } | |
159 | ||
160 | DBG(SETUP, ul_debugobj(pty, "pty setup done [master=%d, slave=%d, rc=%d]", | |
161 | pty->master, pty->slave, rc)); | |
162 | return rc; | |
163 | } | |
164 | ||
165 | /* cleanup in parent process */ | |
166 | void ul_pty_cleanup(struct ul_pty *pty) | |
167 | { | |
168 | struct termios rtt; | |
169 | ||
170 | if (pty->master == -1 || !pty->isterm) | |
171 | return; | |
172 | ||
173 | DBG(DONE, ul_debugobj(pty, "cleanup")); | |
174 | rtt = pty->stdin_attrs; | |
175 | tcsetattr(STDIN_FILENO, TCSADRAIN, &rtt); | |
176 | } | |
177 | ||
178 | /* call me in child process */ | |
179 | void ul_pty_init_slave(struct ul_pty *pty) | |
180 | { | |
181 | DBG(SETUP, ul_debugobj(pty, "initialize slave")); | |
182 | ||
183 | setsid(); | |
184 | ||
185 | ioctl(pty->slave, TIOCSCTTY, 1); | |
186 | close(pty->master); | |
187 | ||
188 | dup2(pty->slave, STDIN_FILENO); | |
189 | dup2(pty->slave, STDOUT_FILENO); | |
190 | dup2(pty->slave, STDERR_FILENO); | |
191 | ||
192 | close(pty->slave); | |
193 | ||
194 | if (pty->sigfd >= 0) | |
195 | close(pty->sigfd); | |
196 | ||
197 | pty->slave = -1; | |
198 | pty->master = -1; | |
199 | pty->sigfd = -1; | |
200 | ||
201 | sigprocmask(SIG_SETMASK, &pty->orgsig, NULL); | |
202 | ||
203 | DBG(SETUP, ul_debugobj(pty, "... initialize slave done")); | |
204 | } | |
205 | ||
206 | static int write_output(char *obuf, ssize_t bytes) | |
207 | { | |
208 | DBG(IO, ul_debug(" writing output")); | |
209 | ||
210 | if (write_all(STDOUT_FILENO, obuf, bytes)) { | |
211 | DBG(IO, ul_debug(" writing output *failed*")); | |
212 | return -errno; | |
213 | } | |
214 | ||
215 | return 0; | |
216 | } | |
217 | ||
4d5b2fed | 218 | static int write_to_child(struct ul_pty *pty, char *buf, size_t bufsz) |
6954895c KZ |
219 | { |
220 | return write_all(pty->master, buf, bufsz); | |
221 | } | |
222 | ||
223 | /* | |
224 | * The pty is usually faster than shell, so it's a good idea to wait until | |
225 | * the previous message has been already read by shell from slave before we | |
226 | * write to master. This is necessary especially for EOF situation when we can | |
227 | * send EOF to master before shell is fully initialized, to workaround this | |
228 | * problem we wait until slave is empty. For example: | |
229 | * | |
230 | * echo "date" | su --pty | |
231 | * | |
232 | * Unfortunately, the child (usually shell) can ignore stdin at all, so we | |
233 | * don't wait forever to avoid dead locks... | |
234 | * | |
235 | * Note that su --pty is primarily designed for interactive sessions as it | |
236 | * maintains master+slave tty stuff within the session. Use pipe to write to | |
237 | * pty and assume non-interactive (tee-like) behavior is NOT well supported. | |
238 | */ | |
239 | static void write_eof_to_child(struct ul_pty *pty) | |
240 | { | |
241 | unsigned int tries = 0; | |
242 | struct pollfd fds[] = { | |
243 | { .fd = pty->slave, .events = POLLIN } | |
244 | }; | |
245 | char c = DEF_EOF; | |
246 | ||
247 | DBG(IO, ul_debugobj(pty, " waiting for empty slave")); | |
248 | while (poll(fds, 1, 10) == 1 && tries < 8) { | |
249 | DBG(IO, ul_debugobj(pty, " slave is not empty")); | |
250 | xusleep(250000); | |
251 | tries++; | |
252 | } | |
253 | if (tries < 8) | |
254 | DBG(IO, ul_debugobj(pty, " slave is empty now")); | |
255 | ||
256 | DBG(IO, ul_debugobj(pty, " sending EOF to master")); | |
257 | write_to_child(pty, &c, sizeof(char)); | |
258 | } | |
259 | ||
4d5b2fed KZ |
260 | static int mainloop_callback(struct ul_pty *pty) |
261 | { | |
262 | if (!pty->callbacks.mainloop) | |
263 | return 0; | |
264 | ||
265 | DBG(IO, ul_debugobj(pty, "calling mainloop callback")); | |
266 | return pty->callbacks.mainloop(pty->callback_data); | |
267 | } | |
268 | ||
6954895c KZ |
269 | static int handle_io(struct ul_pty *pty, int fd, int *eof) |
270 | { | |
271 | char buf[BUFSIZ]; | |
272 | ssize_t bytes; | |
273 | ||
274 | DBG(IO, ul_debugobj(pty, "%d FD active", fd)); | |
275 | *eof = 0; | |
276 | ||
277 | /* read from active FD */ | |
278 | bytes = read(fd, buf, sizeof(buf)); | |
279 | if (bytes < 0) { | |
280 | if (errno == EAGAIN || errno == EINTR) | |
281 | return 0; | |
282 | return -errno; | |
283 | } | |
284 | ||
285 | if (bytes == 0) { | |
286 | *eof = 1; | |
287 | return 0; | |
288 | } | |
289 | ||
290 | /* from stdin (user) to command */ | |
291 | if (fd == STDIN_FILENO) { | |
292 | DBG(IO, ul_debugobj(pty, " stdin --> master %zd bytes", bytes)); | |
293 | ||
294 | if (write_to_child(pty, buf, bytes)) | |
295 | return -errno; | |
296 | ||
297 | /* without sync write_output() will write both input & | |
298 | * shell output that looks like double echoing */ | |
299 | fdatasync(pty->master); | |
300 | ||
301 | /* from command (master) to stdout */ | |
302 | } else if (fd == pty->master) { | |
303 | DBG(IO, ul_debugobj(pty, " master --> stdout %zd bytes", bytes)); | |
304 | write_output(buf, bytes); | |
305 | } | |
306 | ||
307 | return 0; | |
308 | } | |
309 | ||
310 | static int handle_signal(struct ul_pty *pty, int fd) | |
311 | { | |
312 | struct signalfd_siginfo info; | |
313 | ssize_t bytes; | |
314 | ||
315 | DBG(SIG, ul_debugobj(pty, "signal FD %d active", fd)); | |
316 | ||
317 | bytes = read(fd, &info, sizeof(info)); | |
318 | if (bytes != sizeof(info)) { | |
319 | if (bytes < 0 && (errno == EAGAIN || errno == EINTR)) | |
320 | return 0; | |
321 | return -errno; | |
322 | } | |
323 | ||
324 | switch (info.ssi_signo) { | |
325 | case SIGCHLD: | |
326 | DBG(SIG, ul_debugobj(pty, " get signal SIGCHLD")); | |
327 | ||
328 | if (info.ssi_code == CLD_EXITED | |
329 | || info.ssi_code == CLD_KILLED | |
330 | || info.ssi_code == CLD_DUMPED) | |
331 | pty->callbacks.child_wait(pty->callback_data); | |
332 | ||
333 | else if (info.ssi_status == SIGSTOP && pty->child > 0) | |
334 | pty->callbacks.child_sigstop(pty->callback_data); | |
335 | ||
984082fa | 336 | if (pty->child <= 0) { |
6954895c | 337 | pty->poll_timeout = 10; |
984082fa KZ |
338 | timerclear(&pty->next_callback_time); |
339 | } | |
6954895c KZ |
340 | return 0; |
341 | case SIGWINCH: | |
342 | DBG(SIG, ul_debugobj(pty, " get signal SIGWINCH")); | |
343 | if (pty->isterm) { | |
344 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win); | |
345 | ioctl(pty->slave, TIOCSWINSZ, (char *)&pty->win); | |
346 | } | |
347 | break; | |
348 | case SIGTERM: | |
349 | /* fallthrough */ | |
350 | case SIGINT: | |
351 | /* fallthrough */ | |
352 | case SIGQUIT: | |
353 | DBG(SIG, ul_debugobj(pty, " get signal SIG{TERM,INT,QUIT}")); | |
354 | pty->delivered_signal = info.ssi_signo; | |
355 | /* Child termination is going to generate SIGCHILD (see above) */ | |
356 | if (pty->child > 0) | |
357 | kill(pty->child, SIGTERM); | |
358 | break; | |
359 | default: | |
360 | abort(); | |
361 | } | |
362 | ||
363 | return 0; | |
364 | } | |
365 | ||
366 | /* loop in parent */ | |
367 | int ul_pty_proxy_master(struct ul_pty *pty) | |
368 | { | |
369 | sigset_t ourset; | |
370 | int rc = 0, ret, eof = 0; | |
371 | enum { | |
372 | POLLFD_SIGNAL = 0, | |
373 | POLLFD_MASTER, | |
374 | POLLFD_STDIN | |
375 | ||
376 | }; | |
377 | struct pollfd pfd[] = { | |
378 | [POLLFD_SIGNAL] = { .fd = -1, .events = POLLIN | POLLERR | POLLHUP }, | |
379 | [POLLFD_MASTER] = { .fd = pty->master, .events = POLLIN | POLLERR | POLLHUP }, | |
380 | [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP } | |
381 | }; | |
382 | ||
383 | /* We use signalfd and standard signals by handlers are blocked | |
384 | * at all | |
385 | */ | |
386 | sigfillset(&ourset); | |
387 | if (sigprocmask(SIG_BLOCK, &ourset, NULL)) | |
388 | return -errno; | |
389 | ||
390 | sigemptyset(&ourset); | |
391 | sigaddset(&ourset, SIGCHLD); | |
392 | sigaddset(&ourset, SIGWINCH); | |
393 | sigaddset(&ourset, SIGALRM); | |
394 | sigaddset(&ourset, SIGTERM); | |
395 | sigaddset(&ourset, SIGINT); | |
396 | sigaddset(&ourset, SIGQUIT); | |
397 | ||
398 | if ((pty->sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) { | |
399 | rc = -errno; | |
400 | goto done; | |
401 | } | |
402 | ||
403 | pfd[POLLFD_SIGNAL].fd = pty->sigfd; | |
404 | pty->poll_timeout = -1; | |
405 | ||
406 | while (!pty->delivered_signal) { | |
407 | size_t i; | |
4d5b2fed KZ |
408 | int errsv, timeout; |
409 | ||
410 | DBG(IO, ul_debugobj(pty, "--poll() loop--")); | |
411 | ||
412 | /* note, callback usually updates @next_callback_time */ | |
413 | if (timerisset(&pty->next_callback_time)) { | |
414 | struct timeval now; | |
415 | ||
416 | DBG(IO, ul_debugobj(pty, " callback requested")); | |
417 | gettime_monotonic(&now); | |
418 | if (timercmp(&now, &pty->next_callback_time, >)) { | |
419 | rc = mainloop_callback(pty); | |
420 | if (rc) | |
421 | break; | |
422 | } | |
423 | } | |
424 | ||
425 | /* set timeout */ | |
426 | if (timerisset(&pty->next_callback_time)) { | |
427 | struct timeval now, rest; | |
428 | ||
429 | gettime_monotonic(&now); | |
430 | timersub(&pty->next_callback_time, &now, &rest); | |
431 | timeout = (rest.tv_sec * 1000) + (rest.tv_usec / 1000); | |
432 | } else | |
433 | timeout = pty->poll_timeout; | |
6954895c | 434 | |
4d5b2fed KZ |
435 | /* wait for input, signal or timeout */ |
436 | DBG(IO, ul_debugobj(pty, "calling poll() [timeout=%dms]", timeout)); | |
437 | ret = poll(pfd, ARRAY_SIZE(pfd), timeout); | |
6954895c | 438 | |
6954895c KZ |
439 | errsv = errno; |
440 | DBG(IO, ul_debugobj(pty, "poll() rc=%d", ret)); | |
441 | ||
4d5b2fed | 442 | /* error */ |
6954895c KZ |
443 | if (ret < 0) { |
444 | if (errsv == EAGAIN) | |
445 | continue; | |
446 | rc = -errno; | |
447 | break; | |
448 | } | |
4d5b2fed KZ |
449 | |
450 | /* timeout */ | |
6954895c | 451 | if (ret == 0) { |
4d5b2fed KZ |
452 | if (timerisset(&pty->next_callback_time)) { |
453 | rc = mainloop_callback(pty); | |
454 | if (rc == 0) | |
455 | continue; | |
456 | } else | |
457 | rc = 0; | |
458 | ||
984082fa | 459 | DBG(IO, ul_debugobj(pty, "leaving poll() loop [timeout=%d, rc=%d]", timeout, rc)); |
6954895c KZ |
460 | break; |
461 | } | |
4d5b2fed | 462 | /* event */ |
6954895c KZ |
463 | for (i = 0; i < ARRAY_SIZE(pfd); i++) { |
464 | rc = 0; | |
465 | ||
466 | if (pfd[i].revents == 0) | |
467 | continue; | |
468 | ||
469 | DBG(IO, ul_debugobj(pty, " active pfd[%s].fd=%d %s %s %s", | |
470 | i == POLLFD_STDIN ? "stdin" : | |
471 | i == POLLFD_MASTER ? "master" : | |
472 | i == POLLFD_SIGNAL ? "signal" : "???", | |
473 | pfd[i].fd, | |
474 | pfd[i].revents & POLLIN ? "POLLIN" : "", | |
475 | pfd[i].revents & POLLHUP ? "POLLHUP" : "", | |
476 | pfd[i].revents & POLLERR ? "POLLERR" : "")); | |
477 | switch (i) { | |
478 | case POLLFD_STDIN: | |
479 | case POLLFD_MASTER: | |
480 | /* data */ | |
481 | if (pfd[i].revents & POLLIN) | |
482 | rc = handle_io(pty, pfd[i].fd, &eof); | |
483 | /* EOF maybe detected by two ways: | |
484 | * A) poll() return POLLHUP event after close() | |
485 | * B) read() returns 0 (no data) */ | |
486 | if ((pfd[i].revents & POLLHUP) || eof) { | |
487 | DBG(IO, ul_debugobj(pty, " ignore FD")); | |
488 | pfd[i].fd = -1; | |
489 | if (i == POLLFD_STDIN) { | |
490 | write_eof_to_child(pty); | |
491 | DBG(IO, ul_debugobj(pty, " ignore STDIN")); | |
492 | } | |
493 | } | |
494 | continue; | |
495 | case POLLFD_SIGNAL: | |
496 | rc = handle_signal(pty, pfd[i].fd); | |
497 | break; | |
498 | } | |
499 | if (rc) | |
500 | break; | |
501 | } | |
502 | } | |
503 | ||
504 | done: | |
505 | if (pty->sigfd != -1) | |
506 | close(pty->sigfd); | |
507 | pty->sigfd = -1; | |
508 | ||
509 | /* restore original setting */ | |
510 | sigprocmask(SIG_SETMASK, &pty->orgsig, NULL); | |
511 | ||
512 | DBG(IO, ul_debug("poll() done [signal=%d, rc=%d]", pty->delivered_signal, rc)); | |
513 | return rc; | |
514 | } | |
515 | ||
516 | #ifdef TEST_PROGRAM_PTY | |
517 | /* | |
518 | * $ make test_pty | |
519 | * $ ./test_pty | |
520 | * | |
521 | * ... and see for example tty(1) or "ps afu" | |
522 | */ | |
523 | struct ptytest { | |
524 | pid_t child; | |
525 | int childstatus; | |
526 | ||
527 | struct ul_pty *pty; | |
528 | }; | |
529 | ||
530 | /* on child exit/dump/... */ | |
531 | static void wait_for_child(void *data) | |
532 | { | |
533 | struct ptytest *ss = (struct ptytest *) data; | |
534 | int status; | |
535 | pid_t pid; | |
536 | int options = 0; | |
537 | ||
538 | if (ss->child == (pid_t) -1) | |
539 | return; | |
540 | ||
541 | if (ul_pty_is_running(ss->pty)) { | |
542 | /* wait for specific child */ | |
543 | options = WNOHANG; | |
544 | for (;;) { | |
545 | pid = waitpid(ss->child, &status, options); | |
546 | if (pid != (pid_t) - 1) { | |
547 | ss->childstatus = status; | |
548 | ss->child = (pid_t) -1; | |
549 | ul_pty_set_child(ss->pty, (pid_t) -1); | |
550 | } else | |
551 | break; | |
552 | } | |
553 | } else { | |
554 | /* final wait */ | |
555 | while ((pid = wait3(&status, options, NULL)) > 0) { | |
556 | if (pid == ss->child) { | |
557 | ss->childstatus = status; | |
558 | ss->child = (pid_t) -1; | |
559 | ul_pty_set_child(ss->pty, (pid_t) -1); | |
560 | } | |
561 | } | |
562 | } | |
563 | } | |
564 | ||
565 | static void child_sigstop(void *data) | |
566 | { | |
567 | struct ptytest *ss = (struct ptytest *) data; | |
568 | kill(getpid(), SIGSTOP); | |
569 | kill(ss->child, SIGCONT); | |
570 | } | |
571 | ||
572 | int main(int argc, char *argv[]) | |
573 | { | |
574 | struct ptytest ss = { .child = (pid_t) -1 }; | |
575 | struct ul_pty_callbacks *cb; | |
576 | const char *shell, *command = NULL, *shname = NULL; | |
577 | int caught_signal = 0; | |
578 | ||
579 | shell = getenv("SHELL"); | |
580 | if (shell == NULL) | |
581 | shell = _PATH_BSHELL; | |
582 | if (argc == 2) | |
583 | command = argv[1]; | |
584 | ||
585 | ul_pty_init_debug(0); | |
586 | ||
587 | ss.pty = ul_new_pty(isatty(STDIN_FILENO)); | |
588 | if (!ss.pty) | |
589 | err(EXIT_FAILURE, "failed to allocate PTY handler"); | |
590 | ||
591 | ul_pty_set_callback_data(ss.pty, (void *) &ss); | |
592 | cb = ul_pty_get_callbacks(ss.pty); | |
593 | cb->child_wait = wait_for_child; | |
594 | cb->child_sigstop = child_sigstop; | |
595 | ||
6954895c KZ |
596 | if (ul_pty_setup(ss.pty)) |
597 | err(EXIT_FAILURE, "failed to create pseudo-terminal"); | |
598 | ||
599 | fflush(stdout); /* ??? */ | |
600 | ||
601 | switch ((int) (ss.child = fork())) { | |
602 | case -1: /* error */ | |
603 | ul_pty_cleanup(ss.pty); | |
604 | err(EXIT_FAILURE, "cannot create child process"); | |
605 | break; | |
606 | ||
607 | case 0: /* child */ | |
608 | ul_pty_init_slave(ss.pty); | |
609 | ||
610 | signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */ | |
611 | ||
612 | shname = strrchr(shell, '/'); | |
613 | shname = shname ? shname + 1 : shell; | |
614 | ||
615 | if (command) | |
616 | execl(shell, shname, "-c", command, NULL); | |
617 | else | |
618 | execl(shell, shname, "-i", NULL); | |
619 | err(EXIT_FAILURE, "failed to execute %s", shell); | |
620 | break; | |
621 | ||
622 | default: | |
623 | break; | |
624 | } | |
625 | ||
626 | /* parent */ | |
627 | ul_pty_set_child(ss.pty, ss.child); | |
628 | ||
629 | /* this is the main loop */ | |
630 | ul_pty_proxy_master(ss.pty); | |
631 | ||
632 | /* all done; cleanup and kill */ | |
633 | caught_signal = ul_pty_get_delivered_signal(ss.pty); | |
634 | ||
635 | if (!caught_signal && ss.child != (pid_t)-1) | |
636 | wait_for_child(&ss); /* final wait */ | |
637 | ||
638 | if (caught_signal && ss.child != (pid_t)-1) { | |
639 | fprintf(stderr, "\nSession terminated, killing shell..."); | |
640 | kill(ss.child, SIGTERM); | |
641 | sleep(2); | |
642 | kill(ss.child, SIGKILL); | |
643 | fprintf(stderr, " ...killed.\n"); | |
644 | } | |
645 | ||
646 | ul_pty_cleanup(ss.pty); | |
647 | return EXIT_SUCCESS; | |
648 | } | |
649 | ||
650 | #endif /* TEST_PROGRAM */ | |
651 |