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;
50 bool stdin_readable:1;
52 bool stdout_writable:1;
54 bool master_readable:1;
55 bool master_writable:1;
58 /* Continue reading after hangup? */
59 bool ignore_vhangup:1;
64 char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
65 size_t in_buffer_full, out_buffer_full;
67 usec_t escape_timestamp;
68 unsigned escape_counter;
71 #define ESCAPE_USEC (1*USEC_PER_SEC)
73 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
80 for (p = buffer; p < buffer + n; p++) {
84 usec_t nw = now(CLOCK_MONOTONIC);
86 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
87 f->escape_timestamp = nw;
88 f->escape_counter = 1;
90 (f->escape_counter)++;
92 if (f->escape_counter >= 3)
96 f->escape_timestamp = 0;
97 f->escape_counter = 0;
104 static int shovel(PTYForward *f) {
109 while ((f->stdin_readable && f->in_buffer_full <= 0) ||
110 (f->master_writable && f->in_buffer_full > 0) ||
111 (f->master_readable && f->out_buffer_full <= 0) ||
112 (f->stdout_writable && f->out_buffer_full > 0)) {
114 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
116 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
120 f->stdin_readable = false;
121 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
122 f->stdin_readable = false;
123 f->stdin_hangup = true;
125 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
127 log_error_errno(errno, "read(): %m");
128 return sd_event_exit(f->event, EXIT_FAILURE);
132 f->stdin_readable = false;
133 f->stdin_hangup = true;
135 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
137 /* Check if ^] has been
138 * pressed three times within
139 * one second. If we get this
140 * we quite immediately. */
141 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
142 return sd_event_exit(f->event, EXIT_FAILURE);
144 f->in_buffer_full += (size_t) k;
148 if (f->master_writable && f->in_buffer_full > 0) {
150 k = write(f->master, f->in_buffer, f->in_buffer_full);
153 if (errno == EAGAIN || errno == EIO)
154 f->master_writable = false;
155 else if (errno == EPIPE || errno == ECONNRESET) {
156 f->master_writable = f->master_readable = false;
157 f->master_hangup = true;
159 f->master_event_source = sd_event_source_unref(f->master_event_source);
161 log_error_errno(errno, "write(): %m");
162 return sd_event_exit(f->event, EXIT_FAILURE);
165 assert(f->in_buffer_full >= (size_t) k);
166 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
167 f->in_buffer_full -= k;
171 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
173 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
176 /* Note that EIO on the master device
177 * might be caused by vhangup() or
178 * temporary closing of everything on
179 * the other side, we treat it like
180 * EAGAIN here and try again, unless
181 * ignore_vhangup is off. */
183 if (errno == EAGAIN || (errno == EIO && f->ignore_vhangup))
184 f->master_readable = false;
185 else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
186 f->master_readable = f->master_writable = false;
187 f->master_hangup = true;
189 f->master_event_source = sd_event_source_unref(f->master_event_source);
191 log_error_errno(errno, "read(): %m");
192 return sd_event_exit(f->event, EXIT_FAILURE);
195 f->out_buffer_full += (size_t) k;
198 if (f->stdout_writable && f->out_buffer_full > 0) {
200 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
204 f->stdout_writable = false;
205 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
206 f->stdout_writable = false;
207 f->stdout_hangup = true;
208 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
210 log_error_errno(errno, "write(): %m");
211 return sd_event_exit(f->event, EXIT_FAILURE);
217 f->last_char = f->out_buffer[k-1];
218 f->last_char_set = true;
221 assert(f->out_buffer_full >= (size_t) k);
222 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
223 f->out_buffer_full -= k;
228 if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
229 /* Exit the loop if any side hung up and if there's
230 * nothing more to write or nothing we could write. */
232 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
233 (f->in_buffer_full <= 0 || f->master_hangup))
234 return sd_event_exit(f->event, EXIT_SUCCESS);
240 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
241 PTYForward *f = userdata;
245 assert(e == f->master_event_source);
247 assert(fd == f->master);
249 if (revents & (EPOLLIN|EPOLLHUP))
250 f->master_readable = true;
252 if (revents & (EPOLLOUT|EPOLLHUP))
253 f->master_writable = true;
258 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
259 PTYForward *f = userdata;
263 assert(e == f->stdin_event_source);
265 assert(fd == STDIN_FILENO);
267 if (revents & (EPOLLIN|EPOLLHUP))
268 f->stdin_readable = true;
273 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
274 PTYForward *f = userdata;
278 assert(e == f->stdout_event_source);
280 assert(fd == STDOUT_FILENO);
282 if (revents & (EPOLLOUT|EPOLLHUP))
283 f->stdout_writable = true;
288 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
289 PTYForward *f = userdata;
294 assert(e == f->sigwinch_event_source);
296 /* The window size changed, let's forward that. */
297 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
298 (void) ioctl(f->master, TIOCSWINSZ, &ws);
310 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
314 f = new0(PTYForward, 1);
318 f->read_only = read_only;
319 f->ignore_vhangup = ignore_vhangup;
322 f->event = sd_event_ref(event);
324 r = sd_event_default(&f->event);
330 r = fd_nonblock(STDIN_FILENO, true);
334 r = fd_nonblock(STDOUT_FILENO, true);
339 r = fd_nonblock(master, true);
345 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
346 (void)ioctl(master, TIOCSWINSZ, &ws);
349 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
350 struct termios raw_stdin_attr;
352 f->saved_stdin = true;
354 raw_stdin_attr = f->saved_stdin_attr;
355 cfmakeraw(&raw_stdin_attr);
356 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
357 tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
360 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
361 struct termios raw_stdout_attr;
363 f->saved_stdout = true;
365 raw_stdout_attr = f->saved_stdout_attr;
366 cfmakeraw(&raw_stdout_attr);
367 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
368 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
369 tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
372 r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
373 if (r < 0 && r != -EPERM)
377 r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
379 /* stdout without epoll support. Likely redirected to regular file. */
380 f->stdout_writable = true;
384 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
388 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
398 PTYForward *pty_forward_free(PTYForward *f) {
401 sd_event_source_unref(f->stdin_event_source);
402 sd_event_source_unref(f->stdout_event_source);
403 sd_event_source_unref(f->master_event_source);
404 sd_event_unref(f->event);
407 tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
409 tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
414 /* STDIN/STDOUT should not be nonblocking normally, so let's
415 * unconditionally reset it */
416 fd_nonblock(STDIN_FILENO, false);
417 fd_nonblock(STDOUT_FILENO, false);
422 int pty_forward_get_last_char(PTYForward *f, char *ch) {
426 if (!f->last_char_set)
433 int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup) {
438 if (f->ignore_vhangup == ignore_vhangup)
441 f->ignore_vhangup = ignore_vhangup;
442 if (!f->ignore_vhangup) {
444 /* We shall now react to vhangup()s? Let's check
445 * immediately if we might be in one */
447 f->master_readable = true;
456 int pty_forward_get_ignore_vhangup(PTYForward *f) {
459 return f->ignore_vhangup;