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