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