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 char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
57 size_t in_buffer_full, out_buffer_full;
59 usec_t escape_timestamp;
60 unsigned escape_counter;
63 #define ESCAPE_USEC (1*USEC_PER_SEC)
65 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
72 for (p = buffer; p < buffer + n; p++) {
76 usec_t nw = now(CLOCK_MONOTONIC);
78 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
79 f->escape_timestamp = nw;
80 f->escape_counter = 1;
82 (f->escape_counter)++;
84 if (f->escape_counter >= 3)
88 f->escape_timestamp = 0;
89 f->escape_counter = 0;
96 static int shovel(PTYForward *f) {
101 while ((f->stdin_readable && f->in_buffer_full <= 0) ||
102 (f->master_writable && f->in_buffer_full > 0) ||
103 (f->master_readable && f->out_buffer_full <= 0) ||
104 (f->stdout_writable && f->out_buffer_full > 0)) {
106 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
108 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
112 f->stdin_readable = false;
113 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
114 f->stdin_readable = false;
115 f->stdin_hangup = true;
117 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
119 log_error_errno(errno, "read(): %m");
120 return sd_event_exit(f->event, EXIT_FAILURE);
124 f->stdin_readable = false;
125 f->stdin_hangup = true;
127 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
129 /* Check if ^] has been
130 * pressed three times within
131 * one second. If we get this
132 * we quite immediately. */
133 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
134 return sd_event_exit(f->event, EXIT_FAILURE);
136 f->in_buffer_full += (size_t) k;
140 if (f->master_writable && f->in_buffer_full > 0) {
142 k = write(f->master, f->in_buffer, f->in_buffer_full);
145 if (errno == EAGAIN || errno == EIO)
146 f->master_writable = false;
147 else if (errno == EPIPE || errno == ECONNRESET) {
148 f->master_writable = f->master_readable = false;
149 f->master_hangup = true;
151 f->master_event_source = sd_event_source_unref(f->master_event_source);
153 log_error_errno(errno, "write(): %m");
154 return sd_event_exit(f->event, EXIT_FAILURE);
157 assert(f->in_buffer_full >= (size_t) k);
158 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
159 f->in_buffer_full -= k;
163 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
165 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
168 /* Note that EIO on the master device
169 * might be cause by vhangup() or
170 * temporary closing of everything on
171 * the other side, we treat it like
172 * EAGAIN here and try again. */
174 if (errno == EAGAIN || errno == EIO)
175 f->master_readable = false;
176 else if (errno == EPIPE || errno == ECONNRESET) {
177 f->master_readable = f->master_writable = false;
178 f->master_hangup = true;
180 f->master_event_source = sd_event_source_unref(f->master_event_source);
182 log_error_errno(errno, "read(): %m");
183 return sd_event_exit(f->event, EXIT_FAILURE);
186 f->out_buffer_full += (size_t) k;
189 if (f->stdout_writable && f->out_buffer_full > 0) {
191 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
195 f->stdout_writable = false;
196 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
197 f->stdout_writable = false;
198 f->stdout_hangup = true;
199 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
201 log_error_errno(errno, "write(): %m");
202 return sd_event_exit(f->event, EXIT_FAILURE);
206 assert(f->out_buffer_full >= (size_t) k);
207 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
208 f->out_buffer_full -= k;
213 if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
214 /* Exit the loop if any side hung up and if there's
215 * nothing more to write or nothing we could write. */
217 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
218 (f->in_buffer_full <= 0 || f->master_hangup))
219 return sd_event_exit(f->event, EXIT_SUCCESS);
225 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
226 PTYForward *f = userdata;
230 assert(e == f->master_event_source);
232 assert(fd == f->master);
234 if (revents & (EPOLLIN|EPOLLHUP))
235 f->master_readable = true;
237 if (revents & (EPOLLOUT|EPOLLHUP))
238 f->master_writable = true;
243 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
244 PTYForward *f = userdata;
248 assert(e == f->stdin_event_source);
250 assert(fd == STDIN_FILENO);
252 if (revents & (EPOLLIN|EPOLLHUP))
253 f->stdin_readable = true;
258 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
259 PTYForward *f = userdata;
263 assert(e == f->stdout_event_source);
265 assert(fd == STDOUT_FILENO);
267 if (revents & (EPOLLOUT|EPOLLHUP))
268 f->stdout_writable = true;
273 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
274 PTYForward *f = userdata;
279 assert(e == f->sigwinch_event_source);
281 /* The window size changed, let's forward that. */
282 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
283 (void)ioctl(f->master, TIOCSWINSZ, &ws);
288 int pty_forward_new(sd_event *event, int master, PTYForward **ret) {
289 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
293 f = new0(PTYForward, 1);
298 f->event = sd_event_ref(event);
300 r = sd_event_default(&f->event);
305 r = fd_nonblock(STDIN_FILENO, true);
309 r = fd_nonblock(STDOUT_FILENO, true);
313 r = fd_nonblock(master, true);
319 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
320 (void)ioctl(master, TIOCSWINSZ, &ws);
322 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
323 struct termios raw_stdin_attr;
325 f->saved_stdin = true;
327 raw_stdin_attr = f->saved_stdin_attr;
328 cfmakeraw(&raw_stdin_attr);
329 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
330 tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
333 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
334 struct termios raw_stdout_attr;
336 f->saved_stdout = true;
338 raw_stdout_attr = f->saved_stdout_attr;
339 cfmakeraw(&raw_stdout_attr);
340 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
341 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
342 tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
345 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
349 r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
350 if (r < 0 && r != -EPERM)
353 r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
355 /* stdout without epoll support. Likely redirected to regular file. */
356 f->stdout_writable = true;
360 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
368 PTYForward *pty_forward_free(PTYForward *f) {
371 sd_event_source_unref(f->stdin_event_source);
372 sd_event_source_unref(f->stdout_event_source);
373 sd_event_source_unref(f->master_event_source);
374 sd_event_unref(f->event);
377 tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
379 tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
384 /* STDIN/STDOUT should not be nonblocking normally, so let's
385 * unconditionally reset it */
386 fd_nonblock(STDIN_FILENO, false);
387 fd_nonblock(STDOUT_FILENO, false);