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