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