]>
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) | |
86139a70 | 6 | * - parent can log all traffic between user and child's terminal (e.g. script(1)) |
6954895c KZ |
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> | |
f02286fd | 21 | #include <inttypes.h> |
6954895c KZ |
22 | |
23 | #include "c.h" | |
24 | #include "all-io.h" | |
25 | #include "ttyutils.h" | |
26 | #include "pty-session.h" | |
4d5b2fed | 27 | #include "monotonic.h" |
6954895c KZ |
28 | #include "debug.h" |
29 | ||
30 | static UL_DEBUG_DEFINE_MASK(ulpty); | |
31 | UL_DEBUG_DEFINE_MASKNAMES(ulpty) = UL_DEBUG_EMPTY_MASKNAMES; | |
32 | ||
33 | #define ULPTY_DEBUG_INIT (1 << 1) | |
34 | #define ULPTY_DEBUG_SETUP (1 << 2) | |
35 | #define ULPTY_DEBUG_SIG (1 << 3) | |
36 | #define ULPTY_DEBUG_IO (1 << 4) | |
37 | #define ULPTY_DEBUG_DONE (1 << 5) | |
38 | #define ULPTY_DEBUG_ALL 0xFFFF | |
39 | ||
40 | #define DBG(m, x) __UL_DBG(ulpty, ULPTY_DEBUG_, m, x) | |
41 | #define ON_DBG(m, x) __UL_DBG_CALL(ulpty, ULPTY_DEBUG_, m, x) | |
42 | ||
43 | #define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpty) | |
44 | #include "debugobj.h" | |
45 | ||
46 | void ul_pty_init_debug(int mask) | |
47 | { | |
48 | if (ulpty_debug_mask) | |
49 | return; | |
50 | __UL_INIT_DEBUG_FROM_ENV(ulpty, ULPTY_DEBUG_, mask, ULPTY_DEBUG); | |
51 | } | |
52 | ||
53 | struct ul_pty *ul_new_pty(int is_stdin_tty) | |
54 | { | |
55 | struct ul_pty *pty = calloc(1, sizeof(*pty)); | |
56 | ||
57 | if (!pty) | |
58 | return NULL; | |
59 | ||
60 | DBG(SETUP, ul_debugobj(pty, "alloc handler")); | |
61 | pty->isterm = is_stdin_tty; | |
62 | pty->master = -1; | |
63 | pty->slave = -1; | |
64 | pty->sigfd = -1; | |
65 | pty->child = (pid_t) -1; | |
66 | ||
67 | return pty; | |
68 | } | |
69 | ||
ab61a038 | 70 | void ul_free_pty(struct ul_pty *pty) |
6954895c | 71 | { |
2a6d1768 | 72 | struct ul_pty_child_buffer *hd; |
fd5698ff KZ |
73 | |
74 | while ((hd = pty->child_buffer_head)) { | |
2a6d1768 | 75 | pty->child_buffer_head = hd->next; |
76 | free(hd); | |
77 | } | |
fd5698ff KZ |
78 | |
79 | while ((hd = pty->free_buffers)) { | |
2a6d1768 | 80 | pty->free_buffers = hd->next; |
81 | free(hd); | |
82 | } | |
ab61a038 | 83 | free(pty); |
6954895c KZ |
84 | } |
85 | ||
1eee1acb | 86 | void ul_pty_slave_echo(struct ul_pty *pty, int enable) |
4169bcb7 KZ |
87 | { |
88 | assert(pty); | |
1eee1acb | 89 | pty->slave_echo = enable ? 1 : 0; |
4169bcb7 | 90 | } |
ab61a038 | 91 | |
6954895c KZ |
92 | int ul_pty_get_delivered_signal(struct ul_pty *pty) |
93 | { | |
94 | assert(pty); | |
95 | return pty->delivered_signal; | |
96 | } | |
97 | ||
98 | struct ul_pty_callbacks *ul_pty_get_callbacks(struct ul_pty *pty) | |
99 | { | |
100 | assert(pty); | |
101 | return &pty->callbacks; | |
102 | } | |
103 | ||
104 | void ul_pty_set_callback_data(struct ul_pty *pty, void *data) | |
105 | { | |
106 | assert(pty); | |
107 | pty->callback_data = data; | |
108 | } | |
109 | ||
110 | void ul_pty_set_child(struct ul_pty *pty, pid_t child) | |
111 | { | |
112 | assert(pty); | |
113 | pty->child = child; | |
114 | } | |
115 | ||
4d5b2fed KZ |
116 | int ul_pty_get_childfd(struct ul_pty *pty) |
117 | { | |
118 | assert(pty); | |
119 | return pty->master; | |
120 | } | |
121 | ||
bdd43357 KZ |
122 | pid_t ul_pty_get_child(struct ul_pty *pty) |
123 | { | |
124 | assert(pty); | |
125 | return pty->child; | |
126 | } | |
127 | ||
c556dc59 | 128 | /* it's active when signals are redirected to sigfd */ |
6954895c KZ |
129 | int ul_pty_is_running(struct ul_pty *pty) |
130 | { | |
131 | assert(pty); | |
132 | return pty->sigfd >= 0; | |
133 | } | |
134 | ||
4d5b2fed KZ |
135 | void ul_pty_set_mainloop_time(struct ul_pty *pty, struct timeval *tv) |
136 | { | |
137 | assert(pty); | |
138 | if (!tv) { | |
139 | DBG(IO, ul_debugobj(pty, "mainloop time: clear")); | |
140 | timerclear(&pty->next_callback_time); | |
141 | } else { | |
142 | pty->next_callback_time.tv_sec = tv->tv_sec; | |
143 | pty->next_callback_time.tv_usec = tv->tv_usec; | |
f02286fd KZ |
144 | DBG(IO, ul_debugobj(pty, "mainloop time: %"PRId64".%06"PRId64, |
145 | (int64_t) tv->tv_sec, (int64_t) tv->tv_usec)); | |
4d5b2fed KZ |
146 | } |
147 | } | |
148 | ||
b1154c4e KZ |
149 | static void pty_signals_cleanup(struct ul_pty *pty) |
150 | { | |
151 | if (pty->sigfd != -1) | |
152 | close(pty->sigfd); | |
153 | pty->sigfd = -1; | |
154 | ||
155 | /* restore original setting */ | |
156 | sigprocmask(SIG_SETMASK, &pty->orgsig, NULL); | |
157 | } | |
158 | ||
6954895c KZ |
159 | /* call me before fork() */ |
160 | int ul_pty_setup(struct ul_pty *pty) | |
161 | { | |
75ccd75a | 162 | struct termios attrs; |
b1154c4e KZ |
163 | int rc = 0; |
164 | ||
165 | assert(pty->sigfd == -1); | |
6954895c | 166 | |
4681d88b KZ |
167 | /* save the current signals setting (to make ul_pty_cleanup() usable, |
168 | * otherwise see ul_pty_signals_setup() */ | |
ab61a038 KZ |
169 | sigprocmask(0, NULL, &pty->orgsig); |
170 | ||
6954895c KZ |
171 | if (pty->isterm) { |
172 | DBG(SETUP, ul_debugobj(pty, "create for terminal")); | |
173 | ||
174 | /* original setting of the current terminal */ | |
b1154c4e KZ |
175 | if (tcgetattr(STDIN_FILENO, &pty->stdin_attrs) != 0) { |
176 | rc = -errno; | |
177 | goto done; | |
178 | } | |
75ccd75a SG |
179 | |
180 | attrs = pty->stdin_attrs; | |
181 | if (pty->slave_echo) | |
182 | attrs.c_lflag |= ECHO; | |
183 | else | |
184 | attrs.c_lflag &= ~ECHO; | |
185 | ||
6954895c KZ |
186 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win); |
187 | /* create master+slave */ | |
75ccd75a | 188 | rc = openpty(&pty->master, &pty->slave, NULL, &attrs, &pty->win); |
b1154c4e KZ |
189 | if (rc) |
190 | goto done; | |
6954895c KZ |
191 | |
192 | /* set the current terminal to raw mode; pty_cleanup() reverses this change on exit */ | |
75ccd75a SG |
193 | cfmakeraw(&attrs); |
194 | tcsetattr(STDIN_FILENO, TCSANOW, &attrs); | |
6954895c | 195 | } else { |
2a6d1768 | 196 | DBG(SETUP, ul_debugobj(pty, "create for non-terminal")); |
6954895c | 197 | |
1eee1acb KZ |
198 | rc = openpty(&pty->master, &pty->slave, NULL, NULL, NULL); |
199 | if (rc) | |
b1154c4e | 200 | goto done; |
1eee1acb | 201 | |
75ccd75a | 202 | tcgetattr(pty->slave, &attrs); |
1eee1acb KZ |
203 | |
204 | if (pty->slave_echo) | |
75ccd75a | 205 | attrs.c_lflag |= ECHO; |
1eee1acb | 206 | else |
75ccd75a | 207 | attrs.c_lflag &= ~ECHO; |
1eee1acb | 208 | |
75ccd75a | 209 | tcsetattr(pty->slave, TCSANOW, &attrs); |
b1154c4e KZ |
210 | } |
211 | ||
2a6d1768 | 212 | fcntl(pty->master, F_SETFL, O_NONBLOCK); |
213 | ||
4681d88b KZ |
214 | done: |
215 | if (rc) | |
216 | ul_pty_cleanup(pty); | |
217 | ||
218 | DBG(SETUP, ul_debugobj(pty, "pty setup done [master=%d, slave=%d, rc=%d]", | |
219 | pty->master, pty->slave, rc)); | |
220 | return rc; | |
221 | } | |
222 | ||
223 | /* call me before fork() */ | |
224 | int ul_pty_signals_setup(struct ul_pty *pty) | |
225 | { | |
226 | sigset_t ourset; | |
227 | int rc = 0; | |
228 | ||
229 | assert(pty->sigfd == -1); | |
230 | ||
231 | /* save the current signals setting */ | |
232 | sigprocmask(0, NULL, &pty->orgsig); | |
233 | ||
b1154c4e KZ |
234 | sigfillset(&ourset); |
235 | if (sigprocmask(SIG_BLOCK, &ourset, NULL)) { | |
236 | rc = -errno; | |
237 | goto done; | |
6954895c KZ |
238 | } |
239 | ||
b1154c4e KZ |
240 | sigemptyset(&ourset); |
241 | sigaddset(&ourset, SIGCHLD); | |
242 | sigaddset(&ourset, SIGWINCH); | |
243 | sigaddset(&ourset, SIGALRM); | |
244 | sigaddset(&ourset, SIGTERM); | |
245 | sigaddset(&ourset, SIGINT); | |
246 | sigaddset(&ourset, SIGQUIT); | |
247 | ||
7727be1a KZ |
248 | if (pty->callbacks.flush_logs) |
249 | sigaddset(&ourset, SIGUSR1); | |
250 | ||
b1154c4e KZ |
251 | if ((pty->sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) |
252 | rc = -errno; | |
253 | done: | |
254 | if (rc) | |
255 | ul_pty_cleanup(pty); | |
256 | ||
4681d88b | 257 | DBG(SETUP, ul_debugobj(pty, "pty signals setup done [rc=%d]", rc)); |
6954895c KZ |
258 | return rc; |
259 | } | |
260 | ||
261 | /* cleanup in parent process */ | |
262 | void ul_pty_cleanup(struct ul_pty *pty) | |
263 | { | |
264 | struct termios rtt; | |
265 | ||
b1154c4e KZ |
266 | pty_signals_cleanup(pty); |
267 | ||
6954895c KZ |
268 | if (pty->master == -1 || !pty->isterm) |
269 | return; | |
270 | ||
271 | DBG(DONE, ul_debugobj(pty, "cleanup")); | |
272 | rtt = pty->stdin_attrs; | |
273 | tcsetattr(STDIN_FILENO, TCSADRAIN, &rtt); | |
274 | } | |
275 | ||
17d5b264 KZ |
276 | int ul_pty_chownmod_slave(struct ul_pty *pty, uid_t uid, gid_t gid, mode_t mode) |
277 | { | |
278 | if (fchown(pty->slave, uid, gid)) | |
279 | return -errno; | |
280 | if (fchmod(pty->slave, mode)) | |
281 | return -errno; | |
282 | return 0; | |
283 | } | |
284 | ||
6954895c KZ |
285 | /* call me in child process */ |
286 | void ul_pty_init_slave(struct ul_pty *pty) | |
287 | { | |
288 | DBG(SETUP, ul_debugobj(pty, "initialize slave")); | |
289 | ||
290 | setsid(); | |
291 | ||
292 | ioctl(pty->slave, TIOCSCTTY, 1); | |
293 | close(pty->master); | |
294 | ||
295 | dup2(pty->slave, STDIN_FILENO); | |
296 | dup2(pty->slave, STDOUT_FILENO); | |
297 | dup2(pty->slave, STDERR_FILENO); | |
298 | ||
299 | close(pty->slave); | |
300 | ||
301 | if (pty->sigfd >= 0) | |
302 | close(pty->sigfd); | |
303 | ||
304 | pty->slave = -1; | |
305 | pty->master = -1; | |
306 | pty->sigfd = -1; | |
307 | ||
308 | sigprocmask(SIG_SETMASK, &pty->orgsig, NULL); | |
309 | ||
310 | DBG(SETUP, ul_debugobj(pty, "... initialize slave done")); | |
311 | } | |
312 | ||
313 | static int write_output(char *obuf, ssize_t bytes) | |
314 | { | |
315 | DBG(IO, ul_debug(" writing output")); | |
316 | ||
317 | if (write_all(STDOUT_FILENO, obuf, bytes)) { | |
318 | DBG(IO, ul_debug(" writing output *failed*")); | |
319 | return -errno; | |
320 | } | |
321 | ||
322 | return 0; | |
323 | } | |
324 | ||
2a6d1768 | 325 | static int schedule_child_write(struct ul_pty *pty, char *buf, size_t bufsz, int final) |
6954895c | 326 | { |
2a6d1768 | 327 | struct ul_pty_child_buffer *stash; |
fd5698ff | 328 | |
2a6d1768 | 329 | if (pty->free_buffers) { |
330 | stash = pty->free_buffers; | |
331 | pty->free_buffers = stash->next; | |
332 | memset(stash, 0, sizeof(*stash)); | |
333 | } else | |
334 | stash = calloc(1, sizeof(*stash)); | |
335 | if (!stash) | |
336 | return -1; | |
337 | ||
fd5698ff KZ |
338 | assert(bufsz <= sizeof(stash->buf)); |
339 | ||
2a6d1768 | 340 | memcpy(stash->buf, buf, bufsz); |
341 | stash->size = bufsz; | |
342 | stash->final_input = final ? 1 : 0; | |
343 | ||
344 | if (pty->child_buffer_head) | |
345 | pty->child_buffer_tail = pty->child_buffer_tail->next = stash; | |
346 | else | |
347 | pty->child_buffer_head = pty->child_buffer_tail = stash; | |
348 | return 0; | |
6954895c KZ |
349 | } |
350 | ||
351 | /* | |
352 | * The pty is usually faster than shell, so it's a good idea to wait until | |
353 | * the previous message has been already read by shell from slave before we | |
354 | * write to master. This is necessary especially for EOF situation when we can | |
355 | * send EOF to master before shell is fully initialized, to workaround this | |
356 | * problem we wait until slave is empty. For example: | |
357 | * | |
358 | * echo "date" | su --pty | |
359 | * | |
360 | * Unfortunately, the child (usually shell) can ignore stdin at all, so we | |
361 | * don't wait forever to avoid dead locks... | |
362 | * | |
363 | * Note that su --pty is primarily designed for interactive sessions as it | |
364 | * maintains master+slave tty stuff within the session. Use pipe to write to | |
365 | * pty and assume non-interactive (tee-like) behavior is NOT well supported. | |
366 | */ | |
2a6d1768 | 367 | static void drain_child_buffers(struct ul_pty *pty) |
6954895c KZ |
368 | { |
369 | unsigned int tries = 0; | |
2a6d1768 | 370 | struct pollfd fd = { .fd = pty->slave, .events = POLLIN }; |
6954895c KZ |
371 | |
372 | DBG(IO, ul_debugobj(pty, " waiting for empty slave")); | |
2a6d1768 | 373 | while (poll(&fd, 1, 10) == 1 && tries < 8) { |
6954895c KZ |
374 | DBG(IO, ul_debugobj(pty, " slave is not empty")); |
375 | xusleep(250000); | |
376 | tries++; | |
377 | } | |
378 | if (tries < 8) | |
379 | DBG(IO, ul_debugobj(pty, " slave is empty now")); | |
380 | ||
381 | DBG(IO, ul_debugobj(pty, " sending EOF to master")); | |
2a6d1768 | 382 | } |
383 | ||
384 | static int flush_child_buffers(struct ul_pty *pty, int *anything) | |
385 | { | |
fd5698ff KZ |
386 | int rc = 0, any = 0; |
387 | ||
2a6d1768 | 388 | while (pty->child_buffer_head) { |
389 | struct ul_pty_child_buffer *hd = pty->child_buffer_head; | |
fd5698ff | 390 | ssize_t ret; |
2a6d1768 | 391 | |
fd5698ff | 392 | if (hd->final_input) |
2a6d1768 | 393 | drain_child_buffers(pty); |
394 | ||
395 | DBG(IO, ul_debugobj(hd, " stdin --> master trying %zu bytes", hd->size - hd->cursor)); | |
fd5698ff KZ |
396 | |
397 | ret = write(pty->master, hd->buf + hd->cursor, hd->size - hd->cursor); | |
2a6d1768 | 398 | if (ret == -1) { |
399 | DBG(IO, ul_debugobj(hd, " EAGAIN")); | |
400 | if (!(errno == EINTR || errno == EAGAIN)) | |
fd5698ff | 401 | rc = -errno; |
2a6d1768 | 402 | goto out; |
403 | } | |
404 | DBG(IO, ul_debugobj(hd, " wrote %zd", ret)); | |
405 | any = 1; | |
406 | hd->cursor += ret; | |
fd5698ff | 407 | |
2a6d1768 | 408 | if (hd->cursor == hd->size) { |
409 | pty->child_buffer_head = hd->next; | |
fd5698ff | 410 | if (!hd->next) |
2a6d1768 | 411 | pty->child_buffer_tail = NULL; |
412 | ||
413 | hd->next = pty->free_buffers; | |
414 | pty->free_buffers = hd; | |
415 | } | |
416 | } | |
2a6d1768 | 417 | out: |
418 | /* without sync write_output() will write both input & | |
419 | * shell output that looks like double echoing */ | |
420 | if (any) | |
421 | fdatasync(pty->master); | |
422 | ||
423 | if (anything) | |
424 | *anything = any; | |
fd5698ff | 425 | return rc; |
2a6d1768 | 426 | } |
427 | ||
428 | void ul_pty_write_eof_to_child(struct ul_pty *pty) | |
429 | { | |
430 | char c = DEF_EOF; | |
431 | schedule_child_write(pty, &c, sizeof(char), 1); | |
6954895c KZ |
432 | } |
433 | ||
4d5b2fed KZ |
434 | static int mainloop_callback(struct ul_pty *pty) |
435 | { | |
f896aef3 KZ |
436 | int rc; |
437 | ||
4d5b2fed KZ |
438 | if (!pty->callbacks.mainloop) |
439 | return 0; | |
440 | ||
441 | DBG(IO, ul_debugobj(pty, "calling mainloop callback")); | |
f896aef3 KZ |
442 | rc = pty->callbacks.mainloop(pty->callback_data); |
443 | ||
444 | DBG(IO, ul_debugobj(pty, " callback done [rc=%d]", rc)); | |
445 | return rc; | |
4d5b2fed KZ |
446 | } |
447 | ||
6954895c KZ |
448 | static int handle_io(struct ul_pty *pty, int fd, int *eof) |
449 | { | |
450 | char buf[BUFSIZ]; | |
451 | ssize_t bytes; | |
4f7f723b | 452 | int rc = 0; |
5c6903b1 | 453 | sigset_t set; |
6954895c | 454 | |
c4bacbd1 | 455 | DBG(IO, ul_debugobj(pty, " handle I/O on fd=%d", fd)); |
6954895c KZ |
456 | *eof = 0; |
457 | ||
5c6903b1 SG |
458 | sigemptyset(&set); |
459 | sigaddset(&set, SIGTTIN); | |
460 | sigprocmask(SIG_UNBLOCK, &set, NULL); | |
6954895c KZ |
461 | /* read from active FD */ |
462 | bytes = read(fd, buf, sizeof(buf)); | |
5c6903b1 | 463 | sigprocmask(SIG_BLOCK, &set, NULL); |
2a6d1768 | 464 | if (bytes == -1) { |
6954895c KZ |
465 | if (errno == EAGAIN || errno == EINTR) |
466 | return 0; | |
467 | return -errno; | |
468 | } | |
469 | ||
470 | if (bytes == 0) { | |
471 | *eof = 1; | |
472 | return 0; | |
473 | } | |
474 | ||
475 | /* from stdin (user) to command */ | |
476 | if (fd == STDIN_FILENO) { | |
2a6d1768 | 477 | DBG(IO, ul_debugobj(pty, " stdin --> master %zd bytes queued", bytes)); |
6954895c | 478 | |
2a6d1768 | 479 | if (schedule_child_write(pty, buf, bytes, 0)) |
6954895c KZ |
480 | return -errno; |
481 | ||
6954895c KZ |
482 | /* from command (master) to stdout */ |
483 | } else if (fd == pty->master) { | |
484 | DBG(IO, ul_debugobj(pty, " master --> stdout %zd bytes", bytes)); | |
485 | write_output(buf, bytes); | |
486 | } | |
487 | ||
4f7f723b KZ |
488 | if (pty->callbacks.log_stream_activity) |
489 | rc = pty->callbacks.log_stream_activity( | |
490 | pty->callback_data, fd, buf, bytes); | |
491 | ||
492 | return rc; | |
6954895c KZ |
493 | } |
494 | ||
bdd43357 KZ |
495 | void ul_pty_wait_for_child(struct ul_pty *pty) |
496 | { | |
497 | int status; | |
498 | pid_t pid; | |
499 | int options = 0; | |
500 | ||
501 | if (pty->child == (pid_t) -1) | |
502 | return; | |
503 | ||
125314c0 | 504 | DBG(SIG, ul_debug("waiting for child [child=%d]", (int) pty->child)); |
bdd43357 KZ |
505 | |
506 | if (ul_pty_is_running(pty)) { | |
507 | /* wait for specific child */ | |
508 | options = WNOHANG; | |
509 | for (;;) { | |
510 | pid = waitpid(pty->child, &status, options); | |
125314c0 | 511 | DBG(SIG, ul_debug(" waitpid done [rc=%d]", (int) pid)); |
bdd43357 KZ |
512 | if (pid != (pid_t) - 1) { |
513 | if (pty->callbacks.child_die) | |
514 | pty->callbacks.child_die( | |
515 | pty->callback_data, | |
516 | pty->child, status); | |
517 | ul_pty_set_child(pty, (pid_t) -1); | |
518 | } else | |
519 | break; | |
520 | } | |
521 | } else { | |
522 | /* final wait */ | |
5c6903b1 SG |
523 | while ((pid = waitpid(-1, &status, options)) > 0) { |
524 | DBG(SIG, ul_debug(" waitpid done [rc=%d]", (int) pid)); | |
bdd43357 KZ |
525 | if (pid == pty->child) { |
526 | if (pty->callbacks.child_die) | |
527 | pty->callbacks.child_die( | |
528 | pty->callback_data, | |
529 | pty->child, status); | |
530 | ul_pty_set_child(pty, (pid_t) -1); | |
531 | } | |
532 | } | |
533 | } | |
534 | } | |
535 | ||
6954895c KZ |
536 | static int handle_signal(struct ul_pty *pty, int fd) |
537 | { | |
538 | struct signalfd_siginfo info; | |
539 | ssize_t bytes; | |
4f7f723b | 540 | int rc = 0; |
6954895c | 541 | |
c4bacbd1 | 542 | DBG(SIG, ul_debugobj(pty, " handle signal on fd=%d", fd)); |
6954895c KZ |
543 | |
544 | bytes = read(fd, &info, sizeof(info)); | |
545 | if (bytes != sizeof(info)) { | |
546 | if (bytes < 0 && (errno == EAGAIN || errno == EINTR)) | |
547 | return 0; | |
548 | return -errno; | |
549 | } | |
550 | ||
551 | switch (info.ssi_signo) { | |
552 | case SIGCHLD: | |
553 | DBG(SIG, ul_debugobj(pty, " get signal SIGCHLD")); | |
554 | ||
555 | if (info.ssi_code == CLD_EXITED | |
556 | || info.ssi_code == CLD_KILLED | |
bdd43357 KZ |
557 | || info.ssi_code == CLD_DUMPED) { |
558 | ||
559 | if (pty->callbacks.child_wait) | |
560 | pty->callbacks.child_wait(pty->callback_data, | |
561 | pty->child); | |
562 | else | |
563 | ul_pty_wait_for_child(pty); | |
6954895c | 564 | |
5c6903b1 | 565 | } else if (info.ssi_status == SIGSTOP && pty->child > 0) { |
bdd43357 KZ |
566 | pty->callbacks.child_sigstop(pty->callback_data, |
567 | pty->child); | |
5c6903b1 | 568 | } |
6954895c | 569 | |
984082fa | 570 | if (pty->child <= 0) { |
125314c0 | 571 | DBG(SIG, ul_debugobj(pty, " no child, setting leaving timeout")); |
6954895c | 572 | pty->poll_timeout = 10; |
984082fa KZ |
573 | timerclear(&pty->next_callback_time); |
574 | } | |
6954895c KZ |
575 | return 0; |
576 | case SIGWINCH: | |
577 | DBG(SIG, ul_debugobj(pty, " get signal SIGWINCH")); | |
578 | if (pty->isterm) { | |
579 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win); | |
580 | ioctl(pty->slave, TIOCSWINSZ, (char *)&pty->win); | |
4f7f723b KZ |
581 | |
582 | if (pty->callbacks.log_signal) | |
583 | rc = pty->callbacks.log_signal(pty->callback_data, | |
584 | &info, (void *) &pty->win); | |
6954895c KZ |
585 | } |
586 | break; | |
587 | case SIGTERM: | |
588 | /* fallthrough */ | |
589 | case SIGINT: | |
590 | /* fallthrough */ | |
591 | case SIGQUIT: | |
592 | DBG(SIG, ul_debugobj(pty, " get signal SIG{TERM,INT,QUIT}")); | |
593 | pty->delivered_signal = info.ssi_signo; | |
c556dc59 | 594 | /* Child termination is going to generate SIGCHLD (see above) */ |
6954895c KZ |
595 | if (pty->child > 0) |
596 | kill(pty->child, SIGTERM); | |
4f7f723b KZ |
597 | |
598 | if (pty->callbacks.log_signal) | |
599 | rc = pty->callbacks.log_signal(pty->callback_data, | |
600 | &info, (void *) &pty->win); | |
6954895c | 601 | break; |
7727be1a KZ |
602 | case SIGUSR1: |
603 | DBG(SIG, ul_debugobj(pty, " get signal SIGUSR1")); | |
604 | if (pty->callbacks.flush_logs) | |
605 | rc = pty->callbacks.flush_logs(pty->callback_data); | |
606 | break; | |
6954895c KZ |
607 | default: |
608 | abort(); | |
609 | } | |
610 | ||
4f7f723b | 611 | return rc; |
6954895c KZ |
612 | } |
613 | ||
614 | /* loop in parent */ | |
615 | int ul_pty_proxy_master(struct ul_pty *pty) | |
616 | { | |
6954895c KZ |
617 | int rc = 0, ret, eof = 0; |
618 | enum { | |
619 | POLLFD_SIGNAL = 0, | |
620 | POLLFD_MASTER, | |
621 | POLLFD_STDIN | |
622 | ||
623 | }; | |
624 | struct pollfd pfd[] = { | |
625 | [POLLFD_SIGNAL] = { .fd = -1, .events = POLLIN | POLLERR | POLLHUP }, | |
626 | [POLLFD_MASTER] = { .fd = pty->master, .events = POLLIN | POLLERR | POLLHUP }, | |
627 | [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP } | |
628 | }; | |
629 | ||
c556dc59 | 630 | /* We use signalfd, and standard signals by handlers are completely blocked */ |
b1154c4e | 631 | assert(pty->sigfd >= 0); |
6954895c KZ |
632 | |
633 | pfd[POLLFD_SIGNAL].fd = pty->sigfd; | |
634 | pty->poll_timeout = -1; | |
635 | ||
636 | while (!pty->delivered_signal) { | |
637 | size_t i; | |
4d5b2fed KZ |
638 | int errsv, timeout; |
639 | ||
640 | DBG(IO, ul_debugobj(pty, "--poll() loop--")); | |
641 | ||
642 | /* note, callback usually updates @next_callback_time */ | |
643 | if (timerisset(&pty->next_callback_time)) { | |
644 | struct timeval now; | |
645 | ||
646 | DBG(IO, ul_debugobj(pty, " callback requested")); | |
647 | gettime_monotonic(&now); | |
648 | if (timercmp(&now, &pty->next_callback_time, >)) { | |
649 | rc = mainloop_callback(pty); | |
650 | if (rc) | |
651 | break; | |
652 | } | |
653 | } | |
654 | ||
655 | /* set timeout */ | |
656 | if (timerisset(&pty->next_callback_time)) { | |
657 | struct timeval now, rest; | |
658 | ||
659 | gettime_monotonic(&now); | |
660 | timersub(&pty->next_callback_time, &now, &rest); | |
661 | timeout = (rest.tv_sec * 1000) + (rest.tv_usec / 1000); | |
662 | } else | |
663 | timeout = pty->poll_timeout; | |
6954895c | 664 | |
fd5698ff | 665 | /* use POLLOUT (aka "writing is now possible") if data queued */ |
2a6d1768 | 666 | if (pty->child_buffer_head) |
667 | pfd[POLLFD_MASTER].events |= POLLOUT; | |
668 | else | |
669 | pfd[POLLFD_MASTER].events &= ~POLLOUT; | |
670 | ||
4d5b2fed KZ |
671 | /* wait for input, signal or timeout */ |
672 | DBG(IO, ul_debugobj(pty, "calling poll() [timeout=%dms]", timeout)); | |
673 | ret = poll(pfd, ARRAY_SIZE(pfd), timeout); | |
6954895c | 674 | |
6954895c KZ |
675 | errsv = errno; |
676 | DBG(IO, ul_debugobj(pty, "poll() rc=%d", ret)); | |
677 | ||
4d5b2fed | 678 | /* error */ |
6954895c KZ |
679 | if (ret < 0) { |
680 | if (errsv == EAGAIN) | |
681 | continue; | |
682 | rc = -errno; | |
683 | break; | |
684 | } | |
4d5b2fed KZ |
685 | |
686 | /* timeout */ | |
6954895c | 687 | if (ret == 0) { |
4d5b2fed KZ |
688 | if (timerisset(&pty->next_callback_time)) { |
689 | rc = mainloop_callback(pty); | |
690 | if (rc == 0) | |
691 | continue; | |
5c6903b1 | 692 | } else { |
4d5b2fed | 693 | rc = 0; |
5c6903b1 | 694 | } |
4d5b2fed | 695 | |
984082fa | 696 | DBG(IO, ul_debugobj(pty, "leaving poll() loop [timeout=%d, rc=%d]", timeout, rc)); |
6954895c KZ |
697 | break; |
698 | } | |
4d5b2fed | 699 | /* event */ |
6954895c | 700 | for (i = 0; i < ARRAY_SIZE(pfd); i++) { |
6954895c KZ |
701 | if (pfd[i].revents == 0) |
702 | continue; | |
703 | ||
2a6d1768 | 704 | DBG(IO, ul_debugobj(pty, " active pfd[%s].fd=%d %s %s %s %s %s", |
6954895c KZ |
705 | i == POLLFD_STDIN ? "stdin" : |
706 | i == POLLFD_MASTER ? "master" : | |
707 | i == POLLFD_SIGNAL ? "signal" : "???", | |
708 | pfd[i].fd, | |
709 | pfd[i].revents & POLLIN ? "POLLIN" : "", | |
2a6d1768 | 710 | pfd[i].revents & POLLOUT ? "POLLOUT" : "", |
6954895c | 711 | pfd[i].revents & POLLHUP ? "POLLHUP" : "", |
f896aef3 KZ |
712 | pfd[i].revents & POLLERR ? "POLLERR" : "", |
713 | pfd[i].revents & POLLNVAL ? "POLLNVAL" : "")); | |
714 | ||
5c6903b1 | 715 | if (i == POLLFD_SIGNAL) |
6954895c | 716 | rc = handle_signal(pty, pfd[i].fd); |
2a6d1768 | 717 | else { |
718 | if (pfd[i].revents & POLLIN) | |
719 | rc = handle_io(pty, pfd[i].fd, &eof); /* data */ | |
720 | if (pfd[i].revents & POLLOUT) /* i == POLLFD_MASTER */ | |
721 | rc = flush_child_buffers(pty, NULL); | |
722 | } | |
5c6903b1 SG |
723 | |
724 | if (rc) { | |
fd5698ff KZ |
725 | int anything = 1; |
726 | ||
5c6903b1 | 727 | ul_pty_write_eof_to_child(pty); |
fd5698ff | 728 | for (anything = 1; anything;) |
2a6d1768 | 729 | flush_child_buffers(pty, &anything); |
6954895c KZ |
730 | break; |
731 | } | |
5c6903b1 SG |
732 | |
733 | if (i == POLLFD_SIGNAL) | |
734 | continue; | |
735 | ||
736 | /* EOF maybe detected in two ways; they are as follows: | |
737 | * A) poll() return POLLHUP event after close() | |
738 | * B) read() returns 0 (no data) | |
739 | * | |
740 | * POLLNVAL means that fd is closed. | |
741 | */ | |
742 | if ((pfd[i].revents & POLLHUP) || (pfd[i].revents & POLLNVAL) || eof) { | |
743 | DBG(IO, ul_debugobj(pty, " ignore FD")); | |
5c6903b1 | 744 | if (i == POLLFD_STDIN) { |
2a6d1768 | 745 | pfd[i].fd = -1; |
5c6903b1 | 746 | ul_pty_write_eof_to_child(pty); |
2a6d1768 | 747 | } else /* i == POLLFD_MASTER */ |
748 | pfd[i].revents &= ~POLLIN; | |
5c6903b1 | 749 | } |
6954895c | 750 | } |
5c6903b1 SG |
751 | if (rc) |
752 | break; | |
6954895c KZ |
753 | } |
754 | ||
8deb45fc KZ |
755 | if (rc && pty->child && pty->child != (pid_t) -1 && !pty->delivered_signal) { |
756 | kill(pty->child, SIGTERM); | |
757 | sleep(2); | |
758 | kill(pty->child, SIGKILL); | |
759 | } | |
760 | ||
b1154c4e | 761 | pty_signals_cleanup(pty); |
6954895c KZ |
762 | |
763 | DBG(IO, ul_debug("poll() done [signal=%d, rc=%d]", pty->delivered_signal, rc)); | |
764 | return rc; | |
765 | } | |
766 | ||
767 | #ifdef TEST_PROGRAM_PTY | |
768 | /* | |
769 | * $ make test_pty | |
770 | * $ ./test_pty | |
771 | * | |
772 | * ... and see for example tty(1) or "ps afu" | |
773 | */ | |
bdd43357 | 774 | static void child_sigstop(void *data __attribute__((__unused__)), pid_t child) |
6954895c | 775 | { |
6954895c | 776 | kill(getpid(), SIGSTOP); |
bdd43357 | 777 | kill(child, SIGCONT); |
6954895c KZ |
778 | } |
779 | ||
780 | int main(int argc, char *argv[]) | |
781 | { | |
6954895c KZ |
782 | struct ul_pty_callbacks *cb; |
783 | const char *shell, *command = NULL, *shname = NULL; | |
784 | int caught_signal = 0; | |
bdd43357 | 785 | pid_t child; |
8cf448e9 | 786 | struct ul_pty *pty; |
6954895c KZ |
787 | |
788 | shell = getenv("SHELL"); | |
789 | if (shell == NULL) | |
790 | shell = _PATH_BSHELL; | |
791 | if (argc == 2) | |
792 | command = argv[1]; | |
793 | ||
794 | ul_pty_init_debug(0); | |
795 | ||
8cf448e9 KZ |
796 | pty = ul_new_pty(isatty(STDIN_FILENO)); |
797 | if (!pty) | |
6954895c KZ |
798 | err(EXIT_FAILURE, "failed to allocate PTY handler"); |
799 | ||
8cf448e9 | 800 | cb = ul_pty_get_callbacks(pty); |
6954895c KZ |
801 | cb->child_sigstop = child_sigstop; |
802 | ||
8cf448e9 | 803 | if (ul_pty_setup(pty)) |
6954895c | 804 | err(EXIT_FAILURE, "failed to create pseudo-terminal"); |
4681d88b KZ |
805 | if (ul_pty_signals_setup(pty)) |
806 | err(EXIT_FAILURE, "failed to initialize signals handler"); | |
6954895c KZ |
807 | |
808 | fflush(stdout); /* ??? */ | |
809 | ||
bdd43357 | 810 | switch ((int) (child = fork())) { |
6954895c | 811 | case -1: /* error */ |
8cf448e9 | 812 | ul_pty_cleanup(pty); |
6954895c KZ |
813 | err(EXIT_FAILURE, "cannot create child process"); |
814 | break; | |
815 | ||
816 | case 0: /* child */ | |
8cf448e9 | 817 | ul_pty_init_slave(pty); |
6954895c KZ |
818 | |
819 | signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */ | |
820 | ||
821 | shname = strrchr(shell, '/'); | |
822 | shname = shname ? shname + 1 : shell; | |
823 | ||
824 | if (command) | |
1b10fa0e | 825 | execl(shell, shname, "-c", command, (char *)NULL); |
6954895c | 826 | else |
1b10fa0e | 827 | execl(shell, shname, "-i", (char *)NULL); |
6954895c KZ |
828 | err(EXIT_FAILURE, "failed to execute %s", shell); |
829 | break; | |
830 | ||
831 | default: | |
832 | break; | |
833 | } | |
834 | ||
835 | /* parent */ | |
8cf448e9 | 836 | ul_pty_set_child(pty, child); |
6954895c KZ |
837 | |
838 | /* this is the main loop */ | |
8cf448e9 | 839 | ul_pty_proxy_master(pty); |
6954895c KZ |
840 | |
841 | /* all done; cleanup and kill */ | |
8cf448e9 | 842 | caught_signal = ul_pty_get_delivered_signal(pty); |
6954895c | 843 | |
8cf448e9 KZ |
844 | if (!caught_signal && ul_pty_get_child(pty) != (pid_t)-1) |
845 | ul_pty_wait_for_child(pty); /* final wait */ | |
6954895c | 846 | |
8cf448e9 | 847 | if (caught_signal && ul_pty_get_child(pty) != (pid_t)-1) { |
6954895c | 848 | fprintf(stderr, "\nSession terminated, killing shell..."); |
bdd43357 | 849 | kill(child, SIGTERM); |
6954895c | 850 | sleep(2); |
bdd43357 | 851 | kill(child, SIGKILL); |
6954895c KZ |
852 | fprintf(stderr, " ...killed.\n"); |
853 | } | |
854 | ||
8cf448e9 KZ |
855 | ul_pty_cleanup(pty); |
856 | ul_free_pty(pty); | |
6954895c KZ |
857 | return EXIT_SUCCESS; |
858 | } | |
859 | ||
860 | #endif /* TEST_PROGRAM */ | |
861 |