]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ptyfwd.c
Merge pull request #1681 from ssahani/journal
[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 "fd-util.h"
28 #include "ptyfwd.h"
29 #include "util.h"
30
31 struct PTYForward {
32 sd_event *event;
33
34 int master;
35
36 PTYForwardFlags flags;
37
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
58 bool read_from_master: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 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
115 static int shovel(PTYForward *f) {
116 ssize_t k;
117
118 assert(f);
119
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)) {
124
125 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
126
127 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
128 if (k < 0) {
129
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;
135
136 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
137 } else {
138 log_error_errno(errno, "read(): %m");
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 }
157 }
158
159 if (f->master_writable && f->in_buffer_full > 0) {
160
161 k = write(f->master, f->in_buffer, f->in_buffer_full);
162 if (k < 0) {
163
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;
169
170 f->master_event_source = sd_event_source_unref(f->master_event_source);
171 } else {
172 log_error_errno(errno, "write(): %m");
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 }
180 }
181
182 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
183
184 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
185 if (k < 0) {
186
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. */
193
194 if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
195 f->master_readable = false;
196 else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
197 f->master_readable = f->master_writable = false;
198 f->master_hangup = true;
199
200 f->master_event_source = sd_event_source_unref(f->master_event_source);
201 } else {
202 log_error_errno(errno, "read(): %m");
203 return sd_event_exit(f->event, EXIT_FAILURE);
204 }
205 } else {
206 f->read_from_master = true;
207 f->out_buffer_full += (size_t) k;
208 }
209 }
210
211 if (f->stdout_writable && f->out_buffer_full > 0) {
212
213 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
214 if (k < 0) {
215
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);
222 } else {
223 log_error_errno(errno, "write(): %m");
224 return sd_event_exit(f->event, EXIT_FAILURE);
225 }
226
227 } else {
228
229 if (k > 0) {
230 f->last_char = f->out_buffer[k-1];
231 f->last_char_set = true;
232 }
233
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;
237 }
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. */
244
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 }
249
250 return 0;
251 }
252
253 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
254 PTYForward *f = userdata;
255
256 assert(f);
257 assert(e);
258 assert(e == f->master_event_source);
259 assert(fd >= 0);
260 assert(fd == f->master);
261
262 if (revents & (EPOLLIN|EPOLLHUP))
263 f->master_readable = true;
264
265 if (revents & (EPOLLOUT|EPOLLHUP))
266 f->master_writable = true;
267
268 return shovel(f);
269 }
270
271 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
272 PTYForward *f = userdata;
273
274 assert(f);
275 assert(e);
276 assert(e == f->stdin_event_source);
277 assert(fd >= 0);
278 assert(fd == STDIN_FILENO);
279
280 if (revents & (EPOLLIN|EPOLLHUP))
281 f->stdin_readable = true;
282
283 return shovel(f);
284 }
285
286 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
287 PTYForward *f = userdata;
288
289 assert(f);
290 assert(e);
291 assert(e == f->stdout_event_source);
292 assert(fd >= 0);
293 assert(fd == STDOUT_FILENO);
294
295 if (revents & (EPOLLOUT|EPOLLHUP))
296 f->stdout_writable = true;
297
298 return shovel(f);
299 }
300
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;
304
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)
311 (void) ioctl(f->master, TIOCSWINSZ, &ws);
312
313 return 0;
314 }
315
316 int pty_forward_new(
317 sd_event *event,
318 int master,
319 PTYForwardFlags flags,
320 PTYForward **ret) {
321
322 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
323 struct winsize ws;
324 int r;
325
326 f = new0(PTYForward, 1);
327 if (!f)
328 return -ENOMEM;
329
330 f->flags = flags;
331
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
340 if (!(flags & PTY_FORWARD_READ_ONLY)) {
341 r = fd_nonblock(STDIN_FILENO, true);
342 if (r < 0)
343 return r;
344
345 r = fd_nonblock(STDOUT_FILENO, true);
346 if (r < 0)
347 return r;
348 }
349
350 r = fd_nonblock(master, true);
351 if (r < 0)
352 return r;
353
354 f->master = master;
355
356 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
357 (void) ioctl(master, TIOCSWINSZ, &ws);
358
359 if (!(flags & PTY_FORWARD_READ_ONLY)) {
360 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
361 struct termios raw_stdin_attr;
362
363 f->saved_stdin = true;
364
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 }
370
371 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
372 struct termios raw_stdout_attr;
373
374 f->saved_stdout = true;
375
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 }
382
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 }
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
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
399 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
400 if (r < 0)
401 return r;
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 }
424
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
430 return NULL;
431 }
432
433 int pty_forward_get_last_char(PTYForward *f, char *ch) {
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 }
443
444 int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
445 int r;
446
447 assert(f);
448
449 if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
450 return 0;
451
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)) {
458
459 /* We shall now react to vhangup()s? Let's check
460 * immediately if we might be in one */
461
462 f->master_readable = true;
463 r = shovel(f);
464 if (r < 0)
465 return r;
466 }
467
468 return 0;
469 }
470
471 int pty_forward_get_ignore_vhangup(PTYForward *f) {
472 assert(f);
473
474 return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
475 }