[lxc-devel] [PATCH v2] console API improvements

Serge Hallyn serge.hallyn at ubuntu.com
Wed Jun 12 16:15:10 UTC 2013


Quoting Dwight Engen (dwight.engen at oracle.com):
> Add a higher level console API that opens a tty/console and runs the
> mainloop as well. Rename existing API to console_getfd(). Use these in
> the python binding.
> 
> Allow attaching a console peer after container bootup, including if the
> container was launched with -d. This is made possible by allocation of a
> "proxy" pty as the peer when the console is attached to.
> 
> Improve handling of SIGWINCH, the pty size will be correctly set at the
> beginning of a session and future changes when using the lxc_console() API
> will be propagated to it as well.
> 
> Refactor some common code between lxc_console.c and console.c. The variable
> wait4q (renamed to saw_escape) was static, making the mainloop callback not
> safe across threads. This wasn't a problem when the callback was in the
> non-threaded lxc-console, but now that it is internal to console.c, we have
> to take care of it. This is now contained in a per-tty state structure.
> 
> Don't attempt to open /dev/null as the console peer since /dev/null cannot
> be added to the mainloop (epoll_ctl() fails with EPERM). This isn't needed
> to get the console setup (and the log to work) since the case of not having
> a peer at console init time has to be handled to allow for attaching to it
> later.
> 
> Move signalfd libc wrapper/replacement to utils.h.
> 
> Signed-off-by: Dwight Engen <dwight.engen at oracle.com>
> ---
>  doc/lxc-console.sgml.in                  |  11 +-
>  src/lxc/commands.c                       |  64 +--
>  src/lxc/commands.h                       |   2 +
>  src/lxc/conf.c                           |  11 +-
>  src/lxc/conf.h                           |   5 +
>  src/lxc/console.c                        | 742 +++++++++++++++++++++++++------
>  src/lxc/console.h                        |  18 +-
>  src/lxc/lxc.h                            |   9 -
>  src/lxc/lxc_console.c                    | 189 +-------
>  src/lxc/lxccontainer.c                   |  12 +-
>  src/lxc/lxccontainer.h                   |  21 +-
>  src/lxc/start.c                          |  83 +---
>  src/lxc/utils.h                          |  65 +++
>  src/python-lxc/examples/pyconsole-vte.py |  58 +++
>  src/python-lxc/examples/pyconsole.py     |  33 ++
>  src/python-lxc/lxc.c                     |  46 ++
>  src/python-lxc/lxc/__init__.py           |  18 +-
>  src/tests/console.c                      |   6 +-
>  18 files changed, 962 insertions(+), 431 deletions(-)
>  create mode 100755 src/python-lxc/examples/pyconsole-vte.py
>  create mode 100755 src/python-lxc/examples/pyconsole.py
> 
> diff --git a/doc/lxc-console.sgml.in b/doc/lxc-console.sgml.in
> index 9299778..f4737d1 100644
> --- a/doc/lxc-console.sgml.in
> +++ b/doc/lxc-console.sgml.in
> @@ -78,6 +78,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>      </para>
>  
>      <para>
> +      A <replaceable>ttynum</replaceable> of 0 may be given to attach
> +      to the container's /dev/console instead of its
> +      dev/tty<<replaceable>ttynum</replaceable>>.
> +    </para>
> +
> +    <para>
>        A keyboard escape sequence may be used to disconnect from the tty
>        and quit lxc-console. The default escape sequence is <Ctrl+a q>.
>      </para>
> @@ -107,8 +113,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>  	</term>
>  	<listitem>
>  	  <para>
> -	    Specify the tty number to connect, if not specified a tty
> -	    number will be automatically choosen by the container.
> +	    Specify the tty number to connect to or 0 for the console. If not
> +	    specified the next available tty number will be automatically
> +	    choosen by the container.
>  	  </para>
>  	</listitem>
>        </varlistentry>
> diff --git a/src/lxc/commands.c b/src/lxc/commands.c
> index b4afc07..b23eb98 100644
> --- a/src/lxc/commands.c
> +++ b/src/lxc/commands.c
> @@ -40,6 +40,7 @@
>  #include <lxc/utils.h>
>  
>  #include "commands.h"
> +#include "console.h"
>  #include "confile.h"
>  #include "mainloop.h"
>  #include "af_unix.h"
> @@ -546,6 +547,37 @@ static int lxc_cmd_stop_callback(int fd, struct lxc_cmd_req *req,
>  }
>  
>  /*
> + * lxc_cmd_console_winch: To process as if a SIGWINCH were received
> + *
> + * @name      : name of container to connect to
> + * @lxcpath   : the lxcpath in which the container is running
> + *
> + * Returns 0 on success, < 0 on failure
> + */
> +int lxc_cmd_console_winch(const char *name, const char *lxcpath)
> +{
> +	int ret, stopped = 0;
> +	struct lxc_cmd_rr cmd = {
> +		.req = { .cmd = LXC_CMD_CONSOLE_WINCH },
> +	};
> +
> +	ret = lxc_cmd(name, &cmd, &stopped, lxcpath);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int lxc_cmd_console_winch_callback(int fd, struct lxc_cmd_req *req,
> +					  struct lxc_handler *handler)
> +{
> +	struct lxc_cmd_rsp rsp = { .data = 0 };
> +
> +	lxc_console_sigwinch(SIGWINCH);
> +	return lxc_cmd_rsp_send(fd, &rsp);
> +}
> +
> +/*
>   * lxc_cmd_console: Open an fd to a tty in the container
>   *
>   * @name           : name of container to connect to
> @@ -599,39 +631,21 @@ static int lxc_cmd_console_callback(int fd, struct lxc_cmd_req *req,
>  				    struct lxc_handler *handler)
>  {
>  	int ttynum = PTR_TO_INT(req->data);
> -	struct lxc_tty_info *tty_info = &handler->conf->tty_info;
> +	int masterfd;
>  	struct lxc_cmd_rsp rsp;
>  
> -	if (ttynum > 0) {
> -		if (ttynum > tty_info->nbtty)
> -			goto out_close;
> -
> -		if (tty_info->pty_info[ttynum - 1].busy)
> -			goto out_close;
> -
> -		/* the requested tty is available */
> -		goto out_send;
> -	}
> -
> -	/* 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)
> +	masterfd = lxc_console_allocate(handler->conf, fd, &ttynum);
> +	if (masterfd < 0)
>  		goto out_close;
>  
> -out_send:
>  	memset(&rsp, 0, sizeof(rsp));
>  	rsp.data = INT_TO_PTR(ttynum);
> -	if (lxc_af_unix_send_fd(fd, tty_info->pty_info[ttynum - 1].master,
> -				&rsp, sizeof(rsp)) < 0) {
> +	if (lxc_af_unix_send_fd(fd, masterfd, &rsp, sizeof(rsp)) < 0) {
>  		ERROR("failed to send tty to client");
> +		lxc_console_free(handler->conf, fd);
>  		goto out_close;
>  	}
>  
> -	tty_info->pty_info[ttynum - 1].busy = fd;
>  	return 0;
>  
>  out_close:
> @@ -650,6 +664,7 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req,
>  
>  	callback cb[LXC_CMD_MAX] = {
>  		[LXC_CMD_CONSOLE]         = lxc_cmd_console_callback,
> +		[LXC_CMD_CONSOLE_WINCH]   = lxc_cmd_console_winch_callback,
>  		[LXC_CMD_STOP]            = lxc_cmd_stop_callback,
>  		[LXC_CMD_GET_STATE]       = lxc_cmd_get_state_callback,
>  		[LXC_CMD_GET_INIT_PID]    = lxc_cmd_get_init_pid_callback,
> @@ -668,8 +683,7 @@ static int lxc_cmd_process(int fd, struct lxc_cmd_req *req,
>  static void lxc_cmd_fd_cleanup(int fd, struct lxc_handler *handler,
>  			       struct lxc_epoll_descr *descr)
>  {
> -	extern void lxc_console_remove_fd(int, struct lxc_tty_info *);
> -	lxc_console_remove_fd(fd, &handler->conf->tty_info);
> +	lxc_console_free(handler->conf, fd);
>  	lxc_mainloop_del_handler(descr, fd);
>  	close(fd);
>  }
> diff --git a/src/lxc/commands.h b/src/lxc/commands.h
> index c3738cd..46806cb 100644
> --- a/src/lxc/commands.h
> +++ b/src/lxc/commands.h
> @@ -34,6 +34,7 @@
>  
>  typedef enum {
>  	LXC_CMD_CONSOLE,
> +	LXC_CMD_CONSOLE_WINCH,
>  	LXC_CMD_STOP,
>  	LXC_CMD_GET_STATE,
>  	LXC_CMD_GET_INIT_PID,
> @@ -65,6 +66,7 @@ struct lxc_cmd_console_rsp_data {
>  	int ttynum;
>  };
>  
> +extern int lxc_cmd_console_winch(const char *name, const char *lxcpath);
>  extern int lxc_cmd_console(const char *name, int *ttynum, int *fd,
>  			   const char *lxcpath);
>  extern char *lxc_cmd_get_cgroup_path(const char *name, const char *lxcpath);
> diff --git a/src/lxc/conf.c b/src/lxc/conf.c
> index 5700eff..90aea60 100644
> --- a/src/lxc/conf.c
> +++ b/src/lxc/conf.c
> @@ -1293,8 +1293,8 @@ static int setup_dev_console(const struct lxc_rootfs *rootfs,
>  		return 0;
>  	}
>  
> -	if (console->peer == -1) {
> -		INFO("no console output required");
> +	if (console->master < 0) {
> +		INFO("no console");
>  		return 0;
>  	}
>  
> @@ -1359,8 +1359,8 @@ static int setup_ttydir_console(const struct lxc_rootfs *rootfs,
>  	if (ret >= 0)
>  		close(ret);
>  
> -	if (console->peer == -1) {
> -		INFO("no console output required");
> +	if (console->master < 0) {
> +		INFO("no console");
>  		return 0;
>  	}
>  
> @@ -2127,6 +2127,9 @@ struct lxc_conf *lxc_conf_init(void)
>  	new->console.log_fd = -1;
>  	new->console.path = NULL;
>  	new->console.peer = -1;
> +	new->console.peerpty.busy = -1;
> +	new->console.peerpty.master = -1;
> +	new->console.peerpty.slave = -1;
>  	new->console.master = -1;
>  	new->console.slave = -1;
>  	new->console.name[0] = '\0';
> diff --git a/src/lxc/conf.h b/src/lxc/conf.h
> index a2efa7c..2fd3ab1 100644
> --- a/src/lxc/conf.h
> +++ b/src/lxc/conf.h
> @@ -188,6 +188,8 @@ struct lxc_tty_info {
>  	struct lxc_pty_info *pty_info;
>  };
>  
> +struct lxc_tty_state;
> +
>  /*
>   * Defines the structure to store the console information
>   * @peer   : the file descriptor put/get console traffic
> @@ -197,11 +199,14 @@ struct lxc_console {
>  	int slave;
>  	int master;
>  	int peer;
> +	struct lxc_pty_info peerpty;
> +	struct lxc_epoll_descr *descr;
>  	char *path;
>  	char *log_path;
>  	int log_fd;
>  	char name[MAXPATHLEN];
>  	struct termios *tios;
> +	struct lxc_tty_state *tty_state;
>  };
>  
>  /*
> diff --git a/src/lxc/console.c b/src/lxc/console.c
> index 93c16b5..3720c5b 100644
> --- a/src/lxc/console.c
> +++ b/src/lxc/console.c
> @@ -21,6 +21,8 @@
>   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
>   */
>  
> +#include <assert.h>
> +#include <signal.h>
>  #include <stdio.h>
>  #include <stdlib.h>
>  #include <unistd.h>
> @@ -29,6 +31,7 @@
>  #include <sys/types.h>
>  #include <termios.h>
>  
> +#include "lxccontainer.h"
>  #include "log.h"
>  #include "conf.h"
>  #include "config.h"
> @@ -37,6 +40,8 @@
>  #include "commands.h"
>  #include "mainloop.h"
>  #include "af_unix.h"
> +#include "lxclock.h"
> +#include "utils.h"
>  
>  #if HAVE_PTY_H
>  #include <pty.h>
> @@ -46,156 +51,490 @@
>  
>  lxc_log_define(lxc_console, lxc);
>  
> -extern void lxc_console_remove_fd(int fd, struct lxc_tty_info *tty_info)
> -{
> -	int i;
> +static struct lxc_list lxc_ttys;
>  
> -	for (i = 0; i < tty_info->nbtty; i++) {
> -
> -		if (tty_info->pty_info[i].busy != fd)
> -			continue;
> +typedef void (*sighandler_t)(int);
> +struct lxc_tty_state
> +{
> +	struct lxc_list node;
> +	int stdinfd;
> +	int stdoutfd;
> +	int masterfd;
> +	int escape;
> +	int saw_escape;
> +	const char *winch_proxy;
> +	const char *winch_proxy_lxcpath;
> +	int sigfd;
> +	sigset_t oldmask;
> +};
> +
> +__attribute__((constructor))
> +void lxc_console_init(void)
> +{
> +	lxc_list_init(&lxc_ttys);
> +}
>  
> -		tty_info->pty_info[i].busy = 0;
> +/* lxc_console_winsz: propagte winsz from one terminal to another
> + *
> + * @srcfd : terminal to get size from (typically a slave pty)
> + * @dstfd : terminal to set size on (typically a master pty)
> + */
> +static void lxc_console_winsz(int srcfd, int dstfd)
> +{
> +	struct winsize wsz;
> +	if (isatty(srcfd) && ioctl(srcfd, TIOCGWINSZ, &wsz) == 0) {
> +		DEBUG("set winsz dstfd:%d cols:%d rows:%d", dstfd,
> +		      wsz.ws_col, wsz.ws_row);
> +		ioctl(dstfd, TIOCSWINSZ, &wsz);
>  	}
> +}
>  
> -	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);
> +	}
>  }
>  
> -static int get_default_console(char **console)
> +void lxc_console_sigwinch(int sig)
>  {
> -	int fd;
> +	if (process_lock() == 0) {
> +		struct lxc_list *it;
> +		struct lxc_tty_state *ts;
>  
> -	if (!access("/dev/tty", F_OK)) {
> -		fd = open("/dev/tty", O_RDWR);
> -		if (fd >= 0) {
> -			close(fd);
> -			*console = strdup("/dev/tty");
> -			goto out;
> +		lxc_list_for_each(it, &lxc_ttys) {
> +			ts = it->elem;
> +			lxc_console_winch(ts);
>  		}
> +		process_unlock();
>  	}
> +}
>  
> -	if (!access("/dev/null", F_OK)) {
> -		*console = strdup("/dev/null");
> -		goto out;
> +static int lxc_console_cb_sigwinch_fd(int fd, void *cbdata,
> +				      struct lxc_epoll_descr *descr)
> +{
> +	struct signalfd_siginfo siginfo;
> +	struct lxc_tty_state *ts = cbdata;
> +
> +	if (read(fd, &siginfo, sizeof(siginfo)) < 0) {
> +		ERROR("failed to read signal info");
> +		return -1;
>  	}
>  
> -	ERROR("No suitable default console");
> +	lxc_console_winch(ts);
> +	return 0;
> +}
> +
> +/*
> + * lxc_console_sigwinch_init: install SIGWINCH handler
> + *
> + * @srcfd  : src for winsz in SIGWINCH handler
> + * @dstfd  : dst for winsz in SIGWINCH handler
> + *
> + * Returns lxc_tty_state structure on success or NULL on failure. The sigfd
> + * member of the returned lxc_tty_state can be select()/poll()ed/epoll()ed
> + * on (ie added to a mainloop) for SIGWINCH.
> + *
> + * Must be called with process_lock held to protect the lxc_ttys list, or
> + * from a non-threaded context.

Would it be worth putting lxc_ttys list in lxc_container struct, passing
in the lxc_container and doing a mem_lock?

It seems like the cleaner thing to do (if it suffices), but we can do it
as a follow-on later if we agree.  (No need to rework this patch, I'm
saying)




More information about the lxc-devel mailing list