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 PTYForwardFlags flags
;
37 sd_event_source
*stdin_event_source
;
38 sd_event_source
*stdout_event_source
;
39 sd_event_source
*master_event_source
;
41 sd_event_source
*sigwinch_event_source
;
43 struct termios saved_stdin_attr
;
44 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 bool read_from_master
: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 bool ignore_vhangup(PTYForward
*f
) {
105 if (f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
)
108 if ((f
->flags
& PTY_FORWARD_IGNORE_INITIAL_VHANGUP
) && !f
->read_from_master
)
114 static int shovel(PTYForward
*f
) {
119 while ((f
->stdin_readable
&& f
->in_buffer_full
<= 0) ||
120 (f
->master_writable
&& f
->in_buffer_full
> 0) ||
121 (f
->master_readable
&& f
->out_buffer_full
<= 0) ||
122 (f
->stdout_writable
&& f
->out_buffer_full
> 0)) {
124 if (f
->stdin_readable
&& f
->in_buffer_full
< LINE_MAX
) {
126 k
= read(STDIN_FILENO
, f
->in_buffer
+ f
->in_buffer_full
, LINE_MAX
- f
->in_buffer_full
);
130 f
->stdin_readable
= false;
131 else if (errno
== EIO
|| errno
== EPIPE
|| errno
== ECONNRESET
) {
132 f
->stdin_readable
= false;
133 f
->stdin_hangup
= true;
135 f
->stdin_event_source
= sd_event_source_unref(f
->stdin_event_source
);
137 log_error_errno(errno
, "read(): %m");
138 return sd_event_exit(f
->event
, EXIT_FAILURE
);
142 f
->stdin_readable
= false;
143 f
->stdin_hangup
= true;
145 f
->stdin_event_source
= sd_event_source_unref(f
->stdin_event_source
);
147 /* Check if ^] has been
148 * pressed three times within
149 * one second. If we get this
150 * we quite immediately. */
151 if (look_for_escape(f
, f
->in_buffer
+ f
->in_buffer_full
, k
))
152 return sd_event_exit(f
->event
, EXIT_FAILURE
);
154 f
->in_buffer_full
+= (size_t) k
;
158 if (f
->master_writable
&& f
->in_buffer_full
> 0) {
160 k
= write(f
->master
, f
->in_buffer
, f
->in_buffer_full
);
163 if (errno
== EAGAIN
|| errno
== EIO
)
164 f
->master_writable
= false;
165 else if (errno
== EPIPE
|| errno
== ECONNRESET
) {
166 f
->master_writable
= f
->master_readable
= false;
167 f
->master_hangup
= true;
169 f
->master_event_source
= sd_event_source_unref(f
->master_event_source
);
171 log_error_errno(errno
, "write(): %m");
172 return sd_event_exit(f
->event
, EXIT_FAILURE
);
175 assert(f
->in_buffer_full
>= (size_t) k
);
176 memmove(f
->in_buffer
, f
->in_buffer
+ k
, f
->in_buffer_full
- k
);
177 f
->in_buffer_full
-= k
;
181 if (f
->master_readable
&& f
->out_buffer_full
< LINE_MAX
) {
183 k
= read(f
->master
, f
->out_buffer
+ f
->out_buffer_full
, LINE_MAX
- f
->out_buffer_full
);
186 /* Note that EIO on the master device
187 * might be caused by vhangup() or
188 * temporary closing of everything on
189 * the other side, we treat it like
190 * EAGAIN here and try again, unless
191 * ignore_vhangup is off. */
193 if (errno
== EAGAIN
|| (errno
== EIO
&& ignore_vhangup(f
)))
194 f
->master_readable
= false;
195 else if (errno
== EPIPE
|| errno
== ECONNRESET
|| errno
== EIO
) {
196 f
->master_readable
= f
->master_writable
= false;
197 f
->master_hangup
= true;
199 f
->master_event_source
= sd_event_source_unref(f
->master_event_source
);
201 log_error_errno(errno
, "read(): %m");
202 return sd_event_exit(f
->event
, EXIT_FAILURE
);
205 f
->read_from_master
= true;
206 f
->out_buffer_full
+= (size_t) k
;
210 if (f
->stdout_writable
&& f
->out_buffer_full
> 0) {
212 k
= write(STDOUT_FILENO
, f
->out_buffer
, f
->out_buffer_full
);
216 f
->stdout_writable
= false;
217 else if (errno
== EIO
|| errno
== EPIPE
|| errno
== ECONNRESET
) {
218 f
->stdout_writable
= false;
219 f
->stdout_hangup
= true;
220 f
->stdout_event_source
= sd_event_source_unref(f
->stdout_event_source
);
222 log_error_errno(errno
, "write(): %m");
223 return sd_event_exit(f
->event
, EXIT_FAILURE
);
229 f
->last_char
= f
->out_buffer
[k
-1];
230 f
->last_char_set
= true;
233 assert(f
->out_buffer_full
>= (size_t) k
);
234 memmove(f
->out_buffer
, f
->out_buffer
+ k
, f
->out_buffer_full
- k
);
235 f
->out_buffer_full
-= k
;
240 if (f
->stdin_hangup
|| f
->stdout_hangup
|| f
->master_hangup
) {
241 /* Exit the loop if any side hung up and if there's
242 * nothing more to write or nothing we could write. */
244 if ((f
->out_buffer_full
<= 0 || f
->stdout_hangup
) &&
245 (f
->in_buffer_full
<= 0 || f
->master_hangup
))
246 return sd_event_exit(f
->event
, EXIT_SUCCESS
);
252 static int on_master_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
253 PTYForward
*f
= userdata
;
257 assert(e
== f
->master_event_source
);
259 assert(fd
== f
->master
);
261 if (revents
& (EPOLLIN
|EPOLLHUP
))
262 f
->master_readable
= true;
264 if (revents
& (EPOLLOUT
|EPOLLHUP
))
265 f
->master_writable
= true;
270 static int on_stdin_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
271 PTYForward
*f
= userdata
;
275 assert(e
== f
->stdin_event_source
);
277 assert(fd
== STDIN_FILENO
);
279 if (revents
& (EPOLLIN
|EPOLLHUP
))
280 f
->stdin_readable
= true;
285 static int on_stdout_event(sd_event_source
*e
, int fd
, uint32_t revents
, void *userdata
) {
286 PTYForward
*f
= userdata
;
290 assert(e
== f
->stdout_event_source
);
292 assert(fd
== STDOUT_FILENO
);
294 if (revents
& (EPOLLOUT
|EPOLLHUP
))
295 f
->stdout_writable
= true;
300 static int on_sigwinch_event(sd_event_source
*e
, const struct signalfd_siginfo
*si
, void *userdata
) {
301 PTYForward
*f
= userdata
;
306 assert(e
== f
->sigwinch_event_source
);
308 /* The window size changed, let's forward that. */
309 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &ws
) >= 0)
310 (void) ioctl(f
->master
, TIOCSWINSZ
, &ws
);
318 PTYForwardFlags flags
,
321 _cleanup_(pty_forward_freep
) PTYForward
*f
= NULL
;
325 f
= new0(PTYForward
, 1);
332 f
->event
= sd_event_ref(event
);
334 r
= sd_event_default(&f
->event
);
339 if (!(flags
& PTY_FORWARD_READ_ONLY
)) {
340 r
= fd_nonblock(STDIN_FILENO
, true);
344 r
= fd_nonblock(STDOUT_FILENO
, true);
349 r
= fd_nonblock(master
, true);
355 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &ws
) >= 0)
356 (void) ioctl(master
, TIOCSWINSZ
, &ws
);
358 if (!(flags
& PTY_FORWARD_READ_ONLY
)) {
359 if (tcgetattr(STDIN_FILENO
, &f
->saved_stdin_attr
) >= 0) {
360 struct termios raw_stdin_attr
;
362 f
->saved_stdin
= true;
364 raw_stdin_attr
= f
->saved_stdin_attr
;
365 cfmakeraw(&raw_stdin_attr
);
366 raw_stdin_attr
.c_oflag
= f
->saved_stdin_attr
.c_oflag
;
367 tcsetattr(STDIN_FILENO
, TCSANOW
, &raw_stdin_attr
);
370 if (tcgetattr(STDOUT_FILENO
, &f
->saved_stdout_attr
) >= 0) {
371 struct termios raw_stdout_attr
;
373 f
->saved_stdout
= true;
375 raw_stdout_attr
= f
->saved_stdout_attr
;
376 cfmakeraw(&raw_stdout_attr
);
377 raw_stdout_attr
.c_iflag
= f
->saved_stdout_attr
.c_iflag
;
378 raw_stdout_attr
.c_lflag
= f
->saved_stdout_attr
.c_lflag
;
379 tcsetattr(STDOUT_FILENO
, TCSANOW
, &raw_stdout_attr
);
382 r
= sd_event_add_io(f
->event
, &f
->stdin_event_source
, STDIN_FILENO
, EPOLLIN
|EPOLLET
, on_stdin_event
, f
);
383 if (r
< 0 && r
!= -EPERM
)
387 r
= sd_event_add_io(f
->event
, &f
->stdout_event_source
, STDOUT_FILENO
, EPOLLOUT
|EPOLLET
, on_stdout_event
, f
);
389 /* stdout without epoll support. Likely redirected to regular file. */
390 f
->stdout_writable
= true;
394 r
= sd_event_add_io(f
->event
, &f
->master_event_source
, master
, EPOLLIN
|EPOLLOUT
|EPOLLET
, on_master_event
, f
);
398 r
= sd_event_add_signal(f
->event
, &f
->sigwinch_event_source
, SIGWINCH
, on_sigwinch_event
, f
);
408 PTYForward
*pty_forward_free(PTYForward
*f
) {
411 sd_event_source_unref(f
->stdin_event_source
);
412 sd_event_source_unref(f
->stdout_event_source
);
413 sd_event_source_unref(f
->master_event_source
);
414 sd_event_unref(f
->event
);
417 tcsetattr(STDOUT_FILENO
, TCSANOW
, &f
->saved_stdout_attr
);
419 tcsetattr(STDIN_FILENO
, TCSANOW
, &f
->saved_stdin_attr
);
424 /* STDIN/STDOUT should not be nonblocking normally, so let's
425 * unconditionally reset it */
426 fd_nonblock(STDIN_FILENO
, false);
427 fd_nonblock(STDOUT_FILENO
, false);
432 int pty_forward_get_last_char(PTYForward
*f
, char *ch
) {
436 if (!f
->last_char_set
)
443 int pty_forward_set_ignore_vhangup(PTYForward
*f
, bool b
) {
448 if (!!(f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
) == b
)
452 f
->flags
|= PTY_FORWARD_IGNORE_VHANGUP
;
454 f
->flags
&= ~PTY_FORWARD_IGNORE_VHANGUP
;
456 if (!ignore_vhangup(f
)) {
458 /* We shall now react to vhangup()s? Let's check
459 * immediately if we might be in one */
461 f
->master_readable
= true;
470 int pty_forward_get_ignore_vhangup(PTYForward
*f
) {
473 return !!(f
->flags
& PTY_FORWARD_IGNORE_VHANGUP
);