]>
Commit | Line | Data |
---|---|---|
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 |
31 | struct 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 | ||
72 | static 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 |
103 | static 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 |
115 | static 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 |
253 | static 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 |
271 | static 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 |
286 | static 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 |
301 | static 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 |
316 | int 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 | ||
409 | PTYForward *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 | 433 | int 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 | 444 | int 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 | 471 | int pty_forward_get_ignore_vhangup(PTYForward *f) { |
0ec5543c LP |
472 | assert(f); |
473 | ||
ae3dde80 | 474 | return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); |
0ec5543c | 475 | } |