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