chiark / gitweb /
nspawn: fix invocation of the raw clone() system call on s390 and cris
[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
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 }