chiark / gitweb /
164c9b6ff37dbbe1bf61b55ab84afc1cdff35980
[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/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 read_only:1;
46
47         bool saved_stdin:1;
48         bool saved_stdout:1;
49
50         bool stdin_readable:1;
51         bool stdin_hangup:1;
52         bool stdout_writable:1;
53         bool stdout_hangup:1;
54         bool master_readable:1;
55         bool master_writable:1;
56         bool master_hangup:1;
57
58         /* Continue reading after hangup? */
59         bool ignore_vhangup:1;
60
61         bool last_char_set:1;
62         char last_char;
63
64         char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
65         size_t in_buffer_full, out_buffer_full;
66
67         usec_t escape_timestamp;
68         unsigned escape_counter;
69 };
70
71 #define ESCAPE_USEC (1*USEC_PER_SEC)
72
73 static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
74         const char *p;
75
76         assert(f);
77         assert(buffer);
78         assert(n > 0);
79
80         for (p = buffer; p < buffer + n; p++) {
81
82                 /* Check for ^] */
83                 if (*p == 0x1D) {
84                         usec_t nw = now(CLOCK_MONOTONIC);
85
86                         if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC)  {
87                                 f->escape_timestamp = nw;
88                                 f->escape_counter = 1;
89                         } else {
90                                 (f->escape_counter)++;
91
92                                 if (f->escape_counter >= 3)
93                                         return true;
94                         }
95                 } else {
96                         f->escape_timestamp = 0;
97                         f->escape_counter = 0;
98                 }
99         }
100
101         return false;
102 }
103
104 static int shovel(PTYForward *f) {
105         ssize_t k;
106
107         assert(f);
108
109         while ((f->stdin_readable && f->in_buffer_full <= 0) ||
110                (f->master_writable && f->in_buffer_full > 0) ||
111                (f->master_readable && f->out_buffer_full <= 0) ||
112                (f->stdout_writable && f->out_buffer_full > 0)) {
113
114                 if (f->stdin_readable && f->in_buffer_full < LINE_MAX) {
115
116                         k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full);
117                         if (k < 0) {
118
119                                 if (errno == EAGAIN)
120                                         f->stdin_readable = false;
121                                 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
122                                         f->stdin_readable = false;
123                                         f->stdin_hangup = true;
124
125                                         f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
126                                 } else {
127                                         log_error_errno(errno, "read(): %m");
128                                         return sd_event_exit(f->event, EXIT_FAILURE);
129                                 }
130                         } else if (k == 0) {
131                                 /* EOF on stdin */
132                                 f->stdin_readable = false;
133                                 f->stdin_hangup = true;
134
135                                 f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
136                         } else  {
137                                 /* Check if ^] has been
138                                  * pressed three times within
139                                  * one second. If we get this
140                                  * we quite immediately. */
141                                 if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
142                                         return sd_event_exit(f->event, EXIT_FAILURE);
143
144                                 f->in_buffer_full += (size_t) k;
145                         }
146                 }
147
148                 if (f->master_writable && f->in_buffer_full > 0) {
149
150                         k = write(f->master, f->in_buffer, f->in_buffer_full);
151                         if (k < 0) {
152
153                                 if (errno == EAGAIN || errno == EIO)
154                                         f->master_writable = false;
155                                 else if (errno == EPIPE || errno == ECONNRESET) {
156                                         f->master_writable = f->master_readable = false;
157                                         f->master_hangup = true;
158
159                                         f->master_event_source = sd_event_source_unref(f->master_event_source);
160                                 } else {
161                                         log_error_errno(errno, "write(): %m");
162                                         return sd_event_exit(f->event, EXIT_FAILURE);
163                                 }
164                         } else {
165                                 assert(f->in_buffer_full >= (size_t) k);
166                                 memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
167                                 f->in_buffer_full -= k;
168                         }
169                 }
170
171                 if (f->master_readable && f->out_buffer_full < LINE_MAX) {
172
173                         k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
174                         if (k < 0) {
175
176                                 /* Note that EIO on the master device
177                                  * might be caused by vhangup() or
178                                  * temporary closing of everything on
179                                  * the other side, we treat it like
180                                  * EAGAIN here and try again, unless
181                                  * ignore_vhangup is off. */
182
183                                 if (errno == EAGAIN || (errno == EIO && f->ignore_vhangup))
184                                         f->master_readable = false;
185                                 else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
186                                         f->master_readable = f->master_writable = false;
187                                         f->master_hangup = true;
188
189                                         f->master_event_source = sd_event_source_unref(f->master_event_source);
190                                 } else {
191                                         log_error_errno(errno, "read(): %m");
192                                         return sd_event_exit(f->event, EXIT_FAILURE);
193                                 }
194                         }  else
195                                 f->out_buffer_full += (size_t) k;
196                 }
197
198                 if (f->stdout_writable && f->out_buffer_full > 0) {
199
200                         k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full);
201                         if (k < 0) {
202
203                                 if (errno == EAGAIN)
204                                         f->stdout_writable = false;
205                                 else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
206                                         f->stdout_writable = false;
207                                         f->stdout_hangup = true;
208                                         f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
209                                 } else {
210                                         log_error_errno(errno, "write(): %m");
211                                         return sd_event_exit(f->event, EXIT_FAILURE);
212                                 }
213
214                         } else {
215
216                                 if (k > 0) {
217                                         f->last_char = f->out_buffer[k-1];
218                                         f->last_char_set = true;
219                                 }
220
221                                 assert(f->out_buffer_full >= (size_t) k);
222                                 memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k);
223                                 f->out_buffer_full -= k;
224                         }
225                 }
226         }
227
228         if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) {
229                 /* Exit the loop if any side hung up and if there's
230                  * nothing more to write or nothing we could write. */
231
232                 if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
233                     (f->in_buffer_full <= 0 || f->master_hangup))
234                         return sd_event_exit(f->event, EXIT_SUCCESS);
235         }
236
237         return 0;
238 }
239
240 static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
241         PTYForward *f = userdata;
242
243         assert(f);
244         assert(e);
245         assert(e == f->master_event_source);
246         assert(fd >= 0);
247         assert(fd == f->master);
248
249         if (revents & (EPOLLIN|EPOLLHUP))
250                 f->master_readable = true;
251
252         if (revents & (EPOLLOUT|EPOLLHUP))
253                 f->master_writable = true;
254
255         return shovel(f);
256 }
257
258 static int on_stdin_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->stdin_event_source);
264         assert(fd >= 0);
265         assert(fd == STDIN_FILENO);
266
267         if (revents & (EPOLLIN|EPOLLHUP))
268                 f->stdin_readable = true;
269
270         return shovel(f);
271 }
272
273 static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
274         PTYForward *f = userdata;
275
276         assert(f);
277         assert(e);
278         assert(e == f->stdout_event_source);
279         assert(fd >= 0);
280         assert(fd == STDOUT_FILENO);
281
282         if (revents & (EPOLLOUT|EPOLLHUP))
283                 f->stdout_writable = true;
284
285         return shovel(f);
286 }
287
288 static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) {
289         PTYForward *f = userdata;
290         struct winsize ws;
291
292         assert(f);
293         assert(e);
294         assert(e == f->sigwinch_event_source);
295
296         /* The window size changed, let's forward that. */
297         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
298                 (void) ioctl(f->master, TIOCSWINSZ, &ws);
299
300         return 0;
301 }
302
303 int pty_forward_new(
304                 sd_event *event,
305                 int master,
306                 bool ignore_vhangup,
307                 bool read_only,
308                 PTYForward **ret) {
309
310         _cleanup_(pty_forward_freep) PTYForward *f = NULL;
311         struct winsize ws;
312         int r;
313
314         f = new0(PTYForward, 1);
315         if (!f)
316                 return -ENOMEM;
317
318         f->read_only = read_only;
319         f->ignore_vhangup = ignore_vhangup;
320
321         if (event)
322                 f->event = sd_event_ref(event);
323         else {
324                 r = sd_event_default(&f->event);
325                 if (r < 0)
326                         return r;
327         }
328
329         if (!read_only) {
330                 r = fd_nonblock(STDIN_FILENO, true);
331                 if (r < 0)
332                         return r;
333
334                 r = fd_nonblock(STDOUT_FILENO, true);
335                 if (r < 0)
336                         return r;
337         }
338
339         r = fd_nonblock(master, true);
340         if (r < 0)
341                 return r;
342
343         f->master = master;
344
345         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
346                 (void)ioctl(master, TIOCSWINSZ, &ws);
347
348         if (!read_only) {
349                 if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
350                         struct termios raw_stdin_attr;
351
352                         f->saved_stdin = true;
353
354                         raw_stdin_attr = f->saved_stdin_attr;
355                         cfmakeraw(&raw_stdin_attr);
356                         raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
357                         tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
358                 }
359
360                 if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
361                         struct termios raw_stdout_attr;
362
363                         f->saved_stdout = true;
364
365                         raw_stdout_attr = f->saved_stdout_attr;
366                         cfmakeraw(&raw_stdout_attr);
367                         raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
368                         raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
369                         tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
370                 }
371
372                 r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
373                 if (r < 0 && r != -EPERM)
374                         return r;
375         }
376
377         r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
378         if (r == -EPERM)
379                 /* stdout without epoll support. Likely redirected to regular file. */
380                 f->stdout_writable = true;
381         else if (r < 0)
382                 return r;
383
384         r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
385         if (r < 0)
386                 return r;
387
388         r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
389         if (r < 0)
390                 return r;
391
392         *ret = f;
393         f = NULL;
394
395         return 0;
396 }
397
398 PTYForward *pty_forward_free(PTYForward *f) {
399
400         if (f) {
401                 sd_event_source_unref(f->stdin_event_source);
402                 sd_event_source_unref(f->stdout_event_source);
403                 sd_event_source_unref(f->master_event_source);
404                 sd_event_unref(f->event);
405
406                 if (f->saved_stdout)
407                         tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
408                 if (f->saved_stdin)
409                         tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
410
411                 free(f);
412         }
413
414         /* STDIN/STDOUT should not be nonblocking normally, so let's
415          * unconditionally reset it */
416         fd_nonblock(STDIN_FILENO, false);
417         fd_nonblock(STDOUT_FILENO, false);
418
419         return NULL;
420 }
421
422 int pty_forward_get_last_char(PTYForward *f, char *ch) {
423         assert(f);
424         assert(ch);
425
426         if (!f->last_char_set)
427                 return -ENXIO;
428
429         *ch = f->last_char;
430         return 0;
431 }
432
433 int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup) {
434         int r;
435
436         assert(f);
437
438         if (f->ignore_vhangup == ignore_vhangup)
439                 return 0;
440
441         f->ignore_vhangup = ignore_vhangup;
442         if (!f->ignore_vhangup) {
443
444                 /* We shall now react to vhangup()s? Let's check
445                  * immediately if we might be in one */
446
447                 f->master_readable = true;
448                 r = shovel(f);
449                 if (r < 0)
450                         return r;
451         }
452
453         return 0;
454 }
455
456 int pty_forward_get_ignore_vhangup(PTYForward *f) {
457         assert(f);
458
459         return f->ignore_vhangup;
460 }