// setup standard pipes so that the TTY of the calling runc process // is not inherited by the container. func createStdioPipes(p *libcontainer.Process, rootuid int) (*tty, error) { var ( t = &tty{} fds []int ) r, w, err := os.Pipe() if err != nil { return nil, err } fds = append(fds, int(r.Fd()), int(w.Fd())) go io.Copy(w, os.Stdin) t.closers = append(t.closers, w) p.Stdin = r if r, w, err = os.Pipe(); err != nil { return nil, err } fds = append(fds, int(r.Fd()), int(w.Fd())) go io.Copy(os.Stdout, r) p.Stdout = w t.closers = append(t.closers, r) if r, w, err = os.Pipe(); err != nil { return nil, err } fds = append(fds, int(r.Fd()), int(w.Fd())) go io.Copy(os.Stderr, r) p.Stderr = w t.closers = append(t.closers, r) // change the ownership of the pipe fds incase we are in a user namespace. for _, fd := range fds { if err := syscall.Fchown(fd, rootuid, rootuid); err != nil { return nil, err } } return t, nil }
func dupStdio(process *libcontainer.Process, rootuid int) error { process.Stdin = os.Stdin process.Stdout = os.Stdout process.Stderr = os.Stderr for _, fd := range []uintptr{ os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), } { if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil { return err } } return nil }
// setupIO modifies the given process config according to the options. func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool) (*tty, error) { // This is entirely handled by recvtty. if createTTY { process.Stdin = nil process.Stdout = nil process.Stderr = nil return &tty{}, nil } // When we detach, we just dup over stdio and call it a day. There's no // requirement that we set up anything nice for our caller or the // container. if detach { if err := dupStdio(process, rootuid, rootgid); err != nil { return nil, err } return &tty{}, nil } // XXX: This doesn't sit right with me. It's ugly. return createStdioPipes(process, rootuid, rootgid) }
func createTty(p *libcontainer.Process, rootuid int) (*tty, error) { console, err := p.NewConsole(rootuid) if err != nil { return nil, err } go io.Copy(console, os.Stdin) go io.Copy(os.Stdout, console) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return nil, err } t := &tty{ console: console, state: state, closers: []io.Closer{ console, }, } p.Stderr = nil p.Stdout = nil p.Stdin = nil return t, nil }
func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes) error { var term execdriver.Terminal var err error if processConfig.Tty { rootuid, err := container.HostUID() if err != nil { return err } cons, err := p.NewConsole(rootuid) if err != nil { return err } term, err = NewTtyConsole(cons, pipes) } else { p.Stdout = pipes.Stdout p.Stderr = pipes.Stderr r, w, err := os.Pipe() if err != nil { return err } if pipes.Stdin != nil { go func() { io.Copy(w, pipes.Stdin) w.Close() }() p.Stdin = r } term = &execdriver.StdConsole{} } if err != nil { return err } processConfig.Terminal = term return nil }
// setup standard pipes so that the TTY of the calling runc process // is not inherited by the container. func createStdioPipes(p *libcontainer.Process) (*tty, error) { t := &tty{} r, w, err := os.Pipe() if err != nil { return nil, err } go io.Copy(w, os.Stdin) t.closers = append(t.closers, w) p.Stdin = r if r, w, err = os.Pipe(); err != nil { return nil, err } go io.Copy(os.Stdout, r) p.Stdout = w t.closers = append(t.closers, r) if r, w, err = os.Pipe(); err != nil { return nil, err } go io.Copy(os.Stderr, r) p.Stderr = w t.closers = append(t.closers, r) return t, nil }
func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes) error { rootuid, err := container.HostUID() if err != nil { return err } if processConfig.Tty { cons, err := p.NewConsole(rootuid) if err != nil { return err } term, err := NewTtyConsole(cons, pipes) if err != nil { return err } processConfig.Terminal = term return nil } // not a tty--set up stdio pipes term := &execdriver.StdConsole{} processConfig.Terminal = term // if we are not in a user namespace, there is no reason to go through // the hassle of setting up os-level pipes with proper (remapped) ownership // so we will do the prior shortcut for non-userns containers if rootuid == 0 { p.Stdout = pipes.Stdout p.Stderr = pipes.Stderr r, w, err := os.Pipe() if err != nil { return err } if pipes.Stdin != nil { go func() { io.Copy(w, pipes.Stdin) w.Close() }() p.Stdin = r } return nil } // if we have user namespaces enabled (rootuid != 0), we will set // up os pipes for stderr, stdout, stdin so we can chown them to // the proper ownership to allow for proper access to the underlying // fds var fds []int //setup stdout r, w, err := os.Pipe() if err != nil { return err } fds = append(fds, int(r.Fd()), int(w.Fd())) if pipes.Stdout != nil { go io.Copy(pipes.Stdout, r) } term.Closers = append(term.Closers, r) p.Stdout = w //setup stderr r, w, err = os.Pipe() if err != nil { return err } fds = append(fds, int(r.Fd()), int(w.Fd())) if pipes.Stderr != nil { go io.Copy(pipes.Stderr, r) } term.Closers = append(term.Closers, r) p.Stderr = w //setup stdin r, w, err = os.Pipe() if err != nil { return err } fds = append(fds, int(r.Fd()), int(w.Fd())) if pipes.Stdin != nil { go func() { io.Copy(w, pipes.Stdin) w.Close() }() p.Stdin = r } for _, fd := range fds { if err := syscall.Fchown(fd, rootuid, rootuid); err != nil { return fmt.Errorf("Failed to chown pipes fd: %v", err) } } return nil }
func TestEnter(t *testing.T) { if testing.Short() { return } root, err := newTestRoot() ok(t, err) defer os.RemoveAll(root) rootfs, err := newRootfs() ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) container, err := factory.Create("test", config) ok(t, err) defer container.Destroy() // Execute a first process in the container stdinR, stdinW, err := os.Pipe() ok(t, err) var stdout, stdout2 bytes.Buffer pconfig := libcontainer.Process{ Args: []string{"sh", "-c", "cat && readlink /proc/self/ns/pid"}, Env: standardEnvironment, Stdin: stdinR, Stdout: &stdout, } err = container.Start(&pconfig) stdinR.Close() defer stdinW.Close() ok(t, err) pid, err := pconfig.Pid() ok(t, err) // Execute another process in the container stdinR2, stdinW2, err := os.Pipe() ok(t, err) pconfig2 := libcontainer.Process{ Env: standardEnvironment, } pconfig2.Args = []string{"sh", "-c", "cat && readlink /proc/self/ns/pid"} pconfig2.Stdin = stdinR2 pconfig2.Stdout = &stdout2 err = container.Start(&pconfig2) stdinR2.Close() defer stdinW2.Close() ok(t, err) pid2, err := pconfig2.Pid() ok(t, err) processes, err := container.Processes() ok(t, err) n := 0 for i := range processes { if processes[i] == pid || processes[i] == pid2 { n++ } } if n != 2 { t.Fatal("unexpected number of processes", processes, pid, pid2) } // Wait processes stdinW2.Close() waitProcess(&pconfig2, t) stdinW.Close() waitProcess(&pconfig, t) // Check that both processes live in the same pidns pidns := string(stdout.Bytes()) ok(t, err) pidns2 := string(stdout2.Bytes()) ok(t, err) if pidns != pidns2 { t.Fatal("The second process isn't in the required pid namespace", pidns, pidns2) } }
func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes, wg *sync.WaitGroup) ([]io.WriteCloser, error) { writers := []io.WriteCloser{} rootuid, err := container.HostUID() if err != nil { return writers, err } if processConfig.Tty { cons, err := p.NewConsole(rootuid) if err != nil { return writers, err } term, err := NewTtyConsole(cons, pipes) if err != nil { return writers, err } processConfig.Terminal = term return writers, nil } // not a tty--set up stdio pipes term := &execdriver.StdConsole{} processConfig.Terminal = term // if we are not in a user namespace, there is no reason to go through // the hassle of setting up os-level pipes with proper (remapped) ownership // so we will do the prior shortcut for non-userns containers if rootuid == 0 { p.Stdout = pipes.Stdout p.Stderr = pipes.Stderr r, w, err := os.Pipe() if err != nil { return writers, err } if pipes.Stdin != nil { go func() { io.Copy(w, pipes.Stdin) w.Close() }() p.Stdin = r } return writers, nil } // if we have user namespaces enabled (rootuid != 0), we will set // up os pipes for stderr, stdout, stdin so we can chown them to // the proper ownership to allow for proper access to the underlying // fds var fds []uintptr copyPipes := func(out io.Writer, in io.ReadCloser) { defer wg.Done() io.Copy(out, in) in.Close() } //setup stdout r, w, err := os.Pipe() if err != nil { w.Close() return writers, err } writers = append(writers, w) fds = append(fds, r.Fd(), w.Fd()) if pipes.Stdout != nil { wg.Add(1) go copyPipes(pipes.Stdout, r) } term.Closers = append(term.Closers, r) p.Stdout = w //setup stderr r, w, err = os.Pipe() if err != nil { w.Close() return writers, err } writers = append(writers, w) fds = append(fds, r.Fd(), w.Fd()) if pipes.Stderr != nil { wg.Add(1) go copyPipes(pipes.Stderr, r) } term.Closers = append(term.Closers, r) p.Stderr = w //setup stdin r, w, err = os.Pipe() if err != nil { r.Close() return writers, err } fds = append(fds, r.Fd(), w.Fd()) if pipes.Stdin != nil { go func() { io.Copy(w, pipes.Stdin) w.Close() }() p.Stdin = r } for _, fd := range fds { if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil { return writers, fmt.Errorf("Failed to chown pipes fd: %v", err) } } return writers, nil }