]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/ptyfwd.c
Merge pull request #1668 from ssahani/net1
[thirdparty/systemd.git] / src / shared / ptyfwd.c
CommitLineData
4ba93280
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010-2013 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <sys/epoll.h>
4ba93280
LP
23#include <sys/ioctl.h>
24#include <limits.h>
25#include <termios.h>
26
3ffd4af2 27#include "fd-util.h"
4ba93280 28#include "ptyfwd.h"
3ffd4af2 29#include "util.h"
4ba93280 30
023fb90b
LP
31struct PTYForward {
32 sd_event *event;
04d39279 33
023fb90b
LP
34 int master;
35
ae3dde80
LP
36 PTYForwardFlags flags;
37
023fb90b
LP
38 sd_event_source *stdin_event_source;
39 sd_event_source *stdout_event_source;
40 sd_event_source *master_event_source;
41
42 sd_event_source *sigwinch_event_source;
43
44 struct termios saved_stdin_attr;
45 struct termios saved_stdout_attr;
46
47 bool saved_stdin:1;
48 bool saved_stdout:1;
49
50 bool stdin_readable:1;
51 bool stdin_hangup:1;
52 bool stdout_writable:1;
53 bool stdout_hangup:1;
54 bool master_readable:1;
55 bool master_writable:1;
56 bool master_hangup:1;
57
ae3dde80 58 bool read_from_master:1;
9b15b784
LP
59
60 bool last_char_set:1;
61 char last_char;
62
023fb90b
LP
63 char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
64 size_t in_buffer_full, out_buffer_full;
65
66 usec_t escape_timestamp;
67 unsigned escape_counter;
68};
69
70#define ESCAPE_USEC (1*USEC_PER_SEC)
71
72static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
04d39279
LP
73 const char *p;
74
023fb90b 75 assert(f);
04d39279
LP
76 assert(buffer);
77 assert(n > 0);
78
79 for (p = buffer; p < buffer + n; p++) {
80
81 /* Check for ^] */
82 if (*p == 0x1D) {
83 usec_t nw = now(CLOCK_MONOTONIC);
84
023fb90b
LP
85 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
86 f->escape_timestamp = nw;
87 f->escape_counter = 1;
04d39279 88 } else {
023fb90b 89 (f->escape_counter)++;
04d39279 90
023fb90b 91 if (f->escape_counter >= 3)
04d39279
LP
92 return true;
93 }
94 } else {
023fb90b
LP
95 f->escape_timestamp = 0;
96 f->escape_counter = 0;
04d39279
LP
97 }
98 }
99
100 return false;
101}
102
ae3dde80
LP
103static bool ignore_vhangup(PTYForward *f) {
104 assert(f);
105
106 if (f->flags & PTY_FORWARD_IGNORE_VHANGUP)
107 return true;
108
109 if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master)
110 return true;
111
112 return false;
113}
114
023fb90b
LP
115static int shovel(PTYForward *f) {
116 ssize_t k;
4ba93280 117
023fb90b 118 assert(f);
4ba93280 119
023fb90b
LP
120 while ((f->stdin_readable && f->in_buffer_full <= 0) ||
121 (f->master_writable && f->in_buffer_full > 0) ||
122 (f->master_readable && f->out_buffer_full <= 0) ||
123 (f->stdout_writable && f->out_buffer_full > 0)) {
4ba93280 124
023fb90b 125 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
4ba93280 126
023fb90b
LP
127 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
128 if (k < 0) {
4ba93280 129
023fb90b
LP
130 if (errno == EAGAIN)
131 f->stdin_readable = false;
132 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
133 f->stdin_readable = false;
134 f->stdin_hangup = true;
4ba93280 135
023fb90b
LP
136 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
137 } else {
56f64d95 138 log_error_errno(errno, "read(): %m");
023fb90b
LP
139 return sd_event_exit(f->event, EXIT_FAILURE);
140 }
141 } else if (k == 0) {
142 /* EOF on stdin */
143 f->stdin_readable = false;
144 f->stdin_hangup = true;
145
146 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
147 } else {
148 /* Check if ^] has been
149 * pressed three times within
150 * one second. If we get this
151 * we quite immediately. */
152 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
153 return sd_event_exit(f->event, EXIT_FAILURE);
154
155 f->in_buffer_full += (size_t) k;
156 }
4ba93280
LP
157 }
158
023fb90b 159 if (f->master_writable && f->in_buffer_full > 0) {
4ba93280 160
023fb90b
LP
161 k = write(f->master, f->in_buffer, f->in_buffer_full);
162 if (k < 0) {
4ba93280 163
023fb90b
LP
164 if (errno == EAGAIN || errno == EIO)
165 f->master_writable = false;
166 else if (errno == EPIPE || errno == ECONNRESET) {
167 f->master_writable = f->master_readable = false;
168 f->master_hangup = true;
4ba93280 169
023fb90b
LP
170 f->master_event_source = sd_event_source_unref(f->master_event_source);
171 } else {
56f64d95 172 log_error_errno(errno, "write(): %m");
023fb90b
LP
173 return sd_event_exit(f->event, EXIT_FAILURE);
174 }
175 } else {
176 assert(f->in_buffer_full >= (size_t) k);
177 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
178 f->in_buffer_full -= k;
179 }
4ba93280
LP
180 }
181
023fb90b 182 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
4ba93280 183
023fb90b
LP
184 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
185 if (k < 0) {
4ba93280 186
da054c37
LP
187 /* Note that EIO on the master device
188 * might be caused by vhangup() or
189 * temporary closing of everything on
190 * the other side, we treat it like
191 * EAGAIN here and try again, unless
192 * ignore_vhangup is off. */
4ba93280 193
ae3dde80 194 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
023fb90b 195 f->master_readable = false;
da054c37 196 else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
023fb90b
LP
197 f->master_readable = f->master_writable = false;
198 f->master_hangup = true;
4ba93280 199
023fb90b 200 f->master_event_source = sd_event_source_unref(f->master_event_source);
04d39279 201 } else {
56f64d95 202 log_error_errno(errno, "read(): %m");
023fb90b 203 return sd_event_exit(f->event, EXIT_FAILURE);
04d39279 204 }
ae3dde80
LP
205 } else {
206 f->read_from_master = true;
023fb90b 207 f->out_buffer_full += (size_t) k;
ae3dde80 208 }
023fb90b 209 }
4ba93280 210
023fb90b 211 if (f->stdout_writable && f->out_buffer_full > 0) {
4ba93280 212
023fb90b
LP
213 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
214 if (k < 0) {
4ba93280 215
023fb90b
LP
216 if (errno == EAGAIN)
217 f->stdout_writable = false;
218 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
219 f->stdout_writable = false;
220 f->stdout_hangup = true;
221 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
4ba93280 222 } else {
56f64d95 223 log_error_errno(errno, "write(): %m");
023fb90b 224 return sd_event_exit(f->event, EXIT_FAILURE);
4ba93280 225 }
4ba93280 226
023fb90b 227 } else {
9b15b784
LP
228
229 if (k > 0) {
230 f->last_char = f->out_buffer[k-1];
231 f->last_char_set = true;
232 }
233
023fb90b
LP
234 assert(f->out_buffer_full >= (size_t) k);
235 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
236 f->out_buffer_full -= k;
4ba93280 237 }
023fb90b
LP
238 }
239 }
240
241 if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
242 /* Exit the loop if any side hung up and if there's
243 * nothing more to write or nothing we could write. */
4ba93280 244
023fb90b
LP
245 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
246 (f->in_buffer_full <= 0 || f->master_hangup))
247 return sd_event_exit(f->event, EXIT_SUCCESS);
248 }
4ba93280 249
023fb90b
LP
250 return 0;
251}
4ba93280 252
023fb90b
LP
253static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
254 PTYForward *f = userdata;
4ba93280 255
023fb90b
LP
256 assert(f);
257 assert(e);
258 assert(e == f->master_event_source);
259 assert(fd >= 0);
260 assert(fd == f->master);
04d39279 261
023fb90b
LP
262 if (revents & (EPOLLIN|EPOLLHUP))
263 f->master_readable = true;
04d39279 264
023fb90b
LP
265 if (revents & (EPOLLOUT|EPOLLHUP))
266 f->master_writable = true;
04d39279 267
023fb90b
LP
268 return shovel(f);
269}
04d39279 270
023fb90b
LP
271static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
272 PTYForward *f = userdata;
04d39279 273
023fb90b
LP
274 assert(f);
275 assert(e);
276 assert(e == f->stdin_event_source);
277 assert(fd >= 0);
278 assert(fd == STDIN_FILENO);
04d39279 279
023fb90b
LP
280 if (revents & (EPOLLIN|EPOLLHUP))
281 f->stdin_readable = true;
04d39279 282
023fb90b
LP
283 return shovel(f);
284}
04d39279 285
023fb90b
LP
286static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
287 PTYForward *f = userdata;
04d39279 288
023fb90b
LP
289 assert(f);
290 assert(e);
291 assert(e == f->stdout_event_source);
292 assert(fd >= 0);
293 assert(fd == STDOUT_FILENO);
04d39279 294
023fb90b
LP
295 if (revents & (EPOLLOUT|EPOLLHUP))
296 f->stdout_writable = true;
04d39279 297
023fb90b
LP
298 return shovel(f);
299}
04d39279 300
023fb90b
LP
301static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
302 PTYForward *f = userdata;
303 struct winsize ws;
04d39279 304
023fb90b
LP
305 assert(f);
306 assert(e);
307 assert(e == f->sigwinch_event_source);
308
309 /* The window size changed, let's forward that. */
310 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
679bc6cb 311 (void) ioctl(f->master, TIOCSWINSZ, &ws);
023fb90b
LP
312
313 return 0;
4ba93280 314}
04d39279 315
9c857b9d
LP
316int pty_forward_new(
317 sd_event *event,
318 int master,
ae3dde80 319 PTYForwardFlags flags,
9c857b9d
LP
320 PTYForward **ret) {
321
023fb90b 322 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
04d39279
LP
323 struct winsize ws;
324 int r;
325
023fb90b
LP
326 f = new0(PTYForward, 1);
327 if (!f)
328 return -ENOMEM;
329
ae3dde80 330 f->flags = flags;
9b15b784 331
023fb90b
LP
332 if (event)
333 f->event = sd_event_ref(event);
334 else {
335 r = sd_event_default(&f->event);
336 if (r < 0)
337 return r;
338 }
339
ae3dde80 340 if (!(flags & PTY_FORWARD_READ_ONLY)) {
9c857b9d
LP
341 r = fd_nonblock(STDIN_FILENO, true);
342 if (r < 0)
343 return r;
023fb90b 344
9c857b9d
LP
345 r = fd_nonblock(STDOUT_FILENO, true);
346 if (r < 0)
347 return r;
348 }
023fb90b
LP
349
350 r = fd_nonblock(master, true);
351 if (r < 0)
352 return r;
353
354 f->master = master;
355
eaf73b06 356 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
dc751688 357 (void) ioctl(master, TIOCSWINSZ, &ws);
04d39279 358
ae3dde80 359 if (!(flags & PTY_FORWARD_READ_ONLY)) {
9c857b9d
LP
360 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
361 struct termios raw_stdin_attr;
023fb90b 362
9c857b9d 363 f->saved_stdin = true;
90d14d20 364
9c857b9d
LP
365 raw_stdin_attr = f->saved_stdin_attr;
366 cfmakeraw(&raw_stdin_attr);
367 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
368 tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
369 }
023fb90b 370
9c857b9d
LP
371 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
372 struct termios raw_stdout_attr;
023fb90b 373
9c857b9d 374 f->saved_stdout = true;
04d39279 375
9c857b9d
LP
376 raw_stdout_attr = f->saved_stdout_attr;
377 cfmakeraw(&raw_stdout_attr);
378 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
379 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
380 tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
381 }
04d39279 382
9c857b9d
LP
383 r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
384 if (r < 0 && r != -EPERM)
385 return r;
386 }
023fb90b
LP
387
388 r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
389 if (r == -EPERM)
390 /* stdout without epoll support. Likely redirected to regular file. */
391 f->stdout_writable = true;
392 else if (r < 0)
393 return r;
394
9c857b9d
LP
395 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
396 if (r < 0)
397 return r;
398
023fb90b 399 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
679bc6cb
LP
400 if (r < 0)
401 return r;
023fb90b
LP
402
403 *ret = f;
404 f = NULL;
405
406 return 0;
407}
408
409PTYForward *pty_forward_free(PTYForward *f) {
410
411 if (f) {
412 sd_event_source_unref(f->stdin_event_source);
413 sd_event_source_unref(f->stdout_event_source);
414 sd_event_source_unref(f->master_event_source);
415 sd_event_unref(f->event);
416
417 if (f->saved_stdout)
418 tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
419 if (f->saved_stdin)
420 tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
421
422 free(f);
423 }
04d39279 424
d60473c7
LP
425 /* STDIN/STDOUT should not be nonblocking normally, so let's
426 * unconditionally reset it */
427 fd_nonblock(STDIN_FILENO, false);
428 fd_nonblock(STDOUT_FILENO, false);
429
023fb90b 430 return NULL;
04d39279 431}
9b15b784 432
0ec5543c 433int pty_forward_get_last_char(PTYForward *f, char *ch) {
9b15b784
LP
434 assert(f);
435 assert(ch);
436
437 if (!f->last_char_set)
438 return -ENXIO;
439
440 *ch = f->last_char;
441 return 0;
442}
0ec5543c 443
ae3dde80 444int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
0ec5543c
LP
445 int r;
446
447 assert(f);
448
ae3dde80 449 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
0ec5543c
LP
450 return 0;
451
ae3dde80
LP
452 if (b)
453 f->flags |= PTY_FORWARD_IGNORE_VHANGUP;
454 else
455 f->flags &= ~PTY_FORWARD_IGNORE_VHANGUP;
456
457 if (!ignore_vhangup(f)) {
0ec5543c 458
da054c37
LP
459 /* We shall now react to vhangup()s? Let's check
460 * immediately if we might be in one */
0ec5543c
LP
461
462 f->master_readable = true;
463 r = shovel(f);
464 if (r < 0)
465 return r;
466 }
467
468 return 0;
469}
470
da054c37 471int pty_forward_get_ignore_vhangup(PTYForward *f) {
0ec5543c
LP
472 assert(f);
473
ae3dde80 474 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
0ec5543c 475}