add option to pass additional fds to container process
This can be usefull to implement socket activated containers for example. Signed-off-by: Jörg Thalheim <joerg@higgsboson.tk>
This commit is contained in:
parent
b120ecf74d
commit
708b25e61e
|
@ -139,7 +139,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
|
||||||
if cmd.SysProcAttr == nil {
|
if cmd.SysProcAttr == nil {
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
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
|
// 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
|
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
|
||||||
// even with the parent still running.
|
// 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 {
|
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||||
return &initConfig{
|
return &initConfig{
|
||||||
Config: c.config,
|
Config: c.config,
|
||||||
Args: process.Args,
|
Args: process.Args,
|
||||||
Env: process.Env,
|
Env: process.Env,
|
||||||
User: process.User,
|
User: process.User,
|
||||||
Cwd: process.Cwd,
|
Cwd: process.Cwd,
|
||||||
Console: process.consolePath,
|
Console: process.consolePath,
|
||||||
Capabilities: process.Capabilities,
|
Capabilities: process.Capabilities,
|
||||||
|
PassedFilesCount: len(process.ExtraFiles),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,14 +40,15 @@ type network struct {
|
||||||
|
|
||||||
// initConfig is used for transferring parameters from Exec() to Init()
|
// initConfig is used for transferring parameters from Exec() to Init()
|
||||||
type initConfig struct {
|
type initConfig struct {
|
||||||
Args []string `json:"args"`
|
Args []string `json:"args"`
|
||||||
Env []string `json:"env"`
|
Env []string `json:"env"`
|
||||||
Cwd string `json:"cwd"`
|
Cwd string `json:"cwd"`
|
||||||
Capabilities []string `json:"capabilities"`
|
Capabilities []string `json:"capabilities"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Config *configs.Config `json:"config"`
|
Config *configs.Config `json:"config"`
|
||||||
Console string `json:"console"`
|
Console string `json:"console"`
|
||||||
Networks []*network `json:"network"`
|
Networks []*network `json:"network"`
|
||||||
|
PassedFilesCount int `json:"passed_files_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type initer interface {
|
type initer interface {
|
||||||
|
@ -95,10 +96,22 @@ func populateProcessEnvironment(env []string) error {
|
||||||
// and working dir, and closes any leaked file descriptors
|
// and working dir, and closes any leaked file descriptors
|
||||||
// before executing the command inside the namespace
|
// before executing the command inside the namespace
|
||||||
func finalizeNamespace(config *initConfig) error {
|
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
|
// inherited are marked close-on-exec so they stay out of the
|
||||||
// container
|
// container
|
||||||
if err := utils.CloseExecFrom(3); err != nil {
|
|
||||||
|
if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -648,3 +648,69 @@ func TestContainerState(t *testing.T) {
|
||||||
stdinW.Close()
|
stdinW.Close()
|
||||||
p.Wait()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -38,6 +38,9 @@ type Process struct {
|
||||||
// Stderr is a pointer to a writer which receives the standard error stream.
|
// Stderr is a pointer to a writer which receives the standard error stream.
|
||||||
Stderr io.Writer
|
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 is the path to the console allocated to the container.
|
||||||
consolePath string
|
consolePath string
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue