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