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