Refactor execin code to be simpler

Signed-off-by: Michael Crosby <michael@docker.com>
This commit is contained in:
Michael Crosby 2014-08-06 18:44:41 -07:00
parent 390f413a92
commit a48b001013
5 changed files with 135 additions and 64 deletions

View File

@ -3,70 +3,79 @@
package namespaces
import (
"encoding/json"
"io"
"os"
"os/exec"
"strconv"
"syscall"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/label"
"github.com/docker/libcontainer/syncpipe"
"github.com/docker/libcontainer/system"
)
// ExecIn uses an existing pid and joins the pid's namespaces with the new command.
func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []string) error {
// Enter the namespace and then finish setup
args, err := GetNsEnterCommand(strconv.Itoa(state.InitPid), container, "", args)
if err != nil {
return err
}
func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath string,
stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
finalArgs := append([]string{os.Args[0]}, args...)
if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil {
return err
}
panic("unreachable")
}
func getContainerJson(container *libcontainer.Config) (string, error) {
// TODO(vmarmol): If this gets too long, send it over a pipe to the child.
// Marshall the container into JSON since it won't be available in the namespace.
containerJson, err := json.Marshal(container)
if err != nil {
return "", err
}
return string(containerJson), nil
}
func GetNsEnterCommand(initPid string, container *libcontainer.Config, console string, args []string) ([]string, error) {
containerJson, err := getContainerJson(container)
if err != nil {
return nil, err
}
out := []string{
"--nspid", initPid,
"--containerjson", containerJson,
}
args := []string{"--nspid", strconv.Itoa(state.InitPid)}
if console != "" {
out = append(out, "--console", console)
args = append(args, "--console", console)
}
out = append(out, "nsenter")
out = append(out, "--")
out = append(out, args...)
return out, nil
args = append(args, "nsenter", "--")
args = append(args, userArgs...)
cmd := exec.Command(initPath, args...)
pipe, err := syncpipe.NewSyncPipe()
if err != nil {
return -1, err
}
defer pipe.Close()
// 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
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.ExtraFiles = []*os.File{pipe.Child()}
if err := cmd.Start(); err != nil {
return -1, err
}
pipe.CloseChild()
if err := pipe.SendToChild(container); err != nil {
cmd.Process.Kill()
cmd.Wait()
return -1, err
}
if startCallback != nil {
startCallback(cmd)
}
if err := cmd.Wait(); err != nil {
if _, ok := err.(*exec.ExitError); !ok {
return -1, err
}
}
return cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil
}
// Run a command in a container after entering the namespace.
func NsEnter(container *libcontainer.Config, args []string) error {
// clear the current processes env and replace it with the environment
// defined on the container
// 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 {
// clear the current processes env and replace it with the environment defined on the container
if err := LoadContainerEnvironment(container); err != nil {
return err
}
if err := FinalizeNamespace(container); err != nil {
return err
}
@ -80,5 +89,6 @@ func NsEnter(container *libcontainer.Config, args []string) error {
if err := system.Execv(args[0], args[0:], container.Env); err != nil {
return err
}
panic("unreachable")
}

View File

@ -96,7 +96,7 @@ void nsenter()
}
static const struct option longopts[] = {
{"nspid", required_argument, NULL, 'n'},
{"containerjson", required_argument, NULL, 'c'},
{"containerjson", optional_argument, NULL, 'c'},
{"console", optional_argument, NULL, 't'},
{NULL, 0, NULL, 0}
};
@ -126,7 +126,7 @@ void nsenter()
return;
}
if (container_json == NULL || init_pid_str == NULL) {
if (init_pid_str == NULL) {
print_usage();
exit(1);
}
@ -147,6 +147,7 @@ void nsenter()
fprintf(stderr, "setsid failed. Error: %s\n", strerror(errno));
exit(1);
}
// before we setns we need to dup the console
int consolefd = -1;
if (console != NULL) {
@ -158,6 +159,7 @@ void nsenter()
exit(1);
}
}
// Setns on all supported namespaces.
char ns_dir[PATH_MAX];
memset(ns_dir, 0, PATH_MAX);
@ -211,6 +213,7 @@ void nsenter()
exit(1);
}
}
// Finish executing, let the Go runtime take over.
return;
} else {
@ -222,12 +225,14 @@ void nsenter()
strerror(errno));
exit(1);
}
// Forward the child's exit code or re-send its death signal.
if (WIFEXITED(status)) {
exit(WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
kill(getpid(), WTERMSIG(status));
}
exit(1);
}

View File

@ -26,9 +26,11 @@ func NsInit() {
app.Author = "libcontainer maintainers"
app.Flags = []cli.Flag{
cli.StringFlag{Name: "nspid"},
cli.StringFlag{Name: "containerjson"},
cli.StringFlag{Name: "console"}}
cli.StringFlag{Name: "console"},
}
app.Before = preload
app.Commands = []cli.Command{
execCommand,
initCommand,

View File

@ -36,7 +36,7 @@ func execAction(context *cli.Context) {
}
if state != nil {
err = namespaces.ExecIn(container, state, []string(context.Args()))
exitCode, err = startInExistingContainer(container, state, context)
} else {
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
}
@ -48,6 +48,63 @@ 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 file setup of the namespace and exec the users
// application.
func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, 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], stdin, stdout, stderr, console, startCallback)
}
// startContainer starts the container. Returns the exit status or -1 and an
// error.
//

View File

@ -2,10 +2,11 @@ package nsinit
import (
"log"
"strconv"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/namespaces"
"github.com/docker/libcontainer/syncpipe"
)
var nsenterCommand = cli.Command{
@ -14,24 +15,20 @@ var nsenterCommand = cli.Command{
Action: nsenterAction,
}
// 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 nsenterAction(context *cli.Context) {
args := context.Args()
if len(args) == 0 {
args = []string{"/bin/bash"}
}
container, err := loadContainerFromJson(context.GlobalString("containerjson"))
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
if err != nil {
log.Fatalf("unable to load container: %s", err)
log.Fatalf("unable to create sync pipe: %s", err)
}
nspid, err := strconv.Atoi(context.GlobalString("nspid"))
if nspid <= 0 || err != nil {
log.Fatalf("cannot enter into namespaces without valid pid: %q - %s", nspid, err)
var config *libcontainer.Config
if err := syncPipe.ReadFromParent(&config); err != nil {
log.Fatalf("reading container config from parent: %s", err)
}
if err := namespaces.NsEnter(container, args); err != nil {
if err := namespaces.FinalizeSetns(config, context.Args()); err != nil {
log.Fatalf("failed to nsenter: %s", err)
}
}