All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Jörg Thalheim" <joerg@higgsboson.tk>
To: util-linux@vger.kernel.org
Subject: [PATCH] nsenter: add support for pty
Date: Wed, 18 Mar 2015 10:53:19 +0100	[thread overview]
Message-ID: <20150318105319.2c6bab99@turingmachine> (raw)

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

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.

Signed-off-by: Jörg Thalheim <joerg@higgsboson.tk>
---
 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  9:58 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-03-18  9:53 Jörg Thalheim [this message]
2015-03-18 11:13 ` [PATCH] nsenter: add support for pty Karel Zak
2015-03-18 13:40   ` Jörg Thalheim
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=20150318105319.2c6bab99@turingmachine \
    --to=joerg@higgsboson.tk \
    --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.