diff --git a/libcontainer/nsinit/command.go b/libcontainer/nsinit/command.go deleted file mode 100644 index 3c7a035..0000000 --- a/libcontainer/nsinit/command.go +++ /dev/null @@ -1,37 +0,0 @@ -package nsinit - -import ( - "os" - "os/exec" - - "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/system" -) - -// CommandFactory takes the container's configuration and options passed by the -// parent processes and creates an *exec.Cmd that will be used to fork/exec the -// namespaced init process -type CommandFactory interface { - Create(container *libcontainer.Container, console string, syncFd *os.File, args []string) *exec.Cmd -} - -type DefaultCommandFactory struct { - Root string -} - -// Create will return an exec.Cmd with the Cloneflags set to the proper namespaces -// defined on the container's configuration and use the current binary as the init with the -// args provided -func (c *DefaultCommandFactory) Create(container *libcontainer.Container, console string, pipe *os.File, args []string) *exec.Cmd { - // get our binary name from arg0 so we can always reexec ourself - command := exec.Command(os.Args[0], append([]string{ - "-console", console, - "-pipe", "3", - "-root", c.Root, - "init"}, args...)...) - - system.SetCloneFlags(command, uintptr(GetNamespaceFlags(container.Namespaces))) - command.Env = container.Env - command.ExtraFiles = []*os.File{pipe} - return command -} diff --git a/libcontainer/nsinit/exec.go b/libcontainer/nsinit/exec.go index 45a2a8b..5aa98af 100644 --- a/libcontainer/nsinit/exec.go +++ b/libcontainer/nsinit/exec.go @@ -20,7 +20,7 @@ import ( // Exec performes setup outside of a namespace so that a container can be // executed. Exec is a high level function for working with container namespaces. -func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, pidRoot string, args []string, startCallback func()) (int, error) { +func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath string, args []string, startCallback func()) (int, error) { var ( master *os.File console string @@ -42,7 +42,7 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, pidRoo term.SetMaster(master) } - command := ns.commandFactory.Create(container, console, syncPipe.child, args) + command := CreateCommand(container, console, rootfs, dataPath, os.Args[0], syncPipe.child, args) if err := term.Attach(command); err != nil { return -1, err } @@ -56,11 +56,11 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, pidRoo if err != nil { return -1, err } - if err := WritePid(pidRoot, command.Process.Pid, started); err != nil { + if err := WritePid(dataPath, command.Process.Pid, started); err != nil { command.Process.Kill() return -1, err } - defer DeletePid(pidRoot) + defer DeletePid(dataPath) // Do this before syncing with child so that no children // can escape the cgroup @@ -90,8 +90,45 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, pidRoo return -1, err } } - status := command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() - return status, err + return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil +} + +// CreateCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces +// defined on the container's configuration and use the current binary as the init with the +// args provided +// +// console: the /dev/console to setup inside the container +// init: the progam executed inside the namespaces +// root: the path to the container json file and information +// pipe: sync pipe to syncronize the parent and child processes +// args: the arguemnts to pass to the container to run as the user's program +func CreateCommand(container *libcontainer.Container, console, rootfs, 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, + "pipe=3", + "data_path=" + dataPath, + } + + /* + TODO: move user and wd into env + if user != "" { + env = append(env, "user="+user) + } + if workingDir != "" { + env = append(env, "wd="+workingDir) + } + */ + + command := exec.Command(init, append([]string{"init"}, args...)...) + // make sure the process is executed inside the context of the rootfs + command.Dir = rootfs + command.Env = append(os.Environ(), env...) + + system.SetCloneFlags(command, uintptr(GetNamespaceFlags(container.Namespaces))) + command.ExtraFiles = []*os.File{pipe} + + return command } // SetupCgroups applies the cgroup restrictions to the process running in the contaienr based diff --git a/libcontainer/nsinit/execin.go b/libcontainer/nsinit/execin.go index 8507d9b..ac405e1 100644 --- a/libcontainer/nsinit/execin.go +++ b/libcontainer/nsinit/execin.go @@ -16,7 +16,7 @@ import ( ) // ExecIn uses an existing pid and joins the pid's namespaces with the new command. -func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) { +func ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) { for _, nsv := range container.Namespaces { // skip the PID namespace on unshare because it it not supported if nsv.Key != "NEWPID" { @@ -25,7 +25,7 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s } } } - fds, err := ns.getNsFds(nspid, container) + fds, err := getNsFds(nspid, container) closeFds := func() { for _, f := range fds { system.Closefd(f) @@ -95,7 +95,7 @@ dropAndExec: panic("unreachable") } -func (ns *linuxNs) getNsFds(pid int, container *libcontainer.Container) ([]uintptr, error) { +func getNsFds(pid int, container *libcontainer.Container) ([]uintptr, error) { fds := make([]uintptr, len(container.Namespaces)) for i, ns := range container.Namespaces { f, err := os.OpenFile(filepath.Join("/proc/", strconv.Itoa(pid), "ns", ns.File), os.O_RDONLY, 0) diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index 02785bf..faec12a 100644 --- a/libcontainer/nsinit/init.go +++ b/libcontainer/nsinit/init.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "runtime" + "strings" "syscall" "github.com/dotcloud/docker/pkg/apparmor" @@ -22,12 +23,18 @@ import ( // 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. -func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error { +func Init(container *libcontainer.Container, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error { rootfs, err := utils.ResolveRootfs(uncleanRootfs) if err != nil { return err } + // clear the current processes env and replace it with the environment + // defined on the container + if err := LoadContainerEnvironment(container); err != nil { + return err + } + // We always read this as it is a way to sync with the parent as well context, err := syncPipe.ReadFromParent() if err != nil { @@ -132,3 +139,14 @@ func FinalizeNamespace(container *libcontainer.Container) error { } return nil } + +func LoadContainerEnvironment(container *libcontainer.Container) error { + os.Clearenv() + for _, pair := range container.Env { + p := strings.SplitN(pair, "=", 2) + if err := os.Setenv(p[0], p[1]); err != nil { + return err + } + } + return nil +} diff --git a/libcontainer/nsinit/nsinit.go b/libcontainer/nsinit/nsinit.go deleted file mode 100644 index 506a39e..0000000 --- a/libcontainer/nsinit/nsinit.go +++ /dev/null @@ -1,22 +0,0 @@ -package nsinit - -import "github.com/dotcloud/docker/pkg/libcontainer" - -// NsInit is an interface with the public facing methods to provide high level -// exec operations on a container -type NsInit interface { - Exec(container *libcontainer.Container, term Terminal, pidRoot string, args []string, startCallback func()) (int, error) - ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) - Init(container *libcontainer.Container, uncleanRootfs, console string, syncPipe *SyncPipe, args []string) error -} - -type linuxNs struct { - root string - commandFactory CommandFactory -} - -func NewNsInit(command CommandFactory) NsInit { - return &linuxNs{ - commandFactory: command, - } -} diff --git a/libcontainer/nsinit/nsinit/main.go b/libcontainer/nsinit/nsinit/main.go index bcb0068..6faa9c6 100644 --- a/libcontainer/nsinit/nsinit/main.go +++ b/libcontainer/nsinit/nsinit/main.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "flag" "io/ioutil" "log" "os" @@ -14,76 +13,65 @@ import ( ) var ( - root, console, logs string - pipeFd int + dataPath = os.Getenv("data_path") + console = os.Getenv("console") + rawPipeFd = os.Getenv("pipe") ) -func registerFlags() { - flag.StringVar(&console, "console", "", "console (pty slave) path") - flag.IntVar(&pipeFd, "pipe", 0, "sync pipe fd") - flag.StringVar(&root, "root", ".", "root for storing configuration data") - flag.StringVar(&logs, "log", "none", "set stderr or a filepath to enable logging") - - flag.Parse() -} - func main() { - registerFlags() - - if flag.NArg() < 1 { - log.Fatalf("wrong number of arguments %d", flag.NArg()) + if len(os.Args) < 2 { + log.Fatalf("invalid number of arguments %d", len(os.Args)) } + container, err := loadContainer() if err != nil { - log.Fatalf("Unable to load container: %s", err) + log.Fatalf("unable to load container: %s", err) } - ns, err := newNsInit() - if err != nil { - log.Fatalf("Unable to initialize nsinit: %s", err) - } - - switch flag.Arg(0) { + switch os.Args[1] { case "exec": // this is executed outside of the namespace in the cwd - var exitCode int - nspid, err := readPid() - if err != nil { - if !os.IsNotExist(err) { - log.Fatalf("Unable to read pid: %s", err) - } + var nspid, exitCode int + if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { + log.Fatalf("unable to read pid: %s", err) } + if nspid > 0 { - exitCode, err = ns.ExecIn(container, nspid, flag.Args()[1:]) + exitCode, err = nsinit.ExecIn(container, nspid, os.Args[2:]) } else { term := nsinit.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) - exitCode, err = ns.Exec(container, term, root, flag.Args()[1:], nil) + exitCode, err = nsinit.Exec(container, term, "", dataPath, os.Args[2:], nil) } + if err != nil { - log.Fatalf("Failed to exec: %s", err) + log.Fatalf("failed to exec: %s", err) } os.Exit(exitCode) case "init": // this is executed inside of the namespace to setup the container - cwd, err := os.Getwd() + // by default our current dir is always our rootfs + rootfs, err := os.Getwd() if err != nil { log.Fatal(err) } - if flag.NArg() < 2 { - log.Fatalf("wrong number of arguments %d", flag.NArg()) + + pipeFd, err := strconv.Atoi(rawPipeFd) + if err != nil { + log.Fatal(err) } syncPipe, err := nsinit.NewSyncPipeFromFd(0, uintptr(pipeFd)) if err != nil { - log.Fatalf("Unable to create sync pipe: %s", err) + log.Fatalf("unable to create sync pipe: %s", err) } - if err := ns.Init(container, cwd, console, syncPipe, flag.Args()[1:]); err != nil { - log.Fatalf("Unable to initialize for container: %s", err) + + if err := nsinit.Init(container, rootfs, console, syncPipe, os.Args[2:]); err != nil { + log.Fatalf("unable to initialize for container: %s", err) } default: - log.Fatalf("command not supported for nsinit %s", flag.Arg(0)) + log.Fatalf("command not supported for nsinit %s", os.Args[0]) } } func loadContainer() (*libcontainer.Container, error) { - f, err := os.Open(filepath.Join(root, "container.json")) + f, err := os.Open(filepath.Join(dataPath, "container.json")) if err != nil { return nil, err } @@ -97,7 +85,7 @@ func loadContainer() (*libcontainer.Container, error) { } func readPid() (int, error) { - data, err := ioutil.ReadFile(filepath.Join(root, "pid")) + data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) if err != nil { return -1, err } @@ -107,7 +95,3 @@ func readPid() (int, error) { } return pid, nil } - -func newNsInit() (nsinit.NsInit, error) { - return nsinit.NewNsInit(&nsinit.DefaultCommandFactory{root}), nil -} diff --git a/libcontainer/nsinit/unsupported.go b/libcontainer/nsinit/unsupported.go index 6274870..972d905 100644 --- a/libcontainer/nsinit/unsupported.go +++ b/libcontainer/nsinit/unsupported.go @@ -6,18 +6,6 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" ) -func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, pidRoot string, args []string, startCallback func()) (int, error) { - return -1, libcontainer.ErrUnsupported -} - -func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) { - return -1, libcontainer.ErrUnsupported -} - -func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, console string, syncPipe *SyncPipe, args []string) error { - return libcontainer.ErrUnsupported -} - func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { return 0 }