]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ptyfwd.c
Merge pull request #11827 from keszybz/pkgconfig-variables
[thirdparty/systemd.git] / src / shared / ptyfwd.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <errno.h>
4 #include <limits.h>
5 #include <signal.h>
6 #include <stddef.h>
7 #include <stdint.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/epoll.h>
11 #include <sys/ioctl.h>
12 #include <sys/time.h>
13 #include <termios.h>
14 #include <unistd.h>
15
16 #include "sd-event.h"
17
18 #include "alloc-util.h"
19 #include "fd-util.h"
20 #include "log.h"
21 #include "macro.h"
22 #include "ptyfwd.h"
23 #include "terminal-util.h"
24 #include "time-util.h"
25
26 struct PTYForward {
27 sd_event *event;
28
29 int master;
30
31 PTYForwardFlags flags;
32
33 sd_event_source *stdin_event_source;
34 sd_event_source *stdout_event_source;
35 sd_event_source *master_event_source;
36
37 sd_event_source *sigwinch_event_source;
38
39 struct termios saved_stdin_attr;
40 struct termios saved_stdout_attr;
41
42 bool saved_stdin:1;
43 bool saved_stdout:1;
44
45 bool stdin_readable:1;
46 bool stdin_hangup:1;
47 bool stdout_writable:1;
48 bool stdout_hangup:1;
49 bool master_readable:1;
50 bool master_writable:1;
51 bool master_hangup:1;
52
53 bool read_from_master:1;
54
55 bool done:1;
56 bool drain:1;
57
58 bool last_char_set:1;
59 char last_char;
60
61 char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
62 size_t in_buffer_full, out_buffer_full;
63
64 usec_t escape_timestamp;
65 unsigned escape_counter;
66
67 PTYForwardHandler handler;
68 void *userdata;
69 };
70
71 #define ESCAPE_USEC (1*USEC_PER_SEC)
72
73 static void pty_forward_disconnect(PTYForward *f) {
74
75 if (f) {
76 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
77 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
78
79 f->master_event_source = sd_event_source_unref(f->master_event_source);
80 f->sigwinch_event_source = sd_event_source_unref(f->sigwinch_event_source);
81 f->event = sd_event_unref(f->event);
82
83 if (f->saved_stdout)
84 tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
85 if (f->saved_stdin)
86 tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
87
88 f->saved_stdout = f->saved_stdin = false;
89 }
90
91 /* STDIN/STDOUT should not be nonblocking normally, so let's unconditionally reset it */
92 (void) fd_nonblock(STDIN_FILENO, false);
93 (void) fd_nonblock(STDOUT_FILENO, false);
94 }
95
96 static int pty_forward_done(PTYForward *f, int rcode) {
97 _cleanup_(sd_event_unrefp) sd_event *e = NULL;
98 assert(f);
99
100 if (f->done)
101 return 0;
102
103 e = sd_event_ref(f->event);
104
105 f->done = true;
106 pty_forward_disconnect(f);
107
108 if (f->handler)
109 return f->handler(f, rcode, f->userdata);
110 else
111 return sd_event_exit(e, rcode < 0 ? EXIT_FAILURE : rcode);
112 }
113
114 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
115 const char *p;
116
117 assert(f);
118 assert(buffer);
119 assert(n > 0);
120
121 for (p = buffer; p < buffer + n; p++) {
122
123 /* Check for ^] */
124 if (*p == 0x1D) {
125 usec_t nw = now(CLOCK_MONOTONIC);
126
127 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
128 f->escape_timestamp = nw;
129 f->escape_counter = 1;
130 } else {
131 (f->escape_counter)++;
132
133 if (f->escape_counter >= 3)
134 return true;
135 }
136 } else {
137 f->escape_timestamp = 0;
138 f->escape_counter = 0;
139 }
140 }
141
142 return false;
143 }
144
145 static bool ignore_vhangup(PTYForward *f) {
146 assert(f);
147
148 if (f->flags & PTY_FORWARD_IGNORE_VHANGUP)
149 return true;
150
151 if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master)
152 return true;
153
154 return false;
155 }
156
157 static bool drained(PTYForward *f) {
158 int q = 0;
159
160 assert(f);
161
162 if (f->out_buffer_full > 0)
163 return false;
164
165 if (f->master_readable)
166 return false;
167
168 if (ioctl(f->master, TIOCINQ, &q) < 0)
169 log_debug_errno(errno, "TIOCINQ failed on master: %m");
170 else if (q > 0)
171 return false;
172
173 if (ioctl(f->master, TIOCOUTQ, &q) < 0)
174 log_debug_errno(errno, "TIOCOUTQ failed on master: %m");
175 else if (q > 0)
176 return false;
177
178 return true;
179 }
180
181 static int shovel(PTYForward *f) {
182 ssize_t k;
183
184 assert(f);
185
186 while ((f->stdin_readable && f->in_buffer_full <= 0) ||
187 (f->master_writable && f->in_buffer_full > 0) ||
188 (f->master_readable && f->out_buffer_full <= 0) ||
189 (f->stdout_writable && f->out_buffer_full > 0)) {
190
191 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
192
193 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
194 if (k < 0) {
195
196 if (errno == EAGAIN)
197 f->stdin_readable = false;
198 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
199 f->stdin_readable = false;
200 f->stdin_hangup = true;
201
202 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
203 } else {
204 log_error_errno(errno, "read(): %m");
205 return pty_forward_done(f, -errno);
206 }
207 } else if (k == 0) {
208 /* EOF on stdin */
209 f->stdin_readable = false;
210 f->stdin_hangup = true;
211
212 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
213 } else {
214 /* Check if ^] has been pressed three times within one second. If we get this we quite
215 * immediately. */
216 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
217 return pty_forward_done(f, -ECANCELED);
218
219 f->in_buffer_full += (size_t) k;
220 }
221 }
222
223 if (f->master_writable && f->in_buffer_full > 0) {
224
225 k = write(f->master, f->in_buffer, f->in_buffer_full);
226 if (k < 0) {
227
228 if (IN_SET(errno, EAGAIN, EIO))
229 f->master_writable = false;
230 else if (IN_SET(errno, EPIPE, ECONNRESET)) {
231 f->master_writable = f->master_readable = false;
232 f->master_hangup = true;
233
234 f->master_event_source = sd_event_source_unref(f->master_event_source);
235 } else {
236 log_error_errno(errno, "write(): %m");
237 return pty_forward_done(f, -errno);
238 }
239 } else {
240 assert(f->in_buffer_full >= (size_t) k);
241 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
242 f->in_buffer_full -= k;
243 }
244 }
245
246 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
247
248 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
249 if (k < 0) {
250
251 /* Note that EIO on the master device
252 * might be caused by vhangup() or
253 * temporary closing of everything on
254 * the other side, we treat it like
255 * EAGAIN here and try again, unless
256 * ignore_vhangup is off. */
257
258 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
259 f->master_readable = false;
260 else if (IN_SET(errno, EPIPE, ECONNRESET, EIO)) {
261 f->master_readable = f->master_writable = false;
262 f->master_hangup = true;
263
264 f->master_event_source = sd_event_source_unref(f->master_event_source);
265 } else {
266 log_error_errno(errno, "read(): %m");
267 return pty_forward_done(f, -errno);
268 }
269 } else {
270 f->read_from_master = true;
271 f->out_buffer_full += (size_t) k;
272 }
273 }
274
275 if (f->stdout_writable && f->out_buffer_full > 0) {
276
277 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
278 if (k < 0) {
279
280 if (errno == EAGAIN)
281 f->stdout_writable = false;
282 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
283 f->stdout_writable = false;
284 f->stdout_hangup = true;
285 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
286 } else {
287 log_error_errno(errno, "write(): %m");
288 return pty_forward_done(f, -errno);
289 }
290
291 } else {
292
293 if (k > 0) {
294 f->last_char = f->out_buffer[k-1];
295 f->last_char_set = true;
296 }
297
298 assert(f->out_buffer_full >= (size_t) k);
299 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
300 f->out_buffer_full -= k;
301 }
302 }
303 }
304
305 if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
306 /* Exit the loop if any side hung up and if there's
307 * nothing more to write or nothing we could write. */
308
309 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
310 (f->in_buffer_full <= 0 || f->master_hangup))
311 return pty_forward_done(f, 0);
312 }
313
314 /* If we were asked to drain, and there's nothing more to handle from the master, then call the callback
315 * too. */
316 if (f->drain && drained(f))
317 return pty_forward_done(f, 0);
318
319 return 0;
320 }
321
322 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
323 PTYForward *f = userdata;
324
325 assert(f);
326 assert(e);
327 assert(e == f->master_event_source);
328 assert(fd >= 0);
329 assert(fd == f->master);
330
331 if (revents & (EPOLLIN|EPOLLHUP))
332 f->master_readable = true;
333
334 if (revents & (EPOLLOUT|EPOLLHUP))
335 f->master_writable = true;
336
337 return shovel(f);
338 }
339
340 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
341 PTYForward *f = userdata;
342
343 assert(f);
344 assert(e);
345 assert(e == f->stdin_event_source);
346 assert(fd >= 0);
347 assert(fd == STDIN_FILENO);
348
349 if (revents & (EPOLLIN|EPOLLHUP))
350 f->stdin_readable = true;
351
352 return shovel(f);
353 }
354
355 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
356 PTYForward *f = userdata;
357
358 assert(f);
359 assert(e);
360 assert(e == f->stdout_event_source);
361 assert(fd >= 0);
362 assert(fd == STDOUT_FILENO);
363
364 if (revents & (EPOLLOUT|EPOLLHUP))
365 f->stdout_writable = true;
366
367 return shovel(f);
368 }
369
370 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
371 PTYForward *f = userdata;
372 struct winsize ws;
373
374 assert(f);
375 assert(e);
376 assert(e == f->sigwinch_event_source);
377
378 /* The window size changed, let's forward that. */
379 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
380 (void) ioctl(f->master, TIOCSWINSZ, &ws);
381
382 return 0;
383 }
384
385 int pty_forward_new(
386 sd_event *event,
387 int master,
388 PTYForwardFlags flags,
389 PTYForward **ret) {
390
391 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
392 struct winsize ws;
393 int r;
394
395 f = new(PTYForward, 1);
396 if (!f)
397 return -ENOMEM;
398
399 *f = (struct PTYForward) {
400 .flags = flags,
401 .master = -1,
402 };
403
404 if (event)
405 f->event = sd_event_ref(event);
406 else {
407 r = sd_event_default(&f->event);
408 if (r < 0)
409 return r;
410 }
411
412 if (!(flags & PTY_FORWARD_READ_ONLY)) {
413 r = fd_nonblock(STDIN_FILENO, true);
414 if (r < 0)
415 return r;
416
417 r = fd_nonblock(STDOUT_FILENO, true);
418 if (r < 0)
419 return r;
420 }
421
422 r = fd_nonblock(master, true);
423 if (r < 0)
424 return r;
425
426 f->master = master;
427
428 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
429 /* If we can't get the resolution from the output fd, then use our internal, regular width/height,
430 * i.e. something derived from $COLUMNS and $LINES if set. */
431
432 ws = (struct winsize) {
433 .ws_row = lines(),
434 .ws_col = columns(),
435 };
436 }
437
438 (void) ioctl(master, TIOCSWINSZ, &ws);
439
440 if (!(flags & PTY_FORWARD_READ_ONLY)) {
441 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
442 struct termios raw_stdin_attr;
443
444 f->saved_stdin = true;
445
446 raw_stdin_attr = f->saved_stdin_attr;
447 cfmakeraw(&raw_stdin_attr);
448 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
449 tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
450 }
451
452 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
453 struct termios raw_stdout_attr;
454
455 f->saved_stdout = true;
456
457 raw_stdout_attr = f->saved_stdout_attr;
458 cfmakeraw(&raw_stdout_attr);
459 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
460 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
461 tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
462 }
463
464 r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
465 if (r < 0 && r != -EPERM)
466 return r;
467
468 if (r >= 0)
469 (void) sd_event_source_set_description(f->stdin_event_source, "ptyfwd-stdin");
470 }
471
472 r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
473 if (r == -EPERM)
474 /* stdout without epoll support. Likely redirected to regular file. */
475 f->stdout_writable = true;
476 else if (r < 0)
477 return r;
478 else
479 (void) sd_event_source_set_description(f->stdout_event_source, "ptyfwd-stdout");
480
481 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
482 if (r < 0)
483 return r;
484
485 (void) sd_event_source_set_description(f->master_event_source, "ptyfwd-master");
486
487 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
488 if (r < 0)
489 return r;
490
491 (void) sd_event_source_set_description(f->sigwinch_event_source, "ptyfwd-sigwinch");
492
493 *ret = TAKE_PTR(f);
494
495 return 0;
496 }
497
498 PTYForward *pty_forward_free(PTYForward *f) {
499 pty_forward_disconnect(f);
500 return mfree(f);
501 }
502
503 int pty_forward_get_last_char(PTYForward *f, char *ch) {
504 assert(f);
505 assert(ch);
506
507 if (!f->last_char_set)
508 return -ENXIO;
509
510 *ch = f->last_char;
511 return 0;
512 }
513
514 int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
515 int r;
516
517 assert(f);
518
519 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
520 return 0;
521
522 SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
523
524 if (!ignore_vhangup(f)) {
525
526 /* We shall now react to vhangup()s? Let's check
527 * immediately if we might be in one */
528
529 f->master_readable = true;
530 r = shovel(f);
531 if (r < 0)
532 return r;
533 }
534
535 return 0;
536 }
537
538 bool pty_forward_get_ignore_vhangup(PTYForward *f) {
539 assert(f);
540
541 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
542 }
543
544 bool pty_forward_is_done(PTYForward *f) {
545 assert(f);
546
547 return f->done;
548 }
549
550 void pty_forward_set_handler(PTYForward *f, PTYForwardHandler cb, void *userdata) {
551 assert(f);
552
553 f->handler = cb;
554 f->userdata = userdata;
555 }
556
557 bool pty_forward_drain(PTYForward *f) {
558 assert(f);
559
560 /* Starts draining the forwarder. Specifically:
561 *
562 * - Returns true if there are no unprocessed bytes from the pty, false otherwise
563 *
564 * - Makes sure the handler function is called the next time the number of unprocessed bytes hits zero
565 */
566
567 f->drain = true;
568 return drained(f);
569 }
570
571 int pty_forward_set_priority(PTYForward *f, int64_t priority) {
572 int r;
573 assert(f);
574
575 r = sd_event_source_set_priority(f->stdin_event_source, priority);
576 if (r < 0)
577 return r;
578
579 r = sd_event_source_set_priority(f->stdout_event_source, priority);
580 if (r < 0)
581 return r;
582
583 r = sd_event_source_set_priority(f->master_event_source, priority);
584 if (r < 0)
585 return r;
586
587 r = sd_event_source_set_priority(f->sigwinch_event_source, priority);
588 if (r < 0)
589 return r;
590
591 return 0;
592 }
593
594 int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height) {
595 struct winsize ws;
596
597 assert(f);
598
599 if (width == (unsigned) -1 && height == (unsigned) -1)
600 return 0; /* noop */
601
602 if (width != (unsigned) -1 &&
603 (width == 0 || width > USHRT_MAX))
604 return -ERANGE;
605
606 if (height != (unsigned) -1 &&
607 (height == 0 || height > USHRT_MAX))
608 return -ERANGE;
609
610 if (width == (unsigned) -1 || height == (unsigned) -1) {
611 if (ioctl(f->master, TIOCGWINSZ, &ws) < 0)
612 return -errno;
613
614 if (width != (unsigned) -1)
615 ws.ws_col = width;
616 if (height != (unsigned) -1)
617 ws.ws_row = height;
618 } else
619 ws = (struct winsize) {
620 .ws_row = height,
621 .ws_col = width,
622 };
623
624 if (ioctl(f->master, TIOCSWINSZ, &ws) < 0)
625 return -errno;
626
627 /* Make sure we ignore SIGWINCH window size events from now on */
628 f->sigwinch_event_source = sd_event_source_unref(f->sigwinch_event_source);
629
630 return 0;
631 }