]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/ptyfwd.c
network: fix typo in log message
[thirdparty/systemd.git] / src / shared / ptyfwd.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
4ba93280
LP
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
a8fbdf54 21#include <errno.h>
cf0fbc49 22#include <limits.h>
a8fbdf54
TA
23#include <signal.h>
24#include <stddef.h>
25#include <stdint.h>
26#include <stdlib.h>
27#include <string.h>
4ba93280 28#include <sys/epoll.h>
4ba93280 29#include <sys/ioctl.h>
a8fbdf54 30#include <sys/time.h>
4ba93280 31#include <termios.h>
a8fbdf54
TA
32#include <unistd.h>
33
34#include "sd-event.h"
4ba93280 35
b5efdb8a 36#include "alloc-util.h"
3ffd4af2 37#include "fd-util.h"
a8fbdf54
TA
38#include "log.h"
39#include "macro.h"
4ba93280 40#include "ptyfwd.h"
a8fbdf54 41#include "time-util.h"
4ba93280 42
023fb90b
LP
43struct PTYForward {
44 sd_event *event;
04d39279 45
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
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
ae3dde80 70 bool read_from_master:1;
9b15b784 71
2a453c2e 72 bool done:1;
95f1d6bf 73 bool drain:1;
2a453c2e 74
9b15b784
LP
75 bool last_char_set:1;
76 char last_char;
77
023fb90b
LP
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;
2a453c2e
LP
83
84 PTYForwardHandler handler;
85 void *userdata;
023fb90b
LP
86};
87
88#define ESCAPE_USEC (1*USEC_PER_SEC)
89
2a453c2e
LP
90static 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
113static 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
023fb90b 131static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
04d39279
LP
132 const char *p;
133
023fb90b 134 assert(f);
04d39279
LP
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
023fb90b
LP
144 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
145 f->escape_timestamp = nw;
146 f->escape_counter = 1;
04d39279 147 } else {
023fb90b 148 (f->escape_counter)++;
04d39279 149
023fb90b 150 if (f->escape_counter >= 3)
04d39279
LP
151 return true;
152 }
153 } else {
023fb90b
LP
154 f->escape_timestamp = 0;
155 f->escape_counter = 0;
04d39279
LP
156 }
157 }
158
159 return false;
160}
161
ae3dde80
LP
162static 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
e22e69a3
LP
174static 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
023fb90b
LP
198static int shovel(PTYForward *f) {
199 ssize_t k;
4ba93280 200
023fb90b 201 assert(f);
4ba93280 202
023fb90b
LP
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)) {
4ba93280 207
023fb90b 208 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
4ba93280 209
023fb90b
LP
210 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
211 if (k < 0) {
4ba93280 212
023fb90b
LP
213 if (errno == EAGAIN)
214 f->stdin_readable = false;
3742095b 215 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
023fb90b
LP
216 f->stdin_readable = false;
217 f->stdin_hangup = true;
4ba93280 218
023fb90b
LP
219 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
220 } else {
56f64d95 221 log_error_errno(errno, "read(): %m");
2a453c2e 222 return pty_forward_done(f, -errno);
023fb90b
LP
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 {
2a453c2e
LP
231 /* Check if ^] has been pressed three times within one second. If we get this we quite
232 * immediately. */
023fb90b 233 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
2a453c2e 234 return pty_forward_done(f, -ECANCELED);
023fb90b
LP
235
236 f->in_buffer_full += (size_t) k;
237 }
4ba93280
LP
238 }
239
023fb90b 240 if (f->master_writable && f->in_buffer_full > 0) {
4ba93280 241
023fb90b
LP
242 k = write(f->master, f->in_buffer, f->in_buffer_full);
243 if (k < 0) {
4ba93280 244
3742095b 245 if (IN_SET(errno, EAGAIN, EIO))
023fb90b 246 f->master_writable = false;
3742095b 247 else if (IN_SET(errno, EPIPE, ECONNRESET)) {
023fb90b
LP
248 f->master_writable = f->master_readable = false;
249 f->master_hangup = true;
4ba93280 250
023fb90b
LP
251 f->master_event_source = sd_event_source_unref(f->master_event_source);
252 } else {
56f64d95 253 log_error_errno(errno, "write(): %m");
2a453c2e 254 return pty_forward_done(f, -errno);
023fb90b
LP
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 }
4ba93280
LP
261 }
262
023fb90b 263 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
4ba93280 264
023fb90b
LP
265 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
266 if (k < 0) {
4ba93280 267
da054c37
LP
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. */
4ba93280 274
ae3dde80 275 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
023fb90b 276 f->master_readable = false;
3742095b 277 else if (IN_SET(errno, EPIPE, ECONNRESET, EIO)) {
023fb90b
LP
278 f->master_readable = f->master_writable = false;
279 f->master_hangup = true;
4ba93280 280
023fb90b 281 f->master_event_source = sd_event_source_unref(f->master_event_source);
04d39279 282 } else {
56f64d95 283 log_error_errno(errno, "read(): %m");
2a453c2e 284 return pty_forward_done(f, -errno);
04d39279 285 }
ae3dde80
LP
286 } else {
287 f->read_from_master = true;
023fb90b 288 f->out_buffer_full += (size_t) k;
ae3dde80 289 }
023fb90b 290 }
4ba93280 291
023fb90b 292 if (f->stdout_writable && f->out_buffer_full > 0) {
4ba93280 293
023fb90b
LP
294 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
295 if (k < 0) {
4ba93280 296
023fb90b
LP
297 if (errno == EAGAIN)
298 f->stdout_writable = false;
3742095b 299 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
023fb90b
LP
300 f->stdout_writable = false;
301 f->stdout_hangup = true;
302 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
4ba93280 303 } else {
56f64d95 304 log_error_errno(errno, "write(): %m");
2a453c2e 305 return pty_forward_done(f, -errno);
4ba93280 306 }
4ba93280 307
023fb90b 308 } else {
9b15b784
LP
309
310 if (k > 0) {
311 f->last_char = f->out_buffer[k-1];
312 f->last_char_set = true;
313 }
314
023fb90b
LP
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;
4ba93280 318 }
023fb90b
LP
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. */
4ba93280 325
023fb90b
LP
326 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
327 (f->in_buffer_full <= 0 || f->master_hangup))
2a453c2e 328 return pty_forward_done(f, 0);
023fb90b 329 }
4ba93280 330
95f1d6bf
LP
331 /* If we were asked to drain, and there's nothing more to handle from the master, then call the callback
332 * too. */
e22e69a3 333 if (f->drain && drained(f))
95f1d6bf
LP
334 return pty_forward_done(f, 0);
335
023fb90b
LP
336 return 0;
337}
4ba93280 338
023fb90b
LP
339static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
340 PTYForward *f = userdata;
4ba93280 341
023fb90b
LP
342 assert(f);
343 assert(e);
344 assert(e == f->master_event_source);
345 assert(fd >= 0);
346 assert(fd == f->master);
04d39279 347
023fb90b
LP
348 if (revents & (EPOLLIN|EPOLLHUP))
349 f->master_readable = true;
04d39279 350
023fb90b
LP
351 if (revents & (EPOLLOUT|EPOLLHUP))
352 f->master_writable = true;
04d39279 353
023fb90b
LP
354 return shovel(f);
355}
04d39279 356
023fb90b
LP
357static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
358 PTYForward *f = userdata;
04d39279 359
023fb90b
LP
360 assert(f);
361 assert(e);
362 assert(e == f->stdin_event_source);
363 assert(fd >= 0);
364 assert(fd == STDIN_FILENO);
04d39279 365
023fb90b
LP
366 if (revents & (EPOLLIN|EPOLLHUP))
367 f->stdin_readable = true;
04d39279 368
023fb90b
LP
369 return shovel(f);
370}
04d39279 371
023fb90b
LP
372static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
373 PTYForward *f = userdata;
04d39279 374
023fb90b
LP
375 assert(f);
376 assert(e);
377 assert(e == f->stdout_event_source);
378 assert(fd >= 0);
379 assert(fd == STDOUT_FILENO);
04d39279 380
023fb90b
LP
381 if (revents & (EPOLLOUT|EPOLLHUP))
382 f->stdout_writable = true;
04d39279 383
023fb90b
LP
384 return shovel(f);
385}
04d39279 386
023fb90b
LP
387static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
388 PTYForward *f = userdata;
389 struct winsize ws;
04d39279 390
023fb90b
LP
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)
679bc6cb 397 (void) ioctl(f->master, TIOCSWINSZ, &ws);
023fb90b
LP
398
399 return 0;
4ba93280 400}
04d39279 401
9c857b9d
LP
402int pty_forward_new(
403 sd_event *event,
404 int master,
ae3dde80 405 PTYForwardFlags flags,
9c857b9d
LP
406 PTYForward **ret) {
407
023fb90b 408 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
04d39279
LP
409 struct winsize ws;
410 int r;
411
023fb90b
LP
412 f = new0(PTYForward, 1);
413 if (!f)
414 return -ENOMEM;
415
ae3dde80 416 f->flags = flags;
9b15b784 417
023fb90b
LP
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
ae3dde80 426 if (!(flags & PTY_FORWARD_READ_ONLY)) {
9c857b9d
LP
427 r = fd_nonblock(STDIN_FILENO, true);
428 if (r < 0)
429 return r;
023fb90b 430
9c857b9d
LP
431 r = fd_nonblock(STDOUT_FILENO, true);
432 if (r < 0)
433 return r;
434 }
023fb90b
LP
435
436 r = fd_nonblock(master, true);
437 if (r < 0)
438 return r;
439
440 f->master = master;
441
eaf73b06 442 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
dc751688 443 (void) ioctl(master, TIOCSWINSZ, &ws);
04d39279 444
ae3dde80 445 if (!(flags & PTY_FORWARD_READ_ONLY)) {
9c857b9d
LP
446 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
447 struct termios raw_stdin_attr;
023fb90b 448
9c857b9d 449 f->saved_stdin = true;
90d14d20 450
9c857b9d
LP
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 }
023fb90b 456
9c857b9d
LP
457 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
458 struct termios raw_stdout_attr;
023fb90b 459
9c857b9d 460 f->saved_stdout = true;
04d39279 461
9c857b9d
LP
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 }
04d39279 468
9c857b9d
LP
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;
9a1c8f2d
LP
472
473 if (r >= 0)
474 (void) sd_event_source_set_description(f->stdin_event_source, "ptyfwd-stdin");
9c857b9d 475 }
023fb90b
LP
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;
9a1c8f2d
LP
483 else
484 (void) sd_event_source_set_description(f->stdout_event_source, "ptyfwd-stdout");
023fb90b 485
9c857b9d
LP
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
9a1c8f2d
LP
490 (void) sd_event_source_set_description(f->master_event_source, "ptyfwd-master");
491
023fb90b 492 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
679bc6cb
LP
493 if (r < 0)
494 return r;
023fb90b 495
9a1c8f2d
LP
496 (void) sd_event_source_set_description(f->sigwinch_event_source, "ptyfwd-sigwinch");
497
023fb90b
LP
498 *ret = f;
499 f = NULL;
500
501 return 0;
502}
503
504PTYForward *pty_forward_free(PTYForward *f) {
2a453c2e 505 pty_forward_disconnect(f);
6b430fdb 506 return mfree(f);
04d39279 507}
9b15b784 508
0ec5543c 509int pty_forward_get_last_char(PTYForward *f, char *ch) {
9b15b784
LP
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}
0ec5543c 519
ae3dde80 520int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
0ec5543c
LP
521 int r;
522
523 assert(f);
524
ae3dde80 525 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
0ec5543c
LP
526 return 0;
527
5883ff60 528 SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
ae3dde80
LP
529
530 if (!ignore_vhangup(f)) {
0ec5543c 531
da054c37
LP
532 /* We shall now react to vhangup()s? Let's check
533 * immediately if we might be in one */
0ec5543c
LP
534
535 f->master_readable = true;
536 r = shovel(f);
537 if (r < 0)
538 return r;
539 }
540
541 return 0;
542}
543
2a453c2e 544bool pty_forward_get_ignore_vhangup(PTYForward *f) {
0ec5543c
LP
545 assert(f);
546
ae3dde80 547 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
0ec5543c 548}
2a453c2e
LP
549
550bool pty_forward_is_done(PTYForward *f) {
551 assert(f);
552
553 return f->done;
554}
555
556void pty_forward_set_handler(PTYForward *f, PTYForwardHandler cb, void *userdata) {
557 assert(f);
558
559 f->handler = cb;
560 f->userdata = userdata;
561}
95f1d6bf
LP
562
563bool 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;
e22e69a3 574 return drained(f);
95f1d6bf 575}
d147457c
LP
576
577int 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}