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/ioctl.h>
35 sd_event_source *stdin_event_source;
36 sd_event_source *stdout_event_source;
37 sd_event_source *master_event_source;
39 sd_event_source *sigwinch_event_source;
41 struct termios saved_stdin_attr;
42 struct termios saved_stdout_attr;
49 bool stdin_readable:1;
51 bool stdout_writable:1;
53 bool master_readable:1;
54 bool master_writable:1;
57 /* Continue reading after hangup? */
58 bool ignore_vhangup:1;
63 char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
64 size_t in_buffer_full, out_buffer_full;
66 usec_t escape_timestamp;
67 unsigned escape_counter;
70 #define ESCAPE_USEC (1*USEC_PER_SEC)
72 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
79 for (p = buffer; p < buffer + n; p++) {
83 usec_t nw = now(CLOCK_MONOTONIC);
85 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
86 f->escape_timestamp = nw;
87 f->escape_counter = 1;
89 (f->escape_counter)++;
91 if (f->escape_counter >= 3)
95 f->escape_timestamp = 0;
96 f->escape_counter = 0;
103 static int shovel(PTYForward *f) {
108 while ((f->stdin_readable && f->in_buffer_full <= 0) ||
109 (f->master_writable && f->in_buffer_full > 0) ||
110 (f->master_readable && f->out_buffer_full <= 0) ||
111 (f->stdout_writable && f->out_buffer_full > 0)) {
113 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
115 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
119 f->stdin_readable = false;
120 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
121 f->stdin_readable = false;
122 f->stdin_hangup = true;
124 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
126 log_error_errno(errno, "read(): %m");
127 return sd_event_exit(f->event, EXIT_FAILURE);
131 f->stdin_readable = false;
132 f->stdin_hangup = true;
134 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
136 /* Check if ^] has been
137 * pressed three times within
138 * one second. If we get this
139 * we quite immediately. */
140 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
141 return sd_event_exit(f->event, EXIT_FAILURE);
143 f->in_buffer_full += (size_t) k;
147 if (f->master_writable && f->in_buffer_full > 0) {
149 k = write(f->master, f->in_buffer, f->in_buffer_full);
152 if (errno == EAGAIN || errno == EIO)
153 f->master_writable = false;
154 else if (errno == EPIPE || errno == ECONNRESET) {
155 f->master_writable = f->master_readable = false;
156 f->master_hangup = true;
158 f->master_event_source = sd_event_source_unref(f->master_event_source);
160 log_error_errno(errno, "write(): %m");
161 return sd_event_exit(f->event, EXIT_FAILURE);
164 assert(f->in_buffer_full >= (size_t) k);
165 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
166 f->in_buffer_full -= k;
170 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
172 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
175 /* Note that EIO on the master device
176 * might be caused by vhangup() or
177 * temporary closing of everything on
178 * the other side, we treat it like
179 * EAGAIN here and try again, unless
180 * ignore_vhangup is off. */
182 if (errno == EAGAIN || (errno == EIO && f->ignore_vhangup))
183 f->master_readable = false;
184 else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
185 f->master_readable = f->master_writable = false;
186 f->master_hangup = true;
188 f->master_event_source = sd_event_source_unref(f->master_event_source);
190 log_error_errno(errno, "read(): %m");
191 return sd_event_exit(f->event, EXIT_FAILURE);
194 f->out_buffer_full += (size_t) k;
197 if (f->stdout_writable && f->out_buffer_full > 0) {
199 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
203 f->stdout_writable = false;
204 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
205 f->stdout_writable = false;
206 f->stdout_hangup = true;
207 f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
209 log_error_errno(errno, "write(): %m");
210 return sd_event_exit(f->event, EXIT_FAILURE);
216 f->last_char = f->out_buffer[k-1];
217 f->last_char_set = true;
220 assert(f->out_buffer_full >= (size_t) k);
221 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
222 f->out_buffer_full -= k;
227 if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
228 /* Exit the loop if any side hung up and if there's
229 * nothing more to write or nothing we could write. */
231 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
232 (f->in_buffer_full <= 0 || f->master_hangup))
233 return sd_event_exit(f->event, EXIT_SUCCESS);
239 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
240 PTYForward *f = userdata;
244 assert(e == f->master_event_source);
246 assert(fd == f->master);
248 if (revents & (EPOLLIN|EPOLLHUP))
249 f->master_readable = true;
251 if (revents & (EPOLLOUT|EPOLLHUP))
252 f->master_writable = true;
257 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
258 PTYForward *f = userdata;
262 assert(e == f->stdin_event_source);
264 assert(fd == STDIN_FILENO);
266 if (revents & (EPOLLIN|EPOLLHUP))
267 f->stdin_readable = true;
272 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
273 PTYForward *f = userdata;
277 assert(e == f->stdout_event_source);
279 assert(fd == STDOUT_FILENO);
281 if (revents & (EPOLLOUT|EPOLLHUP))
282 f->stdout_writable = true;
287 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
288 PTYForward *f = userdata;
293 assert(e == f->sigwinch_event_source);
295 /* The window size changed, let's forward that. */
296 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
297 (void) ioctl(f->master, TIOCSWINSZ, &ws);
309 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
313 f = new0(PTYForward, 1);
317 f->read_only = read_only;
318 f->ignore_vhangup = ignore_vhangup;
321 f->event = sd_event_ref(event);
323 r = sd_event_default(&f->event);
329 r = fd_nonblock(STDIN_FILENO, true);
333 r = fd_nonblock(STDOUT_FILENO, true);
338 r = fd_nonblock(master, true);
344 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
345 (void)ioctl(master, TIOCSWINSZ, &ws);
348 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
349 struct termios raw_stdin_attr;
351 f->saved_stdin = true;
353 raw_stdin_attr = f->saved_stdin_attr;
354 cfmakeraw(&raw_stdin_attr);
355 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
356 tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
359 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
360 struct termios raw_stdout_attr;
362 f->saved_stdout = true;
364 raw_stdout_attr = f->saved_stdout_attr;
365 cfmakeraw(&raw_stdout_attr);
366 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
367 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
368 tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
371 r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
372 if (r < 0 && r != -EPERM)
376 r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
378 /* stdout without epoll support. Likely redirected to regular file. */
379 f->stdout_writable = true;
383 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
387 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
397 PTYForward *pty_forward_free(PTYForward *f) {
400 sd_event_source_unref(f->stdin_event_source);
401 sd_event_source_unref(f->stdout_event_source);
402 sd_event_source_unref(f->master_event_source);
403 sd_event_unref(f->event);
406 tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
408 tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
413 /* STDIN/STDOUT should not be nonblocking normally, so let's
414 * unconditionally reset it */
415 fd_nonblock(STDIN_FILENO, false);
416 fd_nonblock(STDOUT_FILENO, false);
421 int pty_forward_get_last_char(PTYForward *f, char *ch) {
425 if (!f->last_char_set)
432 int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup) {
437 if (f->ignore_vhangup == ignore_vhangup)
440 f->ignore_vhangup = ignore_vhangup;
441 if (!f->ignore_vhangup) {
443 /* We shall now react to vhangup()s? Let's check
444 * immediately if we might be in one */
446 f->master_readable = true;
455 int pty_forward_get_ignore_vhangup(PTYForward *f) {
458 return f->ignore_vhangup;