chiark / gitweb /
shared: add formats-util.h
[elogind.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/ioctl.h>
24 #include <limits.h>
25 #include <termios.h>
26
27 #include "util.h"
28 #include "ptyfwd.h"
29
30 struct PTYForward {
31         sd_event *event;
32
33         int master;
34
35         sd_event_source *stdin_event_source;
36         sd_event_source *stdout_event_source;
37         sd_event_source *master_event_source;
38
39         sd_event_source *sigwinch_event_source;
40
41         struct termios saved_stdin_attr;
42         struct termios saved_stdout_attr;
43
44         bool read_only:1;
45
46         bool saved_stdin:1;
47         bool saved_stdout:1;
48
49         bool stdin_readable:1;
50         bool stdin_hangup:1;
51         bool stdout_writable:1;
52         bool stdout_hangup:1;
53         bool master_readable:1;
54         bool master_writable:1;
55         bool master_hangup:1;
56
57         /* Continue reading after hangup? */
58         bool ignore_vhangup:1;
59
60         bool last_char_set:1;
61         char last_char;
62
63         char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
64         size_t in_buffer_full, out_buffer_full;
65
66         usec_t escape_timestamp;
67         unsigned escape_counter;
68 };
69
70 #define ESCAPE_USEC (1*USEC_PER_SEC)
71
72 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
73         const char *p;
74
75         assert(f);
76         assert(buffer);
77         assert(n > 0);
78
79         for (p = buffer; p < buffer + n; p++) {
80
81                 /* Check for ^] */
82                 if (*p == 0x1D) {
83                         usec_t nw = now(CLOCK_MONOTONIC);
84
85                         if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC)  {
86                                 f->escape_timestamp = nw;
87                                 f->escape_counter = 1;
88                         } else {
89                                 (f->escape_counter)++;
90
91                                 if (f->escape_counter >= 3)
92                                         return true;
93                         }
94                 } else {
95                         f->escape_timestamp = 0;
96                         f->escape_counter = 0;
97                 }
98         }
99
100         return false;
101 }
102
103 static int shovel(PTYForward *f) {
104         ssize_t k;
105
106         assert(f);
107
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)) {
112
113                 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
114
115                         k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
116                         if (k < 0) {
117
118                                 if (errno == EAGAIN)
119                                         f->stdin_readable = false;
120                                 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
121                                         f->stdin_readable = false;
122                                         f->stdin_hangup = true;
123
124                                         f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
125                                 } else {
126                                         log_error_errno(errno, "read(): %m");
127                                         return sd_event_exit(f->event, EXIT_FAILURE);
128                                 }
129                         } else if (k == 0) {
130                                 /* EOF on stdin */
131                                 f->stdin_readable = false;
132                                 f->stdin_hangup = true;
133
134                                 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
135                         } else  {
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);
142
143                                 f->in_buffer_full += (size_t) k;
144                         }
145                 }
146
147                 if (f->master_writable && f->in_buffer_full > 0) {
148
149                         k = write(f->master, f->in_buffer, f->in_buffer_full);
150                         if (k < 0) {
151
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;
157
158                                         f->master_event_source = sd_event_source_unref(f->master_event_source);
159                                 } else {
160                                         log_error_errno(errno, "write(): %m");
161                                         return sd_event_exit(f->event, EXIT_FAILURE);
162                                 }
163                         } else {
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;
167                         }
168                 }
169
170                 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
171
172                         k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
173                         if (k < 0) {
174
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. */
181
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;
187
188                                         f->master_event_source = sd_event_source_unref(f->master_event_source);
189                                 } else {
190                                         log_error_errno(errno, "read(): %m");
191                                         return sd_event_exit(f->event, EXIT_FAILURE);
192                                 }
193                         }  else
194                                 f->out_buffer_full += (size_t) k;
195                 }
196
197                 if (f->stdout_writable && f->out_buffer_full > 0) {
198
199                         k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
200                         if (k < 0) {
201
202                                 if (errno == EAGAIN)
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);
208                                 } else {
209                                         log_error_errno(errno, "write(): %m");
210                                         return sd_event_exit(f->event, EXIT_FAILURE);
211                                 }
212
213                         } else {
214
215                                 if (k > 0) {
216                                         f->last_char = f->out_buffer[k-1];
217                                         f->last_char_set = true;
218                                 }
219
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;
223                         }
224                 }
225         }
226
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. */
230
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);
234         }
235
236         return 0;
237 }
238
239 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
240         PTYForward *f = userdata;
241
242         assert(f);
243         assert(e);
244         assert(e == f->master_event_source);
245         assert(fd >= 0);
246         assert(fd == f->master);
247
248         if (revents & (EPOLLIN|EPOLLHUP))
249                 f->master_readable = true;
250
251         if (revents & (EPOLLOUT|EPOLLHUP))
252                 f->master_writable = true;
253
254         return shovel(f);
255 }
256
257 static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
258         PTYForward *f = userdata;
259
260         assert(f);
261         assert(e);
262         assert(e == f->stdin_event_source);
263         assert(fd >= 0);
264         assert(fd == STDIN_FILENO);
265
266         if (revents & (EPOLLIN|EPOLLHUP))
267                 f->stdin_readable = true;
268
269         return shovel(f);
270 }
271
272 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
273         PTYForward *f = userdata;
274
275         assert(f);
276         assert(e);
277         assert(e == f->stdout_event_source);
278         assert(fd >= 0);
279         assert(fd == STDOUT_FILENO);
280
281         if (revents & (EPOLLOUT|EPOLLHUP))
282                 f->stdout_writable = true;
283
284         return shovel(f);
285 }
286
287 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
288         PTYForward *f = userdata;
289         struct winsize ws;
290
291         assert(f);
292         assert(e);
293         assert(e == f->sigwinch_event_source);
294
295         /* The window size changed, let's forward that. */
296         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
297                 (void) ioctl(f->master, TIOCSWINSZ, &ws);
298
299         return 0;
300 }
301
302 int pty_forward_new(
303                 sd_event *event,
304                 int master,
305                 bool ignore_vhangup,
306                 bool read_only,
307                 PTYForward **ret) {
308
309         _cleanup_(pty_forward_freep) PTYForward *f = NULL;
310         struct winsize ws;
311         int r;
312
313         f = new0(PTYForward, 1);
314         if (!f)
315                 return -ENOMEM;
316
317         f->read_only = read_only;
318         f->ignore_vhangup = ignore_vhangup;
319
320         if (event)
321                 f->event = sd_event_ref(event);
322         else {
323                 r = sd_event_default(&f->event);
324                 if (r < 0)
325                         return r;
326         }
327
328         if (!read_only) {
329                 r = fd_nonblock(STDIN_FILENO, true);
330                 if (r < 0)
331                         return r;
332
333                 r = fd_nonblock(STDOUT_FILENO, true);
334                 if (r < 0)
335                         return r;
336         }
337
338         r = fd_nonblock(master, true);
339         if (r < 0)
340                 return r;
341
342         f->master = master;
343
344         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
345                 (void) ioctl(master, TIOCSWINSZ, &ws);
346
347         if (!read_only) {
348                 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
349                         struct termios raw_stdin_attr;
350
351                         f->saved_stdin = true;
352
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);
357                 }
358
359                 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
360                         struct termios raw_stdout_attr;
361
362                         f->saved_stdout = true;
363
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);
369                 }
370
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)
373                         return r;
374         }
375
376         r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
377         if (r == -EPERM)
378                 /* stdout without epoll support. Likely redirected to regular file. */
379                 f->stdout_writable = true;
380         else if (r < 0)
381                 return r;
382
383         r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
384         if (r < 0)
385                 return r;
386
387         r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
388         if (r < 0)
389                 return r;
390
391         *ret = f;
392         f = NULL;
393
394         return 0;
395 }
396
397 PTYForward *pty_forward_free(PTYForward *f) {
398
399         if (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);
404
405                 if (f->saved_stdout)
406                         tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
407                 if (f->saved_stdin)
408                         tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
409
410                 free(f);
411         }
412
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);
417
418         return NULL;
419 }
420
421 int pty_forward_get_last_char(PTYForward *f, char *ch) {
422         assert(f);
423         assert(ch);
424
425         if (!f->last_char_set)
426                 return -ENXIO;
427
428         *ch = f->last_char;
429         return 0;
430 }
431
432 int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup) {
433         int r;
434
435         assert(f);
436
437         if (f->ignore_vhangup == ignore_vhangup)
438                 return 0;
439
440         f->ignore_vhangup = ignore_vhangup;
441         if (!f->ignore_vhangup) {
442
443                 /* We shall now react to vhangup()s? Let's check
444                  * immediately if we might be in one */
445
446                 f->master_readable = true;
447                 r = shovel(f);
448                 if (r < 0)
449                         return r;
450         }
451
452         return 0;
453 }
454
455 int pty_forward_get_ignore_vhangup(PTYForward *f) {
456         assert(f);
457
458         return f->ignore_vhangup;
459 }