]> git.proxmox.com Git - systemd.git/blame - src/shared/ptyfwd.c
Imported Upstream version 218
[systemd.git] / src / shared / ptyfwd.c
CommitLineData
60f067b4
JS
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
f47781d8
MP
31struct PTYForward {
32 sd_event *event;
60f067b4 33
f47781d8
MP
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
65static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
60f067b4
JS
66 const char *p;
67
f47781d8 68 assert(f);
60f067b4
JS
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
f47781d8
MP
78 if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
79 f->escape_timestamp = nw;
80 f->escape_counter = 1;
60f067b4 81 } else {
f47781d8 82 (f->escape_counter)++;
60f067b4 83
f47781d8 84 if (f->escape_counter >= 3)
60f067b4
JS
85 return true;
86 }
87 } else {
f47781d8
MP
88 f->escape_timestamp = 0;
89 f->escape_counter = 0;
60f067b4
JS
90 }
91 }
92
93 return false;
94}
95
f47781d8
MP
96static int shovel(PTYForward *f) {
97 ssize_t k;
60f067b4 98
f47781d8 99 assert(f);
60f067b4 100
f47781d8
MP
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)) {
60f067b4 105
f47781d8 106 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
60f067b4 107
f47781d8
MP
108 k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
109 if (k < 0) {
60f067b4 110
f47781d8
MP
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;
60f067b4 116
f47781d8
MP
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 }
60f067b4
JS
138 }
139
f47781d8 140 if (f->master_writable && f->in_buffer_full > 0) {
60f067b4 141
f47781d8
MP
142 k = write(f->master, f->in_buffer, f->in_buffer_full);
143 if (k < 0) {
60f067b4 144
f47781d8
MP
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;
60f067b4 150
f47781d8
MP
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 }
60f067b4
JS
161 }
162
f47781d8 163 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
60f067b4 164
f47781d8
MP
165 k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
166 if (k < 0) {
60f067b4 167
f47781d8
MP
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. */
60f067b4 173
f47781d8
MP
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;
60f067b4 179
f47781d8 180 f->master_event_source = sd_event_source_unref(f->master_event_source);
60f067b4 181 } else {
f47781d8
MP
182 log_error_errno(errno, "read(): %m");
183 return sd_event_exit(f->event, EXIT_FAILURE);
60f067b4 184 }
f47781d8
MP
185 } else
186 f->out_buffer_full += (size_t) k;
187 }
60f067b4 188
f47781d8 189 if (f->stdout_writable && f->out_buffer_full > 0) {
60f067b4 190
f47781d8
MP
191 k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
192 if (k < 0) {
60f067b4 193
f47781d8
MP
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);
60f067b4 200 } else {
f47781d8
MP
201 log_error_errno(errno, "write(): %m");
202 return sd_event_exit(f->event, EXIT_FAILURE);
60f067b4 203 }
60f067b4 204
f47781d8
MP
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;
60f067b4 209 }
f47781d8
MP
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. */
60f067b4 216
f47781d8
MP
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 }
60f067b4 221
f47781d8
MP
222 return 0;
223}
60f067b4 224
f47781d8
MP
225static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
226 PTYForward *f = userdata;
60f067b4 227
f47781d8
MP
228 assert(f);
229 assert(e);
230 assert(e == f->master_event_source);
231 assert(fd >= 0);
232 assert(fd == f->master);
60f067b4 233
f47781d8
MP
234 if (revents & (EPOLLIN|EPOLLHUP))
235 f->master_readable = true;
60f067b4 236
f47781d8
MP
237 if (revents & (EPOLLOUT|EPOLLHUP))
238 f->master_writable = true;
60f067b4 239
f47781d8
MP
240 return shovel(f);
241}
60f067b4 242
f47781d8
MP
243static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
244 PTYForward *f = userdata;
60f067b4 245
f47781d8
MP
246 assert(f);
247 assert(e);
248 assert(e == f->stdin_event_source);
249 assert(fd >= 0);
250 assert(fd == STDIN_FILENO);
60f067b4 251
f47781d8
MP
252 if (revents & (EPOLLIN|EPOLLHUP))
253 f->stdin_readable = true;
60f067b4 254
f47781d8
MP
255 return shovel(f);
256}
60f067b4 257
f47781d8
MP
258static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
259 PTYForward *f = userdata;
60f067b4 260
f47781d8
MP
261 assert(f);
262 assert(e);
263 assert(e == f->stdout_event_source);
264 assert(fd >= 0);
265 assert(fd == STDOUT_FILENO);
60f067b4 266
f47781d8
MP
267 if (revents & (EPOLLOUT|EPOLLHUP))
268 f->stdout_writable = true;
60f067b4 269
f47781d8
MP
270 return shovel(f);
271}
60f067b4 272
f47781d8
MP
273static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
274 PTYForward *f = userdata;
275 struct winsize ws;
60f067b4 276
f47781d8
MP
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;
60f067b4
JS
286}
287
f47781d8
MP
288int pty_forward_new(sd_event *event, int master, PTYForward **ret) {
289 _cleanup_(pty_forward_freep) PTYForward *f = NULL;
60f067b4
JS
290 struct winsize ws;
291 int r;
292
f47781d8
MP
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
60f067b4 319 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
f47781d8 320 (void)ioctl(master, TIOCSWINSZ, &ws);
60f067b4 321
f47781d8
MP
322 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
323 struct termios raw_stdin_attr;
60f067b4 324
f47781d8
MP
325 f->saved_stdin = true;
326
327 raw_stdin_attr = f->saved_stdin_attr;
60f067b4 328 cfmakeraw(&raw_stdin_attr);
f47781d8 329 raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
60f067b4
JS
330 tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
331 }
60f067b4 332
f47781d8
MP
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;
60f067b4 339 cfmakeraw(&raw_stdout_attr);
f47781d8
MP
340 raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
341 raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
60f067b4
JS
342 tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
343 }
344
f47781d8
MP
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;
60f067b4 348
f47781d8
MP
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
368PTYForward *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 }
60f067b4
JS
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
f47781d8 389 return NULL;
60f067b4 390}