All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Jörg Thalheim" <joerg@higgsboson.tk>
To: Karel Zak <kzak@redhat.com>
Cc: util-linux@vger.kernel.org
Subject: Re: [PATCH] nsenter: add support for pty
Date: Wed, 18 Mar 2015 14:40:54 +0100	[thread overview]
Message-ID: <20150318144054.25768c4f@turingmachine> (raw)
In-Reply-To: <20150318111313.GC28925@ws.net.home>

[-- Attachment #1: Type: text/plain, Size: 12559 bytes --]

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@redhat.com> 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@xmission.com>
> > + * Copyright (C) 2015 Jörg Thalheim <joerg@higgsboson.tk>
> >   *
> >   * 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 #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 603 bytes --]

  reply	other threads:[~2015-03-18 13:40 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-03-18  9:53 [PATCH] nsenter: add support for pty Jörg Thalheim
2015-03-18 11:13 ` Karel Zak
2015-03-18 13:40   ` Jörg Thalheim [this message]
2015-03-18 15:59   ` Eric W. Biederman

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20150318144054.25768c4f@turingmachine \
    --to=joerg@higgsboson.tk \
    --cc=kzak@redhat.com \
    --cc=util-linux@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.