]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ptyfwd.c
Merge pull request #6918 from ssahani/issue-5625
[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 int shovel(PTYForward *f) {
175 ssize_t k;
176
177 assert(f);
178
179 while ((f->stdin_readable && f->in_buffer_full <= 0) ||
180 (f->master_writable && f->in_buffer_full > 0) ||
181 (f->master_readable && f->out_buffer_full <= 0) ||
182 (f->stdout_writable && f->out_buffer_full > 0)) {
183
184 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
185
186 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
187 if (k < 0) {
188
189 if (errno == EAGAIN)
190 f->stdin_readable = false;
191 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
192 f->stdin_readable = false;
193 f->stdin_hangup = true;
194
195 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
196 } else {
197 log_error_errno(errno, "read(): %m");
198 return pty_forward_done(f, -errno);
199 }
200 } else if (k == 0) {
201 /* EOF on stdin */
202 f->stdin_readable = false;
203 f->stdin_hangup = true;
204
205 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
206 } else {
207 /* Check if ^] has been pressed three times within one second. If we get this we quite
208 * immediately. */
209 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
210 return pty_forward_done(f, -ECANCELED);
211
212 f->in_buffer_full += (size_t) k;
213 }
214 }
215
216 if (f->master_writable && f->in_buffer_full > 0) {
217
218 k = write(f->master, f->in_buffer, f->in_buffer_full);
219 if (k < 0) {
220
221 if (IN_SET(errno, EAGAIN, EIO))
222 f->master_writable = false;
223 else if (IN_SET(errno, EPIPE, ECONNRESET)) {
224 f->master_writable = f->master_readable = false;
225 f->master_hangup = true;
226
227 f->master_event_source = sd_event_source_unref(f->master_event_source);
228 } else {
229 log_error_errno(errno, "write(): %m");
230 return pty_forward_done(f, -errno);
231 }
232 } else {
233 assert(f->in_buffer_full >= (size_t) k);
234 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
235 f->in_buffer_full -= k;
236 }
237 }
238
239 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
240
241 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
242 if (k < 0) {
243
244 /* Note that EIO on the master device
245 * might be caused by vhangup() or
246 * temporary closing of everything on
247 * the other side, we treat it like
248 * EAGAIN here and try again, unless
249 * ignore_vhangup is off. */
250
251 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
252 f->master_readable = false;
253 else if (IN_SET(errno, EPIPE, ECONNRESET, EIO)) {
254 f->master_readable = f->master_writable = false;
255 f->master_hangup = true;
256
257 f->master_event_source = sd_event_source_unref(f->master_event_source);
258 } else {
259 log_error_errno(errno, "read(): %m");
260 return pty_forward_done(f, -errno);
261 }
262 } else {
263 f->read_from_master = true;
264 f->out_buffer_full += (size_t) k;
265 }
266 }
267
268 if (f->stdout_writable && f->out_buffer_full > 0) {
269
270 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
271 if (k < 0) {
272
273 if (errno == EAGAIN)
274 f->stdout_writable = false;
275 else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
276 f->stdout_writable = false;
277 f->stdout_hangup = true;
278 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
279 } else {
280 log_error_errno(errno, "write(): %m");
281 return pty_forward_done(f, -errno);
282 }
283
284 } else {
285
286 if (k > 0) {
287 f->last_char = f->out_buffer[k-1];
288 f->last_char_set = true;
289 }
290
291 assert(f->out_buffer_full >= (size_t) k);
292 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
293 f->out_buffer_full -= k;
294 }
295 }
296 }
297
298 if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
299 /* Exit the loop if any side hung up and if there's
300 * nothing more to write or nothing we could write. */
301
302 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
303 (f->in_buffer_full <= 0 || f->master_hangup))
304 return pty_forward_done(f, 0);
305 }
306
307 /* If we were asked to drain, and there's nothing more to handle from the master, then call the callback
308 * too. */
309 if (f->drain && f->out_buffer_full == 0 && !f->master_readable)
310 return pty_forward_done(f, 0);
311
312 return 0;
313 }
314
315 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
316 PTYForward *f = userdata;
317
318 assert(f);
319 assert(e);
320 assert(e == f->master_event_source);
321 assert(fd >= 0);
322 assert(fd == f->master);
323
324 if (revents & (EPOLLIN|EPOLLHUP))
325 f->master_readable = true;
326
327 if (revents & (EPOLLOUT|EPOLLHUP))
328 f->master_writable = true;
329
330 return shovel(f);
331 }
332
333 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
334 PTYForward *f = userdata;
335
336 assert(f);
337 assert(e);
338 assert(e == f->stdin_event_source);
339 assert(fd >= 0);
340 assert(fd == STDIN_FILENO);
341
342 if (revents & (EPOLLIN|EPOLLHUP))
343 f->stdin_readable = true;
344
345 return shovel(f);
346 }
347
348 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
349 PTYForward *f = userdata;
350
351 assert(f);
352 assert(e);
353 assert(e == f->stdout_event_source);
354 assert(fd >= 0);
355 assert(fd == STDOUT_FILENO);
356
357 if (revents & (EPOLLOUT|EPOLLHUP))
358 f->stdout_writable = true;
359
360 return shovel(f);
361 }
362
363 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
364 PTYForward *f = userdata;
365 struct winsize ws;
366
367 assert(f);
368 assert(e);
369 assert(e == f->sigwinch_event_source);
370
371 /* The window size changed, let's forward that. */
372 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
373 (void) ioctl(f->master, TIOCSWINSZ, &ws);
374
375 return 0;
376 }
377
378 int pty_forward_new(
379 sd_event *event,
380 int master,
381 PTYForwardFlags flags,
382 PTYForward **ret) {
383
384 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
385 struct winsize ws;
386 int r;
387
388 f = new0(PTYForward, 1);
389 if (!f)
390 return -ENOMEM;
391
392 f->flags = flags;
393
394 if (event)
395 f->event = sd_event_ref(event);
396 else {
397 r = sd_event_default(&f->event);
398 if (r < 0)
399 return r;
400 }
401
402 if (!(flags & PTY_FORWARD_READ_ONLY)) {
403 r = fd_nonblock(STDIN_FILENO, true);
404 if (r < 0)
405 return r;
406
407 r = fd_nonblock(STDOUT_FILENO, true);
408 if (r < 0)
409 return r;
410 }
411
412 r = fd_nonblock(master, true);
413 if (r < 0)
414 return r;
415
416 f->master = master;
417
418 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
419 (void) ioctl(master, TIOCSWINSZ, &ws);
420
421 if (!(flags & PTY_FORWARD_READ_ONLY)) {
422 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
423 struct termios raw_stdin_attr;
424
425 f->saved_stdin = true;
426
427 raw_stdin_attr = f->saved_stdin_attr;
428 cfmakeraw(&raw_stdin_attr);
429 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
430 tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
431 }
432
433 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
434 struct termios raw_stdout_attr;
435
436 f->saved_stdout = true;
437
438 raw_stdout_attr = f->saved_stdout_attr;
439 cfmakeraw(&raw_stdout_attr);
440 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
441 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
442 tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
443 }
444
445 r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
446 if (r < 0 && r != -EPERM)
447 return r;
448
449 if (r >= 0)
450 (void) sd_event_source_set_description(f->stdin_event_source, "ptyfwd-stdin");
451 }
452
453 r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
454 if (r == -EPERM)
455 /* stdout without epoll support. Likely redirected to regular file. */
456 f->stdout_writable = true;
457 else if (r < 0)
458 return r;
459 else
460 (void) sd_event_source_set_description(f->stdout_event_source, "ptyfwd-stdout");
461
462 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
463 if (r < 0)
464 return r;
465
466 (void) sd_event_source_set_description(f->master_event_source, "ptyfwd-master");
467
468 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
469 if (r < 0)
470 return r;
471
472 (void) sd_event_source_set_description(f->sigwinch_event_source, "ptyfwd-sigwinch");
473
474 *ret = f;
475 f = NULL;
476
477 return 0;
478 }
479
480 PTYForward *pty_forward_free(PTYForward *f) {
481 pty_forward_disconnect(f);
482 return mfree(f);
483 }
484
485 int pty_forward_get_last_char(PTYForward *f, char *ch) {
486 assert(f);
487 assert(ch);
488
489 if (!f->last_char_set)
490 return -ENXIO;
491
492 *ch = f->last_char;
493 return 0;
494 }
495
496 int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
497 int r;
498
499 assert(f);
500
501 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
502 return 0;
503
504 SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
505
506 if (!ignore_vhangup(f)) {
507
508 /* We shall now react to vhangup()s? Let's check
509 * immediately if we might be in one */
510
511 f->master_readable = true;
512 r = shovel(f);
513 if (r < 0)
514 return r;
515 }
516
517 return 0;
518 }
519
520 bool pty_forward_get_ignore_vhangup(PTYForward *f) {
521 assert(f);
522
523 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
524 }
525
526 bool pty_forward_is_done(PTYForward *f) {
527 assert(f);
528
529 return f->done;
530 }
531
532 void pty_forward_set_handler(PTYForward *f, PTYForwardHandler cb, void *userdata) {
533 assert(f);
534
535 f->handler = cb;
536 f->userdata = userdata;
537 }
538
539 bool pty_forward_drain(PTYForward *f) {
540 assert(f);
541
542 /* Starts draining the forwarder. Specifically:
543 *
544 * - Returns true if there are no unprocessed bytes from the pty, false otherwise
545 *
546 * - Makes sure the handler function is called the next time the number of unprocessed bytes hits zero
547 */
548
549 f->drain = true;
550
551 return f->out_buffer_full == 0 && !f->master_readable;
552 }