Update libcontainer to f2e78425c377acc7a67a35c3148

Signed-off-by: Michael Crosby <michael@docker.com>
This commit is contained in:
Michael Crosby 2014-08-08 16:12:35 -07:00
parent 2dc21af70b
commit c74e8b544d
12 changed files with 214 additions and 121 deletions

View File

@ -59,7 +59,7 @@ rm -rf src/code.google.com/p/go
mkdir -p src/code.google.com/p/go/src/pkg/archive mkdir -p src/code.google.com/p/go/src/pkg/archive
mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar 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) # 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 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')" eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"

View File

@ -3,70 +3,88 @@
package namespaces package namespaces
import ( import (
"encoding/json" "io"
"os" "os"
"os/exec"
"path/filepath"
"strconv" "strconv"
"syscall"
"github.com/docker/libcontainer" "github.com/docker/libcontainer"
"github.com/docker/libcontainer/label" "github.com/docker/libcontainer/label"
"github.com/docker/libcontainer/syncpipe"
"github.com/docker/libcontainer/system" "github.com/docker/libcontainer/system"
) )
// ExecIn uses an existing pid and joins the pid's namespaces with the new command. // ExecIn reexec's the initPath with the argv 0 rewrite to "nsenter" so that it is able to run the
func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []string) error { // setns code in a single threaded environment joining the existing containers' namespaces.
// Enter the namespace and then finish setup func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath string,
args, err := GetNsEnterCommand(strconv.Itoa(state.InitPid), container, "", args) stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
if err != nil {
return err
}
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
}
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,
}
if console != "" { 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
} }
// Run a command in a container after entering the namespace. cmd := &exec.Cmd{
func NsEnter(container *libcontainer.Config, args []string) error { Path: initPath,
// clear the current processes env and replace it with the environment Args: append(args, append([]string{"--"}, userArgs...)...),
// defined on the container }
if filepath.Base(initPath) == initPath {
if lp, err := exec.LookPath(initPath); err == nil {
cmd.Path = lp
}
}
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
}
// 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 { if err := LoadContainerEnvironment(container); err != nil {
return err return err
} }
if err := FinalizeNamespace(container); err != nil { if err := FinalizeNamespace(container); err != nil {
return err 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 { if err := system.Execv(args[0], args[0:], container.Env); err != nil {
return err return err
} }
panic("unreachable") panic("unreachable")
} }

View File

@ -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 // We always read this as it is a way to sync with the parent as well
networkState, err := syncPipe.ReadFromParent() var networkState *network.NetworkState
if err != nil { if err := syncPipe.ReadFromParent(&networkState); err != nil {
return err return err
} }

View File

@ -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`.

View File

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

View File

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

View File

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

View File

@ -7,7 +7,14 @@ import (
"github.com/codegangsta/cli" "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 { func preload(context *cli.Context) error {
if logPath != "" { if logPath != "" {
@ -20,21 +27,33 @@ func preload(context *cli.Context) error {
} }
func NsInit() { 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 := cli.NewApp()
app.Name = "nsinit" app.Name = "nsinit"
app.Version = "0.1" app.Version = "0.1"
app.Author = "libcontainer maintainers" app.Author = "libcontainer maintainers"
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.StringFlag{Name: "nspid"}, cli.StringFlag{Name: "nspid"},
cli.StringFlag{Name: "containerjson"}, cli.StringFlag{Name: "console"},
cli.StringFlag{Name: "console"}} }
app.Before = preload app.Before = preload
app.Commands = []cli.Command{ app.Commands = []cli.Command{
execCommand, execCommand,
initCommand, initCommand,
statsCommand, statsCommand,
configCommand, configCommand,
nsenterCommand,
pauseCommand, pauseCommand,
unpauseCommand, unpauseCommand,
} }

View File

@ -36,7 +36,7 @@ func execAction(context *cli.Context) {
} }
if state != nil { if state != nil {
err = namespaces.ExecIn(container, state, []string(context.Args())) exitCode, err = startInExistingContainer(container, state, context)
} else { } else {
exitCode, err = startContainer(container, dataPath, []string(context.Args())) exitCode, err = startContainer(container, dataPath, []string(context.Args()))
} }
@ -48,6 +48,63 @@ func execAction(context *cli.Context) {
os.Exit(exitCode) 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 // startContainer starts the container. Returns the exit status or -1 and an
// error. // error.
// //

View File

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

View File

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

View File

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