From a9644c209f7764f9155db0c4aeb4f690c0cdb585 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Wed, 4 Mar 2015 16:04:20 -0800 Subject: [PATCH] Add tty support for setnsProcess Signed-off-by: Alexander Morozov --- container_linux.go | 5 +++ integration/execin_test.go | 72 ++++++++++++++++++++++++++++++++++++++ nsenter/nsexec.c | 39 +++++++++++++++++++-- 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/container_linux.go b/container_linux.go index 64258792..bf729760 100644 --- a/container_linux.go +++ b/container_linux.go @@ -175,6 +175,11 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()), "_LIBCONTAINER_INITTYPE=setns", ) + + if p.consolePath != "" { + cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath) + } + // TODO: set on container for process management return &setnsProcess{ cmd: cmd, diff --git a/integration/execin_test.go b/integration/execin_test.go index 92a1ae0f..524ee454 100644 --- a/integration/execin_test.go +++ b/integration/execin_test.go @@ -1,9 +1,12 @@ package integration import ( + "bytes" + "io" "os" "strings" "testing" + "time" "github.com/docker/libcontainer" ) @@ -173,3 +176,72 @@ func TestExecInError(t *testing.T) { t.Fatalf("Should be error about not found executable, got %s", err) } } + +func TestExecInTTY(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + process := &libcontainer.Process{ + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + if err != nil { + t.Fatal(err) + } + + var stdout bytes.Buffer + ps := &libcontainer.Process{ + Args: []string{"ps"}, + Env: standardEnvironment, + } + console, err := ps.NewConsole(0) + copy := make(chan struct{}) + go func() { + io.Copy(&stdout, console) + close(copy) + }() + if err != nil { + t.Fatal(err) + } + err = container.Start(ps) + if err != nil { + t.Fatal(err) + } + select { + case <-time.After(5 * time.Second): + t.Fatal("Waiting for copy timed out") + case <-copy: + } + if _, err := ps.Wait(); err != nil { + t.Fatal(err) + } + stdinW.Close() + if _, err := process.Wait(); err != nil { + t.Log(err) + } + out := stdout.String() + if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") { + t.Fatalf("unexpected running process, output %q", out) + } +} diff --git a/nsenter/nsexec.c b/nsenter/nsexec.c index 5b62729a..e7658f38 100644 --- a/nsenter/nsexec.c +++ b/nsenter/nsexec.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -27,14 +28,14 @@ struct clone_arg { jmp_buf *env; }; +#define pr_perror(fmt, ...) fprintf(stderr, "nsenter: " fmt ": %m\n", ##__VA_ARGS__) + static int child_func(void *_arg) { struct clone_arg *arg = (struct clone_arg *)_arg; longjmp(*arg->env, 1); } -#define pr_perror(fmt, ...) fprintf(stderr, "nsenter: " fmt ": %m\n", ##__VA_ARGS__) - // Use raw setns syscall for versions of glibc that don't include it (namely glibc-2.12) #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 14 #define _GNU_SOURCE @@ -65,8 +66,9 @@ void nsexec() const int num = sizeof(namespaces) / sizeof(char *); jmp_buf env; char buf[PATH_MAX], *val; - int i, tfd, child, len; + int i, tfd, child, len, consolefd = -1; pid_t pid; + char *console; val = getenv("_LIBCONTAINER_INITPID"); if (val == NULL) @@ -79,6 +81,15 @@ void nsexec() exit(1); } + console = getenv("_LIBCONTAINER_CONSOLE_PATH"); + if (console != NULL) { + consolefd = open(console, O_RDWR); + if (consolefd < 0) { + pr_perror("Failed to open console %s", console); + exit(1); + } + } + /* Check that the specified process exists */ snprintf(buf, PATH_MAX - 1, "/proc/%d/ns", pid); tfd = open(buf, O_DIRECTORY | O_RDONLY); @@ -113,6 +124,28 @@ void nsexec() } if (setjmp(env) == 1) { + if (setsid() == -1) { + pr_perror("setsid failed"); + exit(1); + } + if (consolefd != -1) { + if (ioctl(consolefd, TIOCSCTTY, 0) == -1) { + pr_perror("ioctl TIOCSCTTY failed"); + exit(1); + } + if (dup2(consolefd, STDIN_FILENO) != STDIN_FILENO) { + pr_perror("Failed to dup 0"); + exit(1); + } + if (dup2(consolefd, STDOUT_FILENO) != STDOUT_FILENO) { + pr_perror("Failed to dup 1"); + exit(1); + } + if (dup2(consolefd, STDERR_FILENO) != STDERR_FILENO) { + pr_perror("Failed to dup 2"); + exit(1); + } + } // Finish executing, let the Go runtime take over. return; }