]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/ptyfwd.c
tree-wide: use ERRNO_IS_PRIVILEGE() whereever appropriate
[thirdparty/systemd.git] / src / shared / ptyfwd.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
4ba93280 2
a8fbdf54 3#include <errno.h>
cf0fbc49 4#include <limits.h>
a8fbdf54
TA
5#include <signal.h>
6#include <stddef.h>
7#include <stdint.h>
8#include <stdlib.h>
9#include <string.h>
4ba93280 10#include <sys/epoll.h>
4ba93280 11#include <sys/ioctl.h>
a8fbdf54 12#include <sys/time.h>
4ba93280 13#include <termios.h>
a8fbdf54
TA
14#include <unistd.h>
15
16#include "sd-event.h"
4ba93280 17
b5efdb8a 18#include "alloc-util.h"
3ffd4af2 19#include "fd-util.h"
a8fbdf54
TA
20#include "log.h"
21#include "macro.h"
4ba93280 22#include "ptyfwd.h"
da22bdbc 23#include "terminal-util.h"
a8fbdf54 24#include "time-util.h"
4ba93280 25
023fb90b
LP
26struct PTYForward {
27 sd_event *event;
04d39279 28
023fb90b
LP
29 int master;
30
ae3dde80
LP
31 PTYForwardFlags flags;
32
023fb90b
LP
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
ae3dde80 53 bool read_from_master:1;
9b15b784 54
2a453c2e 55 bool done:1;
95f1d6bf 56 bool drain:1;
2a453c2e 57
9b15b784
LP
58 bool last_char_set:1;
59 char last_char;
60
023fb90b
LP
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;
2a453c2e
LP
66
67 PTYForwardHandler handler;
68 void *userdata;
023fb90b
LP
69};
70
71#define ESCAPE_USEC (1*USEC_PER_SEC)
72
2a453c2e
LP
73static 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 */
4b3c7212
LP
92 (void) fd_nonblock(STDIN_FILENO, false);
93 (void) fd_nonblock(STDOUT_FILENO, false);
2a453c2e
LP
94}
95
96static 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
023fb90b 114static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
04d39279
LP
115 const char *p;
116
023fb90b 117 assert(f);
04d39279
LP
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
023fb90b
LP
127 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
128 f->escape_timestamp = nw;
129 f->escape_counter = 1;
04d39279 130 } else {
023fb90b 131 (f->escape_counter)++;
04d39279 132
023fb90b 133 if (f->escape_counter >= 3)
04d39279
LP
134 return true;
135 }
136 } else {
023fb90b
LP
137 f->escape_timestamp = 0;
138 f->escape_counter = 0;
04d39279
LP
139 }
140 }
141
142 return false;
143}
144
ae3dde80
LP
145static 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
e22e69a3
LP
157static 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
023fb90b
LP
181static int shovel(PTYForward *f) {
182 ssize_t k;
4ba93280 183
023fb90b 184 assert(f);
4ba93280 185
023fb90b
LP
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)) {
4ba93280 190
023fb90b 191 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
4ba93280 192
023fb90b
LP
193 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
194 if (k < 0) {
4ba93280 195
023fb90b
LP
196 if (errno == EAGAIN)
197 f->stdin_readable = false;
3742095b 198 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
023fb90b
LP
199 f->stdin_readable = false;
200 f->stdin_hangup = true;
4ba93280 201
023fb90b
LP
202 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
203 } else {
56f64d95 204 log_error_errno(errno, "read(): %m");
2a453c2e 205 return pty_forward_done(f, -errno);
023fb90b
LP
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 {
2a453c2e
LP
214 /* Check if ^] has been pressed three times within one second. If we get this we quite
215 * immediately. */
023fb90b 216 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
2a453c2e 217 return pty_forward_done(f, -ECANCELED);
023fb90b
LP
218
219 f->in_buffer_full += (size_t) k;
220 }
4ba93280
LP
221 }
222
023fb90b 223 if (f->master_writable && f->in_buffer_full > 0) {
4ba93280 224
023fb90b
LP
225 k = write(f->master, f->in_buffer, f->in_buffer_full);
226 if (k < 0) {
4ba93280 227
3742095b 228 if (IN_SET(errno, EAGAIN, EIO))
023fb90b 229 f->master_writable = false;
3742095b 230 else if (IN_SET(errno, EPIPE, ECONNRESET)) {
023fb90b
LP
231 f->master_writable = f->master_readable = false;
232 f->master_hangup = true;
4ba93280 233
023fb90b
LP
234 f->master_event_source = sd_event_source_unref(f->master_event_source);
235 } else {
56f64d95 236 log_error_errno(errno, "write(): %m");
2a453c2e 237 return pty_forward_done(f, -errno);
023fb90b
LP
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 }
4ba93280
LP
244 }
245
023fb90b 246 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
4ba93280 247
023fb90b
LP
248 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
249 if (k < 0) {
4ba93280 250
da054c37
LP
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. */
4ba93280 257
ae3dde80 258 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
023fb90b 259 f->master_readable = false;
3742095b 260 else if (IN_SET(errno, EPIPE, ECONNRESET, EIO)) {
023fb90b
LP
261 f->master_readable = f->master_writable = false;
262 f->master_hangup = true;
4ba93280 263
023fb90b 264 f->master_event_source = sd_event_source_unref(f->master_event_source);
04d39279 265 } else {
56f64d95 266 log_error_errno(errno, "read(): %m");
2a453c2e 267 return pty_forward_done(f, -errno);
04d39279 268 }
ae3dde80
LP
269 } else {
270 f->read_from_master = true;
023fb90b 271 f->out_buffer_full += (size_t) k;
ae3dde80 272 }
023fb90b 273 }
4ba93280 274
023fb90b 275 if (f->stdout_writable && f->out_buffer_full > 0) {
4ba93280 276
023fb90b
LP
277 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
278 if (k < 0) {
4ba93280 279
023fb90b
LP
280 if (errno == EAGAIN)
281 f->stdout_writable = false;
3742095b 282 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
023fb90b
LP
283 f->stdout_writable = false;
284 f->stdout_hangup = true;
285 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
4ba93280 286 } else {
56f64d95 287 log_error_errno(errno, "write(): %m");
2a453c2e 288 return pty_forward_done(f, -errno);
4ba93280 289 }
4ba93280 290
023fb90b 291 } else {
9b15b784
LP
292
293 if (k > 0) {
294 f->last_char = f->out_buffer[k-1];
295 f->last_char_set = true;
296 }
297
023fb90b
LP
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;
4ba93280 301 }
023fb90b
LP
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. */
4ba93280 308
023fb90b
LP
309 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
310 (f->in_buffer_full <= 0 || f->master_hangup))
2a453c2e 311 return pty_forward_done(f, 0);
023fb90b 312 }
4ba93280 313
95f1d6bf
LP
314 /* If we were asked to drain, and there's nothing more to handle from the master, then call the callback
315 * too. */
e22e69a3 316 if (f->drain && drained(f))
95f1d6bf
LP
317 return pty_forward_done(f, 0);
318
023fb90b
LP
319 return 0;
320}
4ba93280 321
023fb90b
LP
322static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
323 PTYForward *f = userdata;
4ba93280 324
023fb90b
LP
325 assert(f);
326 assert(e);
327 assert(e == f->master_event_source);
328 assert(fd >= 0);
329 assert(fd == f->master);
04d39279 330
023fb90b
LP
331 if (revents & (EPOLLIN|EPOLLHUP))
332 f->master_readable = true;
04d39279 333
023fb90b
LP
334 if (revents & (EPOLLOUT|EPOLLHUP))
335 f->master_writable = true;
04d39279 336
023fb90b
LP
337 return shovel(f);
338}
04d39279 339
023fb90b
LP
340static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
341 PTYForward *f = userdata;
04d39279 342
023fb90b
LP
343 assert(f);
344 assert(e);
345 assert(e == f->stdin_event_source);
346 assert(fd >= 0);
347 assert(fd == STDIN_FILENO);
04d39279 348
023fb90b
LP
349 if (revents & (EPOLLIN|EPOLLHUP))
350 f->stdin_readable = true;
04d39279 351
023fb90b
LP
352 return shovel(f);
353}
04d39279 354
023fb90b
LP
355static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
356 PTYForward *f = userdata;
04d39279 357
023fb90b
LP
358 assert(f);
359 assert(e);
360 assert(e == f->stdout_event_source);
361 assert(fd >= 0);
362 assert(fd == STDOUT_FILENO);
04d39279 363
023fb90b
LP
364 if (revents & (EPOLLOUT|EPOLLHUP))
365 f->stdout_writable = true;
04d39279 366
023fb90b
LP
367 return shovel(f);
368}
04d39279 369
023fb90b
LP
370static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
371 PTYForward *f = userdata;
372 struct winsize ws;
04d39279 373
023fb90b
LP
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)
679bc6cb 380 (void) ioctl(f->master, TIOCSWINSZ, &ws);
023fb90b
LP
381
382 return 0;
4ba93280 383}
04d39279 384
9c857b9d
LP
385int pty_forward_new(
386 sd_event *event,
387 int master,
ae3dde80 388 PTYForwardFlags flags,
9c857b9d
LP
389 PTYForward **ret) {
390
023fb90b 391 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
04d39279
LP
392 struct winsize ws;
393 int r;
394
d435a182 395 f = new(PTYForward, 1);
023fb90b
LP
396 if (!f)
397 return -ENOMEM;
398
d435a182
LP
399 *f = (struct PTYForward) {
400 .flags = flags,
401 .master = -1,
402 };
9b15b784 403
023fb90b
LP
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
ae3dde80 412 if (!(flags & PTY_FORWARD_READ_ONLY)) {
9c857b9d
LP
413 r = fd_nonblock(STDIN_FILENO, true);
414 if (r < 0)
415 return r;
023fb90b 416
9c857b9d
LP
417 r = fd_nonblock(STDOUT_FILENO, true);
418 if (r < 0)
419 return r;
420 }
023fb90b
LP
421
422 r = fd_nonblock(master, true);
423 if (r < 0)
424 return r;
425
426 f->master = master;
427
da22bdbc
LP
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);
04d39279 439
ae3dde80 440 if (!(flags & PTY_FORWARD_READ_ONLY)) {
9c857b9d
LP
441 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
442 struct termios raw_stdin_attr;
023fb90b 443
9c857b9d 444 f->saved_stdin = true;
90d14d20 445
9c857b9d
LP
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 }
023fb90b 451
9c857b9d
LP
452 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
453 struct termios raw_stdout_attr;
023fb90b 454
9c857b9d 455 f->saved_stdout = true;
04d39279 456
9c857b9d
LP
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 }
04d39279 463
9c857b9d
LP
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;
9a1c8f2d
LP
467
468 if (r >= 0)
469 (void) sd_event_source_set_description(f->stdin_event_source, "ptyfwd-stdin");
9c857b9d 470 }
023fb90b
LP
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;
9a1c8f2d
LP
478 else
479 (void) sd_event_source_set_description(f->stdout_event_source, "ptyfwd-stdout");
023fb90b 480
9c857b9d
LP
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
9a1c8f2d
LP
485 (void) sd_event_source_set_description(f->master_event_source, "ptyfwd-master");
486
023fb90b 487 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
679bc6cb
LP
488 if (r < 0)
489 return r;
023fb90b 490
9a1c8f2d
LP
491 (void) sd_event_source_set_description(f->sigwinch_event_source, "ptyfwd-sigwinch");
492
1cc6c93a 493 *ret = TAKE_PTR(f);
023fb90b
LP
494
495 return 0;
496}
497
498PTYForward *pty_forward_free(PTYForward *f) {
2a453c2e 499 pty_forward_disconnect(f);
6b430fdb 500 return mfree(f);
04d39279 501}
9b15b784 502
0ec5543c 503int pty_forward_get_last_char(PTYForward *f, char *ch) {
9b15b784
LP
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}
0ec5543c 513
ae3dde80 514int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
0ec5543c
LP
515 int r;
516
517 assert(f);
518
ae3dde80 519 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
0ec5543c
LP
520 return 0;
521
5883ff60 522 SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
ae3dde80
LP
523
524 if (!ignore_vhangup(f)) {
0ec5543c 525
da054c37
LP
526 /* We shall now react to vhangup()s? Let's check
527 * immediately if we might be in one */
0ec5543c
LP
528
529 f->master_readable = true;
530 r = shovel(f);
531 if (r < 0)
532 return r;
533 }
534
535 return 0;
536}
537
2a453c2e 538bool pty_forward_get_ignore_vhangup(PTYForward *f) {
0ec5543c
LP
539 assert(f);
540
ae3dde80 541 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
0ec5543c 542}
2a453c2e
LP
543
544bool pty_forward_is_done(PTYForward *f) {
545 assert(f);
546
547 return f->done;
548}
549
550void pty_forward_set_handler(PTYForward *f, PTYForwardHandler cb, void *userdata) {
551 assert(f);
552
553 f->handler = cb;
554 f->userdata = userdata;
555}
95f1d6bf
LP
556
557bool 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;
e22e69a3 568 return drained(f);
95f1d6bf 569}
d147457c
LP
570
571int pty_forward_set_priority(PTYForward *f, int64_t priority) {
572 int r;
573 assert(f);
574
1ba37106
LP
575 if (f->stdin_event_source) {
576 r = sd_event_source_set_priority(f->stdin_event_source, priority);
577 if (r < 0)
578 return r;
579 }
d147457c
LP
580
581 r = sd_event_source_set_priority(f->stdout_event_source, priority);
582 if (r < 0)
583 return r;
584
585 r = sd_event_source_set_priority(f->master_event_source, priority);
586 if (r < 0)
587 return r;
588
589 r = sd_event_source_set_priority(f->sigwinch_event_source, priority);
590 if (r < 0)
591 return r;
592
593 return 0;
594}
d435a182
LP
595
596int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height) {
597 struct winsize ws;
598
599 assert(f);
600
601 if (width == (unsigned) -1 && height == (unsigned) -1)
602 return 0; /* noop */
603
604 if (width != (unsigned) -1 &&
605 (width == 0 || width > USHRT_MAX))
606 return -ERANGE;
607
608 if (height != (unsigned) -1 &&
609 (height == 0 || height > USHRT_MAX))
610 return -ERANGE;
611
612 if (width == (unsigned) -1 || height == (unsigned) -1) {
613 if (ioctl(f->master, TIOCGWINSZ, &ws) < 0)
614 return -errno;
615
616 if (width != (unsigned) -1)
617 ws.ws_col = width;
618 if (height != (unsigned) -1)
619 ws.ws_row = height;
620 } else
621 ws = (struct winsize) {
622 .ws_row = height,
623 .ws_col = width,
624 };
625
626 if (ioctl(f->master, TIOCSWINSZ, &ws) < 0)
627 return -errno;
628
629 /* Make sure we ignore SIGWINCH window size events from now on */
630 f->sigwinch_event_source = sd_event_source_unref(f->sigwinch_event_source);
631
632 return 0;
633}