]>
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 | ||
cf0fbc49 | 22 | #include <limits.h> |
4ba93280 | 23 | #include <sys/epoll.h> |
4ba93280 | 24 | #include <sys/ioctl.h> |
4ba93280 LP |
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 |
32 | struct 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 | ||
73 | static 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 |
104 | static 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 |
116 | static 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 |
254 | static 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 |
272 | static 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 |
287 | static 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 |
302 | static 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 |
317 | int 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 | ||
410 | PTYForward *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 | 435 | int 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 | 446 | int 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 | 473 | int pty_forward_get_ignore_vhangup(PTYForward *f) { |
0ec5543c LP |
474 | assert(f); |
475 | ||
ae3dde80 | 476 | return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); |
0ec5543c | 477 | } |