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