mirror of
https://git.proxmox.com/git/mirror_lxc
synced 2025-08-03 08:52:48 +00:00

lxc.console.size regulates the size of the console log file. This is intended to replace lxc.console.buffer.logfile. The current semantics are: - if lxc.console.size is not set: - no limit is placed on the size of the log file - if lxc.console.size is set: - if lxc.console.rotate is set and the next write would exceed the limit: - write as much as possible into the old log file - rotate the log file - write as much as posible into the new log file - discard remaining bytes (scenario shouldn't be possible in normal circumstances) - if lxc.console.rotate is not set and the next write would exceed the limit: - keep overwriting the current log file To make the log file a mirror of the in-memory ringbuffer simply set: lxc.console.buffer.size == lxc.console.size. Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
1203 lines
27 KiB
C
1203 lines
27 KiB
C
/*
|
|
* lxc: linux Container library
|
|
*
|
|
* (C) Copyright IBM Corp. 2007, 2008
|
|
*
|
|
* Authors:
|
|
* Daniel Lezcano <daniel.lezcano at free.fr>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <lxc/lxccontainer.h>
|
|
|
|
#include "af_unix.h"
|
|
#include "caps.h"
|
|
#include "commands.h"
|
|
#include "conf.h"
|
|
#include "config.h"
|
|
#include "console.h"
|
|
#include "log.h"
|
|
#include "lxclock.h"
|
|
#include "mainloop.h"
|
|
#include "start.h" /* for struct lxc_handler */
|
|
#include "utils.h"
|
|
|
|
#if HAVE_PTY_H
|
|
#include <pty.h>
|
|
#else
|
|
#include <../include/openpty.h>
|
|
#endif
|
|
|
|
#define LXC_CONSOLE_BUFFER_SIZE 1024
|
|
|
|
lxc_log_define(console, lxc);
|
|
|
|
static struct lxc_list lxc_ttys;
|
|
|
|
typedef void (*sighandler_t)(int);
|
|
|
|
__attribute__((constructor)) void lxc_console_init(void)
|
|
{
|
|
lxc_list_init(&lxc_ttys);
|
|
}
|
|
|
|
void lxc_console_winsz(int srcfd, int dstfd)
|
|
{
|
|
int ret;
|
|
struct winsize wsz;
|
|
|
|
if (!isatty(srcfd))
|
|
return;
|
|
|
|
ret = ioctl(srcfd, TIOCGWINSZ, &wsz);
|
|
if (ret < 0) {
|
|
WARN("Failed to get window size");
|
|
return;
|
|
}
|
|
|
|
ret = ioctl(dstfd, TIOCSWINSZ, &wsz);
|
|
if (ret < 0)
|
|
WARN("Failed to set window size");
|
|
else
|
|
DEBUG("Set window size to %d columns and %d rows", wsz.ws_col,
|
|
wsz.ws_row);
|
|
|
|
return;
|
|
}
|
|
|
|
static void lxc_console_winch(struct lxc_tty_state *ts)
|
|
{
|
|
lxc_console_winsz(ts->stdinfd, ts->masterfd);
|
|
|
|
if (ts->winch_proxy)
|
|
lxc_cmd_console_winch(ts->winch_proxy, ts->winch_proxy_lxcpath);
|
|
}
|
|
|
|
void lxc_console_sigwinch(int sig)
|
|
{
|
|
struct lxc_list *it;
|
|
struct lxc_tty_state *ts;
|
|
|
|
lxc_list_for_each(it, &lxc_ttys) {
|
|
ts = it->elem;
|
|
lxc_console_winch(ts);
|
|
}
|
|
}
|
|
|
|
int lxc_console_cb_signal_fd(int fd, uint32_t events, void *cbdata,
|
|
struct lxc_epoll_descr *descr)
|
|
{
|
|
ssize_t ret;
|
|
struct signalfd_siginfo siginfo;
|
|
struct lxc_tty_state *ts = cbdata;
|
|
|
|
ret = read(fd, &siginfo, sizeof(siginfo));
|
|
if (ret < 0 || (size_t)ret < sizeof(siginfo)) {
|
|
ERROR("Failed to read signal info");
|
|
return -1;
|
|
}
|
|
|
|
if (siginfo.ssi_signo == SIGTERM) {
|
|
DEBUG("Received SIGTERM. Detaching from the console");
|
|
return LXC_MAINLOOP_CLOSE;
|
|
}
|
|
|
|
if (siginfo.ssi_signo == SIGWINCH)
|
|
lxc_console_winch(ts);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct lxc_tty_state *lxc_console_signal_init(int srcfd, int dstfd)
|
|
{
|
|
int ret;
|
|
bool istty;
|
|
sigset_t mask;
|
|
struct lxc_tty_state *ts;
|
|
|
|
ts = malloc(sizeof(*ts));
|
|
if (!ts)
|
|
return NULL;
|
|
|
|
memset(ts, 0, sizeof(*ts));
|
|
ts->stdinfd = srcfd;
|
|
ts->masterfd = dstfd;
|
|
ts->sigfd = -1;
|
|
|
|
sigemptyset(&mask);
|
|
|
|
istty = isatty(srcfd) == 1;
|
|
if (!istty) {
|
|
INFO("fd %d does not refer to a tty device", srcfd);
|
|
} else {
|
|
/* Add tty to list to be scanned at SIGWINCH time. */
|
|
lxc_list_add_elem(&ts->node, ts);
|
|
lxc_list_add_tail(&lxc_ttys, &ts->node);
|
|
sigaddset(&mask, SIGWINCH);
|
|
}
|
|
|
|
/* Exit the mainloop cleanly on SIGTERM. */
|
|
sigaddset(&mask, SIGTERM);
|
|
|
|
ret = sigprocmask(SIG_BLOCK, &mask, &ts->oldmask);
|
|
if (ret < 0) {
|
|
WARN("Failed to block signals");
|
|
goto on_error;
|
|
}
|
|
|
|
ts->sigfd = signalfd(-1, &mask, SFD_CLOEXEC);
|
|
if (ts->sigfd < 0) {
|
|
WARN("Failed to create signal fd");
|
|
sigprocmask(SIG_SETMASK, &ts->oldmask, NULL);
|
|
goto on_error;
|
|
}
|
|
|
|
DEBUG("Created signal fd %d", ts->sigfd);
|
|
return ts;
|
|
|
|
on_error:
|
|
ERROR("Failed to create signal fd");
|
|
if (ts->sigfd >= 0) {
|
|
close(ts->sigfd);
|
|
ts->sigfd = -1;
|
|
}
|
|
if (istty)
|
|
lxc_list_del(&ts->node);
|
|
return ts;
|
|
}
|
|
|
|
void lxc_console_signal_fini(struct lxc_tty_state *ts)
|
|
{
|
|
if (ts->sigfd >= 0) {
|
|
close(ts->sigfd);
|
|
|
|
if (sigprocmask(SIG_SETMASK, &ts->oldmask, NULL) < 0)
|
|
WARN("%s - Failed to restore signal mask", strerror(errno));
|
|
}
|
|
|
|
if (isatty(ts->stdinfd))
|
|
lxc_list_del(&ts->node);
|
|
|
|
free(ts);
|
|
}
|
|
|
|
static int lxc_console_truncate_log_file(struct lxc_console *console)
|
|
{
|
|
/* be very certain things are kosher */
|
|
if (!console->log_path || console->log_fd < 0)
|
|
return -EBADF;
|
|
|
|
return lxc_unpriv(ftruncate(console->log_fd, 0));
|
|
}
|
|
|
|
static int lxc_console_rotate_log_file(struct lxc_console *console)
|
|
{
|
|
int ret;
|
|
size_t len;
|
|
char *tmp;
|
|
|
|
/* rotate the console log file */
|
|
if (!console->log_path || console->log_rotate == 0)
|
|
return -EOPNOTSUPP;
|
|
|
|
/* be very certain things are kosher */
|
|
if (console->log_fd < 0)
|
|
return -EBADF;
|
|
|
|
len = strlen(console->log_path) + sizeof(".1");
|
|
tmp = alloca(len);
|
|
|
|
ret = snprintf(tmp, len, "%s.1", console->log_path);
|
|
if (ret < 0 || (size_t)ret >= len)
|
|
return -EFBIG;
|
|
|
|
close(console->log_fd);
|
|
console->log_fd = -1;
|
|
ret = lxc_unpriv(rename(console->log_path, tmp));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return lxc_console_create_log_file(console);
|
|
}
|
|
|
|
static int lxc_console_write_log_file(struct lxc_console *console, char *buf,
|
|
int bytes_read)
|
|
{
|
|
int ret;
|
|
int64_t space_left = -1;
|
|
struct stat st;
|
|
|
|
if (console->log_fd < 0)
|
|
return 0;
|
|
|
|
/* A log size <= 0 means that there's no limit on the size of the log
|
|
* file at which point we simply ignore whether the log is supposed to
|
|
* be rotated or not.
|
|
*/
|
|
if (console->log_size <= 0)
|
|
return lxc_write_nointr(console->log_fd, buf, bytes_read);
|
|
|
|
/* Get current size of the log file. */
|
|
ret = fstat(console->log_fd, &st);
|
|
if (ret < 0) {
|
|
SYSERROR("Failed to stat the console log file descriptor");
|
|
return -1;
|
|
}
|
|
|
|
/* handle non-regular files */
|
|
if ((st.st_mode & S_IFMT) != S_IFREG) {
|
|
/* This isn't a regular file. so rotating the file seems a
|
|
* dangerous thing to do, size limits are also very
|
|
* questionable. Let's not risk anything and tell the user that
|
|
* he's requesting us to do weird stuff.
|
|
*/
|
|
if (console->log_rotate > 0 || console->log_size > 0)
|
|
return -EINVAL;
|
|
|
|
/* I mean, sure log wherever you want to. */
|
|
return lxc_write_nointr(console->log_fd, buf, bytes_read);
|
|
}
|
|
|
|
/* check how much space we have left */
|
|
space_left = console->log_size - st.st_size;
|
|
|
|
/* User doesn't want to rotate the log file and there's no more space
|
|
* left so simply truncate it.
|
|
*/
|
|
if (space_left <= 0 && console->log_rotate <= 0) {
|
|
ret = lxc_console_truncate_log_file(console);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (bytes_read <= console->log_size)
|
|
return lxc_write_nointr(console->log_fd, buf, bytes_read);
|
|
|
|
/* Write as much as we can into the buffer and loose the rest. */
|
|
return lxc_write_nointr(console->log_fd, buf, console->log_size);
|
|
}
|
|
|
|
/* There's enough space left. */
|
|
if (bytes_read <= space_left)
|
|
return lxc_write_nointr(console->log_fd, buf, bytes_read);
|
|
|
|
/* There's not enough space left but at least write as much as we can
|
|
* into the old log file.
|
|
*/
|
|
ret = lxc_write_nointr(console->log_fd, buf, space_left);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
/* Calculate how many bytes we still need to write. */
|
|
bytes_read -= space_left;
|
|
|
|
/* There be more to write but we aren't instructed to rotate the log
|
|
* file so simply return. There's no error on our side here.
|
|
*/
|
|
if (console->log_rotate > 0)
|
|
ret = lxc_console_rotate_log_file(console);
|
|
else
|
|
ret = lxc_console_truncate_log_file(console);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (console->log_size < bytes_read) {
|
|
/* Well, this is unfortunate because it means that there is more
|
|
* to write than the user has granted us space. There are
|
|
* multiple ways to handle this but let's use the simplest one:
|
|
* write as much as we can, tell the user that there was more
|
|
* stuff to write and move on.
|
|
* Note that this scenario shouldn't actually happen with the
|
|
* standard pty-based console that LXC allocates since it will
|
|
* be switched into raw mode. In raw mode only 1 byte at a time
|
|
* should be read and written.
|
|
*/
|
|
WARN("Size of console log file is smaller than the bytes to write");
|
|
ret = lxc_write_nointr(console->log_fd, buf, console->log_size);
|
|
if (ret < 0)
|
|
return -1;
|
|
bytes_read -= ret;
|
|
return bytes_read;
|
|
}
|
|
|
|
/* Yay, we made it. */
|
|
ret = lxc_write_nointr(console->log_fd, buf, bytes_read);
|
|
if (ret < 0)
|
|
return -1;
|
|
bytes_read -= ret;
|
|
return bytes_read;
|
|
}
|
|
|
|
int lxc_console_cb_con(int fd, uint32_t events, void *data,
|
|
struct lxc_epoll_descr *descr)
|
|
{
|
|
struct lxc_console *console = (struct lxc_console *)data;
|
|
char buf[LXC_CONSOLE_BUFFER_SIZE];
|
|
int r, w, w_log, w_rbuf;
|
|
|
|
w = r = lxc_read_nointr(fd, buf, sizeof(buf));
|
|
if (r <= 0) {
|
|
INFO("Console client on fd %d has exited", fd);
|
|
lxc_mainloop_del_handler(descr, fd);
|
|
|
|
if (fd == console->master) {
|
|
console->master = -EBADF;
|
|
} else if (fd == console->peer) {
|
|
if (console->tty_state) {
|
|
lxc_console_signal_fini(console->tty_state);
|
|
console->tty_state = NULL;
|
|
}
|
|
console->peer = -EBADF;
|
|
} else {
|
|
ERROR("Handler received unexpected file descriptor");
|
|
}
|
|
close(fd);
|
|
|
|
return LXC_MAINLOOP_CLOSE;
|
|
}
|
|
|
|
if (fd == console->peer)
|
|
w = lxc_write_nointr(console->master, buf, r);
|
|
|
|
w_rbuf = w_log = 0;
|
|
if (fd == console->master) {
|
|
/* write to peer first */
|
|
if (console->peer >= 0)
|
|
w = lxc_write_nointr(console->peer, buf, r);
|
|
|
|
/* write to console ringbuffer */
|
|
if (console->buffer_size > 0)
|
|
w_rbuf = lxc_ringbuf_write(&console->ringbuf, buf, r);
|
|
|
|
if (console->log_fd > 0)
|
|
w_log = lxc_console_write_log_file(console, buf, r);
|
|
|
|
}
|
|
|
|
if (w != r)
|
|
WARN("Console short write r:%d != w:%d", r, w);
|
|
|
|
if (w_rbuf < 0)
|
|
TRACE("%s - Failed to write %d bytes to console ringbuffer",
|
|
strerror(-w_rbuf), r);
|
|
|
|
if (w_log < 0)
|
|
TRACE("Failed to write %d bytes to console log", r);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lxc_console_mainloop_add_peer(struct lxc_console *console)
|
|
{
|
|
int ret;
|
|
|
|
if (console->peer >= 0) {
|
|
ret = lxc_mainloop_add_handler(console->descr, console->peer,
|
|
lxc_console_cb_con, console);
|
|
if (ret < 0) {
|
|
WARN("Failed to add console peer handler to mainloop");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (!console->tty_state || console->tty_state->sigfd < 0)
|
|
return 0;
|
|
|
|
ret = lxc_mainloop_add_handler(console->descr, console->tty_state->sigfd,
|
|
lxc_console_cb_signal_fd, console->tty_state);
|
|
if (ret < 0) {
|
|
WARN("Failed to add signal handler to mainloop");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lxc_console_mainloop_add(struct lxc_epoll_descr *descr,
|
|
struct lxc_console *console)
|
|
{
|
|
int ret;
|
|
|
|
if (console->master < 0) {
|
|
INFO("no console");
|
|
return 0;
|
|
}
|
|
|
|
ret = lxc_mainloop_add_handler(descr, console->master,
|
|
lxc_console_cb_con, console);
|
|
if (ret < 0) {
|
|
ERROR("Failed to add handler for %d to mainloop", console->master);
|
|
return -1;
|
|
}
|
|
|
|
/* We cache the descr so that we can add an fd to it when someone
|
|
* does attach to it in lxc_console_allocate().
|
|
*/
|
|
console->descr = descr;
|
|
ret = lxc_console_mainloop_add_peer(console);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lxc_setup_tios(int fd, struct termios *oldtios)
|
|
{
|
|
struct termios newtios;
|
|
|
|
if (!isatty(fd)) {
|
|
ERROR("'%d' is not a tty", fd);
|
|
return -1;
|
|
}
|
|
|
|
/* Get current termios */
|
|
if (tcgetattr(fd, oldtios)) {
|
|
SYSERROR("failed to get current terminal settings");
|
|
return -1;
|
|
}
|
|
|
|
/* ensure we don't end up in an endless loop:
|
|
* The kernel might fire SIGTTOU while an
|
|
* ioctl() in tcsetattr() is executed. When the ioctl()
|
|
* is resumed and retries, the signal handler interrupts it again.
|
|
*/
|
|
signal (SIGTTIN, SIG_IGN);
|
|
signal (SIGTTOU, SIG_IGN);
|
|
|
|
newtios = *oldtios;
|
|
|
|
/* We use the same settings that ssh does. */
|
|
newtios.c_iflag |= IGNPAR;
|
|
newtios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
|
|
#ifdef IUCLC
|
|
newtios.c_iflag &= ~IUCLC;
|
|
#endif
|
|
newtios.c_lflag &= ~(TOSTOP | ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
|
|
#ifdef IEXTEN
|
|
newtios.c_lflag &= ~IEXTEN;
|
|
#endif
|
|
newtios.c_oflag &= ~OPOST;
|
|
newtios.c_cc[VMIN] = 1;
|
|
newtios.c_cc[VTIME] = 0;
|
|
|
|
/* Set new attributes. */
|
|
if (tcsetattr(fd, TCSAFLUSH, &newtios)) {
|
|
ERROR("failed to set new terminal settings");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lxc_console_peer_proxy_free(struct lxc_console *console)
|
|
{
|
|
if (console->tty_state) {
|
|
lxc_console_signal_fini(console->tty_state);
|
|
console->tty_state = NULL;
|
|
}
|
|
close(console->peerpty.master);
|
|
close(console->peerpty.slave);
|
|
console->peerpty.master = -1;
|
|
console->peerpty.slave = -1;
|
|
console->peerpty.busy = -1;
|
|
console->peerpty.name[0] = '\0';
|
|
console->peer = -1;
|
|
}
|
|
|
|
static int lxc_console_peer_proxy_alloc(struct lxc_console *console, int sockfd)
|
|
{
|
|
struct termios oldtermio;
|
|
struct lxc_tty_state *ts;
|
|
int ret;
|
|
|
|
if (console->master < 0) {
|
|
ERROR("console not set up");
|
|
return -1;
|
|
}
|
|
if (console->peerpty.busy != -1 || console->peer != -1) {
|
|
NOTICE("console already in use");
|
|
return -1;
|
|
}
|
|
if (console->tty_state) {
|
|
ERROR("console already has tty_state");
|
|
return -1;
|
|
}
|
|
|
|
/* this is the proxy pty that will be given to the client, and that
|
|
* the real pty master will send to / recv from
|
|
*/
|
|
ret = openpty(&console->peerpty.master, &console->peerpty.slave,
|
|
console->peerpty.name, NULL, NULL);
|
|
if (ret) {
|
|
SYSERROR("failed to create proxy pty");
|
|
return -1;
|
|
}
|
|
|
|
if (lxc_setup_tios(console->peerpty.slave, &oldtermio) < 0)
|
|
goto err1;
|
|
|
|
ts = lxc_console_signal_init(console->peerpty.master, console->master);
|
|
if (!ts)
|
|
goto err1;
|
|
|
|
console->tty_state = ts;
|
|
console->peer = console->peerpty.slave;
|
|
console->peerpty.busy = sockfd;
|
|
ret = lxc_console_mainloop_add_peer(console);
|
|
if (ret < 0)
|
|
goto err1;
|
|
|
|
DEBUG("%d %s peermaster:%d sockfd:%d", lxc_raw_getpid(), __FUNCTION__, console->peerpty.master, sockfd);
|
|
return 0;
|
|
|
|
err1:
|
|
lxc_console_peer_proxy_free(console);
|
|
return -1;
|
|
}
|
|
|
|
int lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttyreq)
|
|
{
|
|
int masterfd = -1, ttynum;
|
|
struct lxc_tty_info *tty_info = &conf->tty_info;
|
|
struct lxc_console *console = &conf->console;
|
|
|
|
if (*ttyreq == 0) {
|
|
if (lxc_console_peer_proxy_alloc(console, sockfd) < 0)
|
|
goto out;
|
|
masterfd = console->peerpty.master;
|
|
goto out;
|
|
}
|
|
|
|
if (*ttyreq > 0) {
|
|
if (*ttyreq > tty_info->nbtty)
|
|
goto out;
|
|
|
|
if (tty_info->pty_info[*ttyreq - 1].busy)
|
|
goto out;
|
|
|
|
/* the requested tty is available */
|
|
ttynum = *ttyreq;
|
|
goto out_tty;
|
|
}
|
|
|
|
/* search for next available tty, fixup index tty1 => [0] */
|
|
for (ttynum = 1; ttynum <= tty_info->nbtty && tty_info->pty_info[ttynum - 1].busy; ttynum++)
|
|
;
|
|
|
|
/* we didn't find any available slot for tty */
|
|
if (ttynum > tty_info->nbtty)
|
|
goto out;
|
|
|
|
*ttyreq = ttynum;
|
|
|
|
out_tty:
|
|
tty_info->pty_info[ttynum - 1].busy = sockfd;
|
|
masterfd = tty_info->pty_info[ttynum - 1].master;
|
|
out:
|
|
return masterfd;
|
|
}
|
|
|
|
void lxc_console_free(struct lxc_conf *conf, int fd)
|
|
{
|
|
int i;
|
|
struct lxc_tty_info *tty_info = &conf->tty_info;
|
|
struct lxc_console *console = &conf->console;
|
|
|
|
for (i = 0; i < tty_info->nbtty; i++) {
|
|
if (tty_info->pty_info[i].busy == fd)
|
|
tty_info->pty_info[i].busy = 0;
|
|
}
|
|
|
|
if (console->peerpty.busy == fd) {
|
|
lxc_mainloop_del_handler(console->descr, console->peerpty.slave);
|
|
lxc_console_peer_proxy_free(console);
|
|
}
|
|
}
|
|
|
|
static int lxc_console_peer_default(struct lxc_console *console)
|
|
{
|
|
struct lxc_tty_state *ts;
|
|
const char *path = console->path;
|
|
int fd;
|
|
int ret = 0;
|
|
|
|
/* If no console was given, try current controlling terminal, there
|
|
* won't be one if we were started as a daemon (-d).
|
|
*/
|
|
if (!path && !access("/dev/tty", F_OK)) {
|
|
fd = open("/dev/tty", O_RDWR);
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
path = "/dev/tty";
|
|
}
|
|
}
|
|
|
|
if (!path) {
|
|
errno = ENOTTY;
|
|
DEBUG("process does not have a controlling terminal");
|
|
goto out;
|
|
}
|
|
|
|
console->peer = lxc_unpriv(open(path, O_RDWR | O_CLOEXEC));
|
|
if (console->peer < 0) {
|
|
ERROR("Failed to open \"%s\": %s", path, strerror(errno));
|
|
return -ENOTTY;
|
|
}
|
|
DEBUG("using \"%s\" as peer tty device", path);
|
|
|
|
if (!isatty(console->peer)) {
|
|
ERROR("file descriptor for file \"%s\" does not refer to a tty device", path);
|
|
goto on_error1;
|
|
}
|
|
|
|
ts = lxc_console_signal_init(console->peer, console->master);
|
|
console->tty_state = ts;
|
|
if (!ts) {
|
|
WARN("Failed to install signal handler");
|
|
goto on_error1;
|
|
}
|
|
|
|
lxc_console_winsz(console->peer, console->master);
|
|
|
|
console->tios = malloc(sizeof(*console->tios));
|
|
if (!console->tios) {
|
|
SYSERROR("failed to allocate memory");
|
|
goto on_error1;
|
|
}
|
|
|
|
if (lxc_setup_tios(console->peer, console->tios) < 0)
|
|
goto on_error2;
|
|
else
|
|
goto out;
|
|
|
|
on_error2:
|
|
free(console->tios);
|
|
console->tios = NULL;
|
|
|
|
on_error1:
|
|
close(console->peer);
|
|
console->peer = -1;
|
|
ret = -ENOTTY;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int lxc_console_write_ringbuffer(struct lxc_console *console)
|
|
{
|
|
char *r_addr;
|
|
ssize_t ret;
|
|
uint64_t used;
|
|
struct lxc_ringbuf *buf = &console->ringbuf;
|
|
|
|
if (!console->buffer_log_file)
|
|
return 0;
|
|
|
|
used = lxc_ringbuf_used(buf);
|
|
if (used == 0)
|
|
return 0;
|
|
|
|
r_addr = lxc_ringbuf_get_read_addr(buf);
|
|
ret = lxc_write_nointr(console->buffer_log_file_fd, r_addr, used);
|
|
if (ret < 0)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void lxc_console_delete(struct lxc_console *console)
|
|
{
|
|
int ret;
|
|
|
|
ret = lxc_console_write_ringbuffer(console);
|
|
if (ret < 0)
|
|
WARN("Failed to write console log to disk");
|
|
|
|
if (console->tios && console->peer >= 0) {
|
|
ret = tcsetattr(console->peer, TCSAFLUSH, console->tios);
|
|
if (ret < 0)
|
|
WARN("%s - Failed to set old terminal settings", strerror(errno));
|
|
}
|
|
free(console->tios);
|
|
console->tios = NULL;
|
|
|
|
if (console->peer >= 0)
|
|
close(console->peer);
|
|
console->peer = -1;
|
|
|
|
if (console->master >= 0)
|
|
close(console->master);
|
|
console->master = -1;
|
|
|
|
if (console->slave >= 0)
|
|
close(console->slave);
|
|
console->slave = -1;
|
|
|
|
if (console->log_fd >= 0)
|
|
close(console->log_fd);
|
|
console->log_fd = -1;
|
|
|
|
if (console->buffer_log_file_fd >= 0)
|
|
close(console->buffer_log_file_fd);
|
|
console->buffer_log_file_fd = -1;
|
|
}
|
|
|
|
/* This is the console ringbuffer log file. Please note that the console
|
|
* ringbuffer log file is (implementation wise not content wise) independent of
|
|
* the console log file.
|
|
*/
|
|
static int lxc_console_create_ringbuf_log_file(struct lxc_console *console)
|
|
{
|
|
if (!console->buffer_log_file)
|
|
return 0;
|
|
|
|
console->buffer_log_file_fd = lxc_unpriv(open(console->buffer_log_file,
|
|
O_CLOEXEC | O_RDWR | O_CREAT | O_TRUNC, 0600));
|
|
if (console->buffer_log_file_fd < 0) {
|
|
SYSERROR("Failed to open console ringbuffer log file \"%s\"",
|
|
console->buffer_log_file);
|
|
return -EIO;
|
|
}
|
|
|
|
DEBUG("Using \"%s\" as console ringbuffer log file", console->buffer_log_file);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Note that this function needs to run before the mainloop starts. Since we
|
|
* register a handler for the console's masterfd when we create the mainloop
|
|
* the console handler needs to see an allocated ringbuffer.
|
|
*/
|
|
static int lxc_console_create_ringbuf(struct lxc_console *console)
|
|
{
|
|
int ret;
|
|
struct lxc_ringbuf *buf = &console->ringbuf;
|
|
uint64_t size = console->buffer_size;
|
|
|
|
/* no ringbuffer previously allocated and no ringbuffer requested */
|
|
if (!buf->addr && size <= 0)
|
|
return 0;
|
|
|
|
/* ringbuffer allocated but no new ringbuffer requested */
|
|
if (buf->addr && size <= 0) {
|
|
lxc_ringbuf_release(buf);
|
|
buf->addr = NULL;
|
|
buf->r_off = 0;
|
|
buf->w_off = 0;
|
|
buf->size = 0;
|
|
TRACE("Deallocated console ringbuffer");
|
|
return 0;
|
|
}
|
|
|
|
if (size <= 0)
|
|
return 0;
|
|
|
|
/* check wether the requested size for the ringbuffer has changed */
|
|
if (buf->addr && buf->size != size) {
|
|
TRACE("Console ringbuffer size changed from %" PRIu64
|
|
" to %" PRIu64 " bytes. Deallocating console ringbuffer",
|
|
buf->size, size);
|
|
lxc_ringbuf_release(buf);
|
|
}
|
|
|
|
ret = lxc_ringbuf_create(buf, size);
|
|
if (ret < 0) {
|
|
ERROR("Failed to setup %" PRIu64 " byte console ringbuffer", size);
|
|
return -1;
|
|
}
|
|
|
|
TRACE("Allocated %" PRIu64 " byte console ringbuffer", size);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* This is the console log file. Please note that the console log file is
|
|
* (implementation wise not content wise) independent of the console ringbuffer.
|
|
*/
|
|
int lxc_console_create_log_file(struct lxc_console *console)
|
|
{
|
|
if (!console->log_path)
|
|
return 0;
|
|
|
|
console->log_fd = lxc_unpriv(open(console->log_path, O_CLOEXEC | O_RDWR | O_CREAT | O_APPEND, 0600));
|
|
if (console->log_fd < 0) {
|
|
SYSERROR("Failed to open console log file \"%s\"", console->log_path);
|
|
return -1;
|
|
}
|
|
|
|
DEBUG("Using \"%s\" as console log file", console->log_path);
|
|
return 0;
|
|
}
|
|
|
|
int lxc_pty_create(struct lxc_console *console)
|
|
{
|
|
int ret, saved_errno;
|
|
|
|
ret = openpty(&console->master, &console->slave, console->name, NULL,
|
|
NULL);
|
|
saved_errno = errno;
|
|
if (ret < 0) {
|
|
ERROR("%s - Failed to allocate a pty", strerror(saved_errno));
|
|
return -1;
|
|
}
|
|
|
|
ret = fcntl(console->master, F_SETFD, FD_CLOEXEC);
|
|
if (ret < 0) {
|
|
SYSERROR("Failed to set FD_CLOEXEC flag on console master");
|
|
goto err;
|
|
}
|
|
|
|
ret = fcntl(console->slave, F_SETFD, FD_CLOEXEC);
|
|
if (ret < 0) {
|
|
SYSERROR("Failed to set FD_CLOEXEC flag on console slave");
|
|
goto err;
|
|
}
|
|
|
|
ret = lxc_console_peer_default(console);
|
|
if (ret < 0) {
|
|
ERROR("Failed to allocate a peer pty device");
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
lxc_console_delete(console);
|
|
return -ENODEV;
|
|
}
|
|
|
|
int lxc_console_create(struct lxc_conf *conf)
|
|
{
|
|
int ret;
|
|
struct lxc_console *console = &conf->console;
|
|
|
|
if (console->path && !strcmp(console->path, "none")) {
|
|
INFO("No console was requested");
|
|
return 0;
|
|
}
|
|
|
|
ret = lxc_pty_create(console);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
/* create console log file */
|
|
ret = lxc_console_create_log_file(console);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
/* create console ringbuffer */
|
|
ret = lxc_console_create_ringbuf(console);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
/* create console ringbuffer log file */
|
|
ret = lxc_console_create_ringbuf_log_file(console);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
lxc_console_delete(console);
|
|
return -ENODEV;
|
|
}
|
|
|
|
int lxc_console_set_stdfds(int fd)
|
|
{
|
|
if (fd < 0)
|
|
return 0;
|
|
|
|
if (isatty(STDIN_FILENO))
|
|
if (dup2(fd, STDIN_FILENO) < 0) {
|
|
SYSERROR("failed to duplicate stdin.");
|
|
return -1;
|
|
}
|
|
|
|
if (isatty(STDOUT_FILENO))
|
|
if (dup2(fd, STDOUT_FILENO) < 0) {
|
|
SYSERROR("failed to duplicate stdout.");
|
|
return -1;
|
|
}
|
|
|
|
if (isatty(STDERR_FILENO))
|
|
if (dup2(fd, STDERR_FILENO) < 0) {
|
|
SYSERROR("failed to duplicate stderr.");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lxc_console_cb_tty_stdin(int fd, uint32_t events, void *cbdata,
|
|
struct lxc_epoll_descr *descr)
|
|
{
|
|
struct lxc_tty_state *ts = cbdata;
|
|
char c;
|
|
|
|
if (fd != ts->stdinfd)
|
|
return LXC_MAINLOOP_CLOSE;
|
|
|
|
if (lxc_read_nointr(ts->stdinfd, &c, 1) <= 0)
|
|
return LXC_MAINLOOP_CLOSE;
|
|
|
|
if (ts->escape >= 1) {
|
|
/* we want to exit the console with Ctrl+a q */
|
|
if (c == ts->escape && !ts->saw_escape) {
|
|
ts->saw_escape = 1;
|
|
return 0;
|
|
}
|
|
|
|
if (c == 'q' && ts->saw_escape)
|
|
return LXC_MAINLOOP_CLOSE;
|
|
|
|
ts->saw_escape = 0;
|
|
}
|
|
|
|
if (lxc_write_nointr(ts->masterfd, &c, 1) <= 0)
|
|
return LXC_MAINLOOP_CLOSE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lxc_console_cb_tty_master(int fd, uint32_t events, void *cbdata,
|
|
struct lxc_epoll_descr *descr)
|
|
{
|
|
struct lxc_tty_state *ts = cbdata;
|
|
char buf[LXC_CONSOLE_BUFFER_SIZE];
|
|
int r, w;
|
|
|
|
if (fd != ts->masterfd)
|
|
return LXC_MAINLOOP_CLOSE;
|
|
|
|
r = lxc_read_nointr(fd, buf, sizeof(buf));
|
|
if (r <= 0)
|
|
return LXC_MAINLOOP_CLOSE;
|
|
|
|
w = lxc_write_nointr(ts->stdoutfd, buf, r);
|
|
if (w <= 0) {
|
|
return LXC_MAINLOOP_CLOSE;
|
|
} else if (w != r) {
|
|
SYSERROR("Failed to write");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lxc_console_getfd(struct lxc_container *c, int *ttynum, int *masterfd)
|
|
{
|
|
return lxc_cmd_console(c->name, ttynum, masterfd, c->config_path);
|
|
}
|
|
|
|
int lxc_console(struct lxc_container *c, int ttynum,
|
|
int stdinfd, int stdoutfd, int stderrfd,
|
|
int escape)
|
|
{
|
|
int ret, ttyfd, masterfd;
|
|
struct lxc_epoll_descr descr;
|
|
struct termios oldtios;
|
|
struct lxc_tty_state *ts;
|
|
int istty = 0;
|
|
|
|
ttyfd = lxc_cmd_console(c->name, &ttynum, &masterfd, c->config_path);
|
|
if (ttyfd < 0)
|
|
return -1;
|
|
|
|
ret = setsid();
|
|
if (ret < 0)
|
|
TRACE("Process is already group leader");
|
|
|
|
ts = lxc_console_signal_init(stdinfd, masterfd);
|
|
if (!ts) {
|
|
ret = -1;
|
|
goto close_fds;
|
|
}
|
|
ts->escape = escape;
|
|
ts->winch_proxy = c->name;
|
|
ts->winch_proxy_lxcpath = c->config_path;
|
|
ts->stdoutfd = stdoutfd;
|
|
|
|
istty = isatty(stdinfd);
|
|
if (istty) {
|
|
lxc_console_winsz(stdinfd, masterfd);
|
|
lxc_cmd_console_winch(ts->winch_proxy, ts->winch_proxy_lxcpath);
|
|
} else {
|
|
INFO("File descriptor %d does not refer to a tty device", stdinfd);
|
|
}
|
|
|
|
ret = lxc_mainloop_open(&descr);
|
|
if (ret) {
|
|
ERROR("Failed to create mainloop");
|
|
goto sigwinch_fini;
|
|
}
|
|
|
|
if (ts->sigfd != -1) {
|
|
ret = lxc_mainloop_add_handler(&descr, ts->sigfd,
|
|
lxc_console_cb_signal_fd, ts);
|
|
if (ret < 0) {
|
|
ERROR("Failed to add signal handler to mainloop");
|
|
goto close_mainloop;
|
|
}
|
|
}
|
|
|
|
ret = lxc_mainloop_add_handler(&descr, ts->stdinfd,
|
|
lxc_console_cb_tty_stdin, ts);
|
|
if (ret < 0) {
|
|
ERROR("Failed to add stdin handler");
|
|
goto close_mainloop;
|
|
}
|
|
|
|
ret = lxc_mainloop_add_handler(&descr, ts->masterfd,
|
|
lxc_console_cb_tty_master, ts);
|
|
if (ret < 0) {
|
|
ERROR("Failed to add master handler");
|
|
goto close_mainloop;
|
|
}
|
|
|
|
if (ts->escape >= 1) {
|
|
fprintf(stderr,
|
|
"\n"
|
|
"Connected to tty %1$d\n"
|
|
"Type <Ctrl+%2$c q> to exit the console, "
|
|
"<Ctrl+%2$c Ctrl+%2$c> to enter Ctrl+%2$c itself\n",
|
|
ttynum, 'a' + escape - 1);
|
|
}
|
|
|
|
if (istty) {
|
|
ret = lxc_setup_tios(stdinfd, &oldtios);
|
|
if (ret < 0)
|
|
goto close_mainloop;
|
|
}
|
|
|
|
ret = lxc_mainloop(&descr, -1);
|
|
if (ret < 0) {
|
|
ERROR("The mainloop returned an error");
|
|
goto restore_tios;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
restore_tios:
|
|
if (istty) {
|
|
istty = tcsetattr(stdinfd, TCSAFLUSH, &oldtios);
|
|
if (istty < 0)
|
|
WARN("%s - Failed to restore terminal properties",
|
|
strerror(errno));
|
|
}
|
|
|
|
close_mainloop:
|
|
lxc_mainloop_close(&descr);
|
|
|
|
sigwinch_fini:
|
|
lxc_console_signal_fini(ts);
|
|
|
|
close_fds:
|
|
close(masterfd);
|
|
close(ttyfd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int lxc_make_controlling_pty(int fd)
|
|
{
|
|
int ret;
|
|
|
|
setsid();
|
|
|
|
ret = ioctl(fd, TIOCSCTTY, (char *)NULL);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lxc_login_pty(int fd)
|
|
{
|
|
int ret;
|
|
|
|
ret = lxc_make_controlling_pty(fd);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
ret = lxc_console_set_stdfds(fd);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
if (fd > STDERR_FILENO)
|
|
close(fd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void lxc_pty_info_init(struct lxc_pty_info *pty)
|
|
{
|
|
pty->name[0] = '\0';
|
|
pty->master = -EBADF;
|
|
pty->slave = -EBADF;
|
|
pty->busy = -1;
|
|
}
|
|
|
|
void lxc_pty_init(struct lxc_console *pty)
|
|
{
|
|
memset(pty, 0, sizeof(*pty));
|
|
pty->slave = -EBADF;
|
|
pty->master = -EBADF;
|
|
pty->peer = -EBADF;
|
|
pty->log_fd = -EBADF;
|
|
pty->buffer_log_file_fd = -EBADF;
|
|
lxc_pty_info_init(&pty->peerpty);
|
|
}
|
|
|
|
void lxc_pty_conf_free(struct lxc_console *console)
|
|
{
|
|
free(console->buffer_log_file);
|
|
free(console->log_path);
|
|
free(console->path);
|
|
if (console->buffer_size > 0 && console->ringbuf.addr)
|
|
lxc_ringbuf_release(&console->ringbuf);
|
|
}
|
|
|
|
int lxc_pty_map_ids(struct lxc_conf *c, struct lxc_console *pty)
|
|
{
|
|
int ret;
|
|
|
|
if (lxc_list_empty(&c->id_map))
|
|
return 0;
|
|
|
|
ret = strcmp(pty->name, "");
|
|
if (ret == 0)
|
|
return 0;
|
|
|
|
ret = chown_mapped_root(pty->name, c);
|
|
if (ret < 0) {
|
|
ERROR("Failed to chown \"%s\"", pty->name);
|
|
return -1;
|
|
}
|
|
|
|
TRACE("Chowned \"%s\"", pty->name);
|
|
|
|
return 0;
|
|
}
|