Re: [PATCH] nsenter: add support for pty

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Some interactive applications requires a tty device inside the namespace to work correctly.
That's why docker, lxc, systemd-nspawn and libvirt-lxc implements pseudo ttys on their own.

In my special case, I wanted to update my system using pacman (my package manager), which uses gpg under the hood 
and fails with:

  GPGME error: Inappropriate ioctl for device

The main problem here is that stdin/stdout still refers to a tty and istty() returns true, 
but pathname returned ptsname() is not valid in this context.

The workaround would be:

  nsenter ... script -c '/usr/bin/pacman -Syu' /dev/null

which, spawns an extra subshell and requires double escaping for arguments, when used in shell scripts or 
  
  nsenter ... /usr/bin/pacman -Syu --confirm >/tmp/stdout 2>/tmp/stderr

Having this feature in nsenter, would easily allow to login in to every container solution.
(even if somebody think coreos/rocket sucks, and create yet another system)

The current implementation could be simplified, if forkpty(3) is used, but as this requires libutil, 
which is not in the POSIX standard, I decided to use posix_openpt(3) instead.

Because I need this feature anyway and to fulfil the GPL:

https://github.com/Mic92/nsenter-pty

(Still requires some further clean-up of unused functions, could be done automatically probably)

On Wed, 18 Mar 2015 12:13:13 +0100
Karel Zak <kzak@xxxxxxxxxx> wrote:

> 
> 
> On Wed, Mar 18, 2015 at 10:53:19AM +0100, Jörg Thalheim wrote:
> > If mount namespaces are used, the issued command, will not have
> > access to the tty device attached to its stdin/stdout/stderr. This
> > patch adds an option to allocate a new pseudo tty in the entered
> > mount namespace and bridge between the origin standard file
> > descriptors and the standard file descriptors of the executed
> > command.
> 
> The original nsenter(1) purpose is to have command line interface to
> setns(2) syscall. Your patch is trying to push us to something more
> complex. Not sure if we really want it. Eric, any comment?
> 
> 
> Wouldn't be possible to use (or implement) on nsenter(1) independent
> command to create a bridge between ttys? Something like:
> 
>  nsenter --mount ttybridge <command>
> 
> (maybe socat(1) is able to create a bridge, not sure)
> 
> Do you have any use-case? I'd like to try it.



> 
>     Karel
> 
> >  sys-utils/nsenter.1 |   6 ++
> >  sys-utils/nsenter.c | 203
> > ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files
> > changed, 202 insertions(+), 7 deletions(-)
> > 
> > diff --git a/sys-utils/nsenter.1 b/sys-utils/nsenter.1
> > index 8a3b25e..15218cb 100644
> > --- a/sys-utils/nsenter.1
> > +++ b/sys-utils/nsenter.1
> > @@ -140,6 +140,12 @@ always sets UID for user namespaces, the
> > default is 0. Don't modify UID and GID when enter user namespace.
> > The default is to drops supplementary groups and sets GID and UID
> > to 0. .TP
> > +\fB\-P\fR, \fB\-\-pty\fR\fR
> > +Allocate a pseudo-tty and attach the standart input/output of the
> > command.  This +option can be used for interactive programs, which
> > requires access to its tty +device, if mount namespaces are in use.
> > If this option is set, nsenter will always +fork, before execu'ing
> > the command. +.TP
> >  \fB\-r\fR, \fB\-\-root\fR[=\fIdirectory\fR]
> >  Set the root directory.  If no directory is specified, set the
> > root directory to the root directory of the target process.  If
> > directory is specified, set the diff --git a/sys-utils/nsenter.c
> > b/sys-utils/nsenter.c index b029f80..c6e899b 100644
> > --- a/sys-utils/nsenter.c
> > +++ b/sys-utils/nsenter.c
> > @@ -2,6 +2,7 @@
> >   * nsenter(1) - command-line interface for setns(2)
> >   *
> >   * Copyright (C) 2012-2013 Eric Biederman <ebiederm@xxxxxxxxxxxx>
> > + * Copyright (C) 2015 Jörg Thalheim <joerg@xxxxxxxxxxxxx>
> >   *
> >   * This program is free software; you can redistribute it and/or
> > modify it
> >   * under the terms of the GNU General Public License as published
> > by the @@ -28,6 +29,9 @@
> >  #include <assert.h>
> >  #include <sys/types.h>
> >  #include <sys/wait.h>
> > +#include <sys/ioctl.h>
> > +#include <sys/select.h>
> > +#include <term.h>
> >  #include <grp.h>
> >  
> >  #include "strutils.h"
> > @@ -79,6 +83,7 @@ static void usage(int status)
> >  	fputs(_(" -S, --setuid <uid>     set uid in entered
> > namespace\n"), out); fputs(_(" -G, --setgid <gid>     set gid in
> > entered namespace\n"), out); fputs(_("     --preserve-credentials
> > do not touch uids or gids\n"), out);
> > +	fputs(_(" -P, --pty              allocate a pseudo-TTY
> > (this implies forking)\n"), out); fputs(_(" -r, --root[=<dir>]
> > set the root directory\n"), out); fputs(_(" -w, --wd[=<dir>]
> > set the working directory\n"), out); fputs(_(" -F,
> > --no-fork          do not fork before exec'ing <program>\n"), out);
> > @@ -94,6 +99,8 @@ static void usage(int status) static pid_t
> > namespace_target_pid = 0; static int root_fd = -1;
> >  static int wd_fd = -1;
> > +static struct termios stdin_termios, stdout_termios;
> > +static int tty_master_fd;
> >  
> >  static void open_target_fd(int *fd, const char *type, const char
> > *path) {
> > @@ -132,20 +139,198 @@ static void open_namespace_fd(int nstype,
> > const char *path) assert(nsfile->nstype);
> >  }
> >  
> > -static void continue_as_child(void)
> > +static void resize_on_signal(int signo __attribute__((__unused__)))
> >  {
> > -	pid_t child = fork();
> > +	struct winsize winsize;
> > +
> > +	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &winsize) != -1)
> > +		ioctl(tty_master_fd, TIOCSWINSZ, &winsize);
> > +}
> > +
> > +static void restore_stdin(void)
> > +{
> > +	if (tcsetattr(STDIN_FILENO, TCSANOW, &stdin_termios) == -1)
> > +		errx(EXIT_FAILURE,
> > +				_("failed to restore stdin
> > terminal attributes")); +}
> > +
> > +static void restore_stdout(void)
> > +{
> > +	if (tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_termios) ==
> > -1)
> > +		errx(EXIT_FAILURE,
> > +				_("failed to restore stdout
> > terminal attributes")); +}
> > +
> > +
> > +static int set_tty_raw(int fd, struct termios *origin_attr)
> > +{
> > +	struct termios attr[1];
> > +
> > +	if (tcgetattr(fd, attr) == -1)
> > +		return -1;
> > +
> > +	memcpy(origin_attr, attr, sizeof(struct termios));
> > +
> > +	cfmakeraw(attr);
> > +
> > +	return tcsetattr(fd, TCSANOW, attr);
> > +}
> > +
> > +static void shovel_tty(int master_fd, int in_fd) {
> > +	fd_set read_fds[1];
> > +	int max_fd;
> > +	char buf[BUFSIZ];
> > +	ssize_t bytes;
> > +	int n;
> > +	while (master_fd != -1) {
> > +
> > +		FD_ZERO(read_fds);
> > +
> > +		if (in_fd != -1)
> > +			FD_SET(in_fd, read_fds);
> > +
> > +		if (master_fd != -1)
> > +			FD_SET(master_fd, read_fds);
> > +
> > +		max_fd = (master_fd > in_fd) ? master_fd : in_fd;
> > +
> > +		if ((n = select(max_fd + 1, read_fds, NULL, NULL,
> > NULL)) == -1 && errno != EINTR)
> > +			break;
> > +
> > +		if (n == -1 && errno == EINTR)
> > +			continue;
> > +
> > +		if (in_fd != -1 && FD_ISSET(in_fd, read_fds)) {
> > +			if ((bytes = read(in_fd, buf, BUFSIZ)) >
> > 0) {
> > +				if (master_fd != -1 &&
> > write(master_fd, buf, bytes) == -1)
> > +					break;
> > +			} else if (n == -1 && errno == EINTR) {
> > +				continue;
> > +			} else {
> > +				in_fd = -1;
> > +				continue;
> > +			}
> > +		}
> > +
> > +		if (master_fd != -1 && FD_ISSET(master_fd,
> > read_fds)) {
> > +			if ((bytes = read(master_fd, buf, BUFSIZ))
> > > 0) {
> > +				if (write(STDOUT_FILENO, buf,
> > bytes) == -1)
> > +					break;
> > +			} else if (n == -1 && errno == EINTR) {
> > +				continue;
> > +			} else {
> > +				close(master_fd);
> > +				master_fd = -1;
> > +				continue;
> > +			}
> > +		}
> > +	}
> > +}
> > +
> > +static void setup_pty_parent(int master_fd)
> > +{
> > +	struct sigaction sa;
> > +	struct winsize ws;
> > +
> > +	sigemptyset(&sa.sa_mask);
> > +	sa.sa_flags = 0;
> > +	sa.sa_handler = resize_on_signal;
> > +	sigaction(SIGWINCH, &sa, NULL);
> > +
> > +	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
> > +		(void) ioctl(master_fd, TIOCSWINSZ, &ws);
> > +
> > +	if (set_tty_raw(STDIN_FILENO, &stdin_termios) != -1) {
> > +		atexit((void (*)(void))restore_stdin);
> > +	}
> > +
> > +	if (set_tty_raw(STDOUT_FILENO, &stdout_termios) != -1) {
> > +		atexit((void (*)(void))restore_stdout);
> > +	}
> > +}
> > +
> > +static int setup_pty_child(int master_fd)
> > +{
> > +	pid_t pid;
> > +	int slave_fd;
> > +
> > +	char* slave_name = ptsname(master_fd);
> > +	if (slave_name == NULL)
> > +		return -errno;
> > +
> > +	slave_fd = open(slave_name, O_RDWR | O_NOCTTY | O_CLOEXEC);
> > +	if (slave_fd < -1)
> > +		return -errno;
> > +
> > +	pid = setsid();
> > +	if (pid < 0 && errno != EPERM)
> > +		return -errno;
> > +
> > +	if (ioctl(slave_fd, TIOCSCTTY, 0) < 0)
> > +		return -errno;
> > +
> > +	if (dup2(slave_fd, STDIN_FILENO) != STDIN_FILENO ||
> > +			dup2(slave_fd, STDOUT_FILENO) !=
> > STDOUT_FILENO ||
> > +			dup2(slave_fd, STDERR_FILENO) !=
> > STDERR_FILENO)
> > +		return -errno;
> > +
> > +	/*only close, if slave_fd is not std-fd*/
> > +	if (slave_fd > 2)
> > +		close(slave_fd);
> > +
> > +	return 0;
> > +}
> > +
> > +static int new_pty(void)
> > +{
> > +	int fd = posix_openpt(O_RDWR | O_NOCTTY);
> > +
> > +	if (fd < 0)
> > +		return -errno;
> > +
> > +	if (grantpt(fd) < 0)
> > +		return -errno;
> > +
> > +	if (unlockpt(fd) < 0)
> > +		return -errno;
> > +
> > +	return fd;
> > +}
> > +
> > +static void continue_as_child(bool open_pty)
> > +{
> > +	pid_t child;
> >  	int status;
> >  	pid_t ret;
> > +	int master_fd = -1;
> > +
> > +	if (open_pty) {
> > +		master_fd = new_pty();
> > +		if (master_fd < 0)
> > +			err(EXIT_FAILURE, _("open pseudo tty
> > failed"));
> > +	}
> > +
> > +	child = fork();
> >  
> >  	if (child < 0)
> >  		err(EXIT_FAILURE, _("fork failed"));
> >  
> >  	/* Only the child returns */
> > -	if (child == 0)
> > +	if (child == 0) {
> > +		if (open_pty && setup_pty_child(master_fd) < 0)
> > +			err(EXIT_FAILURE, _("failed to setup slave
> > of pseudo tty"));
> > +		close(master_fd);
> >  		return;
> > +	}
> > +
> > +	if (open_pty) {
> > +		tty_master_fd = master_fd;
> > +		setup_pty_parent(master_fd);
> > +	}
> >  
> >  	for (;;) {
> > +		if (open_pty)
> > +			shovel_tty(master_fd, STDIN_FILENO);
> >  		ret = waitpid(child, &status, WUNTRACED);
> >  		if ((ret == child) && (WIFSTOPPED(status))) {
> >  			/* The child suspended so suspend us as
> > well */ @@ -171,6 +356,7 @@ int main(int argc, char *argv[])
> >  	};
> >  	static const struct option longopts[] = {
> >  		{ "help", no_argument, NULL, 'h' },
> > +		{ "pty", no_argument, NULL, 'P' },
> >  		{ "version", no_argument, NULL, 'V'},
> >  		{ "target", required_argument, NULL, 't' },
> >  		{ "mount", optional_argument, NULL, 'm' },
> > @@ -190,7 +376,7 @@ int main(int argc, char *argv[])
> >  
> >  	struct namespace_file *nsfile;
> >  	int c, namespaces = 0, setgroups_nerrs = 0, preserve_cred
> > = 0;
> > -	bool do_rd = false, do_wd = false, force_uid = false,
> > force_gid = false;
> > +	bool do_rd = false, do_wd = false, force_uid = false,
> > force_gid = false, open_pty = false; int do_fork = -1; /* unknown
> > yet */ uid_t uid = 0;
> >  	gid_t gid = 0;
> > @@ -201,7 +387,7 @@ int main(int argc, char *argv[])
> >  	atexit(close_stdout);
> >  
> >  	while ((c =
> > -		getopt_long(argc, argv,
> > "+hVt:m::u::i::n::p::U::S:G:r::w::F",
> > +		getopt_long(argc, argv,
> > "+hPVt:m::u::i::n::p::U::S:G:r::w::F", longopts, NULL)) != -1) {
> >  		switch (c) {
> >  		case 'h':
> > @@ -243,6 +429,9 @@ int main(int argc, char *argv[])
> >  			else
> >  				namespaces |= CLONE_NEWPID;
> >  			break;
> > +		case 'P':
> > +			open_pty = true;
> > +			break;
> >  		case 'U':
> >  			if (optarg)
> >  				open_namespace_fd(CLONE_NEWUSER,
> > optarg); @@ -358,8 +547,8 @@ int main(int argc, char *argv[])
> >  		wd_fd = -1;
> >  	}
> >  
> > -	if (do_fork == 1)
> > -		continue_as_child();
> > +	if (do_fork == 1 || open_pty)
> > +		continue_as_child(open_pty);
> >  
> >  	if (force_uid || force_gid) {
> >  		if (force_gid && setgroups(0, NULL) != 0 &&
> > setgroups_nerrs)	/* drop supplementary groups */ -- 
> > 2.3.3
> 
> 
> 

Attachment: pgp5MVNxEdycs.pgp
Description: OpenPGP digital signature


[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux