diff --git a/libcontainer/nsinit/exec.go b/libcontainer/nsinit/exec.go new file mode 100644 index 0000000..d4ce1ca --- /dev/null +++ b/libcontainer/nsinit/exec.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "os/signal" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" +) + +var execCommand = cli.Command{ + Name: "exec", + Usage: "execute a new command inside a container", + Action: execAction, +} + +func execAction(context *cli.Context) { + var nspid, exitCode int + + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } + + if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { + log.Fatalf("unable to read pid: %s", err) + } + + if nspid > 0 { + err = namespaces.ExecIn(container, nspid, []string(context.Args())) + } else { + term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) + exitCode, err = startContainer(container, term, dataPath, []string(context.Args())) + } + + if err != nil { + log.Fatalf("failed to exec: %s", err) + } + + os.Exit(exitCode) +} + +// 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.Container, term namespaces.Terminal, dataPath string, args []string) (int, error) { + var ( + cmd *exec.Cmd + sigc = make(chan os.Signal, 10) + ) + + signal.Notify(sigc) + + createCommand := func(container *libcontainer.Container, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { + cmd = namespaces.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) + if logPath != "" { + cmd.Env = append(cmd.Env, fmt.Sprintf("log=%s", logPath)) + } + return cmd + } + + startCallback := func() { + go func() { + for sig := range sigc { + cmd.Process.Signal(sig) + } + }() + } + + return namespaces.Exec(container, term, "", dataPath, args, createCommand, startCallback) +} diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go new file mode 100644 index 0000000..20096f0 --- /dev/null +++ b/libcontainer/nsinit/init.go @@ -0,0 +1,48 @@ +package main + +import ( + "log" + "os" + "strconv" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" +) + +var ( + dataPath = os.Getenv("data_path") + console = os.Getenv("console") + rawPipeFd = os.Getenv("pipe") + + initCommand = cli.Command{ + Name: "init", + Usage: "runs the init process inside the namespace", + Action: initAction, + } +) + +func initAction(context *cli.Context) { + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } + + rootfs, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + + pipeFd, err := strconv.Atoi(rawPipeFd) + if err != nil { + log.Fatal(err) + } + + syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(pipeFd)) + if err != nil { + log.Fatalf("unable to create sync pipe: %s", err) + } + + if err := namespaces.Init(container, rootfs, console, syncPipe, []string(context.Args())); err != nil { + log.Fatalf("unable to initialize for container: %s", err) + } +} diff --git a/libcontainer/nsinit/main.go b/libcontainer/nsinit/main.go index bddc199..20132de 100644 --- a/libcontainer/nsinit/main.go +++ b/libcontainer/nsinit/main.go @@ -1,220 +1,40 @@ package main import ( - "encoding/json" - "fmt" - "io/ioutil" "log" "os" - "os/exec" - "os/signal" - "path/filepath" - "strconv" - "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" - "github.com/dotcloud/docker/pkg/libcontainer/namespaces" + "github.com/codegangsta/cli" ) -var ( - dataPath = os.Getenv("data_path") - console = os.Getenv("console") - rawPipeFd = os.Getenv("pipe") -) +var logPath = os.Getenv("log") + +func preload(context *cli.Context) error { + if logPath != "" { + if err := openLog(logPath); err != nil { + return err + } + } + + return nil +} func main() { - if len(os.Args) < 2 { - log.Fatalf("invalid number of arguments %d", len(os.Args)) + app := cli.NewApp() + app.Name = "nsinit" + app.Version = "0.1" + app.Author = "libcontainer maintainers" + + app.Before = preload + app.Commands = []cli.Command{ + execCommand, + initCommand, + statsCommand, + specCommand, + nsenterCommand, } - switch os.Args[1] { - case "exec": // this is executed outside of the namespace in the cwd - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %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 { - err = namespaces.ExecIn(container, nspid, os.Args[2:]) - } else { - term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) - exitCode, err = startContainer(container, term, dataPath, os.Args[2:]) - } - - if err != nil { - log.Fatalf("failed to exec: %s", err) - } - os.Exit(exitCode) - case "nsenter": // this is executed inside the namespace. - // nsinit nsenter ... - if len(os.Args) < 6 { - log.Fatalf("incorrect usage: nsinit nsenter ...") - } - - container, err := loadContainerFromJson(os.Args[4]) - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - nspid, err := strconv.Atoi(os.Args[2]) - if err != nil { - log.Fatalf("unable to read pid: %s from %q", err, os.Args[2]) - } - - if nspid <= 0 { - log.Fatalf("cannot enter into namespaces without valid pid: %q", nspid) - } - - err = namespaces.NsEnter(container, os.Args[3], nspid, os.Args[5:]) - if err != nil { - log.Fatalf("failed to nsenter: %s", err) - } - case "init": // this is executed inside of the namespace to setup the container - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // by default our current dir is always our rootfs - rootfs, err := os.Getwd() - if err != nil { - log.Fatal(err) - } - - pipeFd, err := strconv.Atoi(rawPipeFd) - if err != nil { - log.Fatal(err) - } - syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(pipeFd)) - if err != nil { - log.Fatalf("unable to create sync pipe: %s", err) - } - - if err := namespaces.Init(container, rootfs, console, syncPipe, os.Args[2:]); err != nil { - log.Fatalf("unable to initialize for container: %s", err) - } - case "stats": - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // returns the stats of the current container. - stats, err := getContainerStats(container) - if err != nil { - log.Printf("Failed to get stats - %v\n", err) - os.Exit(1) - } - fmt.Printf("Stats:\n%v\n", stats) - os.Exit(0) - - case "spec": - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // returns the spec of the current container. - spec, err := getContainerSpec(container) - if err != nil { - log.Printf("Failed to get spec - %v\n", err) - os.Exit(1) - } - fmt.Printf("Spec:\n%v\n", spec) - os.Exit(0) - - default: - log.Fatalf("command not supported for nsinit %s", os.Args[1]) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) } } - -func loadContainer() (*libcontainer.Container, error) { - f, err := os.Open(filepath.Join(dataPath, "container.json")) - if err != nil { - log.Printf("Path: %q", filepath.Join(dataPath, "container.json")) - return nil, err - } - defer f.Close() - - var container *libcontainer.Container - if err := json.NewDecoder(f).Decode(&container); err != nil { - return nil, err - } - return container, nil -} - -func loadContainerFromJson(rawData string) (*libcontainer.Container, error) { - container := &libcontainer.Container{} - err := json.Unmarshal([]byte(rawData), container) - if err != nil { - return nil, err - } - return container, nil -} - -func readPid() (int, error) { - data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) - if err != nil { - return -1, err - } - pid, err := strconv.Atoi(string(data)) - if err != nil { - return -1, err - } - return pid, nil -} - -// 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.Container, term namespaces.Terminal, dataPath string, args []string) (int, error) { - var ( - cmd *exec.Cmd - sigc = make(chan os.Signal, 10) - ) - - signal.Notify(sigc) - - createCommand := func(container *libcontainer.Container, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { - cmd = namespaces.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) - return cmd - } - - startCallback := func() { - go func() { - for sig := range sigc { - cmd.Process.Signal(sig) - } - }() - } - - return namespaces.Exec(container, term, "", dataPath, args, createCommand, startCallback) -} - -// returns the container stats in json format. -func getContainerStats(container *libcontainer.Container) (string, error) { - stats, err := fs.GetStats(container.Cgroups) - if err != nil { - return "", err - } - out, err := json.MarshalIndent(stats, "", "\t") - if err != nil { - return "", err - } - return string(out), nil -} - -// returns the container spec in json format. -func getContainerSpec(container *libcontainer.Container) (string, error) { - spec, err := json.MarshalIndent(container, "", "\t") - if err != nil { - return "", err - } - return string(spec), nil -} diff --git a/libcontainer/nsinit/nsenter.go b/libcontainer/nsinit/nsenter.go new file mode 100644 index 0000000..5464428 --- /dev/null +++ b/libcontainer/nsinit/nsenter.go @@ -0,0 +1,40 @@ +package main + +import ( + "log" + "strconv" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" +) + +var nsenterCommand = cli.Command{ + Name: "nsenter", + Usage: "init process for entering an existing namespace", + Action: nsenterAction, +} + +func nsenterAction(context *cli.Context) { + args := context.Args() + if len(args) < 4 { + log.Fatalf("incorrect usage: ...") + } + + container, err := loadContainerFromJson(args.Get(2)) + if err != nil { + log.Fatalf("unable to load container: %s", err) + } + + nspid, err := strconv.Atoi(args.Get(0)) + if err != nil { + log.Fatalf("unable to read pid: %s from %q", err, args.Get(0)) + } + + if nspid <= 0 { + log.Fatalf("cannot enter into namespaces without valid pid: %q", nspid) + } + + if err := namespaces.NsEnter(container, args.Get(1), nspid, args[3:]); err != nil { + log.Fatalf("failed to nsenter: %s", err) + } +} diff --git a/libcontainer/nsinit/spec.go b/libcontainer/nsinit/spec.go new file mode 100644 index 0000000..2eb4da9 --- /dev/null +++ b/libcontainer/nsinit/spec.go @@ -0,0 +1,40 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" +) + +var specCommand = cli.Command{ + Name: "spec", + Usage: "display the container specification", + Action: specAction, +} + +func specAction(context *cli.Context) { + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } + + spec, err := getContainerSpec(container) + if err != nil { + log.Fatalf("Failed to get spec - %v\n", err) + } + + fmt.Printf("Spec:\n%v\n", spec) +} + +// returns the container spec in json format. +func getContainerSpec(container *libcontainer.Container) (string, error) { + spec, err := json.MarshalIndent(container, "", "\t") + if err != nil { + return "", err + } + + return string(spec), nil +} diff --git a/libcontainer/nsinit/stats.go b/libcontainer/nsinit/stats.go new file mode 100644 index 0000000..023b40a --- /dev/null +++ b/libcontainer/nsinit/stats.go @@ -0,0 +1,46 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" +) + +var statsCommand = cli.Command{ + Name: "stats", + Usage: "display statistics for the container", + Action: statsAction, +} + +func statsAction(context *cli.Context) { + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } + + stats, err := getContainerStats(container) + if err != nil { + log.Fatalf("Failed to get stats - %v\n", err) + } + + fmt.Printf("Stats:\n%v\n", stats) +} + +// returns the container stats in json format. +func getContainerStats(container *libcontainer.Container) (string, error) { + stats, err := fs.GetStats(container.Cgroups) + if err != nil { + return "", err + } + + out, err := json.MarshalIndent(stats, "", "\t") + if err != nil { + return "", err + } + + return string(out), nil +} diff --git a/libcontainer/nsinit/utils.go b/libcontainer/nsinit/utils.go new file mode 100644 index 0000000..9926e27 --- /dev/null +++ b/libcontainer/nsinit/utils.go @@ -0,0 +1,62 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer" +) + +func loadContainer() (*libcontainer.Container, error) { + f, err := os.Open(filepath.Join(dataPath, "container.json")) + if err != nil { + return nil, err + } + defer f.Close() + + var container *libcontainer.Container + if err := json.NewDecoder(f).Decode(&container); err != nil { + return nil, err + } + + return container, nil +} + +func readPid() (int, error) { + data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) + if err != nil { + return -1, err + } + + pid, err := strconv.Atoi(string(data)) + if err != nil { + return -1, err + } + + return pid, nil +} + +func openLog(name string) error { + f, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) + if err != nil { + return err + } + + log.SetOutput(f) + + return nil +} + +func loadContainerFromJson(rawData string) (*libcontainer.Container, error) { + var container *libcontainer.Container + + if err := json.Unmarshal([]byte(rawData), &container); err != nil { + return nil, err + } + + return container, nil +}