]> git.proxmox.com Git - systemd.git/blame - src/shared/ptyfwd.c
New upstream version 236
[systemd.git] / src / shared / ptyfwd.c
CommitLineData
52ad194e 1/* SPDX-License-Identifier: LGPL-2.1+ */
60f067b4
JS
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
4c89c718 21#include <errno.h>
db2df898 22#include <limits.h>
4c89c718
MP
23#include <signal.h>
24#include <stddef.h>
25#include <stdint.h>
26#include <stdlib.h>
27#include <string.h>
60f067b4 28#include <sys/epoll.h>
60f067b4 29#include <sys/ioctl.h>
4c89c718 30#include <sys/time.h>
60f067b4 31#include <termios.h>
4c89c718
MP
32#include <unistd.h>
33
34#include "sd-event.h"
60f067b4 35
db2df898
MP
36#include "alloc-util.h"
37#include "fd-util.h"
4c89c718
MP
38#include "log.h"
39#include "macro.h"
60f067b4 40#include "ptyfwd.h"
4c89c718 41#include "time-util.h"
60f067b4 42
f47781d8
MP
43struct PTYForward {
44 sd_event *event;
60f067b4 45
f47781d8
MP
46 int master;
47
db2df898
MP
48 PTYForwardFlags flags;
49
f47781d8
MP
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
db2df898 70 bool read_from_master:1;
e735f4d4 71
8a584da2 72 bool done:1;
2897b343 73 bool drain:1;
8a584da2 74
e735f4d4
MP
75 bool last_char_set:1;
76 char last_char;
77
f47781d8
MP
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;
8a584da2
MP
83
84 PTYForwardHandler handler;
85 void *userdata;
f47781d8
MP
86};
87
88#define ESCAPE_USEC (1*USEC_PER_SEC)
89
8a584da2
MP
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
f47781d8 131static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
60f067b4
JS
132 const char *p;
133
f47781d8 134 assert(f);
60f067b4
JS
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
f47781d8
MP
144 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
145 f->escape_timestamp = nw;
146 f->escape_counter = 1;
60f067b4 147 } else {
f47781d8 148 (f->escape_counter)++;
60f067b4 149
f47781d8 150 if (f->escape_counter >= 3)
60f067b4
JS
151 return true;
152 }
153 } else {
f47781d8
MP
154 f->escape_timestamp = 0;
155 f->escape_counter = 0;
60f067b4
JS
156 }
157 }
158
159 return false;
160}
161
db2df898
MP
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
52ad194e
MB
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
f47781d8
MP
198static int shovel(PTYForward *f) {
199 ssize_t k;
60f067b4 200
f47781d8 201 assert(f);
60f067b4 202
f47781d8
MP
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)) {
60f067b4 207
f47781d8 208 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
60f067b4 209
f47781d8
MP
210 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
211 if (k < 0) {
60f067b4 212
f47781d8
MP
213 if (errno == EAGAIN)
214 f->stdin_readable = false;
f5e65279 215 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
f47781d8
MP
216 f->stdin_readable = false;
217 f->stdin_hangup = true;
60f067b4 218
f47781d8
MP
219 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
220 } else {
221 log_error_errno(errno, "read(): %m");
8a584da2 222 return pty_forward_done(f, -errno);
f47781d8
MP
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 {
8a584da2
MP
231 /* Check if ^] has been pressed three times within one second. If we get this we quite
232 * immediately. */
f47781d8 233 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
8a584da2 234 return pty_forward_done(f, -ECANCELED);
f47781d8
MP
235
236 f->in_buffer_full += (size_t) k;
237 }
60f067b4
JS
238 }
239
f47781d8 240 if (f->master_writable && f->in_buffer_full > 0) {
60f067b4 241
f47781d8
MP
242 k = write(f->master, f->in_buffer, f->in_buffer_full);
243 if (k < 0) {
60f067b4 244
f5e65279 245 if (IN_SET(errno, EAGAIN, EIO))
f47781d8 246 f->master_writable = false;
f5e65279 247 else if (IN_SET(errno, EPIPE, ECONNRESET)) {
f47781d8
MP
248 f->master_writable = f->master_readable = false;
249 f->master_hangup = true;
60f067b4 250
f47781d8
MP
251 f->master_event_source = sd_event_source_unref(f->master_event_source);
252 } else {
253 log_error_errno(errno, "write(): %m");
8a584da2 254 return pty_forward_done(f, -errno);
f47781d8
MP
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 }
60f067b4
JS
261 }
262
f47781d8 263 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
60f067b4 264
f47781d8
MP
265 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
266 if (k < 0) {
60f067b4 267
f47781d8 268 /* Note that EIO on the master device
e735f4d4 269 * might be caused by vhangup() or
f47781d8
MP
270 * temporary closing of everything on
271 * the other side, we treat it like
e735f4d4
MP
272 * EAGAIN here and try again, unless
273 * ignore_vhangup is off. */
60f067b4 274
db2df898 275 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
f47781d8 276 f->master_readable = false;
f5e65279 277 else if (IN_SET(errno, EPIPE, ECONNRESET, EIO)) {
f47781d8
MP
278 f->master_readable = f->master_writable = false;
279 f->master_hangup = true;
60f067b4 280
f47781d8 281 f->master_event_source = sd_event_source_unref(f->master_event_source);
60f067b4 282 } else {
f47781d8 283 log_error_errno(errno, "read(): %m");
8a584da2 284 return pty_forward_done(f, -errno);
60f067b4 285 }
db2df898
MP
286 } else {
287 f->read_from_master = true;
f47781d8 288 f->out_buffer_full += (size_t) k;
db2df898 289 }
f47781d8 290 }
60f067b4 291
f47781d8 292 if (f->stdout_writable && f->out_buffer_full > 0) {
60f067b4 293
f47781d8
MP
294 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
295 if (k < 0) {
60f067b4 296
f47781d8
MP
297 if (errno == EAGAIN)
298 f->stdout_writable = false;
f5e65279 299 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
f47781d8
MP
300 f->stdout_writable = false;
301 f->stdout_hangup = true;
302 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
60f067b4 303 } else {
f47781d8 304 log_error_errno(errno, "write(): %m");
8a584da2 305 return pty_forward_done(f, -errno);
60f067b4 306 }
60f067b4 307
f47781d8 308 } else {
e735f4d4
MP
309
310 if (k > 0) {
311 f->last_char = f->out_buffer[k-1];
312 f->last_char_set = true;
313 }
314
f47781d8
MP
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;
60f067b4 318 }
f47781d8
MP
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. */
60f067b4 325
f47781d8
MP
326 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
327 (f->in_buffer_full <= 0 || f->master_hangup))
8a584da2 328 return pty_forward_done(f, 0);
f47781d8 329 }
60f067b4 330
2897b343
MP
331 /* If we were asked to drain, and there's nothing more to handle from the master, then call the callback
332 * too. */
52ad194e 333 if (f->drain && drained(f))
2897b343
MP
334 return pty_forward_done(f, 0);
335
f47781d8
MP
336 return 0;
337}
60f067b4 338
f47781d8
MP
339static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
340 PTYForward *f = userdata;
60f067b4 341
f47781d8
MP
342 assert(f);
343 assert(e);
344 assert(e == f->master_event_source);
345 assert(fd >= 0);
346 assert(fd == f->master);
60f067b4 347
f47781d8
MP
348 if (revents & (EPOLLIN|EPOLLHUP))
349 f->master_readable = true;
60f067b4 350
f47781d8
MP
351 if (revents & (EPOLLOUT|EPOLLHUP))
352 f->master_writable = true;
60f067b4 353
f47781d8
MP
354 return shovel(f);
355}
60f067b4 356
f47781d8
MP
357static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
358 PTYForward *f = userdata;
60f067b4 359
f47781d8
MP
360 assert(f);
361 assert(e);
362 assert(e == f->stdin_event_source);
363 assert(fd >= 0);
364 assert(fd == STDIN_FILENO);
60f067b4 365
f47781d8
MP
366 if (revents & (EPOLLIN|EPOLLHUP))
367 f->stdin_readable = true;
60f067b4 368
f47781d8
MP
369 return shovel(f);
370}
60f067b4 371
f47781d8
MP
372static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
373 PTYForward *f = userdata;
60f067b4 374
f47781d8
MP
375 assert(f);
376 assert(e);
377 assert(e == f->stdout_event_source);
378 assert(fd >= 0);
379 assert(fd == STDOUT_FILENO);
60f067b4 380
f47781d8
MP
381 if (revents & (EPOLLOUT|EPOLLHUP))
382 f->stdout_writable = true;
60f067b4 383
f47781d8
MP
384 return shovel(f);
385}
60f067b4 386
f47781d8
MP
387static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
388 PTYForward *f = userdata;
389 struct winsize ws;
60f067b4 390
f47781d8
MP
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)
e735f4d4 397 (void) ioctl(f->master, TIOCSWINSZ, &ws);
f47781d8
MP
398
399 return 0;
60f067b4
JS
400}
401
e3bff60a
MP
402int pty_forward_new(
403 sd_event *event,
404 int master,
db2df898 405 PTYForwardFlags flags,
e3bff60a
MP
406 PTYForward **ret) {
407
f47781d8 408 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
60f067b4
JS
409 struct winsize ws;
410 int r;
411
f47781d8
MP
412 f = new0(PTYForward, 1);
413 if (!f)
414 return -ENOMEM;
415
db2df898 416 f->flags = flags;
e735f4d4 417
f47781d8
MP
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
db2df898 426 if (!(flags & PTY_FORWARD_READ_ONLY)) {
e3bff60a
MP
427 r = fd_nonblock(STDIN_FILENO, true);
428 if (r < 0)
429 return r;
f47781d8 430
e3bff60a
MP
431 r = fd_nonblock(STDOUT_FILENO, true);
432 if (r < 0)
433 return r;
434 }
f47781d8
MP
435
436 r = fd_nonblock(master, true);
437 if (r < 0)
438 return r;
439
440 f->master = master;
441
60f067b4 442 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
e3bff60a 443 (void) ioctl(master, TIOCSWINSZ, &ws);
60f067b4 444
db2df898 445 if (!(flags & PTY_FORWARD_READ_ONLY)) {
e3bff60a
MP
446 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
447 struct termios raw_stdin_attr;
f47781d8 448
e3bff60a 449 f->saved_stdin = true;
60f067b4 450
e3bff60a
MP
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 }
f47781d8 456
e3bff60a
MP
457 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
458 struct termios raw_stdout_attr;
f47781d8 459
e3bff60a 460 f->saved_stdout = true;
60f067b4 461
e3bff60a
MP
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 }
60f067b4 468
e3bff60a
MP
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;
2897b343
MP
472
473 if (r >= 0)
474 (void) sd_event_source_set_description(f->stdin_event_source, "ptyfwd-stdin");
e3bff60a 475 }
f47781d8
MP
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;
2897b343
MP
483 else
484 (void) sd_event_source_set_description(f->stdout_event_source, "ptyfwd-stdout");
f47781d8 485
e3bff60a
MP
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
2897b343
MP
490 (void) sd_event_source_set_description(f->master_event_source, "ptyfwd-master");
491
f47781d8 492 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
e735f4d4
MP
493 if (r < 0)
494 return r;
f47781d8 495
2897b343
MP
496 (void) sd_event_source_set_description(f->sigwinch_event_source, "ptyfwd-sigwinch");
497
f47781d8
MP
498 *ret = f;
499 f = NULL;
500
501 return 0;
502}
503
504PTYForward *pty_forward_free(PTYForward *f) {
8a584da2
MP
505 pty_forward_disconnect(f);
506 return mfree(f);
60f067b4 507}
e735f4d4
MP
508
509int 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
db2df898 520int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
e735f4d4
MP
521 int r;
522
523 assert(f);
524
db2df898 525 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
e735f4d4
MP
526 return 0;
527
aa27b158 528 SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
db2df898
MP
529
530 if (!ignore_vhangup(f)) {
e735f4d4
MP
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
8a584da2 544bool pty_forward_get_ignore_vhangup(PTYForward *f) {
e735f4d4
MP
545 assert(f);
546
db2df898 547 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
e735f4d4 548}
8a584da2
MP
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}
2897b343
MP
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;
52ad194e
MB
574 return drained(f);
575}
576
577int pty_forward_set_priority(PTYForward *f, int64_t priority) {
578 int r;
579 assert(f);
2897b343 580
52ad194e
MB
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;
2897b343 598}