]>
Commit | Line | Data |
---|---|---|
b0a33c1e | 1 | /* |
2 | * lxc: linux Container library | |
3 | * | |
4 | * (C) Copyright IBM Corp. 2007, 2008 | |
5 | * | |
6 | * Authors: | |
9afe19d6 | 7 | * Daniel Lezcano <daniel.lezcano at free.fr> |
b0a33c1e | 8 | * |
9 | * This library is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU Lesser General Public | |
11 | * License as published by the Free Software Foundation; either | |
12 | * version 2.1 of the License, or (at your option) any later version. | |
13 | * | |
14 | * This library is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * Lesser General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU Lesser General Public | |
20 | * License along with this library; if not, write to the Free Software | |
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
22 | */ | |
23 | ||
b5159817 DE |
24 | #include <assert.h> |
25 | #include <signal.h> | |
b0a33c1e | 26 | #include <stdio.h> |
e0dc0de7 | 27 | #include <stdlib.h> |
b0a33c1e | 28 | #include <unistd.h> |
63376d7d | 29 | #include <fcntl.h> |
b0a33c1e | 30 | #include <errno.h> |
31 | #include <sys/types.h> | |
8173e600 | 32 | #include <termios.h> |
b0a33c1e | 33 | |
b5159817 | 34 | #include "lxccontainer.h" |
00dbc43e DL |
35 | #include "log.h" |
36 | #include "conf.h" | |
e827ff7e | 37 | #include "config.h" |
00dbc43e DL |
38 | #include "start.h" /* for struct lxc_handler */ |
39 | #include "caps.h" | |
96fa1ff0 | 40 | #include "commands.h" |
63376d7d | 41 | #include "mainloop.h" |
724e753c | 42 | #include "af_unix.h" |
b5159817 DE |
43 | #include "lxclock.h" |
44 | #include "utils.h" | |
36eb9bde | 45 | |
e827ff7e SG |
46 | #if HAVE_PTY_H |
47 | #include <pty.h> | |
48 | #else | |
49 | #include <../include/openpty.h> | |
50 | #endif | |
51 | ||
36eb9bde CLG |
52 | lxc_log_define(lxc_console, lxc); |
53 | ||
b5159817 | 54 | static struct lxc_list lxc_ttys; |
724e753c | 55 | |
b5159817 DE |
56 | typedef void (*sighandler_t)(int); |
57 | struct lxc_tty_state | |
58 | { | |
59 | struct lxc_list node; | |
60 | int stdinfd; | |
61 | int stdoutfd; | |
62 | int masterfd; | |
63 | int escape; | |
64 | int saw_escape; | |
65 | const char *winch_proxy; | |
66 | const char *winch_proxy_lxcpath; | |
67 | int sigfd; | |
68 | sigset_t oldmask; | |
69 | }; | |
70 | ||
71 | __attribute__((constructor)) | |
72 | void lxc_console_init(void) | |
73 | { | |
74 | lxc_list_init(&lxc_ttys); | |
75 | } | |
724e753c | 76 | |
b5159817 DE |
77 | /* lxc_console_winsz: propagte winsz from one terminal to another |
78 | * | |
79 | * @srcfd : terminal to get size from (typically a slave pty) | |
80 | * @dstfd : terminal to set size on (typically a master pty) | |
81 | */ | |
82 | static void lxc_console_winsz(int srcfd, int dstfd) | |
83 | { | |
84 | struct winsize wsz; | |
85 | if (isatty(srcfd) && ioctl(srcfd, TIOCGWINSZ, &wsz) == 0) { | |
86 | DEBUG("set winsz dstfd:%d cols:%d rows:%d", dstfd, | |
87 | wsz.ws_col, wsz.ws_row); | |
88 | ioctl(dstfd, TIOCSWINSZ, &wsz); | |
724e753c | 89 | } |
b5159817 | 90 | } |
724e753c | 91 | |
b5159817 DE |
92 | static void lxc_console_winch(struct lxc_tty_state *ts) |
93 | { | |
94 | lxc_console_winsz(ts->stdinfd, ts->masterfd); | |
95 | if (ts->winch_proxy) { | |
96 | lxc_cmd_console_winch(ts->winch_proxy, | |
97 | ts->winch_proxy_lxcpath); | |
98 | } | |
724e753c MN |
99 | } |
100 | ||
b5159817 | 101 | void lxc_console_sigwinch(int sig) |
cd453b38 | 102 | { |
b5159817 DE |
103 | if (process_lock() == 0) { |
104 | struct lxc_list *it; | |
105 | struct lxc_tty_state *ts; | |
cd453b38 | 106 | |
b5159817 DE |
107 | lxc_list_for_each(it, &lxc_ttys) { |
108 | ts = it->elem; | |
109 | lxc_console_winch(ts); | |
cd453b38 | 110 | } |
b5159817 | 111 | process_unlock(); |
cd453b38 | 112 | } |
b5159817 | 113 | } |
cd453b38 | 114 | |
b5159817 DE |
115 | static int lxc_console_cb_sigwinch_fd(int fd, void *cbdata, |
116 | struct lxc_epoll_descr *descr) | |
117 | { | |
118 | struct signalfd_siginfo siginfo; | |
119 | struct lxc_tty_state *ts = cbdata; | |
120 | ||
121 | if (read(fd, &siginfo, sizeof(siginfo)) < 0) { | |
122 | ERROR("failed to read signal info"); | |
123 | return -1; | |
cd453b38 DL |
124 | } |
125 | ||
b5159817 DE |
126 | lxc_console_winch(ts); |
127 | return 0; | |
128 | } | |
129 | ||
130 | /* | |
131 | * lxc_console_sigwinch_init: install SIGWINCH handler | |
132 | * | |
133 | * @srcfd : src for winsz in SIGWINCH handler | |
134 | * @dstfd : dst for winsz in SIGWINCH handler | |
135 | * | |
136 | * Returns lxc_tty_state structure on success or NULL on failure. The sigfd | |
137 | * member of the returned lxc_tty_state can be select()/poll()ed/epoll()ed | |
138 | * on (ie added to a mainloop) for SIGWINCH. | |
139 | * | |
140 | * Must be called with process_lock held to protect the lxc_ttys list, or | |
141 | * from a non-threaded context. | |
142 | * | |
143 | * Note that SIGWINCH isn't installed as a classic asychronous handler, | |
144 | * rather signalfd(2) is used so that we can handle the signal when we're | |
145 | * ready for it. This avoids deadlocks since a signal handler | |
146 | * (ie lxc_console_sigwinch()) would need to take the thread mutex to | |
147 | * prevent lxc_ttys list corruption, but using the fd we can provide the | |
148 | * tty_state needed to the callback (lxc_console_cb_sigwinch_fd()). | |
149 | */ | |
150 | static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd) | |
151 | { | |
152 | sigset_t mask; | |
153 | struct lxc_tty_state *ts; | |
154 | ||
155 | ts = malloc(sizeof(*ts)); | |
156 | if (!ts) | |
157 | return NULL; | |
158 | ||
159 | memset(ts, 0, sizeof(*ts)); | |
160 | ts->stdinfd = srcfd; | |
161 | ts->masterfd = dstfd; | |
162 | ts->sigfd = -1; | |
163 | ||
164 | /* add tty to list to be scanned at SIGWINCH time */ | |
165 | lxc_list_add_elem(&ts->node, ts); | |
166 | lxc_list_add_tail(&lxc_ttys, &ts->node); | |
167 | ||
168 | sigemptyset(&mask); | |
169 | sigaddset(&mask, SIGWINCH); | |
170 | if (sigprocmask(SIG_BLOCK, &mask, &ts->oldmask)) { | |
171 | SYSERROR("failed to block SIGWINCH"); | |
172 | goto err1; | |
173 | } | |
174 | ||
175 | ts->sigfd = signalfd(-1, &mask, 0); | |
176 | if (ts->sigfd < 0) { | |
177 | SYSERROR("failed to get signalfd"); | |
178 | goto err2; | |
179 | } | |
180 | ||
181 | DEBUG("%d got SIGWINCH fd %d", getpid(), ts->sigfd); | |
182 | goto out; | |
183 | ||
184 | err2: | |
185 | sigprocmask(SIG_SETMASK, &ts->oldmask, NULL); | |
186 | err1: | |
187 | lxc_list_del(&ts->node); | |
188 | free(ts); | |
189 | ts = NULL; | |
cd453b38 | 190 | out: |
b5159817 | 191 | return ts; |
cd453b38 DL |
192 | } |
193 | ||
b5159817 DE |
194 | /* |
195 | * lxc_console_sigwinch_fini: uninstall SIGWINCH handler | |
196 | * | |
197 | * @ts : the lxc_tty_state returned by lxc_console_sigwinch_init | |
198 | * | |
199 | * Restore the saved signal handler that was in effect at the time | |
200 | * lxc_console_sigwinch_init() was called. | |
201 | * | |
202 | * Must be called with process_lock held to protect the lxc_ttys list, or | |
203 | * from a non-threaded context. | |
204 | */ | |
205 | static void lxc_console_sigwinch_fini(struct lxc_tty_state *ts) | |
63376d7d | 206 | { |
b5159817 DE |
207 | if (ts->sigfd >= 0) |
208 | close(ts->sigfd); | |
209 | lxc_list_del(&ts->node); | |
210 | sigprocmask(SIG_SETMASK, &ts->oldmask, NULL); | |
211 | free(ts); | |
212 | } | |
1560f6c9 | 213 | |
b5159817 DE |
214 | static int lxc_console_cb_con(int fd, void *data, |
215 | struct lxc_epoll_descr *descr) | |
216 | { | |
217 | struct lxc_console *console = (struct lxc_console *)data; | |
218 | char buf[1024]; | |
219 | int r,w; | |
e0dc0de7 | 220 | |
b5159817 DE |
221 | w = r = read(fd, buf, sizeof(buf)); |
222 | if (r < 0) { | |
223 | SYSERROR("failed to read"); | |
224 | return 1; | |
cd453b38 | 225 | } |
f78a1f32 | 226 | |
b5159817 DE |
227 | if (!r) { |
228 | INFO("console client on fd %d has exited", fd); | |
229 | lxc_mainloop_del_handler(descr, fd); | |
230 | close(fd); | |
dff21ef0 | 231 | return 0; |
33fcb7a0 | 232 | } |
63376d7d | 233 | |
b5159817 DE |
234 | if (fd == console->peer) |
235 | w = write(console->master, buf, r); | |
236 | ||
237 | if (fd == console->master) { | |
238 | if (console->log_fd >= 0) | |
239 | w = write(console->log_fd, buf, r); | |
240 | ||
241 | if (console->peer >= 0) | |
242 | w = write(console->peer, buf, r); | |
63376d7d DL |
243 | } |
244 | ||
b5159817 DE |
245 | if (w != r) |
246 | WARN("console short write r:%d w:%d", r, w); | |
247 | return 0; | |
248 | } | |
249 | ||
250 | static void lxc_console_mainloop_add_peer(struct lxc_console *console) | |
251 | { | |
252 | if (console->peer >= 0) { | |
253 | if (lxc_mainloop_add_handler(console->descr, console->peer, | |
254 | lxc_console_cb_con, console)) | |
255 | WARN("console peer not added to mainloop"); | |
63376d7d DL |
256 | } |
257 | ||
b5159817 DE |
258 | if (console->tty_state) { |
259 | if (lxc_mainloop_add_handler(console->descr, | |
260 | console->tty_state->sigfd, | |
261 | lxc_console_cb_sigwinch_fd, | |
262 | console->tty_state)) { | |
263 | WARN("failed to add to mainloop SIGWINCH handler for '%d'", | |
264 | console->tty_state->sigfd); | |
596a818d | 265 | } |
b5159817 DE |
266 | } |
267 | } | |
268 | ||
269 | int lxc_console_mainloop_add(struct lxc_epoll_descr *descr, | |
270 | struct lxc_handler *handler) | |
271 | { | |
272 | struct lxc_conf *conf = handler->conf; | |
273 | struct lxc_console *console = &conf->console; | |
274 | ||
37903589 SH |
275 | if (conf->is_execute) { |
276 | INFO("no console for lxc-execute."); | |
277 | return 0; | |
278 | } | |
279 | ||
b5159817 DE |
280 | if (!conf->rootfs.path) { |
281 | INFO("no rootfs, no console."); | |
282 | return 0; | |
283 | } | |
284 | ||
285 | if (console->master < 0) { | |
286 | INFO("no console"); | |
287 | return 0; | |
596a818d DE |
288 | } |
289 | ||
b5159817 DE |
290 | if (lxc_mainloop_add_handler(descr, console->master, |
291 | lxc_console_cb_con, console)) { | |
292 | ERROR("failed to add to mainloop console handler for '%d'", | |
293 | console->master); | |
294 | return -1; | |
28a4b0e5 DL |
295 | } |
296 | ||
b5159817 DE |
297 | /* we cache the descr so that we can add an fd to it when someone |
298 | * does attach to it in lxc_console_allocate() | |
299 | */ | |
300 | console->descr = descr; | |
301 | lxc_console_mainloop_add_peer(console); | |
cd453b38 | 302 | |
b5159817 DE |
303 | return 0; |
304 | } | |
28a4b0e5 | 305 | |
b5159817 DE |
306 | static int setup_tios(int fd, struct termios *oldtios) |
307 | { | |
308 | struct termios newtios; | |
e0dc0de7 | 309 | |
b5159817 DE |
310 | if (!isatty(fd)) { |
311 | ERROR("'%d' is not a tty", fd); | |
312 | return -1; | |
e0dc0de7 DL |
313 | } |
314 | ||
b5159817 DE |
315 | /* Get current termios */ |
316 | if (tcgetattr(fd, oldtios)) { | |
e0dc0de7 | 317 | SYSERROR("failed to get current terminal settings"); |
b5159817 | 318 | return -1; |
e0dc0de7 DL |
319 | } |
320 | ||
b5159817 | 321 | newtios = *oldtios; |
e0dc0de7 DL |
322 | |
323 | /* Remove the echo characters and signal reception, the echo | |
b5159817 DE |
324 | * will be done with master proxying */ |
325 | newtios.c_iflag &= ~IGNBRK; | |
326 | newtios.c_iflag &= BRKINT; | |
327 | newtios.c_lflag &= ~(ECHO|ICANON|ISIG); | |
328 | newtios.c_cc[VMIN] = 1; | |
329 | newtios.c_cc[VTIME] = 0; | |
e0dc0de7 DL |
330 | |
331 | /* Set new attributes */ | |
b5159817 | 332 | if (tcsetattr(fd, TCSAFLUSH, &newtios)) { |
e0dc0de7 | 333 | ERROR("failed to set new terminal settings"); |
b5159817 | 334 | return -1; |
e0dc0de7 DL |
335 | } |
336 | ||
63376d7d | 337 | return 0; |
b5159817 | 338 | } |
e0dc0de7 | 339 | |
b5159817 DE |
340 | static void lxc_console_peer_proxy_free(struct lxc_console *console) |
341 | { | |
342 | if (console->tty_state) { | |
343 | lxc_console_sigwinch_fini(console->tty_state); | |
344 | console->tty_state = NULL; | |
345 | } | |
346 | close(console->peerpty.master); | |
347 | close(console->peerpty.slave); | |
348 | console->peerpty.master = -1; | |
349 | console->peerpty.slave = -1; | |
350 | console->peerpty.busy = -1; | |
351 | console->peerpty.name[0] = '\0'; | |
596a818d | 352 | console->peer = -1; |
b5159817 | 353 | } |
596a818d | 354 | |
b5159817 DE |
355 | static int lxc_console_peer_proxy_alloc(struct lxc_console *console, int sockfd) |
356 | { | |
357 | struct termios oldtermio; | |
358 | struct lxc_tty_state *ts; | |
359 | ||
360 | if (console->master < 0) { | |
361 | ERROR("console not set up"); | |
362 | return -1; | |
363 | } | |
364 | if (console->peerpty.busy != -1 || console->peer != -1) { | |
365 | NOTICE("console already in use"); | |
366 | return -1; | |
367 | } | |
368 | if (console->tty_state) { | |
369 | ERROR("console already has tty_state"); | |
370 | return -1; | |
596a818d DE |
371 | } |
372 | ||
b5159817 DE |
373 | /* this is the proxy pty that will be given to the client, and that |
374 | * the real pty master will send to / recv from | |
375 | */ | |
376 | if (openpty(&console->peerpty.master, &console->peerpty.slave, | |
377 | console->peerpty.name, NULL, NULL)) { | |
378 | SYSERROR("failed to create proxy pty"); | |
379 | return -1; | |
380 | } | |
596a818d | 381 | |
b5159817 DE |
382 | if (setup_tios(console->peerpty.slave, &oldtermio) < 0) |
383 | goto err1; | |
384 | ||
385 | ts = lxc_console_sigwinch_init(console->peerpty.master, console->master); | |
386 | if (!ts) | |
387 | goto err1; | |
388 | ||
389 | console->tty_state = ts; | |
390 | console->peer = console->peerpty.slave; | |
391 | console->peerpty.busy = sockfd; | |
392 | lxc_console_mainloop_add_peer(console); | |
393 | ||
394 | DEBUG("%d %s peermaster:%d sockfd:%d", getpid(), __FUNCTION__, console->peerpty.master, sockfd); | |
395 | return 0; | |
396 | ||
397 | err1: | |
398 | lxc_console_peer_proxy_free(console); | |
63376d7d DL |
399 | return -1; |
400 | } | |
401 | ||
b5159817 DE |
402 | /* lxc_console_allocate: allocate the console or a tty |
403 | * | |
404 | * @conf : the configuration of the container to allocate from | |
405 | * @sockfd : the socket fd whose remote side when closed, will be an | |
406 | * indication that the console or tty is no longer in use | |
407 | * @ttyreq : the tty requested to be opened, -1 for any, 0 for the console | |
408 | */ | |
409 | int lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttyreq) | |
410 | { | |
411 | int masterfd = -1, ttynum; | |
412 | struct lxc_tty_info *tty_info = &conf->tty_info; | |
413 | struct lxc_console *console = &conf->console; | |
414 | ||
415 | process_lock(); | |
416 | if (*ttyreq == 0) { | |
417 | if (lxc_console_peer_proxy_alloc(console, sockfd) < 0) | |
418 | goto out; | |
419 | masterfd = console->peerpty.master; | |
420 | goto out; | |
421 | } | |
422 | ||
423 | if (*ttyreq > 0) { | |
424 | if (*ttyreq > tty_info->nbtty) | |
425 | goto out; | |
426 | ||
427 | if (tty_info->pty_info[*ttyreq - 1].busy) | |
428 | goto out; | |
429 | ||
430 | /* the requested tty is available */ | |
431 | ttynum = *ttyreq; | |
432 | goto out_tty; | |
433 | } | |
434 | ||
435 | /* search for next available tty, fixup index tty1 => [0] */ | |
436 | for (ttynum = 1; | |
437 | ttynum <= tty_info->nbtty && tty_info->pty_info[ttynum - 1].busy; | |
438 | ttynum++); | |
439 | ||
440 | /* we didn't find any available slot for tty */ | |
441 | if (ttynum > tty_info->nbtty) | |
442 | goto out; | |
443 | ||
444 | *ttyreq = ttynum; | |
445 | ||
446 | out_tty: | |
447 | tty_info->pty_info[ttynum - 1].busy = sockfd; | |
448 | masterfd = tty_info->pty_info[ttynum - 1].master; | |
449 | out: | |
450 | process_unlock(); | |
451 | return masterfd; | |
452 | } | |
453 | ||
454 | /* lxc_console_free: mark the console or a tty as unallocated, free any | |
455 | * resources allocated by lxc_console_allocate(). | |
456 | * | |
457 | * @conf : the configuration of the container whose tty was closed | |
458 | * @fd : the socket fd whose remote side was closed, which indicated | |
459 | * the console or tty is no longer in use. this is used to match | |
460 | * which console/tty is being freed. | |
461 | */ | |
462 | void lxc_console_free(struct lxc_conf *conf, int fd) | |
63376d7d | 463 | { |
b5159817 DE |
464 | int i; |
465 | struct lxc_tty_info *tty_info = &conf->tty_info; | |
466 | struct lxc_console *console = &conf->console; | |
467 | ||
468 | process_lock(); | |
469 | for (i = 0; i < tty_info->nbtty; i++) { | |
470 | if (tty_info->pty_info[i].busy == fd) | |
471 | tty_info->pty_info[i].busy = 0; | |
472 | } | |
473 | ||
474 | if (console->peerpty.busy == fd) { | |
475 | lxc_mainloop_del_handler(console->descr, console->peerpty.slave); | |
476 | lxc_console_peer_proxy_free(console); | |
477 | } | |
478 | process_unlock(); | |
479 | } | |
480 | ||
481 | static void lxc_console_peer_default(struct lxc_console *console) | |
482 | { | |
483 | struct lxc_tty_state *ts; | |
484 | const char *path = console->path; | |
485 | ||
486 | /* if no console was given, try current controlling terminal, there | |
487 | * won't be one if we were started as a daemon (-d) | |
488 | */ | |
489 | if (!path && !access("/dev/tty", F_OK)) { | |
490 | int fd; | |
491 | fd = open("/dev/tty", O_RDWR); | |
492 | if (fd >= 0) { | |
493 | close(fd); | |
494 | path = "/dev/tty"; | |
495 | } | |
496 | } | |
497 | ||
498 | if (!path) | |
499 | goto out; | |
500 | ||
501 | DEBUG("opening %s for console peer", path); | |
502 | console->peer = lxc_unpriv(open(path, O_CLOEXEC | O_RDWR | O_CREAT | | |
503 | O_APPEND, 0600)); | |
504 | if (console->peer < 0) | |
505 | goto out; | |
506 | ||
507 | DEBUG("using '%s' as console", path); | |
508 | ||
509 | if (!isatty(console->peer)) | |
510 | return; | |
511 | ||
512 | ts = lxc_console_sigwinch_init(console->peer, console->master); | |
513 | if (!ts) | |
514 | WARN("Unable to install SIGWINCH"); | |
515 | console->tty_state = ts; | |
516 | ||
517 | lxc_console_winsz(console->peer, console->master); | |
518 | ||
519 | console->tios = malloc(sizeof(*console->tios)); | |
520 | if (!console->tios) { | |
521 | SYSERROR("failed to allocate memory"); | |
522 | goto err1; | |
523 | } | |
524 | ||
525 | if (setup_tios(console->peer, console->tios) < 0) | |
526 | goto err2; | |
527 | ||
528 | return; | |
529 | ||
530 | err2: | |
531 | free(console->tios); | |
532 | console->tios = NULL; | |
533 | err1: | |
534 | close(console->peer); | |
535 | console->peer = -1; | |
536 | out: | |
537 | DEBUG("no console peer"); | |
538 | } | |
539 | ||
540 | void lxc_console_delete(struct lxc_console *console) | |
541 | { | |
542 | if (console->tios && console->peer >= 0 && | |
e0dc0de7 DL |
543 | tcsetattr(console->peer, TCSAFLUSH, console->tios)) |
544 | WARN("failed to set old terminal settings"); | |
596a818d DE |
545 | free(console->tios); |
546 | console->tios = NULL; | |
547 | ||
548 | close(console->peer); | |
549 | console->peer = -1; | |
550 | ||
551 | if (console->log_fd >= 0) { | |
552 | close(console->log_fd); | |
553 | console->log_fd = -1; | |
554 | } | |
555 | ||
63376d7d | 556 | close(console->master); |
596a818d DE |
557 | console->master = -1; |
558 | ||
63376d7d | 559 | close(console->slave); |
596a818d | 560 | console->slave = -1; |
63376d7d DL |
561 | } |
562 | ||
b5159817 | 563 | int lxc_console_create(struct lxc_conf *conf) |
63376d7d | 564 | { |
b5159817 | 565 | struct lxc_console *console = &conf->console; |
63376d7d | 566 | |
37903589 SH |
567 | if (conf->is_execute) { |
568 | INFO("no console for lxc-execute."); | |
569 | return 0; | |
570 | } | |
571 | ||
b5159817 DE |
572 | if (!conf->rootfs.path) |
573 | return 0; | |
574 | ||
575 | if (console->path && !strcmp(console->path, "none")) | |
576 | return 0; | |
577 | ||
578 | if (openpty(&console->master, &console->slave, | |
579 | console->name, NULL, NULL)) { | |
580 | SYSERROR("failed to allocate a pty"); | |
581 | return -1; | |
582 | } | |
583 | ||
584 | if (fcntl(console->master, F_SETFD, FD_CLOEXEC)) { | |
585 | SYSERROR("failed to set console master to close-on-exec"); | |
586 | goto err; | |
587 | } | |
588 | ||
589 | if (fcntl(console->slave, F_SETFD, FD_CLOEXEC)) { | |
590 | SYSERROR("failed to set console slave to close-on-exec"); | |
591 | goto err; | |
592 | } | |
593 | ||
594 | lxc_console_peer_default(console); | |
595 | ||
596 | if (console->log_path) { | |
597 | console->log_fd = lxc_unpriv(open(console->log_path, | |
598 | O_CLOEXEC | O_RDWR | | |
599 | O_CREAT | O_APPEND, 0600)); | |
600 | if (console->log_fd < 0) { | |
601 | SYSERROR("failed to open '%s'", console->log_path); | |
602 | goto err; | |
603 | } | |
604 | DEBUG("using '%s' as console log", console->log_path); | |
605 | } | |
606 | ||
607 | return 0; | |
608 | ||
609 | err: | |
610 | lxc_console_delete(console); | |
611 | return -1; | |
612 | } | |
613 | ||
614 | ||
615 | ||
616 | static int lxc_console_cb_tty_stdin(int fd, void *cbdata, | |
617 | struct lxc_epoll_descr *descr) | |
618 | { | |
619 | struct lxc_tty_state *ts = cbdata; | |
620 | char c; | |
621 | ||
622 | assert(fd == ts->stdinfd); | |
623 | if (read(ts->stdinfd, &c, 1) < 0) { | |
63376d7d DL |
624 | SYSERROR("failed to read"); |
625 | return 1; | |
626 | } | |
627 | ||
b5159817 DE |
628 | /* we want to exit the console with Ctrl+a q */ |
629 | if (c == ts->escape && !ts->saw_escape) { | |
630 | ts->saw_escape = 1; | |
5c294060 DL |
631 | return 0; |
632 | } | |
633 | ||
b5159817 DE |
634 | if (c == 'q' && ts->saw_escape) |
635 | return 1; | |
63376d7d | 636 | |
b5159817 DE |
637 | ts->saw_escape = 0; |
638 | if (write(ts->masterfd, &c, 1) < 0) { | |
639 | SYSERROR("failed to write"); | |
640 | return 1; | |
596a818d | 641 | } |
b5159817 | 642 | |
63376d7d DL |
643 | return 0; |
644 | } | |
645 | ||
b5159817 DE |
646 | static int lxc_console_cb_tty_master(int fd, void *cbdata, |
647 | struct lxc_epoll_descr *descr) | |
63376d7d | 648 | { |
b5159817 DE |
649 | struct lxc_tty_state *ts = cbdata; |
650 | char buf[1024]; | |
651 | int r,w; | |
63376d7d | 652 | |
b5159817 DE |
653 | assert(fd == ts->masterfd); |
654 | r = read(fd, buf, sizeof(buf)); | |
655 | if (r < 0) { | |
656 | SYSERROR("failed to read"); | |
657 | return 1; | |
1560f6c9 DL |
658 | } |
659 | ||
b5159817 DE |
660 | w = write(ts->stdoutfd, buf, r); |
661 | if (w < 0 || w != r) { | |
662 | SYSERROR("failed to write"); | |
663 | return 1; | |
f78a1f32 DL |
664 | } |
665 | ||
b5159817 DE |
666 | return 0; |
667 | } | |
668 | ||
669 | int lxc_console_getfd(struct lxc_container *c, int *ttynum, int *masterfd) | |
670 | { | |
671 | return lxc_cmd_console(c->name, ttynum, masterfd, c->config_path); | |
672 | } | |
673 | ||
674 | int lxc_console(struct lxc_container *c, int ttynum, | |
675 | int stdinfd, int stdoutfd, int stderrfd, | |
676 | int escape) | |
677 | { | |
678 | int ret, ttyfd, masterfd; | |
679 | struct lxc_epoll_descr descr; | |
680 | struct termios oldtios; | |
681 | struct lxc_tty_state *ts; | |
682 | ||
683 | if (!isatty(stdinfd)) { | |
684 | ERROR("stdin is not a tty"); | |
685 | return -1; | |
dff21ef0 DL |
686 | } |
687 | ||
b5159817 DE |
688 | ret = setup_tios(stdinfd, &oldtios); |
689 | if (ret) { | |
690 | ERROR("failed to setup tios"); | |
63376d7d DL |
691 | return -1; |
692 | } | |
693 | ||
b5159817 DE |
694 | process_lock(); |
695 | ttyfd = lxc_cmd_console(c->name, &ttynum, &masterfd, c->config_path); | |
696 | if (ttyfd < 0) { | |
697 | ret = ttyfd; | |
698 | goto err1; | |
699 | } | |
63376d7d | 700 | |
b5159817 DE |
701 | fprintf(stderr, "\n" |
702 | "Connected to tty %1$d\n" | |
703 | "Type <Ctrl+%2$c q> to exit the console, " | |
704 | "<Ctrl+%2$c Ctrl+%2$c> to enter Ctrl+%2$c itself\n", | |
705 | ttynum, 'a' + escape - 1); | |
706 | ||
707 | ret = setsid(); | |
708 | if (ret) | |
709 | INFO("already group leader"); | |
710 | ||
711 | ts = lxc_console_sigwinch_init(stdinfd, masterfd); | |
712 | if (!ts) { | |
713 | ret = -1; | |
714 | goto err2; | |
715 | } | |
716 | ts->escape = escape; | |
717 | ts->winch_proxy = c->name; | |
718 | ts->winch_proxy_lxcpath = c->config_path; | |
719 | ||
720 | lxc_console_winsz(stdinfd, masterfd); | |
721 | lxc_cmd_console_winch(ts->winch_proxy, ts->winch_proxy_lxcpath); | |
722 | ||
723 | ret = lxc_mainloop_open(&descr); | |
724 | if (ret) { | |
725 | ERROR("failed to create mainloop"); | |
726 | goto err3; | |
727 | } | |
728 | ||
729 | ret = lxc_mainloop_add_handler(&descr, ts->sigfd, | |
730 | lxc_console_cb_sigwinch_fd, ts); | |
731 | if (ret) { | |
732 | ERROR("failed to add handler for SIGWINCH fd"); | |
733 | goto err4; | |
734 | } | |
735 | ||
736 | ret = lxc_mainloop_add_handler(&descr, ts->stdinfd, | |
737 | lxc_console_cb_tty_stdin, ts); | |
738 | if (ret) { | |
739 | ERROR("failed to add handler for stdinfd"); | |
740 | goto err4; | |
741 | } | |
742 | ||
743 | ret = lxc_mainloop_add_handler(&descr, ts->masterfd, | |
744 | lxc_console_cb_tty_master, ts); | |
745 | if (ret) { | |
746 | ERROR("failed to add handler for masterfd"); | |
747 | goto err4; | |
748 | } | |
749 | ||
750 | process_unlock(); | |
751 | ret = lxc_mainloop(&descr, -1); | |
752 | process_lock(); | |
753 | if (ret) { | |
754 | ERROR("mainloop returned an error"); | |
755 | goto err4; | |
756 | } | |
757 | ||
758 | ret = 0; | |
759 | ||
760 | err4: | |
761 | lxc_mainloop_close(&descr); | |
762 | err3: | |
763 | lxc_console_sigwinch_fini(ts); | |
764 | err2: | |
765 | close(masterfd); | |
766 | close(ttyfd); | |
767 | err1: | |
768 | tcsetattr(stdinfd, TCSAFLUSH, &oldtios); | |
769 | process_unlock(); | |
770 | ||
771 | return ret; | |
63376d7d | 772 | } |