1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
11 #include <sys/epoll.h>
12 #include <sys/ioctl.h>
19 #include "alloc-util.h"
20 #include "errno-util.h"
21 #include "extract-word.h"
27 #include "stat-util.h"
29 #include "terminal-util.h"
30 #include "time-util.h"
32 typedef enum AnsiColorState
{
33 ANSI_COLOR_STATE_TEXT
,
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
,
50 PTYForwardFlags flags
;
52 sd_event_source
*stdin_event_source
;
53 sd_event_source
*stdout_event_source
;
54 sd_event_source
*master_event_source
;
56 sd_event_source
*sigwinch_event_source
;
58 struct termios saved_stdin_attr
;
59 struct termios saved_stdout_attr
;
61 bool close_input_fd
:1;
62 bool close_output_fd
:1;
67 bool stdin_readable
:1;
69 bool stdout_writable
:1;
71 bool master_readable
:1;
72 bool master_writable
:1;
75 bool read_from_master
:1;
83 char in_buffer
[LINE_MAX
], *out_buffer
;
84 size_t out_buffer_size
;
85 size_t in_buffer_full
, out_buffer_full
;
87 usec_t escape_timestamp
;
88 unsigned escape_counter
;
90 PTYForwardHandler handler
;
93 char *background_color
;
94 AnsiColorState ansi_color_state
;
98 char *title
; /* Window title to show by default */
99 char *title_prefix
; /* If terminal client overrides window title, prefix this string */
102 #define ESCAPE_USEC (1*USEC_PER_SEC)
104 static void pty_forward_disconnect(PTYForward
*f
) {
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
);
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
);
116 if (f
->output_fd
>= 0) {
118 (void) tcsetattr(f
->output_fd
, TCSANOW
, &f
->saved_stdout_attr
);
120 /* STDIN/STDOUT should not be non-blocking normally, so let's reset it */
121 (void) fd_nonblock(f
->output_fd
, false);
123 if (colors_enabled()) {
124 (void) loop_write(f
->output_fd
, ANSI_NORMAL ANSI_ERASE_TO_END_OF_SCREEN
, SIZE_MAX
);
127 (void) loop_write(f
->output_fd
, ANSI_WINDOW_TITLE_POP
, SIZE_MAX
);
130 if (f
->close_output_fd
)
131 f
->output_fd
= safe_close(f
->output_fd
);
134 if (f
->input_fd
>= 0) {
136 (void) tcsetattr(f
->input_fd
, TCSANOW
, &f
->saved_stdin_attr
);
138 (void) fd_nonblock(f
->input_fd
, false);
139 if (f
->close_input_fd
)
140 f
->input_fd
= safe_close(f
->input_fd
);
143 f
->saved_stdout
= f
->saved_stdin
= false;
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;
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
;
155 static int pty_forward_done(PTYForward
*f
, int rcode
) {
156 _cleanup_(sd_event_unrefp
) sd_event
*e
= NULL
;
162 e
= sd_event_ref(f
->event
);
165 pty_forward_disconnect(f
);
168 return f
->handler(f
, rcode
, f
->userdata
);
170 return sd_event_exit(e
, rcode
< 0 ? EXIT_FAILURE
: rcode
);
173 static bool look_for_escape(PTYForward
*f
, const char *buffer
, size_t n
) {
180 for (p
= buffer
; p
< buffer
+ n
; p
++) {
184 usec_t nw
= now(CLOCK_MONOTONIC
);
186 if (f
->escape_counter
== 0 || nw
> f
->escape_timestamp
+ ESCAPE_USEC
) {
187 f
->escape_timestamp
= nw
;
188 f
->escape_counter
= 1;
190 (f
->escape_counter
)++;
192 if (f
->escape_counter
>= 3)
196 f
->escape_timestamp
= 0;
197 f
->escape_counter
= 0;
204 static bool ignore_vhangup(PTYForward
*f
) {
207 if (f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
)
210 if ((f
->flags
& PTY_FORWARD_IGNORE_INITIAL_VHANGUP
) && !f
->read_from_master
)
216 static bool drained(PTYForward
*f
) {
221 if (f
->out_buffer_full
> 0)
224 if (f
->master_readable
)
227 if (ioctl(f
->master
, TIOCINQ
, &q
) < 0)
228 log_debug_errno(errno
, "TIOCINQ failed on master: %m");
232 if (ioctl(f
->master
, TIOCOUTQ
, &q
) < 0)
233 log_debug_errno(errno
, "TIOCOUTQ failed on master: %m");
240 static char *background_color_sequence(PTYForward
*f
) {
242 assert(f
->background_color
);
244 return strjoin("\x1B[", f
->background_color
, "m");
247 static int insert_string(PTYForward
*f
, size_t offset
, const char *s
) {
249 assert(offset
<= f
->out_buffer_full
);
252 size_t l
= strlen(s
);
253 assert(l
<= INT_MAX
); /* Make sure we can still return this */
255 void *p
= realloc(f
->out_buffer
, MAX(f
->out_buffer_full
+ l
, (size_t) LINE_MAX
));
260 f
->out_buffer_size
= MALLOC_SIZEOF_SAFE(f
->out_buffer
);
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
;
269 static int insert_newline_color_erase(PTYForward
*f
, size_t offset
) {
270 _cleanup_free_
char *s
= NULL
;
274 if (!f
->background_color
)
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 */
280 s
= background_color_sequence(f
);
284 if (!strextend(&s
, ANSI_ERASE_TO_END_OF_LINE
))
287 return insert_string(f
, offset
, s
);
290 static int insert_carriage_return_color(PTYForward
*f
, size_t offset
) {
291 _cleanup_free_
char *s
= NULL
;
295 if (!f
->background_color
)
298 /* When we see a carriage return (ASCII 13) this this sets only the background */
300 s
= background_color_sequence(f
);
304 return insert_string(f
, offset
, s
);
307 static int is_csi_background_reset_sequence(const char *seq
) {
315 } token_state
= COLOR_TOKEN_NO
;
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
327 _cleanup_free_
char *token
= NULL
;
329 r
= extract_first_word(&seq
, &token
, ";", EXTRACT_RELAX
|EXTRACT_DONT_COALESCE_SEPARATORS
|EXTRACT_RETAIN_ESCAPE
);
335 switch (token_state
) {
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 */
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 */
348 case COLOR_TOKEN_START
:
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 */
355 token_state
= COLOR_TOKEN_NO
; /* something weird? */
358 case COLOR_TOKEN_24BIT_R
:
359 token_state
= COLOR_TOKEN_24BIT_G
;
362 case COLOR_TOKEN_24BIT_G
:
363 token_state
= COLOR_TOKEN_24BIT_B
;
366 case COLOR_TOKEN_8BIT
:
367 case COLOR_TOKEN_24BIT_B
:
368 token_state
= COLOR_TOKEN_NO
;
376 static int insert_background_fix(PTYForward
*f
, size_t offset
) {
379 if (!f
->background_color
)
382 if (!is_csi_background_reset_sequence(strempty(f
->csi_sequence
)))
385 _cleanup_free_
char *s
= NULL
;
386 s
= strjoin(";", f
->background_color
);
390 return insert_string(f
, offset
, s
);
393 static int insert_window_title_fix(PTYForward
*f
, size_t offset
) {
396 if (!f
->title_prefix
)
399 if (!f
->osc_sequence
)
402 const char *t
= startswith(f
->osc_sequence
, "0;"); /* Set window title OSC sequence*/
406 _cleanup_free_
char *joined
= strjoin("\x1b]0;", f
->title_prefix
, t
, "\a");
410 return insert_string(f
, offset
, joined
);
413 static int pty_forward_ansi_process(PTYForward
*f
, size_t offset
) {
417 assert(offset
<= f
->out_buffer_full
);
419 if (!f
->background_color
&& !f
->title_prefix
)
422 if (FLAGS_SET(f
->flags
, PTY_FORWARD_DUMB_TERMINAL
))
425 for (size_t i
= offset
; i
< f
->out_buffer_full
; i
++) {
426 char c
= f
->out_buffer
[i
];
428 switch (f
->ansi_color_state
) {
430 case ANSI_COLOR_STATE_TEXT
:
433 case ANSI_COLOR_STATE_NEWLINE
: {
434 /* Immediately after a newline insert an ANSI sequence to erase the line with a background color */
436 r
= insert_newline_color_erase(f
, i
);
444 case ANSI_COLOR_STATE_CARRIAGE_RETURN
: {
445 /* Immediately after a carriage return insert an ANSI sequence set the background color back */
447 r
= insert_carriage_return_color(f
, i
);
455 case ANSI_COLOR_STATE_ESC
: {
458 f
->ansi_color_state
= ANSI_COLOR_STATE_CSI_SEQUENCE
;
460 } else if (c
== ']') {
461 f
->ansi_color_state
= ANSI_COLOR_STATE_OSC_SEQUENCE
;
468 case ANSI_COLOR_STATE_CSI_SEQUENCE
: {
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 */
474 if (strlen_ptr(f
->csi_sequence
) >= 64) {
475 /* Safety check: lets not accept unbounded CSI sequences */
477 f
->csi_sequence
= mfree(f
->csi_sequence
);
479 } else if (!strextend(&f
->csi_sequence
, CHAR_TO_STR(c
)))
482 /* Otherwise, the CSI sequence is over */
485 /* This is an "SGR" (Select Graphic Rendition) sequence. Patch in our background color. */
486 r
= insert_background_fix(f
, i
);
493 f
->csi_sequence
= mfree(f
->csi_sequence
);
494 f
->ansi_color_state
= ANSI_COLOR_STATE_TEXT
;
500 case ANSI_COLOR_STATE_OSC_SEQUENCE
: {
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
);
507 } else if (!strextend(&f
->osc_sequence
, CHAR_TO_STR(c
)))
510 /* Otherwise, the OSC sequence is over
512 * There are two allowed ways to end an OSC sequence:
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 \. */
518 if (c
== '\x07' || c
== '\x1b') {
519 r
= insert_window_title_fix(f
, i
+1);
526 f
->osc_sequence
= mfree(f
->osc_sequence
);
527 f
->ansi_color_state
= ANSI_COLOR_STATE_TEXT
;
534 assert_not_reached();
538 f
->ansi_color_state
= ANSI_COLOR_STATE_NEWLINE
;
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
;
544 f
->ansi_color_state
= ANSI_COLOR_STATE_TEXT
;
550 static int do_shovel(PTYForward
*f
) {
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. */
561 if (f
->background_color
) {
562 /* Erase the first line when we start */
563 f
->out_buffer
= background_color_sequence(f
);
567 if (!strextend(&f
->out_buffer
, ANSI_ERASE_TO_END_OF_LINE
))
572 if (!strextend(&f
->out_buffer
,
573 ANSI_WINDOW_TITLE_PUSH
574 "\x1b]2;", f
->title
, "\a"))
579 f
->out_buffer_full
= strlen(f
->out_buffer
);
580 f
->out_buffer_size
= MALLOC_SIZEOF_SAFE(f
->out_buffer
);
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
);
591 f
->out_buffer_size
= MALLOC_SIZEOF_SAFE(p
);
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)) {
599 if (f
->stdin_readable
&& f
->in_buffer_full
< LINE_MAX
) {
601 k
= read(f
->input_fd
, f
->in_buffer
+ f
->in_buffer_full
, LINE_MAX
- f
->in_buffer_full
);
605 f
->stdin_readable
= false;
606 else if (errno
== EIO
|| ERRNO_IS_DISCONNECT(errno
)) {
607 f
->stdin_readable
= false;
608 f
->stdin_hangup
= true;
610 f
->stdin_event_source
= sd_event_source_unref(f
->stdin_event_source
);
612 return log_error_errno(errno
, "read(): %m");
615 f
->stdin_readable
= false;
616 f
->stdin_hangup
= true;
618 f
->stdin_event_source
= sd_event_source_unref(f
->stdin_event_source
);
620 /* Check if ^] has been pressed three times within one second. If we get this we quite
622 if (look_for_escape(f
, f
->in_buffer
+ f
->in_buffer_full
, k
))
625 f
->in_buffer_full
+= (size_t) k
;
629 if (f
->master_writable
&& f
->in_buffer_full
> 0) {
631 k
= write(f
->master
, f
->in_buffer
, f
->in_buffer_full
);
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;
640 f
->master_event_source
= sd_event_source_unref(f
->master_event_source
);
642 return log_error_errno(errno
, "write(): %m");
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
;
650 if (f
->master_readable
&& f
->out_buffer_full
< MIN(f
->out_buffer_size
, (size_t) LINE_MAX
)) {
652 k
= read(f
->master
, f
->out_buffer
+ f
->out_buffer_full
, f
->out_buffer_size
- f
->out_buffer_full
);
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. */
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;
665 f
->master_event_source
= sd_event_source_unref(f
->master_event_source
);
667 return log_error_errno(errno
, "read(): %m");
669 f
->read_from_master
= true;
670 size_t scan_index
= f
->out_buffer_full
;
671 f
->out_buffer_full
+= (size_t) k
;
673 r
= pty_forward_ansi_process(f
, scan_index
);
675 return log_error_errno(r
, "Failed to scan for ANSI sequences: %m");
679 if (f
->stdout_writable
&& f
->out_buffer_full
> 0) {
681 k
= write(f
->output_fd
, f
->out_buffer
, f
->out_buffer_full
);
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
);
691 return log_error_errno(errno
, "write(): %m");
696 f
->last_char
= f
->out_buffer
[k
-1];
697 f
->last_char_set
= true;
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
;
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. */
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);
716 /* If we were asked to drain, and there's nothing more to handle from the master, then call the callback
718 if (f
->drain
&& drained(f
))
719 return pty_forward_done(f
, 0);
724 static int shovel(PTYForward
*f
) {
731 return pty_forward_done(f
, r
);
736 static int on_master_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
737 PTYForward
*f
= ASSERT_PTR(userdata
);
740 assert(e
== f
->master_event_source
);
742 assert(fd
== f
->master
);
744 if (revents
& (EPOLLIN
|EPOLLHUP
))
745 f
->master_readable
= true;
747 if (revents
& (EPOLLOUT
|EPOLLHUP
))
748 f
->master_writable
= true;
753 static int on_stdin_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
754 PTYForward
*f
= ASSERT_PTR(userdata
);
757 assert(e
== f
->stdin_event_source
);
759 assert(fd
== f
->input_fd
);
761 if (revents
& (EPOLLIN
|EPOLLHUP
))
762 f
->stdin_readable
= true;
767 static int on_stdout_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
768 PTYForward
*f
= ASSERT_PTR(userdata
);
771 assert(e
== f
->stdout_event_source
);
773 assert(fd
== f
->output_fd
);
775 if (revents
& (EPOLLOUT
|EPOLLHUP
))
776 f
->stdout_writable
= true;
781 static int on_sigwinch_event(sd_event_source
*e
, const struct signalfd_siginfo
*si
, void *userdata
) {
782 PTYForward
*f
= ASSERT_PTR(userdata
);
786 assert(e
== f
->sigwinch_event_source
);
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
);
798 PTYForwardFlags flags
,
801 _cleanup_(pty_forward_freep
) PTYForward
*f
= NULL
;
805 f
= new(PTYForward
, 1);
809 *f
= (struct PTYForward
) {
817 f
->event
= sd_event_ref(event
);
819 r
= sd_event_default(&f
->event
);
824 if (FLAGS_SET(flags
, PTY_FORWARD_READ_ONLY
))
825 f
->output_fd
= STDOUT_FILENO
;
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).
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
843 log_debug_errno(f
->input_fd
, "Failed to reopen stdin, using original fd: %m");
845 r
= fd_nonblock(STDIN_FILENO
, true);
849 f
->input_fd
= STDIN_FILENO
;
851 f
->close_input_fd
= true;
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");
858 r
= fd_nonblock(STDOUT_FILENO
, true);
862 f
->output_fd
= STDOUT_FILENO
;
864 f
->close_output_fd
= true;
867 r
= fd_nonblock(master
, true);
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
;
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
) {
885 (void) ioctl(master
, TIOCSWINSZ
, &ws
);
887 if (!(flags
& PTY_FORWARD_READ_ONLY
)) {
890 assert(f
->input_fd
>= 0);
892 same
= fd_inode_same(f
->input_fd
, f
->output_fd
);
896 if (tcgetattr(f
->input_fd
, &f
->saved_stdin_attr
) >= 0) {
897 struct termios raw_stdin_attr
;
899 f
->saved_stdin
= true;
901 raw_stdin_attr
= f
->saved_stdin_attr
;
902 cfmakeraw(&raw_stdin_attr
);
905 raw_stdin_attr
.c_oflag
= f
->saved_stdin_attr
.c_oflag
;
907 (void) tcsetattr(f
->input_fd
, TCSANOW
, &raw_stdin_attr
);
910 if (!same
&& tcgetattr(f
->output_fd
, &f
->saved_stdout_attr
) >= 0) {
911 struct termios raw_stdout_attr
;
913 f
->saved_stdout
= true;
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
);
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
)
927 (void) sd_event_source_set_description(f
->stdin_event_source
, "ptyfwd-stdin");
930 r
= sd_event_add_io(f
->event
, &f
->stdout_event_source
, f
->output_fd
, EPOLLOUT
|EPOLLET
, on_stdout_event
, f
);
932 /* stdout without epoll support. Likely redirected to regular file. */
933 f
->stdout_writable
= true;
937 (void) sd_event_source_set_description(f
->stdout_event_source
, "ptyfwd-stdout");
939 r
= sd_event_add_io(f
->event
, &f
->master_event_source
, master
, EPOLLIN
|EPOLLOUT
|EPOLLET
, on_master_event
, f
);
943 (void) sd_event_source_set_description(f
->master_event_source
, "ptyfwd-master");
945 r
= sd_event_add_signal(f
->event
, &f
->sigwinch_event_source
, SIGWINCH
, on_sigwinch_event
, f
);
949 (void) sd_event_source_set_description(f
->sigwinch_event_source
, "ptyfwd-sigwinch");
956 PTYForward
*pty_forward_free(PTYForward
*f
) {
959 pty_forward_disconnect(f
);
960 free(f
->background_color
);
962 free(f
->title_prefix
);
966 int pty_forward_get_last_char(PTYForward
*f
, char *ch
) {
970 if (!f
->last_char_set
)
977 int pty_forward_set_ignore_vhangup(PTYForward
*f
, bool b
) {
982 if (!!(f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
) == b
)
985 SET_FLAG(f
->flags
, PTY_FORWARD_IGNORE_VHANGUP
, b
);
987 if (!ignore_vhangup(f
)) {
989 /* We shall now react to vhangup()s? Let's check
990 * immediately if we might be in one */
992 f
->master_readable
= true;
1001 bool pty_forward_get_ignore_vhangup(PTYForward
*f
) {
1004 return !!(f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
);
1007 bool pty_forward_is_done(PTYForward
*f
) {
1013 void pty_forward_set_handler(PTYForward
*f
, PTYForwardHandler cb
, void *userdata
) {
1017 f
->userdata
= userdata
;
1020 bool pty_forward_drain(PTYForward
*f
) {
1023 /* Starts draining the forwarder. Specifically:
1025 * - Returns true if there are no unprocessed bytes from the pty, false otherwise
1027 * - Makes sure the handler function is called the next time the number of unprocessed bytes hits zero
1034 int pty_forward_set_priority(PTYForward
*f
, int64_t priority
) {
1038 if (f
->stdin_event_source
) {
1039 r
= sd_event_source_set_priority(f
->stdin_event_source
, priority
);
1044 r
= sd_event_source_set_priority(f
->stdout_event_source
, priority
);
1048 r
= sd_event_source_set_priority(f
->master_event_source
, priority
);
1052 r
= sd_event_source_set_priority(f
->sigwinch_event_source
, priority
);
1059 int pty_forward_set_width_height(PTYForward
*f
, unsigned width
, unsigned height
) {
1064 if (width
== UINT_MAX
&& height
== UINT_MAX
)
1065 return 0; /* noop */
1067 if (width
!= UINT_MAX
&&
1068 (width
== 0 || width
> USHRT_MAX
))
1071 if (height
!= UINT_MAX
&&
1072 (height
== 0 || height
> USHRT_MAX
))
1075 if (width
== UINT_MAX
|| height
== UINT_MAX
) {
1076 if (ioctl(f
->master
, TIOCGWINSZ
, &ws
) < 0)
1079 if (width
!= UINT_MAX
)
1081 if (height
!= UINT_MAX
)
1084 ws
= (struct winsize
) {
1089 if (ioctl(f
->master
, TIOCSWINSZ
, &ws
) < 0)
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
);
1098 int pty_forward_set_background_color(PTYForward
*f
, const char *color
) {
1101 return free_and_strdup(&f
->background_color
, color
);
1104 int pty_forward_set_title(PTYForward
*f
, const char *title
) {
1107 /* Refuse accepting a title when we already started shoveling */
1108 if (f
->out_buffer_size
> 0)
1111 return free_and_strdup(&f
->title
, title
);
1114 int pty_forward_set_titlef(PTYForward
*f
, const char *format
, ...) {
1115 _cleanup_free_
char *title
= NULL
;
1122 if (f
->out_buffer_size
> 0)
1125 va_start(ap
, format
);
1126 DISABLE_WARNING_FORMAT_NONLITERAL
;
1127 r
= vasprintf(&title
, format
, ap
);
1133 return free_and_replace(f
->title
, title
);
1136 int pty_forward_set_title_prefix(PTYForward
*f
, const char *title_prefix
) {
1139 return free_and_strdup(&f
->title_prefix
, title_prefix
);