]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ptyfwd.c
085d374ed873494105473adfffc6225455eccb46
[thirdparty/systemd.git] / src / shared / ptyfwd.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2010-2013 Lennart Poettering
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <sys/epoll.h>
23 #include <sys/signalfd.h>
24 #include <sys/ioctl.h>
25 #include <limits.h>
26 #include <termios.h>
27
28 #include "util.h"
29 #include "ptyfwd.h"
30
31 struct PTYForward {
32 sd_event *event;
33
34 int master;
35
36 sd_event_source *stdin_event_source;
37 sd_event_source *stdout_event_source;
38 sd_event_source *master_event_source;
39
40 sd_event_source *sigwinch_event_source;
41
42 struct termios saved_stdin_attr;
43 struct termios saved_stdout_attr;
44
45 bool saved_stdin:1;
46 bool saved_stdout:1;
47
48 bool stdin_readable:1;
49 bool stdin_hangup:1;
50 bool stdout_writable:1;
51 bool stdout_hangup:1;
52 bool master_readable:1;
53 bool master_writable:1;
54 bool master_hangup:1;
55
56 char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
57 size_t in_buffer_full, out_buffer_full;
58
59 usec_t escape_timestamp;
60 unsigned escape_counter;
61 };
62
63 #define ESCAPE_USEC (1*USEC_PER_SEC)
64
65 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
66 const char *p;
67
68 assert(f);
69 assert(buffer);
70 assert(n > 0);
71
72 for (p = buffer; p < buffer + n; p++) {
73
74 /* Check for ^] */
75 if (*p == 0x1D) {
76 usec_t nw = now(CLOCK_MONOTONIC);
77
78 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
79 f->escape_timestamp = nw;
80 f->escape_counter = 1;
81 } else {
82 (f->escape_counter)++;
83
84 if (f->escape_counter >= 3)
85 return true;
86 }
87 } else {
88 f->escape_timestamp = 0;
89 f->escape_counter = 0;
90 }
91 }
92
93 return false;
94 }
95
96 static int shovel(PTYForward *f) {
97 ssize_t k;
98
99 assert(f);
100
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)) {
105
106 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
107
108 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
109 if (k < 0) {
110
111 if (errno == EAGAIN)
112 f->stdin_readable = false;
113 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
114 f->stdin_readable = false;
115 f->stdin_hangup = true;
116
117 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
118 } else {
119 log_error_errno(errno, "read(): %m");
120 return sd_event_exit(f->event, EXIT_FAILURE);
121 }
122 } else if (k == 0) {
123 /* EOF on stdin */
124 f->stdin_readable = false;
125 f->stdin_hangup = true;
126
127 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
128 } else {
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);
135
136 f->in_buffer_full += (size_t) k;
137 }
138 }
139
140 if (f->master_writable && f->in_buffer_full > 0) {
141
142 k = write(f->master, f->in_buffer, f->in_buffer_full);
143 if (k < 0) {
144
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;
150
151 f->master_event_source = sd_event_source_unref(f->master_event_source);
152 } else {
153 log_error_errno(errno, "write(): %m");
154 return sd_event_exit(f->event, EXIT_FAILURE);
155 }
156 } else {
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;
160 }
161 }
162
163 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
164
165 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
166 if (k < 0) {
167
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. */
173
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;
179
180 f->master_event_source = sd_event_source_unref(f->master_event_source);
181 } else {
182 log_error_errno(errno, "read(): %m");
183 return sd_event_exit(f->event, EXIT_FAILURE);
184 }
185 } else
186 f->out_buffer_full += (size_t) k;
187 }
188
189 if (f->stdout_writable && f->out_buffer_full > 0) {
190
191 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
192 if (k < 0) {
193
194 if (errno == EAGAIN)
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);
200 } else {
201 log_error_errno(errno, "write(): %m");
202 return sd_event_exit(f->event, EXIT_FAILURE);
203 }
204
205 } else {
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;
209 }
210 }
211 }
212
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. */
216
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);
220 }
221
222 return 0;
223 }
224
225 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
226 PTYForward *f = userdata;
227
228 assert(f);
229 assert(e);
230 assert(e == f->master_event_source);
231 assert(fd >= 0);
232 assert(fd == f->master);
233
234 if (revents & (EPOLLIN|EPOLLHUP))
235 f->master_readable = true;
236
237 if (revents & (EPOLLOUT|EPOLLHUP))
238 f->master_writable = true;
239
240 return shovel(f);
241 }
242
243 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
244 PTYForward *f = userdata;
245
246 assert(f);
247 assert(e);
248 assert(e == f->stdin_event_source);
249 assert(fd >= 0);
250 assert(fd == STDIN_FILENO);
251
252 if (revents & (EPOLLIN|EPOLLHUP))
253 f->stdin_readable = true;
254
255 return shovel(f);
256 }
257
258 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
259 PTYForward *f = userdata;
260
261 assert(f);
262 assert(e);
263 assert(e == f->stdout_event_source);
264 assert(fd >= 0);
265 assert(fd == STDOUT_FILENO);
266
267 if (revents & (EPOLLOUT|EPOLLHUP))
268 f->stdout_writable = true;
269
270 return shovel(f);
271 }
272
273 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
274 PTYForward *f = userdata;
275 struct winsize ws;
276
277 assert(f);
278 assert(e);
279 assert(e == f->sigwinch_event_source);
280
281 /* The window size changed, let's forward that. */
282 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
283 (void)ioctl(f->master, TIOCSWINSZ, &ws);
284
285 return 0;
286 }
287
288 int pty_forward_new(sd_event *event, int master, PTYForward **ret) {
289 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
290 struct winsize ws;
291 int r;
292
293 f = new0(PTYForward, 1);
294 if (!f)
295 return -ENOMEM;
296
297 if (event)
298 f->event = sd_event_ref(event);
299 else {
300 r = sd_event_default(&f->event);
301 if (r < 0)
302 return r;
303 }
304
305 r = fd_nonblock(STDIN_FILENO, true);
306 if (r < 0)
307 return r;
308
309 r = fd_nonblock(STDOUT_FILENO, true);
310 if (r < 0)
311 return r;
312
313 r = fd_nonblock(master, true);
314 if (r < 0)
315 return r;
316
317 f->master = master;
318
319 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
320 (void)ioctl(master, TIOCSWINSZ, &ws);
321
322 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
323 struct termios raw_stdin_attr;
324
325 f->saved_stdin = true;
326
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);
331 }
332
333 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
334 struct termios raw_stdout_attr;
335
336 f->saved_stdout = true;
337
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);
343 }
344
345 r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
346 if (r < 0)
347 return r;
348
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)
351 return r;
352
353 r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
354 if (r == -EPERM)
355 /* stdout without epoll support. Likely redirected to regular file. */
356 f->stdout_writable = true;
357 else if (r < 0)
358 return r;
359
360 r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
361
362 *ret = f;
363 f = NULL;
364
365 return 0;
366 }
367
368 PTYForward *pty_forward_free(PTYForward *f) {
369
370 if (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);
375
376 if (f->saved_stdout)
377 tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
378 if (f->saved_stdin)
379 tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
380
381 free(f);
382 }
383
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);
388
389 return NULL;
390 }