diff --git a/namespaces/execin.go b/namespaces/execin.go index 2ac9dba7..8958332a 100644 --- a/namespaces/execin.go +++ b/namespaces/execin.go @@ -3,6 +3,7 @@ package namespaces import ( + "fmt" "io" "os" "os/exec" @@ -18,10 +19,10 @@ import ( // 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 string, +func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath, action string, stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) { - args := []string{"nsenter", "--nspid", strconv.Itoa(state.InitPid)} + args := []string{fmt.Sprintf("nsenter-%s", action), "--nspid", strconv.Itoa(state.InitPid)} if console != "" { args = append(args, "--console", console) diff --git a/nsinit/exec.go b/nsinit/exec.go index f48ca17b..ae3e617b 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -36,7 +36,7 @@ func execAction(context *cli.Context) { } if state != nil { - exitCode, err = startInExistingContainer(container, state, context) + exitCode, err = startInExistingContainer(container, state, "exec", context) } else { exitCode, err = startContainer(container, dataPath, []string(context.Args())) } @@ -52,7 +52,7 @@ func execAction(context *cli.Context) { // 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, context *cli.Context) (int, error) { +func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, action string, context *cli.Context) (int, error) { var ( master *os.File console string @@ -102,7 +102,7 @@ func startInExistingContainer(config *libcontainer.Config, state *libcontainer.S }() } - return namespaces.ExecIn(config, state, context.Args(), os.Args[0], stdin, stdout, stderr, console, startCallback) + 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 diff --git a/nsinit/execfunc.go b/nsinit/execfunc.go new file mode 100644 index 00000000..e8fdf8e7 --- /dev/null +++ b/nsinit/execfunc.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "log" + "os" + "text/tabwriter" + + "github.com/codegangsta/cli" + "github.com/docker/libcontainer" +) + +var execFuncCommand = cli.Command{ + Name: "func", + Usage: "execute a registered function inside an existing container", + Action: execFuncAction, + Flags: []cli.Flag{ + cli.BoolFlag{Name: "list", Usage: "list all registered functions"}, + cli.StringFlag{Name: "func", Usage: "function name to exec inside a container"}, + }, +} + +func execFuncAction(context *cli.Context) { + if context.Bool("list") { + w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) + fmt.Fprint(w, "NAME\tUSAGE\n") + + for k, f := range argvs { + fmt.Fprintf(w, "%s\t%s\n", k, f.Usage) + } + + w.Flush() + + return + } + + var exitCode int + + config, err := loadContainer() + if err != nil { + log.Fatal(err) + } + + // FIXME: remove tty from container config, this should be per process + config.Tty = false + + state, err := libcontainer.GetState(dataPath) + if err != nil { + log.Fatalf("unable to read state.json: %s", err) + } + + exitCode, err = startInExistingContainer(config, state, context.String("func"), context) + if err != nil { + log.Fatal(err) + } + + os.Exit(exitCode) +} diff --git a/nsinit/main.go b/nsinit/main.go index 25a80b22..bd944b82 100644 --- a/nsinit/main.go +++ b/nsinit/main.go @@ -9,11 +9,19 @@ import ( var ( logPath = os.Getenv("log") - argvs = make(map[string]func()) + argvs = make(map[string]*rFunc) ) func init() { - argvs["nsenter"] = nsenter + argvs["nsenter-exec"] = &rFunc{ + Usage: "execute a process inside an existing container", + Action: nsenterExec, + } + + argvs["nsenter-mknod"] = &rFunc{ + Usage: "mknod a device inside an existing container", + Action: nsenterMknod, + } } func preload(context *cli.Context) error { @@ -26,13 +34,23 @@ func preload(context *cli.Context) error { return nil } +func runFunc(f *rFunc) { + userArgs := findUserArgs() + + config, err := loadConfigFromFd() + if err != nil { + log.Fatalf("unable to receive config from sync pipe: %s", err) + } + + f.Action(config, userArgs) +} + func main() { // we need to check our argv 0 for any registred functions to run instead of the // normal cli code path - - action, exists := argvs[os.Args[0]] + f, exists := argvs[os.Args[0]] if exists { - action() + runFunc(f) return } @@ -56,6 +74,7 @@ func main() { configCommand, pauseCommand, unpauseCommand, + execFuncCommand, } if err := app.Run(os.Args); err != nil { diff --git a/nsinit/nsenter.go b/nsinit/nsenter.go index 954de992..92c73c74 100644 --- a/nsinit/nsenter.go +++ b/nsinit/nsenter.go @@ -2,41 +2,50 @@ package main import ( "log" - "os" + "strconv" "github.com/docker/libcontainer" + "github.com/docker/libcontainer/devices" + "github.com/docker/libcontainer/mount/nodes" "github.com/docker/libcontainer/namespaces" _ "github.com/docker/libcontainer/namespaces/nsenter" - "github.com/docker/libcontainer/syncpipe" ) -func findUserArgs() []string { - i := 0 - for _, a := range os.Args { - i++ - - if a == "--" { - break - } - } - - return os.Args[i:] -} - -// this expects that we already have our namespaces setup by the C initializer -// we are expected to finalize the namespace and exec the user's application -func nsenter() { - syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3) - if err != nil { - log.Fatalf("unable to create sync pipe: %s", err) - } - - var config *libcontainer.Config - if err := syncPipe.ReadFromParent(&config); err != nil { - log.Fatalf("reading container config from parent: %s", err) - } - - if err := namespaces.FinalizeSetns(config, findUserArgs()); err != nil { +// nsenterExec exec's a process inside an existing container +func nsenterExec(config *libcontainer.Config, args []string) { + if err := namespaces.FinalizeSetns(config, args); err != nil { log.Fatalf("failed to nsenter: %s", err) } } + +// nsenterMknod runs mknod inside an existing container +// +// mknod +func nsenterMknod(config *libcontainer.Config, args []string) { + if len(args) != 4 { + log.Fatalf("expected mknod to have 4 arguments not %d", len(args)) + } + + t := rune(args[1][0]) + + major, err := strconv.Atoi(args[2]) + if err != nil { + log.Fatal(err) + } + + minor, err := strconv.Atoi(args[3]) + if err != nil { + log.Fatal(err) + } + + n := &devices.Device{ + Path: args[0], + Type: t, + MajorNumber: int64(major), + MinorNumber: int64(minor), + } + + if err := nodes.CreateDeviceNode("/", n); err != nil { + log.Fatal(err) + } +} diff --git a/nsinit/utils.go b/nsinit/utils.go index 44194d88..0012adcc 100644 --- a/nsinit/utils.go +++ b/nsinit/utils.go @@ -7,8 +7,15 @@ import ( "path/filepath" "github.com/docker/libcontainer" + "github.com/docker/libcontainer/syncpipe" ) +// rFunc is a function registration for calling after an execin +type rFunc struct { + Usage string + Action func(*libcontainer.Config, []string) +} + func loadContainer() (*libcontainer.Config, error) { f, err := os.Open(filepath.Join(dataPath, "container.json")) if err != nil { @@ -44,3 +51,32 @@ func loadContainerFromJson(rawData string) (*libcontainer.Config, error) { return container, nil } + +func findUserArgs() []string { + i := 0 + for _, a := range os.Args { + i++ + + if a == "--" { + break + } + } + + return os.Args[i:] +} + +// 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) { + syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3) + if err != nil { + return nil, err + } + + var config *libcontainer.Config + if err := syncPipe.ReadFromParent(&config); err != nil { + return nil, err + } + + return config, nil +}