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