diff --git a/config.go b/configs/config.go similarity index 99% rename from config.go rename to configs/config.go index 94c2bd981..ab40b2b4b 100644 --- a/config.go +++ b/configs/config.go @@ -1,4 +1,4 @@ -package libcontainer +package configs import ( "github.com/docker/libcontainer/cgroups" diff --git a/config_test.go b/configs/config_test.go similarity index 97% rename from config_test.go rename to configs/config_test.go index b4e16bf01..f698e3d9a 100644 --- a/config_test.go +++ b/configs/config_test.go @@ -1,4 +1,4 @@ -package libcontainer +package configs import ( "encoding/json" @@ -34,7 +34,7 @@ func containsDevice(expected *devices.Device, values []*devices.Device) bool { } func loadConfig(name string) (*Config, error) { - f, err := os.Open(filepath.Join("sample_configs", name)) + f, err := os.Open(filepath.Join("../sample_configs", name)) if err != nil { return nil, err } diff --git a/state.go b/configs/state.go similarity index 98% rename from state.go rename to configs/state.go index 4ab47ad75..9dc770067 100644 --- a/state.go +++ b/configs/state.go @@ -1,4 +1,4 @@ -package libcontainer +package configs import ( "encoding/json" diff --git a/container.go b/container.go index e53769025..e04a43df4 100644 --- a/container.go +++ b/container.go @@ -3,6 +3,10 @@ NOTE: The API is in flux and mainly not implemented. Proceed with caution until */ package libcontainer +import ( + "github.com/docker/libcontainer/configs" +) + // A libcontainer container object. // // Each container is thread-safe within the same process. Since a container can @@ -17,10 +21,10 @@ type Container interface { // errors: // ContainerDestroyed - Container no longer exists, // Systemerror - System error. - RunState() (RunState, error) + RunState() (configs.RunState, error) // Returns the current config of the container. - Config() *Config + Config() *configs.Config // Returns the PIDs inside this container. The PIDs are in the namespace of the calling process. // diff --git a/factory.go b/factory.go index 4959ff1e1..37e629645 100644 --- a/factory.go +++ b/factory.go @@ -1,5 +1,9 @@ package libcontainer +import ( + "github.com/docker/libcontainer/configs" +) + type Factory interface { // Creates a new container with the given id and starts the initial process inside it. // id must be a string containing only letters, digits and underscores and must contain @@ -17,7 +21,7 @@ type Factory interface { // Systemerror - System error // // On error, any partially created container parts are cleaned up (the operation is atomic). - Create(id string, config *Config) (Container, error) + Create(id string, config *configs.Config) (Container, error) // Load takes an ID for an existing container and returns the container information // from the state. This presents a read only view of the container. diff --git a/integration/exec_test.go b/integration/exec_test.go index cf749efbf..993ca25ce 100644 --- a/integration/exec_test.go +++ b/integration/exec_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/docker/libcontainer" + "github.com/docker/libcontainer/configs" ) func TestExecPS(t *testing.T) { @@ -180,7 +180,7 @@ func TestRlimit(t *testing.T) { } } -func getNamespaceIndex(config *libcontainer.Config, name string) int { +func getNamespaceIndex(config *configs.Config, name string) int { for i, v := range config.Namespaces { if v.Name == name { return i diff --git a/integration/init_test.go b/integration/init_test.go index 9954c0f8e..095263761 100644 --- a/integration/init_test.go +++ b/integration/init_test.go @@ -16,17 +16,7 @@ func init() { } runtime.LockOSThread() - container, err := loadConfig() - if err != nil { - log.Fatal(err) - } - - rootfs, err := os.Getwd() - if err != nil { - log.Fatal(err) - } - - if err := namespaces.Init(container, rootfs, "", os.NewFile(3, "pipe"), os.Args[3:]); err != nil { + if err := namespaces.Init(os.NewFile(3, "pipe")); err != nil { log.Fatalf("unable to initialize for container: %s", err) } os.Exit(1) diff --git a/integration/template_test.go b/integration/template_test.go index f37070ffb..d58bb6133 100644 --- a/integration/template_test.go +++ b/integration/template_test.go @@ -3,8 +3,8 @@ package integration import ( "syscall" - "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" + "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/devices" ) @@ -12,8 +12,8 @@ import ( // // it uses a network strategy of just setting a loopback interface // and the default setup for devices -func newTemplateConfig(rootfs string) *libcontainer.Config { - return &libcontainer.Config{ +func newTemplateConfig(rootfs string) *configs.Config { + return &configs.Config{ RootFs: rootfs, Tty: false, Capabilities: []string{ @@ -32,7 +32,7 @@ func newTemplateConfig(rootfs string) *libcontainer.Config { "KILL", "AUDIT_WRITE", }, - Namespaces: []libcontainer.Namespace{ + Namespaces: []configs.Namespace{ {Name: "NEWNS"}, {Name: "NEWUTS"}, {Name: "NEWIPC"}, @@ -45,7 +45,7 @@ func newTemplateConfig(rootfs string) *libcontainer.Config { AllowedDevices: devices.DefaultAllowedDevices, }, - MountConfig: &libcontainer.MountConfig{ + MountConfig: &configs.MountConfig{ DeviceNodes: devices.DefaultAutoCreatedDevices, }, Hostname: "integration", @@ -55,14 +55,14 @@ func newTemplateConfig(rootfs string) *libcontainer.Config { "HOSTNAME=integration", "TERM=xterm", }, - Networks: []*libcontainer.Network{ + Networks: []*configs.Network{ { Type: "loopback", Address: "127.0.0.1/0", Gateway: "localhost", }, }, - Rlimits: []libcontainer.Rlimit{ + Rlimits: []configs.Rlimit{ { Type: syscall.RLIMIT_NOFILE, Hard: uint64(1024), diff --git a/integration/utils_test.go b/integration/utils_test.go index 6393fb998..93fe3b4b7 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -8,9 +8,10 @@ import ( "os" "os/exec" "path/filepath" + "syscall" "github.com/docker/libcontainer" - "github.com/docker/libcontainer/namespaces" + "github.com/docker/libcontainer/configs" ) func newStdBuffers() *stdBuffers { @@ -27,7 +28,7 @@ type stdBuffers struct { Stderr *bytes.Buffer } -func writeConfig(config *libcontainer.Config) error { +func writeConfig(config *configs.Config) error { f, err := os.OpenFile(filepath.Join(config.RootFs, "container.json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) if err != nil { return err @@ -36,14 +37,14 @@ func writeConfig(config *libcontainer.Config) error { return json.NewEncoder(f).Encode(config) } -func loadConfig() (*libcontainer.Config, error) { +func loadConfig() (*configs.Config, error) { f, err := os.Open(filepath.Join(os.Getenv("data_path"), "container.json")) if err != nil { return nil, err } defer f.Close() - var container *libcontainer.Config + var container *configs.Config if err := json.NewDecoder(f).Decode(&container); err != nil { return nil, err } @@ -83,13 +84,55 @@ func copyBusybox(dest string) error { // // buffers are returned containing the STDOUT and STDERR output for the run // along with the exit code and any go error -func runContainer(config *libcontainer.Config, console string, args ...string) (buffers *stdBuffers, exitCode int, err error) { +func runContainer(config *configs.Config, console string, args ...string) (buffers *stdBuffers, exitCode int, err error) { if err := writeConfig(config); err != nil { return nil, -1, err } buffers = newStdBuffers() - exitCode, err = namespaces.Exec(config, buffers.Stdin, buffers.Stdout, buffers.Stderr, - console, config.RootFs, args, namespaces.DefaultCreateCommand, nil) + + process := &libcontainer.ProcessConfig{ + Args: args, + Env: make([]string, 0), + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + + factory, err := libcontainer.New(".", []string{os.Args[0], "init", "--"}) + if err != nil { + return nil, -1, err + } + + container, err := factory.Create("testCT", config) + if err != nil { + return nil, -1, err + } + defer container.Destroy() + + pid, err := container.StartProcess(process) + if err != nil { + return nil, -1, err + } + + p, err := os.FindProcess(pid) + if err != nil { + return nil, -1, err + } + + ps, err := p.Wait() + if err != nil { + return nil, -1, err + } + + status := ps.Sys().(syscall.WaitStatus) + if status.Exited() { + exitCode = status.ExitStatus() + } else if status.Signaled() { + exitCode = -int(status.Signal()) + } else { + return nil, -1, err + } + return } diff --git a/linux_container.go b/linux_container.go index 10d66bfe1..bbd0cb1de 100644 --- a/linux_container.go +++ b/linux_container.go @@ -10,6 +10,8 @@ import ( "path/filepath" "syscall" + "github.com/docker/libcontainer/configs" + "github.com/docker/libcontainer/namespaces" "github.com/docker/libcontainer/network" "github.com/golang/glog" ) @@ -17,8 +19,8 @@ import ( type linuxContainer struct { id string root string - config *Config - state *State + config *configs.Config + state *configs.State cgroupManager CgroupManager initArgs []string } @@ -27,12 +29,12 @@ func (c *linuxContainer) ID() string { return c.id } -func (c *linuxContainer) Config() *Config { +func (c *linuxContainer) Config() *configs.Config { return c.config } -func (c *linuxContainer) RunState() (RunState, error) { - return Destroyed, nil // FIXME return a real state +func (c *linuxContainer) RunState() (configs.RunState, error) { + return configs.Destroyed, nil // FIXME return a real state } func (c *linuxContainer) Processes() ([]int, error) { @@ -60,18 +62,18 @@ func (c *linuxContainer) Stats() (*ContainerStats, error) { return stats, nil } -func (c *linuxContainer) StartProcess(config *ProcessConfig) (int, error) { +func (c *linuxContainer) StartProcess(pconfig *ProcessConfig) (int, error) { state, err := c.RunState() if err != nil { return -1, err } - if state != Destroyed { + if state != configs.Destroyed { glog.Info("start new container process") panic("not implemented") } - if err := c.startInitProcess(config); err != nil { + if err := c.startInitProcess(pconfig); err != nil { return -1, err } @@ -79,20 +81,16 @@ func (c *linuxContainer) StartProcess(config *ProcessConfig) (int, error) { } func (c *linuxContainer) updateStateFile() error { - data, err := json.MarshalIndent(c.state, "", "\t") - if err != nil { - return newGenericError(err, SystemError) - } - fnew := filepath.Join(c.root, fmt.Sprintf("%s.new", stateFilename)) f, err := os.Create(fnew) if err != nil { return newGenericError(err, SystemError) } - _, err = f.Write(data) + err = json.NewEncoder(f).Encode(c.state) if err != nil { f.Close() + os.Remove(fnew) return newGenericError(err, SystemError) } f.Close() @@ -118,16 +116,17 @@ func (c *linuxContainer) startInitProcess(config *ProcessConfig) error { cmd.SysProcAttr = &syscall.SysProcAttr{} } + cmd.SysProcAttr.Cloneflags = uintptr(namespaces.GetNamespaceFlags(c.config.Namespaces)) cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL - //FIXME call namespaces.Exec() - if err := cmd.Start(); err != nil { + err := namespaces.Exec(config.Args, config.Env, cmd, c.config, c.state) + if err != nil { return err } - c.state.InitPid = cmd.Process.Pid - err := c.updateStateFile() + err = c.updateStateFile() if err != nil { + // FIXME c.Kill() return err } @@ -140,7 +139,7 @@ func (c *linuxContainer) Destroy() error { return err } - if state != Destroyed { + if state != configs.Destroyed { return newGenericError(nil, ContainerNotStopped) } diff --git a/linux_container_test.go b/linux_container_test.go index cd8d33d03..64d4fb8bc 100644 --- a/linux_container_test.go +++ b/linux_container_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/docker/libcontainer/cgroups" + "github.com/docker/libcontainer/configs" ) type mockCgroupManager struct { @@ -24,7 +25,7 @@ func (m *mockCgroupManager) GetStats() (*cgroups.Stats, error) { func TestGetContainerPids(t *testing.T) { container := &linuxContainer{ id: "myid", - config: &Config{}, + config: &configs.Config{}, cgroupManager: &mockCgroupManager{pids: []int{1, 2, 3}}, } @@ -43,7 +44,7 @@ func TestGetContainerPids(t *testing.T) { func TestGetContainerStats(t *testing.T) { container := &linuxContainer{ id: "myid", - config: &Config{}, + config: &configs.Config{}, cgroupManager: &mockCgroupManager{ pids: []int{1, 2, 3}, stats: &cgroups.Stats{ @@ -52,7 +53,7 @@ func TestGetContainerStats(t *testing.T) { }, }, }, - state: &State{}, + state: &configs.State{}, } stats, err := container.Stats() diff --git a/linux_factory.go b/linux_factory.go index 30c63566a..ecef9dcd8 100644 --- a/linux_factory.go +++ b/linux_factory.go @@ -10,6 +10,9 @@ import ( "regexp" "github.com/golang/glog" + + "github.com/docker/libcontainer/configs" + "github.com/docker/libcontainer/namespaces" ) const ( @@ -43,7 +46,7 @@ type linuxFactory struct { initArgs []string } -func (l *linuxFactory) Create(id string, config *Config) (Container, error) { +func (l *linuxFactory) Create(id string, config *configs.Config) (Container, error) { if l.root == "" { return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid) } @@ -91,7 +94,7 @@ func (l *linuxFactory) Create(id string, config *Config) (Container, error) { root: containerRoot, config: config, initArgs: l.initArgs, - state: &State{}, + state: &configs.State{}, cgroupManager: cgroupManager, }, nil } @@ -125,7 +128,7 @@ func (l *linuxFactory) Load(id string) (Container, error) { }, nil } -func (l *linuxFactory) loadContainerConfig(root string) (*Config, error) { +func (l *linuxFactory) loadContainerConfig(root string) (*configs.Config, error) { f, err := os.Open(filepath.Join(root, configFilename)) if err != nil { if os.IsNotExist(err) { @@ -135,14 +138,14 @@ func (l *linuxFactory) loadContainerConfig(root string) (*Config, error) { } defer f.Close() - var config *Config + var config *configs.Config if err := json.NewDecoder(f).Decode(&config); err != nil { return nil, newGenericError(err, ConfigInvalid) } return config, nil } -func (l *linuxFactory) loadContainerState(root string) (*State, error) { +func (l *linuxFactory) loadContainerState(root string) (*configs.State, error) { f, err := os.Open(filepath.Join(root, stateFilename)) if err != nil { if os.IsNotExist(err) { @@ -152,7 +155,7 @@ func (l *linuxFactory) loadContainerState(root string) (*State, error) { } defer f.Close() - var state *State + var state *configs.State if err := json.NewDecoder(f).Decode(&state); err != nil { return nil, newGenericError(err, SystemError) } @@ -162,7 +165,7 @@ func (l *linuxFactory) loadContainerState(root string) (*State, error) { // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state // This is a low level implementation detail of the reexec and should not be consumed externally func (f *linuxFactory) StartInitialization(pipefd uintptr) (err error) { + pipe := os.NewFile(uintptr(pipefd), "pipe") - /* FIXME call namespaces.Init() */ - return nil + return namespaces.Init(pipe) } diff --git a/linux_factory_test.go b/linux_factory_test.go index 168be1be7..3c1e275c0 100644 --- a/linux_factory_test.go +++ b/linux_factory_test.go @@ -8,6 +8,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/docker/libcontainer/configs" ) func newTestRoot() (string, error) { @@ -83,10 +85,10 @@ func TestFactoryLoadContainer(t *testing.T) { // setup default container config and state for mocking var ( id = "1" - expectedConfig = &Config{ + expectedConfig = &configs.Config{ RootFs: "/mycontainer/root", } - expectedState = &State{ + expectedState = &configs.State{ InitPid: 1024, } ) diff --git a/namespaces/create.go b/namespaces/create.go index b6418b6e9..30de84cee 100644 --- a/namespaces/create.go +++ b/namespaces/create.go @@ -4,7 +4,7 @@ import ( "os" "os/exec" - "github.com/docker/libcontainer" + "github.com/docker/libcontainer/configs" ) -type CreateCommand func(container *libcontainer.Config, console, dataPath, init string, childPipe *os.File, args []string) *exec.Cmd +type CreateCommand func(container *configs.Config, console, dataPath, init string, childPipe *os.File, args []string) *exec.Cmd diff --git a/namespaces/exec.go b/namespaces/exec.go index b7873edd0..0822154de 100644 --- a/namespaces/exec.go +++ b/namespaces/exec.go @@ -9,10 +9,10 @@ import ( "os/exec" "syscall" - "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups/fs" "github.com/docker/libcontainer/cgroups/systemd" + "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/network" "github.com/docker/libcontainer/system" ) @@ -21,36 +21,45 @@ import ( // Move this to libcontainer package. // Exec performs setup outside of a namespace so that a container can be // executed. Exec is a high level function for working with container namespaces. -func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Writer, console, dataPath string, args []string, createCommand CreateCommand, startCallback func()) (int, error) { +func Exec(args []string, env []string, command *exec.Cmd, container *configs.Config, state *configs.State) error { var err error // create a pipe so that we can syncronize with the namespaced process and // pass the state and configuration to the child process parent, child, err := newInitPipe() if err != nil { - return -1, err + return err } defer parent.Close() - command := createCommand(container, console, dataPath, os.Args[0], child, args) - // Note: these are only used in non-tty mode - // if there is a tty for the container it will be opened within the namespace and the - // fds will be duped to stdin, stdiout, and stderr - command.Stdin = stdin - command.Stdout = stdout - command.Stderr = stderr + command.ExtraFiles = []*os.File{child} + command.Dir = container.RootFs if err := command.Start(); err != nil { child.Close() - return -1, err + return err } child.Close() - terminate := func(terr error) (int, error) { + terminate := func(terr error) error { // TODO: log the errors for kill and wait command.Process.Kill() command.Wait() - return -1, terr + return terr + } + + encoder := json.NewEncoder(parent) + + if err := encoder.Encode(container); err != nil { + return terminate(err) + } + + process := processArgs{ + Env: append(env[0:], container.Env...), + Args: args, + } + if err := encoder.Encode(process); err != nil { + return terminate(err) } started, err := system.GetProcessStartTime(command.Process.Pid) @@ -71,7 +80,7 @@ func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Wri return terminate(err) } // send the state to the container's init process then shutdown writes for the parent - if err := json.NewEncoder(parent).Encode(networkState); err != nil { + if err := encoder.Encode(networkState); err != nil { return terminate(err) } // shutdown writes for the parent side of the pipe @@ -79,18 +88,6 @@ func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Wri return terminate(err) } - state := &libcontainer.State{ - InitPid: command.Process.Pid, - InitStartTime: started, - NetworkState: networkState, - CgroupPaths: cgroupPaths, - } - - if err := libcontainer.SaveState(dataPath, state); err != nil { - return terminate(err) - } - defer libcontainer.DeleteState(dataPath) - // wait for the child process to fully complete and receive an error message // if one was encoutered var ierr *initError @@ -101,16 +98,12 @@ func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Wri return terminate(ierr) } - if startCallback != nil { - startCallback() - } + state.InitPid = command.Process.Pid + state.InitStartTime = started + state.NetworkState = networkState + state.CgroupPaths = cgroupPaths - if err := command.Wait(); err != nil { - if _, ok := err.(*exec.ExitError); !ok { - return -1, err - } - } - return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil + return nil } // DefaultCreateCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces @@ -122,7 +115,7 @@ func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Wri // root: the path to the container json file and information // pipe: sync pipe to synchronize the parent and child processes // args: the arguments to pass to the container to run as the user's program -func DefaultCreateCommand(container *libcontainer.Config, console, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { +func DefaultCreateCommand(container *configs.Config, console, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { // get our binary name from arg0 so we can always reexec ourself env := []string{ "console=" + console, @@ -148,7 +141,7 @@ func DefaultCreateCommand(container *libcontainer.Config, console, dataPath, ini // SetupCgroups applies the cgroup restrictions to the process running in the container based // on the container's configuration -func SetupCgroups(container *libcontainer.Config, nspid int) (map[string]string, error) { +func SetupCgroups(container *configs.Config, nspid int) (map[string]string, error) { if container.Cgroups != nil { c := container.Cgroups if systemd.UseSystemd() { @@ -161,7 +154,7 @@ func SetupCgroups(container *libcontainer.Config, nspid int) (map[string]string, // InitializeNetworking creates the container's network stack outside of the namespace and moves // interfaces into the container's net namespaces if necessary -func InitializeNetworking(container *libcontainer.Config, nspid int, networkState *network.NetworkState) error { +func InitializeNetworking(container *configs.Config, nspid int, networkState *network.NetworkState) error { for _, config := range container.Networks { strategy, err := network.GetStrategy(config.Type) if err != nil { diff --git a/namespaces/execin.go b/namespaces/execin.go index 430dc72fe..2b63b8c6f 100644 --- a/namespaces/execin.go +++ b/namespaces/execin.go @@ -12,16 +12,16 @@ import ( "strconv" "syscall" - "github.com/docker/libcontainer" "github.com/docker/libcontainer/apparmor" "github.com/docker/libcontainer/cgroups" + "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/label" "github.com/docker/libcontainer/system" ) // ExecIn reexec's the initPath with the argv 0 rewrite to "nsenter" so that it is able to run the // setns code in a single threaded environment joining the existing containers' namespaces. -func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath, action string, +func ExecIn(container *configs.Config, state *configs.State, userArgs []string, initPath, action string, stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) { args := []string{fmt.Sprintf("nsenter-%s", action), "--nspid", strconv.Itoa(state.InitPid)} @@ -91,7 +91,7 @@ func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs // Finalize expects that the setns calls have been setup and that is has joined an // existing namespace -func FinalizeSetns(container *libcontainer.Config, args []string) error { +func FinalizeSetns(container *configs.Config, args []string) error { // clear the current processes env and replace it with the environment defined on the container if err := LoadContainerEnvironment(container); err != nil { return err @@ -118,6 +118,6 @@ func FinalizeSetns(container *libcontainer.Config, args []string) error { panic("unreachable") } -func EnterCgroups(state *libcontainer.State, pid int) error { +func EnterCgroups(state *configs.State, pid int) error { return cgroups.EnterPid(state.CgroupPaths, pid) } diff --git a/namespaces/init.go b/namespaces/init.go index 5c7e1a71d..441b3c340 100644 --- a/namespaces/init.go +++ b/namespaces/init.go @@ -10,8 +10,8 @@ import ( "strings" "syscall" - "github.com/docker/libcontainer" "github.com/docker/libcontainer/apparmor" + "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/console" "github.com/docker/libcontainer/label" "github.com/docker/libcontainer/mount" @@ -24,13 +24,20 @@ import ( "github.com/docker/libcontainer/utils" ) +// Process is used for transferring parameters from Exec() to Init() +type processArgs struct { + Args []string `json:"args,omitempty"` + Env []string `json:"environment,omitempty"` + ConsolePath string `json:"console_path,omitempty"` +} + // TODO(vishh): This is part of the libcontainer API and it does much more than just namespaces related work. // Move this to libcontainer package. // Init is the init process that first runs inside a new namespace to setup mounts, users, networking, // and other options required for the new container. // The caller of Init function has to ensure that the go runtime is locked to an OS thread // (using runtime.LockOSThread) else system calls like setns called within Init may not work as intended. -func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pipe *os.File, args []string) (err error) { +func Init(pipe *os.File) (err error) { defer func() { // if we have an error during the initialization of the container's init then send it back to the // parent process in the form of an initError. @@ -48,6 +55,23 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip pipe.Close() }() + decoder := json.NewDecoder(pipe) + + var container *configs.Config + if err := decoder.Decode(&container); err != nil { + return err + } + + var process *processArgs + if err := decoder.Decode(&process); err != nil { + return err + } + + uncleanRootfs, err := os.Getwd() + if err != nil { + return err + } + rootfs, err := utils.ResolveRootfs(uncleanRootfs) if err != nil { return err @@ -61,22 +85,22 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip // We always read this as it is a way to sync with the parent as well var networkState *network.NetworkState - if err := json.NewDecoder(pipe).Decode(&networkState); err != nil { + if err := decoder.Decode(&networkState); err != nil { return err } // join any namespaces via a path to the namespace fd if provided if err := joinExistingNamespaces(container.Namespaces); err != nil { return err } - if consolePath != "" { - if err := console.OpenAndDup(consolePath); err != nil { + if process.ConsolePath != "" { + if err := console.OpenAndDup(process.ConsolePath); err != nil { return err } } if _, err := syscall.Setsid(); err != nil { return fmt.Errorf("setsid %s", err) } - if consolePath != "" { + if process.ConsolePath != "" { if err := system.Setctty(); err != nil { return fmt.Errorf("setctty %s", err) } @@ -96,7 +120,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip label.Init() if err := mount.InitializeMountNamespace(rootfs, - consolePath, + process.ConsolePath, container.RestrictSys, (*mount.MountConfig)(container.MountConfig)); err != nil { return fmt.Errorf("setup mount namespace %s", err) @@ -138,7 +162,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip return fmt.Errorf("restore parent death signal %s", err) } - return system.Execv(args[0], args[0:], os.Environ()) + return system.Execv(process.Args[0], process.Args[0:], process.Env) } // RestoreParentDeathSignal sets the parent death signal to old. @@ -218,7 +242,7 @@ func SetupUser(u string) error { // setupVethNetwork uses the Network config if it is not nil to initialize // the new veth interface inside the container for use by changing the name to eth0 // setting the MTU and IP address along with the default gateway -func setupNetwork(container *libcontainer.Config, networkState *network.NetworkState) error { +func setupNetwork(container *configs.Config, networkState *network.NetworkState) error { for _, config := range container.Networks { strategy, err := network.GetStrategy(config.Type) if err != nil { @@ -233,7 +257,7 @@ func setupNetwork(container *libcontainer.Config, networkState *network.NetworkS return nil } -func setupRoute(container *libcontainer.Config) error { +func setupRoute(container *configs.Config) error { for _, config := range container.Routes { if err := netlink.AddRoute(config.Destination, config.Source, config.Gateway, config.InterfaceName); err != nil { return err @@ -242,7 +266,7 @@ func setupRoute(container *libcontainer.Config) error { return nil } -func setupRlimits(container *libcontainer.Config) error { +func setupRlimits(container *configs.Config) error { for _, rlimit := range container.Rlimits { l := &syscall.Rlimit{Max: rlimit.Hard, Cur: rlimit.Soft} if err := syscall.Setrlimit(rlimit.Type, l); err != nil { @@ -255,7 +279,7 @@ func setupRlimits(container *libcontainer.Config) error { // FinalizeNamespace drops the caps, sets the correct user // and working dir, and closes any leaky file descriptors // before execing the command inside the namespace -func FinalizeNamespace(container *libcontainer.Config) error { +func FinalizeNamespace(container *configs.Config) error { // Ensure that all non-standard fds we may have accidentally // inherited are marked close-on-exec so they stay out of the // container @@ -295,7 +319,7 @@ func FinalizeNamespace(container *libcontainer.Config) error { return nil } -func LoadContainerEnvironment(container *libcontainer.Config) error { +func LoadContainerEnvironment(container *configs.Config) error { os.Clearenv() for _, pair := range container.Env { p := strings.SplitN(pair, "=", 2) @@ -311,7 +335,7 @@ func LoadContainerEnvironment(container *libcontainer.Config) error { // joinExistingNamespaces gets all the namespace paths specified for the container and // does a setns on the namespace fd so that the current process joins the namespace. -func joinExistingNamespaces(namespaces []libcontainer.Namespace) error { +func joinExistingNamespaces(namespaces []configs.Namespace) error { for _, ns := range namespaces { if ns.Path != "" { f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0) diff --git a/namespaces/utils.go b/namespaces/utils.go index 556ea6699..4aa590fd7 100644 --- a/namespaces/utils.go +++ b/namespaces/utils.go @@ -6,7 +6,7 @@ import ( "os" "syscall" - "github.com/docker/libcontainer" + "github.com/docker/libcontainer/configs" ) type initError struct { @@ -37,7 +37,7 @@ func newInitPipe() (parent *os.File, child *os.File, err error) { // GetNamespaceFlags parses the container's Namespaces options to set the correct // flags on clone, unshare, and setns -func GetNamespaceFlags(namespaces []libcontainer.Namespace) (flag int) { +func GetNamespaceFlags(namespaces []configs.Namespace) (flag int) { for _, v := range namespaces { flag |= namespaceInfo[v.Name] } diff --git a/nsinit/exec.go b/nsinit/exec.go index 4c7d6316c..266f59356 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -3,19 +3,14 @@ package main import ( "crypto/md5" "fmt" - "io" "log" "os" - "os/exec" - "os/signal" "syscall" "text/tabwriter" "github.com/codegangsta/cli" - "github.com/docker/docker/pkg/term" "github.com/docker/libcontainer" - consolepkg "github.com/docker/libcontainer/console" - "github.com/docker/libcontainer/namespaces" + "github.com/docker/libcontainer/configs" ) var ( @@ -66,7 +61,7 @@ func execAction(context *cli.Context) { id := fmt.Sprintf("%x", md5.Sum([]byte(dataPath))) container, err := factory.Load(id) if err != nil && !os.IsNotExist(err) { - var config *libcontainer.Config + var config *configs.Config config, err = loadConfig() if err != nil { @@ -105,144 +100,3 @@ func execAction(context *cli.Context) { os.Exit(exitCode) } - -// the process for execing a new process inside an existing container is that we have to exec ourself -// with the nsenter argument so that the C code can setns an the namespaces that we require. Then that -// code path will drop us into the path that we can do the final setup of the namespace and exec the users -// application. -func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, action string, context *cli.Context) (int, error) { - var ( - master *os.File - console string - err error - - sigc = make(chan os.Signal, 10) - - stdin = os.Stdin - stdout = os.Stdout - stderr = os.Stderr - ) - signal.Notify(sigc) - - if config.Tty { - stdin = nil - stdout = nil - stderr = nil - - master, console, err = consolepkg.CreateMasterAndConsole() - if err != nil { - return -1, err - } - - go io.Copy(master, os.Stdin) - go io.Copy(os.Stdout, master) - - state, err := term.SetRawTerminal(os.Stdin.Fd()) - if err != nil { - return -1, err - } - - defer term.RestoreTerminal(os.Stdin.Fd(), state) - } - - startCallback := func(cmd *exec.Cmd) { - go func() { - resizeTty(master) - - for sig := range sigc { - switch sig { - case syscall.SIGWINCH: - resizeTty(master) - default: - cmd.Process.Signal(sig) - } - } - }() - } - - return namespaces.ExecIn(config, state, context.Args(), os.Args[0], action, stdin, stdout, stderr, console, startCallback) -} - -// startContainer starts the container. Returns the exit status or -1 and an -// error. -// -// Signals sent to the current process will be forwarded to container. -func startContainer(container *libcontainer.Config, dataPath string, args []string) (int, error) { - var ( - cmd *exec.Cmd - sigc = make(chan os.Signal, 10) - ) - - signal.Notify(sigc) - - createCommand := func(container *libcontainer.Config, console, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { - cmd = namespaces.DefaultCreateCommand(container, console, dataPath, init, pipe, args) - if logPath != "" { - cmd.Env = append(cmd.Env, fmt.Sprintf("log=%s", logPath)) - } - return cmd - } - - var ( - master *os.File - console string - err error - - stdin = os.Stdin - stdout = os.Stdout - stderr = os.Stderr - ) - - if container.Tty { - stdin = nil - stdout = nil - stderr = nil - - master, console, err = consolepkg.CreateMasterAndConsole() - if err != nil { - return -1, err - } - - go io.Copy(master, os.Stdin) - go io.Copy(os.Stdout, master) - - state, err := term.SetRawTerminal(os.Stdin.Fd()) - if err != nil { - return -1, err - } - - defer term.RestoreTerminal(os.Stdin.Fd(), state) - } - - startCallback := func() { - go func() { - resizeTty(master) - - for sig := range sigc { - switch sig { - case syscall.SIGWINCH: - resizeTty(master) - default: - cmd.Process.Signal(sig) - } - } - }() - } - - return namespaces.Exec(container, stdin, stdout, stderr, console, dataPath, args, createCommand, startCallback) -} - -func resizeTty(master *os.File) { - if master == nil { - return - } - - ws, err := term.GetWinsize(os.Stdin.Fd()) - if err != nil { - return - } - - if err := term.SetWinsize(master.Fd(), ws); err != nil { - return - } -} diff --git a/nsinit/init.go b/nsinit/init.go index 853c9e8e9..088361390 100644 --- a/nsinit/init.go +++ b/nsinit/init.go @@ -1,9 +1,7 @@ package main import ( - "github.com/docker/libcontainer/system" "log" - "os" "github.com/codegangsta/cli" "github.com/docker/libcontainer" @@ -36,9 +34,5 @@ func initAction(context *cli.Context) { log.Fatal(err) } - args := []string(context.Args()) - - if err := system.Execv(args[0], args[0:], os.Environ()); err != nil { - log.Fatal(err) - } + panic("This line should never been executed") } diff --git a/nsinit/nsenter.go b/nsinit/nsenter.go index 8dc149f4f..8365215e4 100644 --- a/nsinit/nsenter.go +++ b/nsinit/nsenter.go @@ -9,7 +9,7 @@ import ( "strings" "text/tabwriter" - "github.com/docker/libcontainer" + "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/devices" "github.com/docker/libcontainer/mount/nodes" "github.com/docker/libcontainer/namespaces" @@ -17,7 +17,7 @@ import ( ) // nsenterExec exec's a process inside an existing container -func nsenterExec(config *libcontainer.Config, args []string) { +func nsenterExec(config *configs.Config, args []string) { if err := namespaces.FinalizeSetns(config, args); err != nil { log.Fatalf("failed to nsenter: %s", err) } @@ -26,7 +26,7 @@ func nsenterExec(config *libcontainer.Config, args []string) { // nsenterMknod runs mknod inside an existing container // // mknod -func nsenterMknod(config *libcontainer.Config, args []string) { +func nsenterMknod(config *configs.Config, args []string) { if len(args) != 4 { log.Fatalf("expected mknod to have 4 arguments not %d", len(args)) } @@ -56,7 +56,7 @@ func nsenterMknod(config *libcontainer.Config, args []string) { } // nsenterIp displays the network interfaces inside a container's net namespace -func nsenterIp(config *libcontainer.Config, args []string) { +func nsenterIp(config *configs.Config, args []string) { interfaces, err := net.Interfaces() if err != nil { log.Fatal(err) diff --git a/nsinit/utils.go b/nsinit/utils.go index 6a8aafbf1..e02a1b3a3 100644 --- a/nsinit/utils.go +++ b/nsinit/utils.go @@ -7,23 +7,23 @@ import ( "path/filepath" "github.com/codegangsta/cli" - "github.com/docker/libcontainer" + "github.com/docker/libcontainer/configs" ) // rFunc is a function registration for calling after an execin type rFunc struct { Usage string - Action func(*libcontainer.Config, []string) + Action func(*configs.Config, []string) } -func loadConfig() (*libcontainer.Config, error) { +func loadConfig() (*configs.Config, error) { f, err := os.Open(filepath.Join(dataPath, "container.json")) if err != nil { return nil, err } defer f.Close() - var container *libcontainer.Config + var container *configs.Config if err := json.NewDecoder(f).Decode(&container); err != nil { return nil, err } @@ -57,11 +57,11 @@ func findUserArgs() []string { // loadConfigFromFd loads a container's config from the sync pipe that is provided by // fd 3 when running a process -func loadConfigFromFd() (*libcontainer.Config, error) { +func loadConfigFromFd() (*configs.Config, error) { pipe := os.NewFile(3, "pipe") defer pipe.Close() - var config *libcontainer.Config + var config *configs.Config if err := json.NewDecoder(pipe).Decode(&config); err != nil { return nil, err } diff --git a/process.go b/process.go index 489666a58..924de2ecf 100644 --- a/process.go +++ b/process.go @@ -17,11 +17,8 @@ type ProcessConfig struct { // If a reader or writer is nil, the input stream is assumed to be empty and the output is // discarded. // - // The readers and writers, if supplied, are closed when the process terminates. Their Close - // methods should be idempotent. - // // Stdout and Stderr may refer to the same writer in which case the output is interspersed. - Stdin io.ReadCloser - Stdout io.WriteCloser - Stderr io.WriteCloser + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer }