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