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>
36 PTYForwardFlags flags
;
38 sd_event_source
*stdin_event_source
;
39 sd_event_source
*stdout_event_source
;
40 sd_event_source
*master_event_source
;
42 sd_event_source
*sigwinch_event_source
;
44 struct termios saved_stdin_attr
;
45 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 bool read_from_master
: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 bool ignore_vhangup(PTYForward
*f
) {
106 if (f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
)
109 if ((f
->flags
& PTY_FORWARD_IGNORE_INITIAL_VHANGUP
) && !f
->read_from_master
)
115 static int shovel(PTYForward
*f
) {
120 while ((f
->stdin_readable
&& f
->in_buffer_full
<= 0) ||
121 (f
->master_writable
&& f
->in_buffer_full
> 0) ||
122 (f
->master_readable
&& f
->out_buffer_full
<= 0) ||
123 (f
->stdout_writable
&& f
->out_buffer_full
> 0)) {
125 if (f
->stdin_readable
&& f
->in_buffer_full
< LINE_MAX
) {
127 k
= read(STDIN_FILENO
, f
->in_buffer
+ f
->in_buffer_full
, LINE_MAX
- f
->in_buffer_full
);
131 f
->stdin_readable
= false;
132 else if (errno
== EIO
|| errno
== EPIPE
|| errno
== ECONNRESET
) {
133 f
->stdin_readable
= false;
134 f
->stdin_hangup
= true;
136 f
->stdin_event_source
= sd_event_source_unref(f
->stdin_event_source
);
138 log_error_errno(errno
, "read(): %m");
139 return sd_event_exit(f
->event
, EXIT_FAILURE
);
143 f
->stdin_readable
= false;
144 f
->stdin_hangup
= true;
146 f
->stdin_event_source
= sd_event_source_unref(f
->stdin_event_source
);
148 /* Check if ^] has been
149 * pressed three times within
150 * one second. If we get this
151 * we quite immediately. */
152 if (look_for_escape(f
, f
->in_buffer
+ f
->in_buffer_full
, k
))
153 return sd_event_exit(f
->event
, EXIT_FAILURE
);
155 f
->in_buffer_full
+= (size_t) k
;
159 if (f
->master_writable
&& f
->in_buffer_full
> 0) {
161 k
= write(f
->master
, f
->in_buffer
, f
->in_buffer_full
);
164 if (errno
== EAGAIN
|| errno
== EIO
)
165 f
->master_writable
= false;
166 else if (errno
== EPIPE
|| errno
== ECONNRESET
) {
167 f
->master_writable
= f
->master_readable
= false;
168 f
->master_hangup
= true;
170 f
->master_event_source
= sd_event_source_unref(f
->master_event_source
);
172 log_error_errno(errno
, "write(): %m");
173 return sd_event_exit(f
->event
, EXIT_FAILURE
);
176 assert(f
->in_buffer_full
>= (size_t) k
);
177 memmove(f
->in_buffer
, f
->in_buffer
+ k
, f
->in_buffer_full
- k
);
178 f
->in_buffer_full
-= k
;
182 if (f
->master_readable
&& f
->out_buffer_full
< LINE_MAX
) {
184 k
= read(f
->master
, f
->out_buffer
+ f
->out_buffer_full
, LINE_MAX
- f
->out_buffer_full
);
187 /* Note that EIO on the master device
188 * might be caused by vhangup() or
189 * temporary closing of everything on
190 * the other side, we treat it like
191 * EAGAIN here and try again, unless
192 * ignore_vhangup is off. */
194 if (errno
== EAGAIN
|| (errno
== EIO
&& ignore_vhangup(f
)))
195 f
->master_readable
= false;
196 else if (errno
== EPIPE
|| errno
== ECONNRESET
|| errno
== EIO
) {
197 f
->master_readable
= f
->master_writable
= false;
198 f
->master_hangup
= true;
200 f
->master_event_source
= sd_event_source_unref(f
->master_event_source
);
202 log_error_errno(errno
, "read(): %m");
203 return sd_event_exit(f
->event
, EXIT_FAILURE
);
206 f
->read_from_master
= true;
207 f
->out_buffer_full
+= (size_t) k
;
211 if (f
->stdout_writable
&& f
->out_buffer_full
> 0) {
213 k
= write(STDOUT_FILENO
, f
->out_buffer
, f
->out_buffer_full
);
217 f
->stdout_writable
= false;
218 else if (errno
== EIO
|| errno
== EPIPE
|| errno
== ECONNRESET
) {
219 f
->stdout_writable
= false;
220 f
->stdout_hangup
= true;
221 f
->stdout_event_source
= sd_event_source_unref(f
->stdout_event_source
);
223 log_error_errno(errno
, "write(): %m");
224 return sd_event_exit(f
->event
, EXIT_FAILURE
);
230 f
->last_char
= f
->out_buffer
[k
-1];
231 f
->last_char_set
= true;
234 assert(f
->out_buffer_full
>= (size_t) k
);
235 memmove(f
->out_buffer
, f
->out_buffer
+ k
, f
->out_buffer_full
- k
);
236 f
->out_buffer_full
-= k
;
241 if (f
->stdin_hangup
|| f
->stdout_hangup
|| f
->master_hangup
) {
242 /* Exit the loop if any side hung up and if there's
243 * nothing more to write or nothing we could write. */
245 if ((f
->out_buffer_full
<= 0 || f
->stdout_hangup
) &&
246 (f
->in_buffer_full
<= 0 || f
->master_hangup
))
247 return sd_event_exit(f
->event
, EXIT_SUCCESS
);
253 static int on_master_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
254 PTYForward
*f
= userdata
;
258 assert(e
== f
->master_event_source
);
260 assert(fd
== f
->master
);
262 if (revents
& (EPOLLIN
|EPOLLHUP
))
263 f
->master_readable
= true;
265 if (revents
& (EPOLLOUT
|EPOLLHUP
))
266 f
->master_writable
= true;
271 static int on_stdin_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
272 PTYForward
*f
= userdata
;
276 assert(e
== f
->stdin_event_source
);
278 assert(fd
== STDIN_FILENO
);
280 if (revents
& (EPOLLIN
|EPOLLHUP
))
281 f
->stdin_readable
= true;
286 static int on_stdout_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
287 PTYForward
*f
= userdata
;
291 assert(e
== f
->stdout_event_source
);
293 assert(fd
== STDOUT_FILENO
);
295 if (revents
& (EPOLLOUT
|EPOLLHUP
))
296 f
->stdout_writable
= true;
301 static int on_sigwinch_event(sd_event_source
*e
, const struct signalfd_siginfo
*si
, void *userdata
) {
302 PTYForward
*f
= userdata
;
307 assert(e
== f
->sigwinch_event_source
);
309 /* The window size changed, let's forward that. */
310 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &ws
) >= 0)
311 (void) ioctl(f
->master
, TIOCSWINSZ
, &ws
);
319 PTYForwardFlags flags
,
322 _cleanup_(pty_forward_freep
) PTYForward
*f
= NULL
;
326 f
= new0(PTYForward
, 1);
333 f
->event
= sd_event_ref(event
);
335 r
= sd_event_default(&f
->event
);
340 if (!(flags
& PTY_FORWARD_READ_ONLY
)) {
341 r
= fd_nonblock(STDIN_FILENO
, true);
345 r
= fd_nonblock(STDOUT_FILENO
, true);
350 r
= fd_nonblock(master
, true);
356 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &ws
) >= 0)
357 (void) ioctl(master
, TIOCSWINSZ
, &ws
);
359 if (!(flags
& PTY_FORWARD_READ_ONLY
)) {
360 if (tcgetattr(STDIN_FILENO
, &f
->saved_stdin_attr
) >= 0) {
361 struct termios raw_stdin_attr
;
363 f
->saved_stdin
= true;
365 raw_stdin_attr
= f
->saved_stdin_attr
;
366 cfmakeraw(&raw_stdin_attr
);
367 raw_stdin_attr
.c_oflag
= f
->saved_stdin_attr
.c_oflag
;
368 tcsetattr(STDIN_FILENO
, TCSANOW
, &raw_stdin_attr
);
371 if (tcgetattr(STDOUT_FILENO
, &f
->saved_stdout_attr
) >= 0) {
372 struct termios raw_stdout_attr
;
374 f
->saved_stdout
= true;
376 raw_stdout_attr
= f
->saved_stdout_attr
;
377 cfmakeraw(&raw_stdout_attr
);
378 raw_stdout_attr
.c_iflag
= f
->saved_stdout_attr
.c_iflag
;
379 raw_stdout_attr
.c_lflag
= f
->saved_stdout_attr
.c_lflag
;
380 tcsetattr(STDOUT_FILENO
, TCSANOW
, &raw_stdout_attr
);
383 r
= sd_event_add_io(f
->event
, &f
->stdin_event_source
, STDIN_FILENO
, EPOLLIN
|EPOLLET
, on_stdin_event
, f
);
384 if (r
< 0 && r
!= -EPERM
)
388 r
= sd_event_add_io(f
->event
, &f
->stdout_event_source
, STDOUT_FILENO
, EPOLLOUT
|EPOLLET
, on_stdout_event
, f
);
390 /* stdout without epoll support. Likely redirected to regular file. */
391 f
->stdout_writable
= true;
395 r
= sd_event_add_io(f
->event
, &f
->master_event_source
, master
, EPOLLIN
|EPOLLOUT
|EPOLLET
, on_master_event
, f
);
399 r
= sd_event_add_signal(f
->event
, &f
->sigwinch_event_source
, SIGWINCH
, on_sigwinch_event
, f
);
409 PTYForward
*pty_forward_free(PTYForward
*f
) {
412 sd_event_source_unref(f
->stdin_event_source
);
413 sd_event_source_unref(f
->stdout_event_source
);
414 sd_event_source_unref(f
->master_event_source
);
415 sd_event_unref(f
->event
);
418 tcsetattr(STDOUT_FILENO
, TCSANOW
, &f
->saved_stdout_attr
);
420 tcsetattr(STDIN_FILENO
, TCSANOW
, &f
->saved_stdin_attr
);
425 /* STDIN/STDOUT should not be nonblocking normally, so let's
426 * unconditionally reset it */
427 fd_nonblock(STDIN_FILENO
, false);
428 fd_nonblock(STDOUT_FILENO
, false);
433 int pty_forward_get_last_char(PTYForward
*f
, char *ch
) {
437 if (!f
->last_char_set
)
444 int pty_forward_set_ignore_vhangup(PTYForward
*f
, bool b
) {
449 if (!!(f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
) == b
)
453 f
->flags
|= PTY_FORWARD_IGNORE_VHANGUP
;
455 f
->flags
&= ~PTY_FORWARD_IGNORE_VHANGUP
;
457 if (!ignore_vhangup(f
)) {
459 /* We shall now react to vhangup()s? Let's check
460 * immediately if we might be in one */
462 f
->master_readable
= true;
471 int pty_forward_get_ignore_vhangup(PTYForward
*f
) {
474 return !!(f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
);