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