1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010-2013 Lennart Poettering
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.
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.
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/>.
22 #include <sys/epoll.h>
23 #include <sys/signalfd.h>
24 #include <sys/ioctl.h>
36 sd_event_source *stdin_event_source;
37 sd_event_source *stdout_event_source;
38 sd_event_source *master_event_source;
40 sd_event_source *sigwinch_event_source;
42 struct termios saved_stdin_attr;
43 struct termios saved_stdout_attr;
48 bool stdin_readable:1;
50 bool stdout_writable:1;
52 bool master_readable:1;
53 bool master_writable:1;
56 /* Continue reading after hangup? */
57 bool ignore_vhangup:1;
62 char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
63 size_t in_buffer_full, out_buffer_full;
65 usec_t escape_timestamp;
66 unsigned escape_counter;
69 #define ESCAPE_USEC (1*USEC_PER_SEC)
71 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
78 for (p = buffer; p < buffer + n; p++) {
82 usec_t nw = now(CLOCK_MONOTONIC);
84 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
85 f->escape_timestamp = nw;
86 f->escape_counter = 1;
88 (f->escape_counter)++;
90 if (f->escape_counter >= 3)
94 f->escape_timestamp = 0;
95 f->escape_counter = 0;
102 static int shovel(PTYForward *f) {
107 while ((f->stdin_readable && f->in_buffer_full <= 0) ||
108 (f->master_writable && f->in_buffer_full > 0) ||
109 (f->master_readable && f->out_buffer_full <= 0) ||
110 (f->stdout_writable && f->out_buffer_full > 0)) {
112 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
114 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
118 f->stdin_readable = false;
119 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
120 f->stdin_readable = false;
121 f->stdin_hangup = true;
123 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
125 log_error_errno(errno, "read(): %m");
126 return sd_event_exit(f->event, EXIT_FAILURE);
130 f->stdin_readable = false;
131 f->stdin_hangup = true;
133 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
135 /* Check if ^] has been
136 * pressed three times within
137 * one second. If we get this
138 * we quite immediately. */
139 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
140 return sd_event_exit(f->event, EXIT_FAILURE);
142 f->in_buffer_full += (size_t) k;
146 if (f->master_writable && f->in_buffer_full > 0) {
148 k = write(f->master, f->in_buffer, f->in_buffer_full);
151 if (errno == EAGAIN || errno == EIO)
152 f->master_writable = false;
153 else if (errno == EPIPE || errno == ECONNRESET) {
154 f->master_writable = f->master_readable = false;
155 f->master_hangup = true;
157 f->master_event_source = sd_event_source_unref(f->master_event_source);
159 log_error_errno(errno, "write(): %m");
160 return sd_event_exit(f->event, EXIT_FAILURE);
163 assert(f->in_buffer_full >= (size_t) k);
164 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
165 f->in_buffer_full -= k;
169 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
171 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
174 /* Note that EIO on the master device
175 * might be caused by vhangup() or
176 * temporary closing of everything on
177 * the other side, we treat it like
178 * EAGAIN here and try again, unless
179 * ignore_vhangup is off. */
181 if (errno == EAGAIN || (errno == EIO && f->ignore_vhangup))
182 f->master_readable = false;
183 else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
184 f->master_readable = f->master_writable = false;
185 f->master_hangup = true;
187 f->master_event_source = sd_event_source_unref(f->master_event_source);
189 log_error_errno(errno, "read(): %m");
190 return sd_event_exit(f->event, EXIT_FAILURE);
193 f->out_buffer_full += (size_t) k;
196 if (f->stdout_writable && f->out_buffer_full > 0) {
198 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
202 f->stdout_writable = false;
203 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
204 f->stdout_writable = false;
205 f->stdout_hangup = true;
206 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
208 log_error_errno(errno, "write(): %m");
209 return sd_event_exit(f->event, EXIT_FAILURE);
215 f->last_char = f->out_buffer[k-1];
216 f->last_char_set = true;
219 assert(f->out_buffer_full >= (size_t) k);
220 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
221 f->out_buffer_full -= k;
226 if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
227 /* Exit the loop if any side hung up and if there's
228 * nothing more to write or nothing we could write. */
230 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
231 (f->in_buffer_full <= 0 || f->master_hangup))
232 return sd_event_exit(f->event, EXIT_SUCCESS);
238 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
239 PTYForward *f = userdata;
243 assert(e == f->master_event_source);
245 assert(fd == f->master);
247 if (revents & (EPOLLIN|EPOLLHUP))
248 f->master_readable = true;
250 if (revents & (EPOLLOUT|EPOLLHUP))
251 f->master_writable = true;
256 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
257 PTYForward *f = userdata;
261 assert(e == f->stdin_event_source);
263 assert(fd == STDIN_FILENO);
265 if (revents & (EPOLLIN|EPOLLHUP))
266 f->stdin_readable = true;
271 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
272 PTYForward *f = userdata;
276 assert(e == f->stdout_event_source);
278 assert(fd == STDOUT_FILENO);
280 if (revents & (EPOLLOUT|EPOLLHUP))
281 f->stdout_writable = true;
286 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
287 PTYForward *f = userdata;
292 assert(e == f->sigwinch_event_source);
294 /* The window size changed, let's forward that. */
295 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
296 (void) ioctl(f->master, TIOCSWINSZ, &ws);
301 int pty_forward_new(sd_event *event, int master, bool ignore_vhangup, PTYForward **ret) {
302 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
306 f = new0(PTYForward, 1);
310 f->ignore_vhangup = ignore_vhangup;
313 f->event = sd_event_ref(event);
315 r = sd_event_default(&f->event);
320 r = fd_nonblock(STDIN_FILENO, true);
324 r = fd_nonblock(STDOUT_FILENO, true);
328 r = fd_nonblock(master, true);
334 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
335 (void)ioctl(master, TIOCSWINSZ, &ws);
337 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
338 struct termios raw_stdin_attr;
340 f->saved_stdin = true;
342 raw_stdin_attr = f->saved_stdin_attr;
343 cfmakeraw(&raw_stdin_attr);
344 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
345 tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
348 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
349 struct termios raw_stdout_attr;
351 f->saved_stdout = true;
353 raw_stdout_attr = f->saved_stdout_attr;
354 cfmakeraw(&raw_stdout_attr);
355 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
356 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
357 tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
360 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
364 r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
365 if (r < 0 && r != -EPERM)
368 r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
370 /* stdout without epoll support. Likely redirected to regular file. */
371 f->stdout_writable = true;
375 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
385 PTYForward *pty_forward_free(PTYForward *f) {
388 sd_event_source_unref(f->stdin_event_source);
389 sd_event_source_unref(f->stdout_event_source);
390 sd_event_source_unref(f->master_event_source);
391 sd_event_unref(f->event);
394 tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
396 tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
401 /* STDIN/STDOUT should not be nonblocking normally, so let's
402 * unconditionally reset it */
403 fd_nonblock(STDIN_FILENO, false);
404 fd_nonblock(STDOUT_FILENO, false);
409 int pty_forward_get_last_char(PTYForward *f, char *ch) {
413 if (!f->last_char_set)
420 int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup) {
425 if (f->ignore_vhangup == ignore_vhangup)
428 f->ignore_vhangup = ignore_vhangup;
429 if (!f->ignore_vhangup) {
431 /* We shall now react to vhangup()s? Let's check
432 * immediately if we might be in one */
434 f->master_readable = true;
443 int pty_forward_get_ignore_vhangup(PTYForward *f) {
446 return f->ignore_vhangup;