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