]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ptyfwd.c
journal/cat: allow connecting output to specific journal namespace
[thirdparty/systemd.git] / src / shared / ptyfwd.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <signal.h>
7 #include <stddef.h>
8 #include <stdint.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/epoll.h>
12 #include <sys/ioctl.h>
13 #include <sys/time.h>
14 #include <termios.h>
15 #include <unistd.h>
16
17 #include "sd-event.h"
18
19 #include "alloc-util.h"
20 #include "errno-util.h"
21 #include "extract-word.h"
22 #include "fd-util.h"
23 #include "io-util.h"
24 #include "log.h"
25 #include "macro.h"
26 #include "ptyfwd.h"
27 #include "stat-util.h"
28 #include "strv.h"
29 #include "terminal-util.h"
30 #include "time-util.h"
31
32 typedef enum AnsiColorState {
33 ANSI_COLOR_STATE_TEXT,
34 ANSI_COLOR_STATE_ESC,
35 ANSI_COLOR_STATE_CSI_SEQUENCE,
36 ANSI_COLOR_STATE_OSC_SEQUENCE,
37 ANSI_COLOR_STATE_NEWLINE,
38 ANSI_COLOR_STATE_CARRIAGE_RETURN,
39 _ANSI_COLOR_STATE_MAX,
40 _ANSI_COLOR_STATE_INVALID = -EINVAL,
41 } AnsiColorState;
42
43 struct PTYForward {
44 sd_event *event;
45
46 int input_fd;
47 int output_fd;
48 int master;
49
50 PTYForwardFlags flags;
51
52 sd_event_source *stdin_event_source;
53 sd_event_source *stdout_event_source;
54 sd_event_source *master_event_source;
55
56 sd_event_source *sigwinch_event_source;
57
58 struct termios saved_stdin_attr;
59 struct termios saved_stdout_attr;
60
61 bool close_input_fd:1;
62 bool close_output_fd:1;
63
64 bool saved_stdin:1;
65 bool saved_stdout:1;
66
67 bool stdin_readable:1;
68 bool stdin_hangup:1;
69 bool stdout_writable:1;
70 bool stdout_hangup:1;
71 bool master_readable:1;
72 bool master_writable:1;
73 bool master_hangup:1;
74
75 bool read_from_master:1;
76
77 bool done:1;
78 bool drain:1;
79
80 bool last_char_set:1;
81 char last_char;
82
83 char in_buffer[LINE_MAX], *out_buffer;
84 size_t out_buffer_size;
85 size_t in_buffer_full, out_buffer_full;
86
87 usec_t escape_timestamp;
88 unsigned escape_counter;
89
90 PTYForwardHandler handler;
91 void *userdata;
92
93 char *background_color;
94 AnsiColorState ansi_color_state;
95 char *csi_sequence;
96 char *osc_sequence;
97
98 char *title; /* Window title to show by default */
99 char *title_prefix; /* If terminal client overrides window title, prefix this string */
100 };
101
102 #define ESCAPE_USEC (1*USEC_PER_SEC)
103
104 static void pty_forward_disconnect(PTYForward *f) {
105
106 if (!f)
107 return;
108
109 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
110 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
111
112 f->master_event_source = sd_event_source_unref(f->master_event_source);
113 f->sigwinch_event_source = sd_event_source_unref(f->sigwinch_event_source);
114 f->event = sd_event_unref(f->event);
115
116 if (f->output_fd >= 0) {
117 if (f->saved_stdout)
118 (void) tcsetattr(f->output_fd, TCSANOW, &f->saved_stdout_attr);
119
120 /* STDIN/STDOUT should not be non-blocking normally, so let's reset it */
121 (void) fd_nonblock(f->output_fd, false);
122
123 if (colors_enabled()) {
124 (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_SCREEN, SIZE_MAX);
125
126 if (f->title)
127 (void) loop_write(f->output_fd, ANSI_WINDOW_TITLE_POP, SIZE_MAX);
128 }
129
130 if (f->close_output_fd)
131 f->output_fd = safe_close(f->output_fd);
132 }
133
134 if (f->input_fd >= 0) {
135 if (f->saved_stdin)
136 (void) tcsetattr(f->input_fd, TCSANOW, &f->saved_stdin_attr);
137
138 (void) fd_nonblock(f->input_fd, false);
139 if (f->close_input_fd)
140 f->input_fd = safe_close(f->input_fd);
141 }
142
143 f->saved_stdout = f->saved_stdin = false;
144
145 f->out_buffer = mfree(f->out_buffer);
146 f->out_buffer_size = 0;
147 f->out_buffer_full = 0;
148 f->in_buffer_full = 0;
149
150 f->csi_sequence = mfree(f->csi_sequence);
151 f->osc_sequence = mfree(f->osc_sequence);
152 f->ansi_color_state = _ANSI_COLOR_STATE_INVALID;
153 }
154
155 static int pty_forward_done(PTYForward *f, int rcode) {
156 _cleanup_(sd_event_unrefp) sd_event *e = NULL;
157 assert(f);
158
159 if (f->done)
160 return 0;
161
162 e = sd_event_ref(f->event);
163
164 f->done = true;
165 pty_forward_disconnect(f);
166
167 if (f->handler)
168 return f->handler(f, rcode, f->userdata);
169 else
170 return sd_event_exit(e, rcode < 0 ? EXIT_FAILURE : rcode);
171 }
172
173 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
174 const char *p;
175
176 assert(f);
177 assert(buffer);
178 assert(n > 0);
179
180 for (p = buffer; p < buffer + n; p++) {
181
182 /* Check for ^] */
183 if (*p == 0x1D) {
184 usec_t nw = now(CLOCK_MONOTONIC);
185
186 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
187 f->escape_timestamp = nw;
188 f->escape_counter = 1;
189 } else {
190 (f->escape_counter)++;
191
192 if (f->escape_counter >= 3)
193 return true;
194 }
195 } else {
196 f->escape_timestamp = 0;
197 f->escape_counter = 0;
198 }
199 }
200
201 return false;
202 }
203
204 static bool ignore_vhangup(PTYForward *f) {
205 assert(f);
206
207 if (f->flags & PTY_FORWARD_IGNORE_VHANGUP)
208 return true;
209
210 if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master)
211 return true;
212
213 return false;
214 }
215
216 static bool drained(PTYForward *f) {
217 int q = 0;
218
219 assert(f);
220
221 if (f->out_buffer_full > 0)
222 return false;
223
224 if (f->master_readable)
225 return false;
226
227 if (ioctl(f->master, TIOCINQ, &q) < 0)
228 log_debug_errno(errno, "TIOCINQ failed on master: %m");
229 else if (q > 0)
230 return false;
231
232 if (ioctl(f->master, TIOCOUTQ, &q) < 0)
233 log_debug_errno(errno, "TIOCOUTQ failed on master: %m");
234 else if (q > 0)
235 return false;
236
237 return true;
238 }
239
240 static char *background_color_sequence(PTYForward *f) {
241 assert(f);
242 assert(f->background_color);
243
244 return strjoin("\x1B[", f->background_color, "m");
245 }
246
247 static int insert_string(PTYForward *f, size_t offset, const char *s) {
248 assert(f);
249 assert(offset <= f->out_buffer_full);
250 assert(s);
251
252 size_t l = strlen(s);
253 assert(l <= INT_MAX); /* Make sure we can still return this */
254
255 void *p = realloc(f->out_buffer, MAX(f->out_buffer_full + l, (size_t) LINE_MAX));
256 if (!p)
257 return -ENOMEM;
258
259 f->out_buffer = p;
260 f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
261
262 memmove(f->out_buffer + offset + l, f->out_buffer + offset, f->out_buffer_full - offset);
263 memcpy(f->out_buffer + offset, s, l);
264 f->out_buffer_full += l;
265
266 return (int) l;
267 }
268
269 static int insert_newline_color_erase(PTYForward *f, size_t offset) {
270 _cleanup_free_ char *s = NULL;
271
272 assert(f);
273
274 if (!f->background_color)
275 return 0;
276
277 /* When we see a newline (ASCII 10) then this sets the background color to the desired one, and erase the rest
278 * of the line with it */
279
280 s = background_color_sequence(f);
281 if (!s)
282 return -ENOMEM;
283
284 if (!strextend(&s, ANSI_ERASE_TO_END_OF_LINE))
285 return -ENOMEM;
286
287 return insert_string(f, offset, s);
288 }
289
290 static int insert_carriage_return_color(PTYForward *f, size_t offset) {
291 _cleanup_free_ char *s = NULL;
292
293 assert(f);
294
295 if (!f->background_color)
296 return 0;
297
298 /* When we see a carriage return (ASCII 13) this this sets only the background */
299
300 s = background_color_sequence(f);
301 if (!s)
302 return -ENOMEM;
303
304 return insert_string(f, offset, s);
305 }
306
307 static int is_csi_background_reset_sequence(const char *seq) {
308 enum {
309 COLOR_TOKEN_NO,
310 COLOR_TOKEN_START,
311 COLOR_TOKEN_8BIT,
312 COLOR_TOKEN_24BIT_R,
313 COLOR_TOKEN_24BIT_G,
314 COLOR_TOKEN_24BIT_B,
315 } token_state = COLOR_TOKEN_NO;
316
317 bool b = false;
318 int r;
319
320 assert(seq);
321
322 /* This parses CSI "m" sequences, and determines if they reset the background color. If so returns
323 * 1. This can then be used to insert another sequence that sets the color to the desired
324 * replacement. */
325
326 for (;;) {
327 _cleanup_free_ char *token = NULL;
328
329 r = extract_first_word(&seq, &token, ";", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
330 if (r < 0)
331 return r;
332 if (r == 0)
333 break;
334
335 switch (token_state) {
336
337 case COLOR_TOKEN_NO:
338
339 if (STR_IN_SET(token, "", "0", "00", "49"))
340 b = true; /* These tokens set the background back to normal */
341 else if (STR_IN_SET(token, "40", "41", "42", "43", "44", "45", "46", "47", "48"))
342 b = false; /* And these tokens set them to something other than normal */
343
344 if (STR_IN_SET(token, "38", "48", "58"))
345 token_state = COLOR_TOKEN_START; /* These tokens mean an 8bit or 24bit color will follow */
346 break;
347
348 case COLOR_TOKEN_START:
349
350 if (STR_IN_SET(token, "5", "05"))
351 token_state = COLOR_TOKEN_8BIT; /* 8bit color */
352 else if (STR_IN_SET(token, "2", "02"))
353 token_state = COLOR_TOKEN_24BIT_R; /* 24bit color */
354 else
355 token_state = COLOR_TOKEN_NO; /* something weird? */
356 break;
357
358 case COLOR_TOKEN_24BIT_R:
359 token_state = COLOR_TOKEN_24BIT_G;
360 break;
361
362 case COLOR_TOKEN_24BIT_G:
363 token_state = COLOR_TOKEN_24BIT_B;
364 break;
365
366 case COLOR_TOKEN_8BIT:
367 case COLOR_TOKEN_24BIT_B:
368 token_state = COLOR_TOKEN_NO;
369 break;
370 }
371 }
372
373 return b;
374 }
375
376 static int insert_background_fix(PTYForward *f, size_t offset) {
377 assert(f);
378
379 if (!f->background_color)
380 return 0;
381
382 if (!is_csi_background_reset_sequence(strempty(f->csi_sequence)))
383 return 0;
384
385 _cleanup_free_ char *s = NULL;
386 s = strjoin(";", f->background_color);
387 if (!s)
388 return -ENOMEM;
389
390 return insert_string(f, offset, s);
391 }
392
393 static int insert_window_title_fix(PTYForward *f, size_t offset) {
394 assert(f);
395
396 if (!f->title_prefix)
397 return 0;
398
399 if (!f->osc_sequence)
400 return 0;
401
402 const char *t = startswith(f->osc_sequence, "0;"); /* Set window title OSC sequence*/
403 if (!t)
404 return 0;
405
406 _cleanup_free_ char *joined = strjoin("\x1b]0;", f->title_prefix, t, "\a");
407 if (!joined)
408 return -ENOMEM;
409
410 return insert_string(f, offset, joined);
411 }
412
413 static int pty_forward_ansi_process(PTYForward *f, size_t offset) {
414 int r;
415
416 assert(f);
417 assert(offset <= f->out_buffer_full);
418
419 if (!f->background_color && !f->title_prefix)
420 return 0;
421
422 if (FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL))
423 return 0;
424
425 for (size_t i = offset; i < f->out_buffer_full; i++) {
426 char c = f->out_buffer[i];
427
428 switch (f->ansi_color_state) {
429
430 case ANSI_COLOR_STATE_TEXT:
431 break;
432
433 case ANSI_COLOR_STATE_NEWLINE: {
434 /* Immediately after a newline insert an ANSI sequence to erase the line with a background color */
435
436 r = insert_newline_color_erase(f, i);
437 if (r < 0)
438 return r;
439
440 i += r;
441 break;
442 }
443
444 case ANSI_COLOR_STATE_CARRIAGE_RETURN: {
445 /* Immediately after a carriage return insert an ANSI sequence set the background color back */
446
447 r = insert_carriage_return_color(f, i);
448 if (r < 0)
449 return r;
450
451 i += r;
452 break;
453 }
454
455 case ANSI_COLOR_STATE_ESC: {
456
457 if (c == '[') {
458 f->ansi_color_state = ANSI_COLOR_STATE_CSI_SEQUENCE;
459 continue;
460 } else if (c == ']') {
461 f->ansi_color_state = ANSI_COLOR_STATE_OSC_SEQUENCE;
462 continue;
463 }
464
465 break;
466 }
467
468 case ANSI_COLOR_STATE_CSI_SEQUENCE: {
469
470 if (c >= 0x20 && c <= 0x3F) {
471 /* If this is a "parameter" or "intermediary" byte (i.e. ranges 0x20…0x2F and
472 * 0x30…0x3F) then we are still in the CSI sequence */
473
474 if (strlen_ptr(f->csi_sequence) >= 64) {
475 /* Safety check: lets not accept unbounded CSI sequences */
476
477 f->csi_sequence = mfree(f->csi_sequence);
478 break;
479 } else if (!strextend(&f->csi_sequence, CHAR_TO_STR(c)))
480 return -ENOMEM;
481 } else {
482 /* Otherwise, the CSI sequence is over */
483
484 if (c == 'm') {
485 /* This is an "SGR" (Select Graphic Rendition) sequence. Patch in our background color. */
486 r = insert_background_fix(f, i);
487 if (r < 0)
488 return r;
489
490 i += r;
491 }
492
493 f->csi_sequence = mfree(f->csi_sequence);
494 f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
495 }
496
497 continue;
498 }
499
500 case ANSI_COLOR_STATE_OSC_SEQUENCE: {
501
502 if ((uint8_t) c >= ' ') {
503 if (strlen_ptr(f->osc_sequence) >= 64) {
504 /* Safety check: lets not accept unbounded OSC sequences */
505 f->osc_sequence = mfree(f->osc_sequence);
506 break;
507 } else if (!strextend(&f->osc_sequence, CHAR_TO_STR(c)))
508 return -ENOMEM;
509 } else {
510 /* Otherwise, the OSC sequence is over
511 *
512 * There are two allowed ways to end an OSC sequence:
513 * BEL '\x07'
514 * String Terminator (ST): <Esc>\ - "\x1b\x5c"
515 * since we cannot lookahead to see if the Esc is followed by a \
516 * we cut a corner here and assume it will be \. */
517
518 if (c == '\x07' || c == '\x1b') {
519 r = insert_window_title_fix(f, i+1);
520 if (r < 0)
521 return r;
522
523 i += r;
524 }
525
526 f->osc_sequence = mfree(f->osc_sequence);
527 f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
528 }
529
530 continue;
531 }
532
533 default:
534 assert_not_reached();
535 }
536
537 if (c == '\n')
538 f->ansi_color_state = ANSI_COLOR_STATE_NEWLINE;
539 else if (c == '\r')
540 f->ansi_color_state = ANSI_COLOR_STATE_CARRIAGE_RETURN;
541 else if (c == 0x1B) /* ESC */
542 f->ansi_color_state = ANSI_COLOR_STATE_ESC;
543 else
544 f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
545 }
546
547 return 0;
548 }
549
550 static int do_shovel(PTYForward *f) {
551 ssize_t k;
552 int r;
553
554 assert(f);
555
556 if (f->out_buffer_size == 0 && !FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL)) {
557 /* If the output hasn't been allocated yet, we are at the beginning of the first
558 * shovelling. Hence, possibly send some initial ANSI sequences. But do so only if we are
559 * talking to an actual TTY. */
560
561 if (f->background_color) {
562 /* Erase the first line when we start */
563 f->out_buffer = background_color_sequence(f);
564 if (!f->out_buffer)
565 return log_oom();
566
567 if (!strextend(&f->out_buffer, ANSI_ERASE_TO_END_OF_LINE))
568 return log_oom();
569 }
570
571 if (f->title) {
572 if (!strextend(&f->out_buffer,
573 ANSI_WINDOW_TITLE_PUSH
574 "\x1b]2;", f->title, "\a"))
575 return log_oom();
576 }
577
578 if (f->out_buffer) {
579 f->out_buffer_full = strlen(f->out_buffer);
580 f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
581 }
582 }
583
584 if (f->out_buffer_size < LINE_MAX) {
585 /* Make sure we always have room for at least one "line" */
586 void *p = realloc(f->out_buffer, LINE_MAX);
587 if (!p)
588 return log_oom();
589
590 f->out_buffer = p;
591 f->out_buffer_size = MALLOC_SIZEOF_SAFE(p);
592 }
593
594 while ((f->stdin_readable && f->in_buffer_full <= 0) ||
595 (f->master_writable && f->in_buffer_full > 0) ||
596 (f->master_readable && f->out_buffer_full <= 0) ||
597 (f->stdout_writable && f->out_buffer_full > 0)) {
598
599 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
600
601 k = read(f->input_fd, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
602 if (k < 0) {
603
604 if (errno == EAGAIN)
605 f->stdin_readable = false;
606 else if (errno == EIO || ERRNO_IS_DISCONNECT(errno)) {
607 f->stdin_readable = false;
608 f->stdin_hangup = true;
609
610 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
611 } else
612 return log_error_errno(errno, "read(): %m");
613 } else if (k == 0) {
614 /* EOF on stdin */
615 f->stdin_readable = false;
616 f->stdin_hangup = true;
617
618 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
619 } else {
620 /* Check if ^] has been pressed three times within one second. If we get this we quite
621 * immediately. */
622 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
623 return -ECANCELED;
624
625 f->in_buffer_full += (size_t) k;
626 }
627 }
628
629 if (f->master_writable && f->in_buffer_full > 0) {
630
631 k = write(f->master, f->in_buffer, f->in_buffer_full);
632 if (k < 0) {
633
634 if (IN_SET(errno, EAGAIN, EIO))
635 f->master_writable = false;
636 else if (IN_SET(errno, EPIPE, ECONNRESET)) {
637 f->master_writable = f->master_readable = false;
638 f->master_hangup = true;
639
640 f->master_event_source = sd_event_source_unref(f->master_event_source);
641 } else
642 return log_error_errno(errno, "write(): %m");
643 } else {
644 assert(f->in_buffer_full >= (size_t) k);
645 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
646 f->in_buffer_full -= k;
647 }
648 }
649
650 if (f->master_readable && f->out_buffer_full < MIN(f->out_buffer_size, (size_t) LINE_MAX)) {
651
652 k = read(f->master, f->out_buffer + f->out_buffer_full, f->out_buffer_size - f->out_buffer_full);
653 if (k < 0) {
654
655 /* Note that EIO on the master device might be caused by vhangup() or
656 * temporary closing of everything on the other side, we treat it like EAGAIN
657 * here and try again, unless ignore_vhangup is off. */
658
659 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
660 f->master_readable = false;
661 else if (IN_SET(errno, EPIPE, ECONNRESET, EIO)) {
662 f->master_readable = f->master_writable = false;
663 f->master_hangup = true;
664
665 f->master_event_source = sd_event_source_unref(f->master_event_source);
666 } else
667 return log_error_errno(errno, "read(): %m");
668 } else {
669 f->read_from_master = true;
670 size_t scan_index = f->out_buffer_full;
671 f->out_buffer_full += (size_t) k;
672
673 r = pty_forward_ansi_process(f, scan_index);
674 if (r < 0)
675 return log_error_errno(r, "Failed to scan for ANSI sequences: %m");
676 }
677 }
678
679 if (f->stdout_writable && f->out_buffer_full > 0) {
680
681 k = write(f->output_fd, f->out_buffer, f->out_buffer_full);
682 if (k < 0) {
683
684 if (errno == EAGAIN)
685 f->stdout_writable = false;
686 else if (errno == EIO || ERRNO_IS_DISCONNECT(errno)) {
687 f->stdout_writable = false;
688 f->stdout_hangup = true;
689 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
690 } else
691 return log_error_errno(errno, "write(): %m");
692
693 } else {
694
695 if (k > 0) {
696 f->last_char = f->out_buffer[k-1];
697 f->last_char_set = true;
698 }
699
700 assert(f->out_buffer_full >= (size_t) k);
701 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
702 f->out_buffer_full -= k;
703 }
704 }
705 }
706
707 if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
708 /* Exit the loop if any side hung up and if there's
709 * nothing more to write or nothing we could write. */
710
711 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
712 (f->in_buffer_full <= 0 || f->master_hangup))
713 return pty_forward_done(f, 0);
714 }
715
716 /* If we were asked to drain, and there's nothing more to handle from the master, then call the callback
717 * too. */
718 if (f->drain && drained(f))
719 return pty_forward_done(f, 0);
720
721 return 0;
722 }
723
724 static int shovel(PTYForward *f) {
725 int r;
726
727 assert(f);
728
729 r = do_shovel(f);
730 if (r < 0)
731 return pty_forward_done(f, r);
732
733 return r;
734 }
735
736 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
737 PTYForward *f = ASSERT_PTR(userdata);
738
739 assert(e);
740 assert(e == f->master_event_source);
741 assert(fd >= 0);
742 assert(fd == f->master);
743
744 if (revents & (EPOLLIN|EPOLLHUP))
745 f->master_readable = true;
746
747 if (revents & (EPOLLOUT|EPOLLHUP))
748 f->master_writable = true;
749
750 return shovel(f);
751 }
752
753 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
754 PTYForward *f = ASSERT_PTR(userdata);
755
756 assert(e);
757 assert(e == f->stdin_event_source);
758 assert(fd >= 0);
759 assert(fd == f->input_fd);
760
761 if (revents & (EPOLLIN|EPOLLHUP))
762 f->stdin_readable = true;
763
764 return shovel(f);
765 }
766
767 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
768 PTYForward *f = ASSERT_PTR(userdata);
769
770 assert(e);
771 assert(e == f->stdout_event_source);
772 assert(fd >= 0);
773 assert(fd == f->output_fd);
774
775 if (revents & (EPOLLOUT|EPOLLHUP))
776 f->stdout_writable = true;
777
778 return shovel(f);
779 }
780
781 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
782 PTYForward *f = ASSERT_PTR(userdata);
783 struct winsize ws;
784
785 assert(e);
786 assert(e == f->sigwinch_event_source);
787
788 /* The window size changed, let's forward that. */
789 if (ioctl(f->output_fd, TIOCGWINSZ, &ws) >= 0)
790 (void) ioctl(f->master, TIOCSWINSZ, &ws);
791
792 return 0;
793 }
794
795 int pty_forward_new(
796 sd_event *event,
797 int master,
798 PTYForwardFlags flags,
799 PTYForward **ret) {
800
801 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
802 struct winsize ws;
803 int r;
804
805 f = new(PTYForward, 1);
806 if (!f)
807 return -ENOMEM;
808
809 *f = (struct PTYForward) {
810 .flags = flags,
811 .master = -EBADF,
812 .input_fd = -EBADF,
813 .output_fd = -EBADF,
814 };
815
816 if (event)
817 f->event = sd_event_ref(event);
818 else {
819 r = sd_event_default(&f->event);
820 if (r < 0)
821 return r;
822 }
823
824 if (FLAGS_SET(flags, PTY_FORWARD_READ_ONLY))
825 f->output_fd = STDOUT_FILENO;
826 else {
827 /* If we shall be invoked in interactive mode, let's switch on non-blocking mode, so that we
828 * never end up staving one direction while we block on the other. However, let's be careful
829 * here and not turn on O_NONBLOCK for stdin/stdout directly, but of reopened copies of
830 * them. This has two advantages: when we are killed abruptly the stdin/stdout fds won't be
831 * left in O_NONBLOCK state for the next process using them. In addition, if some process
832 * running in the background wants to continue writing to our stdout it can do so without
833 * being confused by O_NONBLOCK.
834 * We keep O_APPEND (if present) on the output FD and (try to) keep current file position on
835 * both input and output FD (principle of least surprise).
836 */
837
838 f->input_fd = fd_reopen_propagate_append_and_position(
839 STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
840 if (f->input_fd < 0) {
841 /* Handle failures gracefully, after all certain fd types cannot be reopened
842 * (sockets, …) */
843 log_debug_errno(f->input_fd, "Failed to reopen stdin, using original fd: %m");
844
845 r = fd_nonblock(STDIN_FILENO, true);
846 if (r < 0)
847 return r;
848
849 f->input_fd = STDIN_FILENO;
850 } else
851 f->close_input_fd = true;
852
853 f->output_fd = fd_reopen_propagate_append_and_position(
854 STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
855 if (f->output_fd < 0) {
856 log_debug_errno(f->output_fd, "Failed to reopen stdout, using original fd: %m");
857
858 r = fd_nonblock(STDOUT_FILENO, true);
859 if (r < 0)
860 return r;
861
862 f->output_fd = STDOUT_FILENO;
863 } else
864 f->close_output_fd = true;
865 }
866
867 r = fd_nonblock(master, true);
868 if (r < 0)
869 return r;
870
871 f->master = master;
872
873 /* Disable color/window title setting unless we talk to a good TTY */
874 if (!isatty_safe(f->output_fd) || get_color_mode() == COLOR_OFF)
875 f->flags |= PTY_FORWARD_DUMB_TERMINAL;
876
877 if (ioctl(f->output_fd, TIOCGWINSZ, &ws) < 0)
878 /* If we can't get the resolution from the output fd, then use our internal, regular width/height,
879 * i.e. something derived from $COLUMNS and $LINES if set. */
880 ws = (struct winsize) {
881 .ws_row = lines(),
882 .ws_col = columns(),
883 };
884
885 (void) ioctl(master, TIOCSWINSZ, &ws);
886
887 if (!(flags & PTY_FORWARD_READ_ONLY)) {
888 int same;
889
890 assert(f->input_fd >= 0);
891
892 same = fd_inode_same(f->input_fd, f->output_fd);
893 if (same < 0)
894 return same;
895
896 if (tcgetattr(f->input_fd, &f->saved_stdin_attr) >= 0) {
897 struct termios raw_stdin_attr;
898
899 f->saved_stdin = true;
900
901 raw_stdin_attr = f->saved_stdin_attr;
902 cfmakeraw(&raw_stdin_attr);
903
904 if (!same)
905 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
906
907 (void) tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr);
908 }
909
910 if (!same && tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) {
911 struct termios raw_stdout_attr;
912
913 f->saved_stdout = true;
914
915 raw_stdout_attr = f->saved_stdout_attr;
916 cfmakeraw(&raw_stdout_attr);
917 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
918 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
919 (void) tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr);
920 }
921
922 r = sd_event_add_io(f->event, &f->stdin_event_source, f->input_fd, EPOLLIN|EPOLLET, on_stdin_event, f);
923 if (r < 0 && r != -EPERM)
924 return r;
925
926 if (r >= 0)
927 (void) sd_event_source_set_description(f->stdin_event_source, "ptyfwd-stdin");
928 }
929
930 r = sd_event_add_io(f->event, &f->stdout_event_source, f->output_fd, EPOLLOUT|EPOLLET, on_stdout_event, f);
931 if (r == -EPERM)
932 /* stdout without epoll support. Likely redirected to regular file. */
933 f->stdout_writable = true;
934 else if (r < 0)
935 return r;
936 else
937 (void) sd_event_source_set_description(f->stdout_event_source, "ptyfwd-stdout");
938
939 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
940 if (r < 0)
941 return r;
942
943 (void) sd_event_source_set_description(f->master_event_source, "ptyfwd-master");
944
945 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
946 if (r < 0)
947 return r;
948
949 (void) sd_event_source_set_description(f->sigwinch_event_source, "ptyfwd-sigwinch");
950
951 *ret = TAKE_PTR(f);
952
953 return 0;
954 }
955
956 PTYForward *pty_forward_free(PTYForward *f) {
957 if (!f)
958 return NULL;
959 pty_forward_disconnect(f);
960 free(f->background_color);
961 free(f->title);
962 free(f->title_prefix);
963 return mfree(f);
964 }
965
966 int pty_forward_get_last_char(PTYForward *f, char *ch) {
967 assert(f);
968 assert(ch);
969
970 if (!f->last_char_set)
971 return -ENXIO;
972
973 *ch = f->last_char;
974 return 0;
975 }
976
977 int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
978 int r;
979
980 assert(f);
981
982 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
983 return 0;
984
985 SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
986
987 if (!ignore_vhangup(f)) {
988
989 /* We shall now react to vhangup()s? Let's check
990 * immediately if we might be in one */
991
992 f->master_readable = true;
993 r = shovel(f);
994 if (r < 0)
995 return r;
996 }
997
998 return 0;
999 }
1000
1001 bool pty_forward_get_ignore_vhangup(PTYForward *f) {
1002 assert(f);
1003
1004 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
1005 }
1006
1007 bool pty_forward_is_done(PTYForward *f) {
1008 assert(f);
1009
1010 return f->done;
1011 }
1012
1013 void pty_forward_set_handler(PTYForward *f, PTYForwardHandler cb, void *userdata) {
1014 assert(f);
1015
1016 f->handler = cb;
1017 f->userdata = userdata;
1018 }
1019
1020 bool pty_forward_drain(PTYForward *f) {
1021 assert(f);
1022
1023 /* Starts draining the forwarder. Specifically:
1024 *
1025 * - Returns true if there are no unprocessed bytes from the pty, false otherwise
1026 *
1027 * - Makes sure the handler function is called the next time the number of unprocessed bytes hits zero
1028 */
1029
1030 f->drain = true;
1031 return drained(f);
1032 }
1033
1034 int pty_forward_set_priority(PTYForward *f, int64_t priority) {
1035 int r;
1036 assert(f);
1037
1038 if (f->stdin_event_source) {
1039 r = sd_event_source_set_priority(f->stdin_event_source, priority);
1040 if (r < 0)
1041 return r;
1042 }
1043
1044 r = sd_event_source_set_priority(f->stdout_event_source, priority);
1045 if (r < 0)
1046 return r;
1047
1048 r = sd_event_source_set_priority(f->master_event_source, priority);
1049 if (r < 0)
1050 return r;
1051
1052 r = sd_event_source_set_priority(f->sigwinch_event_source, priority);
1053 if (r < 0)
1054 return r;
1055
1056 return 0;
1057 }
1058
1059 int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height) {
1060 struct winsize ws;
1061
1062 assert(f);
1063
1064 if (width == UINT_MAX && height == UINT_MAX)
1065 return 0; /* noop */
1066
1067 if (width != UINT_MAX &&
1068 (width == 0 || width > USHRT_MAX))
1069 return -ERANGE;
1070
1071 if (height != UINT_MAX &&
1072 (height == 0 || height > USHRT_MAX))
1073 return -ERANGE;
1074
1075 if (width == UINT_MAX || height == UINT_MAX) {
1076 if (ioctl(f->master, TIOCGWINSZ, &ws) < 0)
1077 return -errno;
1078
1079 if (width != UINT_MAX)
1080 ws.ws_col = width;
1081 if (height != UINT_MAX)
1082 ws.ws_row = height;
1083 } else
1084 ws = (struct winsize) {
1085 .ws_row = height,
1086 .ws_col = width,
1087 };
1088
1089 if (ioctl(f->master, TIOCSWINSZ, &ws) < 0)
1090 return -errno;
1091
1092 /* Make sure we ignore SIGWINCH window size events from now on */
1093 f->sigwinch_event_source = sd_event_source_unref(f->sigwinch_event_source);
1094
1095 return 0;
1096 }
1097
1098 int pty_forward_set_background_color(PTYForward *f, const char *color) {
1099 assert(f);
1100
1101 return free_and_strdup(&f->background_color, color);
1102 }
1103
1104 int pty_forward_set_title(PTYForward *f, const char *title) {
1105 assert(f);
1106
1107 /* Refuse accepting a title when we already started shoveling */
1108 if (f->out_buffer_size > 0)
1109 return -EBUSY;
1110
1111 return free_and_strdup(&f->title, title);
1112 }
1113
1114 int pty_forward_set_titlef(PTYForward *f, const char *format, ...) {
1115 _cleanup_free_ char *title = NULL;
1116 va_list ap;
1117 int r;
1118
1119 assert(f);
1120 assert(format);
1121
1122 if (f->out_buffer_size > 0)
1123 return -EBUSY;
1124
1125 va_start(ap, format);
1126 DISABLE_WARNING_FORMAT_NONLITERAL;
1127 r = vasprintf(&title, format, ap);
1128 REENABLE_WARNING;
1129 va_end(ap);
1130 if (r < 0)
1131 return -ENOMEM;
1132
1133 return free_and_replace(f->title, title);
1134 }
1135
1136 int pty_forward_set_title_prefix(PTYForward *f, const char *title_prefix) {
1137 assert(f);
1138
1139 return free_and_strdup(&f->title_prefix, title_prefix);
1140 }