Просмотр исходного кода

Update libcontainer to f2e78425c377acc7a67a35c3148

Signed-off-by: Michael Crosby <michael@docker.com>
Michael Crosby 11 лет назад
Родитель
Сommit
c74e8b544d

+ 1 - 1
hack/vendor.sh

@@ -59,7 +59,7 @@ rm -rf src/code.google.com/p/go
 mkdir -p src/code.google.com/p/go/src/pkg/archive
 mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
 
-clone git github.com/docker/libcontainer 5589d4d879f1d7e31967a927d3e8b98144fbe06b
+clone git github.com/docker/libcontainer f2e78425c377acc7a67a35c3148069b6285a3c4b
 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
 rm -rf src/github.com/docker/libcontainer/vendor
 eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"

+ 56 - 37
vendor/src/github.com/docker/libcontainer/namespaces/execin.go

@@ -3,70 +3,88 @@
 package namespaces
 
 import (
-	"encoding/json"
+	"io"
 	"os"
+	"os/exec"
+	"path/filepath"
 	"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
-	}
+// 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,
+	stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
 
-	finalArgs := append([]string{os.Args[0]}, args...)
+	args := []string{"nsenter", "--nspid", strconv.Itoa(state.InitPid)}
 
-	if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil {
-		return err
+	if console != "" {
+		args = append(args, "--console", console)
 	}
 
-	panic("unreachable")
-}
+	cmd := &exec.Cmd{
+		Path: initPath,
+		Args: append(args, append([]string{"--"}, userArgs...)...),
+	}
 
-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
+	if filepath.Base(initPath) == initPath {
+		if lp, err := exec.LookPath(initPath); err == nil {
+			cmd.Path = lp
+		}
 	}
-	return string(containerJson), nil
-}
 
-func GetNsEnterCommand(initPid string, container *libcontainer.Config, console string, args []string) ([]string, error) {
-	containerJson, err := getContainerJson(container)
+	pipe, err := syncpipe.NewSyncPipe()
 	if err != nil {
-		return nil, err
+		return -1, err
 	}
+	defer pipe.Close()
 
-	out := []string{
-		"--nspid", initPid,
-		"--containerjson", containerJson,
+	// 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 console != "" {
-		out = append(out, "--console", console)
+	if err := pipe.SendToChild(container); err != nil {
+		cmd.Process.Kill()
+		cmd.Wait()
+		return -1, err
+	}
+
+	if startCallback != nil {
+		startCallback(cmd)
 	}
-	out = append(out, "nsenter")
-	out = append(out, "--")
-	out = append(out, args...)
 
-	return out, nil
+	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 +98,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")
 }

+ 2 - 2
vendor/src/github.com/docker/libcontainer/namespaces/init.go

@@ -48,8 +48,8 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn
 	}
 
 	// We always read this as it is a way to sync with the parent as well
-	networkState, err := syncPipe.ReadFromParent()
-	if err != nil {
+	var networkState *network.NetworkState
+	if err := syncPipe.ReadFromParent(&networkState); err != nil {
 		return err
 	}
 

+ 6 - 0
vendor/src/github.com/docker/libcontainer/namespaces/nsenter/README.md

@@ -0,0 +1,6 @@
+## nsenter
+
+The `nsenter` package registers a special init constructor that is called before the Go runtime has 
+a chance to boot.  This provides us the ability to `setns` on existing namespaces and avoid the issues
+that the Go runtime has with multiple threads.  This constructor is only called if this package is 
+registered, imported, in your go application and the argv 0 is `nsenter`.

+ 13 - 30
vendor/src/github.com/docker/libcontainer/namespaces/nsenter.c → vendor/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c

@@ -71,7 +71,7 @@ int setns(int fd, int nstype)
 void print_usage()
 {
 	fprintf(stderr,
-		"<binary> nsenter --nspid <pid> --containerjson <container_json> -- cmd1 arg1 arg2...\n");
+		"nsenter --nspid <pid> --console <console> -- cmd1 arg1 arg2...\n");
 }
 
 void nsenter()
@@ -80,53 +80,35 @@ void nsenter()
 	char **argv;
 	get_args(&argc, &argv);
 
-	// Ignore if this is not for us.
-	if (argc < 6) {
-		return;
-	}
-	int found_nsenter = 0;
-	for (c = 0; c < argc; ++c) {
-		if (strcmp(argv[c], kNsEnter) == 0) {
-			found_nsenter = 1;
-			break;
-		}
-	}
-	if (!found_nsenter) {
-		return;
-	}
+    // check argv 0 to ensure that we are supposed to setns
+    // we use strncmp to test for a value of "nsenter" but also allows alternate implmentations
+    // after the setns code path to continue to use the argv 0 to determine actions to be run
+    // resulting in the ability to specify "nsenter-mknod", "nsenter-exec", etc...
+    if (strncmp(argv[0], kNsEnter, strlen(kNsEnter)) != 0) {
+        return;
+    }
+
 	static const struct option longopts[] = {
 		{"nspid", required_argument, NULL, 'n'},
-		{"containerjson", required_argument, NULL, 'c'},
-		{"console", optional_argument, NULL, 't'},
+		{"console", required_argument, NULL, 't'},
 		{NULL, 0, NULL, 0}
 	};
 
 	pid_t init_pid = -1;
 	char *init_pid_str = NULL;
-	char *container_json = NULL;
 	char *console = NULL;
-	opterr = 0;
-	while ((c =
-		getopt_long_only(argc, argv, "-n:s:c:", longopts,
-				 NULL)) != -1) {
+	while ((c = getopt_long_only(argc, argv, "n:c:", longopts, NULL)) != -1) {
 		switch (c) {
 		case 'n':
 			init_pid_str = optarg;
 			break;
-		case 'c':
-			container_json = optarg;
-			break;
 		case 't':
 			console = optarg;
 			break;
 		}
 	}
 
-	if (strcmp(argv[optind - 2], kNsEnter) != 0) {
-		return;
-	}
-
-	if (container_json == NULL || init_pid_str == NULL) {
+	if (init_pid_str == NULL) {
 		print_usage();
 		exit(1);
 	}
@@ -228,6 +210,7 @@ void nsenter()
 		} else if (WIFSIGNALED(status)) {
 			kill(getpid(), WTERMSIG(status));
 		}
+
 		exit(1);
 	}
 

+ 1 - 1
vendor/src/github.com/docker/libcontainer/namespaces/nsenter.go → vendor/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.go

@@ -1,6 +1,6 @@
 // +build linux
 
-package namespaces
+package nsenter
 
 /*
 __attribute__((constructor)) init() {

+ 3 - 0
vendor/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter_unsupported.go

@@ -0,0 +1,3 @@
+// +build !linux !cgo
+
+package nsenter

+ 23 - 4
vendor/src/github.com/docker/libcontainer/nsinit/cli.go

@@ -7,7 +7,14 @@ import (
 	"github.com/codegangsta/cli"
 )
 
-var logPath = os.Getenv("log")
+var (
+	logPath = os.Getenv("log")
+	argvs   = make(map[string]func())
+)
+
+func init() {
+	argvs["nsenter"] = nsenter
+}
 
 func preload(context *cli.Context) error {
 	if logPath != "" {
@@ -20,21 +27,33 @@ func preload(context *cli.Context) error {
 }
 
 func NsInit() {
+	// 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]]
+	if exists {
+		action()
+
+		return
+	}
+
 	app := cli.NewApp()
+
 	app.Name = "nsinit"
 	app.Version = "0.1"
 	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,
 		statsCommand,
 		configCommand,
-		nsenterCommand,
 		pauseCommand,
 		unpauseCommand,
 	}

+ 58 - 1
vendor/src/github.com/docker/libcontainer/nsinit/exec.go

@@ -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 final 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.
 //

+ 23 - 18
vendor/src/github.com/docker/libcontainer/nsinit/nsenter.go

@@ -2,36 +2,41 @@ package nsinit
 
 import (
 	"log"
-	"strconv"
+	"os"
 
-	"github.com/codegangsta/cli"
+	"github.com/docker/libcontainer"
 	"github.com/docker/libcontainer/namespaces"
+	_ "github.com/docker/libcontainer/namespaces/nsenter"
+	"github.com/docker/libcontainer/syncpipe"
 )
 
-var nsenterCommand = cli.Command{
-	Name:   "nsenter",
-	Usage:  "init process for entering an existing namespace",
-	Action: nsenterAction,
-}
-
-func nsenterAction(context *cli.Context) {
-	args := context.Args()
+func findUserArgs() []string {
+	i := 0
+	for _, a := range os.Args {
+		i++
 
-	if len(args) == 0 {
-		args = []string{"/bin/bash"}
+		if a == "--" {
+			break
+		}
 	}
 
-	container, err := loadContainerFromJson(context.GlobalString("containerjson"))
+	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 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, findUserArgs()); err != nil {
 		log.Fatalf("failed to nsenter: %s", err)
 	}
 }

+ 9 - 10
vendor/src/github.com/docker/libcontainer/syncpipe/sync_pipe.go

@@ -6,8 +6,6 @@ import (
 	"io/ioutil"
 	"os"
 	"syscall"
-
-	"github.com/docker/libcontainer/network"
 )
 
 // SyncPipe allows communication to and from the child processes
@@ -39,8 +37,8 @@ func (s *SyncPipe) Parent() *os.File {
 	return s.parent
 }
 
-func (s *SyncPipe) SendToChild(networkState *network.NetworkState) error {
-	data, err := json.Marshal(networkState)
+func (s *SyncPipe) SendToChild(v interface{}) error {
+	data, err := json.Marshal(v)
 	if err != nil {
 		return err
 	}
@@ -63,18 +61,19 @@ func (s *SyncPipe) ReadFromChild() error {
 	return nil
 }
 
-func (s *SyncPipe) ReadFromParent() (*network.NetworkState, error) {
+func (s *SyncPipe) ReadFromParent(v interface{}) error {
 	data, err := ioutil.ReadAll(s.child)
 	if err != nil {
-		return nil, fmt.Errorf("error reading from sync pipe %s", err)
+		return fmt.Errorf("error reading from sync pipe %s", err)
 	}
-	var networkState *network.NetworkState
+
 	if len(data) > 0 {
-		if err := json.Unmarshal(data, &networkState); err != nil {
-			return nil, err
+		if err := json.Unmarshal(data, v); err != nil {
+			return err
 		}
 	}
-	return networkState, nil
+
+	return nil
 }
 
 func (s *SyncPipe) ReportChildError(err error) {

+ 9 - 7
vendor/src/github.com/docker/libcontainer/syncpipe/sync_pipe_test.go

@@ -3,10 +3,12 @@ package syncpipe
 import (
 	"fmt"
 	"testing"
-
-	"github.com/docker/libcontainer/network"
 )
 
+type testStruct struct {
+	Name string
+}
+
 func TestSendErrorFromChild(t *testing.T) {
 	pipe, err := NewSyncPipe()
 	if err != nil {
@@ -46,16 +48,16 @@ func TestSendPayloadToChild(t *testing.T) {
 
 	expected := "libcontainer"
 
-	if err := pipe.SendToChild(&network.NetworkState{VethHost: expected}); err != nil {
+	if err := pipe.SendToChild(testStruct{Name: expected}); err != nil {
 		t.Fatal(err)
 	}
 
-	payload, err := pipe.ReadFromParent()
-	if err != nil {
+	var s *testStruct
+	if err := pipe.ReadFromParent(&s); err != nil {
 		t.Fatal(err)
 	}
 
-	if payload.VethHost != expected {
-		t.Fatalf("expected veth host %q but received %q", expected, payload.VethHost)
+	if s.Name != expected {
+		t.Fatalf("expected name %q but received %q", expected, s.Name)
 	}
 }