diff --git a/container_linux.go b/container_linux.go index d52610f0..f2c2ab07 100644 --- a/container_linux.go +++ b/container_linux.go @@ -139,7 +139,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } - cmd.ExtraFiles = []*os.File{childPipe} + cmd.ExtraFiles = append([]*os.File{childPipe}, p.ExtraFiles...) // NOTE: when running a container with no PID namespace and the parent process spawning the container is // PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason // even with the parent still running. @@ -195,13 +195,14 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, func (c *linuxContainer) newInitConfig(process *Process) *initConfig { return &initConfig{ - Config: c.config, - Args: process.Args, - Env: process.Env, - User: process.User, - Cwd: process.Cwd, - Console: process.consolePath, - Capabilities: process.Capabilities, + Config: c.config, + Args: process.Args, + Env: process.Env, + User: process.User, + Cwd: process.Cwd, + Console: process.consolePath, + Capabilities: process.Capabilities, + PassedFilesCount: len(process.ExtraFiles), } } diff --git a/init_linux.go b/init_linux.go index 1786b1ed..66e46f16 100644 --- a/init_linux.go +++ b/init_linux.go @@ -40,14 +40,15 @@ type network struct { // initConfig is used for transferring parameters from Exec() to Init() type initConfig struct { - Args []string `json:"args"` - Env []string `json:"env"` - Cwd string `json:"cwd"` - Capabilities []string `json:"capabilities"` - User string `json:"user"` - Config *configs.Config `json:"config"` - Console string `json:"console"` - Networks []*network `json:"network"` + Args []string `json:"args"` + Env []string `json:"env"` + Cwd string `json:"cwd"` + Capabilities []string `json:"capabilities"` + User string `json:"user"` + Config *configs.Config `json:"config"` + Console string `json:"console"` + Networks []*network `json:"network"` + PassedFilesCount int `json:"passed_files_count"` } type initer interface { @@ -95,10 +96,22 @@ func populateProcessEnvironment(env []string) error { // and working dir, and closes any leaked file descriptors // before executing the command inside the namespace func finalizeNamespace(config *initConfig) error { - // Ensure that all non-standard fds we may have accidentally + // FD 3 is the child pipe, which needs to be closed. + // Additional file descriptors starts from 3 to (3 + n) + // To fix the order all additional file descriptors + // are shiftet by one right + for fd := 3; fd < (config.PassedFilesCount + 3); fd++ { + err := syscall.Dup2(fd+1, fd) + if err != nil { + return err + } + } + + // Ensure that all unwanted fds we may have accidentally // inherited are marked close-on-exec so they stay out of the // container - if err := utils.CloseExecFrom(3); err != nil { + + if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil { return err } diff --git a/integration/exec_test.go b/integration/exec_test.go index 12457ba1..be35b5e9 100644 --- a/integration/exec_test.go +++ b/integration/exec_test.go @@ -648,3 +648,69 @@ func TestContainerState(t *testing.T) { stdinW.Close() p.Wait() } + +func TestPassExtraFiles(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + factory, err := libcontainer.New(rootfs, libcontainer.Cgroupfs) + if err != nil { + t.Fatal(err) + } + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + var stdout bytes.Buffer + pipeout1, pipein1, err := os.Pipe() + pipeout2, pipein2, err := os.Pipe() + process := libcontainer.Process{ + Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + ExtraFiles: []*os.File{pipein1, pipein2}, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&process) + if err != nil { + t.Fatal(err) + } + + waitProcess(&process, t) + + out := string(stdout.Bytes()) + // fd 5 is the directory handle for /proc/$$/fd + if out != "0 1 2 3 4 5" { + t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to init, got '%s'", out) + } + var buf = []byte{0} + _, err = pipeout1.Read(buf) + if err != nil { + t.Fatal(err) + } + out1 := string(buf) + if out1 != "1" { + t.Fatalf("expected first pipe to receive '1', got '%s'", out1) + } + + _, err = pipeout2.Read(buf) + if err != nil { + t.Fatal(err) + } + out2 := string(buf) + if out2 != "2" { + t.Fatalf("expected second pipe to receive '2', got '%s'", out2) + } +} diff --git a/process.go b/process.go index 82fcff8c..caabbea9 100644 --- a/process.go +++ b/process.go @@ -38,6 +38,9 @@ type Process struct { // Stderr is a pointer to a writer which receives the standard error stream. Stderr io.Writer + // ExtraFiles specifies additional open files to be inherited by the container + ExtraFiles []*os.File + // consolePath is the path to the console allocated to the container. consolePath string