]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ptyfwd.c
Merge pull request #1748 from karelzak/todo
[thirdparty/systemd.git] / src / shared / ptyfwd.c
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>
23 #include <sys/ioctl.h>
24 #include <limits.h>
25 #include <termios.h>
26
27 #include "alloc-util.h"
28 #include "fd-util.h"
29 #include "ptyfwd.h"
30 #include "util.h"
31
32 struct PTYForward {
33 sd_event *event;
34
35 int master;
36
37 PTYForwardFlags flags;
38
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
59 bool read_from_master:1;
60
61 bool last_char_set:1;
62 char last_char;
63
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) {
74 const char *p;
75
76 assert(f);
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
86 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
87 f->escape_timestamp = nw;
88 f->escape_counter = 1;
89 } else {
90 (f->escape_counter)++;
91
92 if (f->escape_counter >= 3)
93 return true;
94 }
95 } else {
96 f->escape_timestamp = 0;
97 f->escape_counter = 0;
98 }
99 }
100
101 return false;
102 }
103
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
116 static int shovel(PTYForward *f) {
117 ssize_t k;
118
119 assert(f);
120
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)) {
125
126 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
127
128 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
129 if (k < 0) {
130
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;
136
137 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
138 } else {
139 log_error_errno(errno, "read(): %m");
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 }
158 }
159
160 if (f->master_writable && f->in_buffer_full > 0) {
161
162 k = write(f->master, f->in_buffer, f->in_buffer_full);
163 if (k < 0) {
164
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;
170
171 f->master_event_source = sd_event_source_unref(f->master_event_source);
172 } else {
173 log_error_errno(errno, "write(): %m");
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 }
181 }
182
183 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
184
185 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
186 if (k < 0) {
187
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. */
194
195 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
196 f->master_readable = false;
197 else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
198 f->master_readable = f->master_writable = false;
199 f->master_hangup = true;
200
201 f->master_event_source = sd_event_source_unref(f->master_event_source);
202 } else {
203 log_error_errno(errno, "read(): %m");
204 return sd_event_exit(f->event, EXIT_FAILURE);
205 }
206 } else {
207 f->read_from_master = true;
208 f->out_buffer_full += (size_t) k;
209 }
210 }
211
212 if (f->stdout_writable && f->out_buffer_full > 0) {
213
214 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
215 if (k < 0) {
216
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);
223 } else {
224 log_error_errno(errno, "write(): %m");
225 return sd_event_exit(f->event, EXIT_FAILURE);
226 }
227
228 } else {
229
230 if (k > 0) {
231 f->last_char = f->out_buffer[k-1];
232 f->last_char_set = true;
233 }
234
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;
238 }
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. */
245
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 }
250
251 return 0;
252 }
253
254 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
255 PTYForward *f = userdata;
256
257 assert(f);
258 assert(e);
259 assert(e == f->master_event_source);
260 assert(fd >= 0);
261 assert(fd == f->master);
262
263 if (revents & (EPOLLIN|EPOLLHUP))
264 f->master_readable = true;
265
266 if (revents & (EPOLLOUT|EPOLLHUP))
267 f->master_writable = true;
268
269 return shovel(f);
270 }
271
272 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
273 PTYForward *f = userdata;
274
275 assert(f);
276 assert(e);
277 assert(e == f->stdin_event_source);
278 assert(fd >= 0);
279 assert(fd == STDIN_FILENO);
280
281 if (revents & (EPOLLIN|EPOLLHUP))
282 f->stdin_readable = true;
283
284 return shovel(f);
285 }
286
287 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
288 PTYForward *f = userdata;
289
290 assert(f);
291 assert(e);
292 assert(e == f->stdout_event_source);
293 assert(fd >= 0);
294 assert(fd == STDOUT_FILENO);
295
296 if (revents & (EPOLLOUT|EPOLLHUP))
297 f->stdout_writable = true;
298
299 return shovel(f);
300 }
301
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;
305
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)
312 (void) ioctl(f->master, TIOCSWINSZ, &ws);
313
314 return 0;
315 }
316
317 int pty_forward_new(
318 sd_event *event,
319 int master,
320 PTYForwardFlags flags,
321 PTYForward **ret) {
322
323 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
324 struct winsize ws;
325 int r;
326
327 f = new0(PTYForward, 1);
328 if (!f)
329 return -ENOMEM;
330
331 f->flags = flags;
332
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
341 if (!(flags & PTY_FORWARD_READ_ONLY)) {
342 r = fd_nonblock(STDIN_FILENO, true);
343 if (r < 0)
344 return r;
345
346 r = fd_nonblock(STDOUT_FILENO, true);
347 if (r < 0)
348 return r;
349 }
350
351 r = fd_nonblock(master, true);
352 if (r < 0)
353 return r;
354
355 f->master = master;
356
357 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
358 (void) ioctl(master, TIOCSWINSZ, &ws);
359
360 if (!(flags & PTY_FORWARD_READ_ONLY)) {
361 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
362 struct termios raw_stdin_attr;
363
364 f->saved_stdin = true;
365
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 }
371
372 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
373 struct termios raw_stdout_attr;
374
375 f->saved_stdout = true;
376
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 }
383
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 }
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
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
400 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
401 if (r < 0)
402 return r;
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);
416 sd_event_source_unref(f->sigwinch_event_source);
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 }
426
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
432 return NULL;
433 }
434
435 int pty_forward_get_last_char(PTYForward *f, char *ch) {
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 }
445
446 int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
447 int r;
448
449 assert(f);
450
451 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
452 return 0;
453
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)) {
460
461 /* We shall now react to vhangup()s? Let's check
462 * immediately if we might be in one */
463
464 f->master_readable = true;
465 r = shovel(f);
466 if (r < 0)
467 return r;
468 }
469
470 return 0;
471 }
472
473 int pty_forward_get_ignore_vhangup(PTYForward *f) {
474 assert(f);
475
476 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
477 }