summaryrefslogtreecommitdiffhomepage
path: root/runsc
diff options
context:
space:
mode:
Diffstat (limited to 'runsc')
-rw-r--r--runsc/BUILD22
-rw-r--r--runsc/boot/BUILD2
-rw-r--r--runsc/boot/compat.go2
-rw-r--r--runsc/boot/loader_test.go57
-rw-r--r--runsc/boot/vfs.go32
-rw-r--r--runsc/cgroup/cgroup.go42
-rw-r--r--runsc/cli/BUILD22
-rw-r--r--runsc/cli/main.go256
-rw-r--r--runsc/cmd/do.go45
-rw-r--r--runsc/container/container.go2
-rw-r--r--runsc/container/container_test.go6
-rw-r--r--runsc/main.go240
12 files changed, 400 insertions, 328 deletions
diff --git a/runsc/BUILD b/runsc/BUILD
index 33d8554af..3b91b984a 100644
--- a/runsc/BUILD
+++ b/runsc/BUILD
@@ -13,16 +13,7 @@ go_binary(
"//visibility:public",
],
x_defs = {"main.version": "{STABLE_VERSION}"},
- deps = [
- "//pkg/log",
- "//pkg/refs",
- "//pkg/sentry/platform",
- "//runsc/cmd",
- "//runsc/config",
- "//runsc/flag",
- "//runsc/specutils",
- "@com_github_google_subcommands//:go_default_library",
- ],
+ deps = ["//runsc/cli"],
)
# The runsc-race target is a race-compatible BUILD target. This must be built
@@ -49,16 +40,7 @@ go_binary(
"//visibility:public",
],
x_defs = {"main.version": "{STABLE_VERSION}"},
- deps = [
- "//pkg/log",
- "//pkg/refs",
- "//pkg/sentry/platform",
- "//runsc/cmd",
- "//runsc/config",
- "//runsc/flag",
- "//runsc/specutils",
- "@com_github_google_subcommands//:go_default_library",
- ],
+ deps = ["//runsc/cli"],
)
sh_test(
diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD
index 2d9517f4a..248f77c34 100644
--- a/runsc/boot/BUILD
+++ b/runsc/boot/BUILD
@@ -110,8 +110,8 @@ go_library(
"//runsc/config",
"//runsc/specutils",
"//runsc/specutils/seccomp",
- "@com_github_golang_protobuf//proto:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
+ "@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
diff --git a/runsc/boot/compat.go b/runsc/boot/compat.go
index 84c67cbc2..7076ae2e2 100644
--- a/runsc/boot/compat.go
+++ b/runsc/boot/compat.go
@@ -19,7 +19,7 @@ import (
"os"
"syscall"
- "github.com/golang/protobuf/proto"
+ "google.golang.org/protobuf/proto"
"gvisor.dev/gvisor/pkg/eventchannel"
"gvisor.dev/gvisor/pkg/log"
rpb "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto"
diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go
index e376f944b..b77b4762e 100644
--- a/runsc/boot/loader_test.go
+++ b/runsc/boot/loader_test.go
@@ -266,7 +266,7 @@ type CreateMountTestcase struct {
func createMountTestcases() []*CreateMountTestcase {
testCases := []*CreateMountTestcase{
- &CreateMountTestcase{
+ {
// Only proc.
name: "only proc mount",
spec: specs.Spec{
@@ -304,11 +304,10 @@ func createMountTestcases() []*CreateMountTestcase {
},
},
},
- // /some/deep/path should be mounted, along with /proc,
- // /dev, and /sys.
+ // /some/deep/path should be mounted, along with /proc, /dev, and /sys.
expectedPaths: []string{"/some/very/very/deep/path", "/proc", "/dev", "/sys"},
},
- &CreateMountTestcase{
+ {
// Mounts are nested inside each other.
name: "nested mounts",
spec: specs.Spec{
@@ -352,7 +351,7 @@ func createMountTestcases() []*CreateMountTestcase {
expectedPaths: []string{"/foo", "/foo/bar", "/foo/bar/baz", "/foo/qux",
"/foo/qux-quz", "/foo/some/very/very/deep/path", "/proc", "/dev", "/sys"},
},
- &CreateMountTestcase{
+ {
name: "mount inside /dev",
spec: specs.Spec{
Root: &specs.Root{
@@ -395,35 +394,37 @@ func createMountTestcases() []*CreateMountTestcase {
},
expectedPaths: []string{"/proc", "/dev", "/dev/fd-foo", "/dev/foo", "/dev/bar", "/sys"},
},
- }
-
- vfsCase := &CreateMountTestcase{
- name: "mounts inside mandatory mounts",
- spec: specs.Spec{
- Root: &specs.Root{
- Path: os.TempDir(),
- Readonly: true,
- },
- Mounts: []specs.Mount{
- {
- Destination: "/proc",
- Type: "tmpfs",
- },
- {
- Destination: "/sys/bar",
- Type: "tmpfs",
+ {
+ name: "mounts inside mandatory mounts",
+ spec: specs.Spec{
+ Root: &specs.Root{
+ Path: os.TempDir(),
+ Readonly: true,
},
-
- {
- Destination: "/tmp/baz",
- Type: "tmpfs",
+ Mounts: []specs.Mount{
+ {
+ Destination: "/proc",
+ Type: "tmpfs",
+ },
+ {
+ Destination: "/sys/bar",
+ Type: "tmpfs",
+ },
+ {
+ Destination: "/tmp/baz",
+ Type: "tmpfs",
+ },
+ {
+ Destination: "/dev/goo",
+ Type: "tmpfs",
+ },
},
},
+ expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz", "/dev/goo"},
},
- expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz"},
}
- return append(testCases, vfsCase)
+ return testCases
}
// Test that MountNamespace can be created with various specs.
diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go
index 82e459f46..004da5b40 100644
--- a/runsc/boot/vfs.go
+++ b/runsc/boot/vfs.go
@@ -264,10 +264,38 @@ func (c *containerMounter) configureOverlay(ctx context.Context, creds *auth.Cre
}
cu.Add(func() { lower.DecRef(ctx) })
+ // Propagate the lower layer's root's owner, group, and mode to the upper
+ // layer's root for consistency with VFS1.
+ upperRootVD := vfs.MakeVirtualDentry(upper, upper.Root())
+ lowerRootVD := vfs.MakeVirtualDentry(lower, lower.Root())
+ stat, err := c.k.VFS().StatAt(ctx, creds, &vfs.PathOperation{
+ Root: lowerRootVD,
+ Start: lowerRootVD,
+ }, &vfs.StatOptions{
+ Mask: linux.STATX_UID | linux.STATX_GID | linux.STATX_MODE,
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+ err = c.k.VFS().SetStatAt(ctx, creds, &vfs.PathOperation{
+ Root: upperRootVD,
+ Start: upperRootVD,
+ }, &vfs.SetStatOptions{
+ Stat: linux.Statx{
+ Mask: (linux.STATX_UID | linux.STATX_GID | linux.STATX_MODE) & stat.Mask,
+ UID: stat.UID,
+ GID: stat.GID,
+ Mode: stat.Mode,
+ },
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+
// Configure overlay with both layers.
overlayOpts.GetFilesystemOptions.InternalData = overlay.FilesystemOptions{
- UpperRoot: vfs.MakeVirtualDentry(upper, upper.Root()),
- LowerRoots: []vfs.VirtualDentry{vfs.MakeVirtualDentry(lower, lower.Root())},
+ UpperRoot: upperRootVD,
+ LowerRoots: []vfs.VirtualDentry{lowerRootVD},
}
return &overlayOpts, cu.Release(), nil
}
diff --git a/runsc/cgroup/cgroup.go b/runsc/cgroup/cgroup.go
index 8fbc3887a..56da21584 100644
--- a/runsc/cgroup/cgroup.go
+++ b/runsc/cgroup/cgroup.go
@@ -201,13 +201,15 @@ func LoadPaths(pid string) (map[string]string, error) {
paths := make(map[string]string)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
- // Format: ID:controller1,controller2:path
+ // Format: ID:[name=]controller1,controller2:path
// Example: 2:cpu,cpuacct:/user.slice
tokens := strings.Split(scanner.Text(), ":")
if len(tokens) != 3 {
return nil, fmt.Errorf("invalid cgroups file, line: %q", scanner.Text())
}
for _, ctrlr := range strings.Split(tokens[1], ",") {
+ // Remove prefix for cgroups with no controller, eg. systemd.
+ ctrlr = strings.TrimPrefix(ctrlr, "name=")
paths[ctrlr] = tokens[2]
}
}
@@ -237,7 +239,7 @@ func New(spec *specs.Spec) (*Cgroup, error) {
var err error
parents, err = LoadPaths("self")
if err != nil {
- return nil, fmt.Errorf("finding current cgroups: %v", err)
+ return nil, fmt.Errorf("finding current cgroups: %w", err)
}
}
return &Cgroup{
@@ -276,10 +278,8 @@ func (c *Cgroup) Install(res *specs.LinuxResources) error {
}
return err
}
- if res != nil {
- if err := cfg.ctrlr.set(res, path); err != nil {
- return err
- }
+ if err := cfg.ctrlr.set(res, path); err != nil {
+ return err
}
}
clean.Release()
@@ -304,14 +304,15 @@ func (c *Cgroup) Uninstall() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx)
- if err := backoff.Retry(func() error {
+ fn := func() error {
err := syscall.Rmdir(path)
if os.IsNotExist(err) {
return nil
}
return err
- }, b); err != nil {
- return fmt.Errorf("removing cgroup path %q: %v", path, err)
+ }
+ if err := backoff.Retry(fn, b); err != nil {
+ return fmt.Errorf("removing cgroup path %q: %w", path, err)
}
}
return nil
@@ -332,7 +333,6 @@ func (c *Cgroup) Join() (func(), error) {
if _, ok := controllers[ctrlr]; ok {
fullPath := filepath.Join(cgroupRoot, ctrlr, path)
undoPaths = append(undoPaths, fullPath)
- break
}
}
@@ -422,7 +422,7 @@ func (*noop) set(*specs.LinuxResources, string) error {
type memory struct{}
func (*memory) set(spec *specs.LinuxResources, path string) error {
- if spec.Memory == nil {
+ if spec == nil || spec.Memory == nil {
return nil
}
if err := setOptionalValueInt(path, "memory.limit_in_bytes", spec.Memory.Limit); err != nil {
@@ -455,7 +455,7 @@ func (*memory) set(spec *specs.LinuxResources, path string) error {
type cpu struct{}
func (*cpu) set(spec *specs.LinuxResources, path string) error {
- if spec.CPU == nil {
+ if spec == nil || spec.CPU == nil {
return nil
}
if err := setOptionalValueUint(path, "cpu.shares", spec.CPU.Shares); err != nil {
@@ -478,7 +478,7 @@ type cpuSet struct{}
func (*cpuSet) set(spec *specs.LinuxResources, path string) error {
// cpuset.cpus and mems are required fields, but are not set on a new cgroup.
// If not set in the spec, get it from one of the ancestors cgroup.
- if spec.CPU == nil || spec.CPU.Cpus == "" {
+ if spec == nil || spec.CPU == nil || spec.CPU.Cpus == "" {
if _, err := fillFromAncestor(filepath.Join(path, "cpuset.cpus")); err != nil {
return err
}
@@ -488,18 +488,17 @@ func (*cpuSet) set(spec *specs.LinuxResources, path string) error {
}
}
- if spec.CPU == nil || spec.CPU.Mems == "" {
+ if spec == nil || spec.CPU == nil || spec.CPU.Mems == "" {
_, err := fillFromAncestor(filepath.Join(path, "cpuset.mems"))
return err
}
- mems := spec.CPU.Mems
- return setValue(path, "cpuset.mems", mems)
+ return setValue(path, "cpuset.mems", spec.CPU.Mems)
}
type blockIO struct{}
func (*blockIO) set(spec *specs.LinuxResources, path string) error {
- if spec.BlockIO == nil {
+ if spec == nil || spec.BlockIO == nil {
return nil
}
@@ -549,7 +548,7 @@ func setThrottle(path, name string, devs []specs.LinuxThrottleDevice) error {
type networkClass struct{}
func (*networkClass) set(spec *specs.LinuxResources, path string) error {
- if spec.Network == nil {
+ if spec == nil || spec.Network == nil {
return nil
}
return setOptionalValueUint32(path, "net_cls.classid", spec.Network.ClassID)
@@ -558,7 +557,7 @@ func (*networkClass) set(spec *specs.LinuxResources, path string) error {
type networkPrio struct{}
func (*networkPrio) set(spec *specs.LinuxResources, path string) error {
- if spec.Network == nil {
+ if spec == nil || spec.Network == nil {
return nil
}
for _, prio := range spec.Network.Priorities {
@@ -573,7 +572,7 @@ func (*networkPrio) set(spec *specs.LinuxResources, path string) error {
type pids struct{}
func (*pids) set(spec *specs.LinuxResources, path string) error {
- if spec.Pids == nil || spec.Pids.Limit <= 0 {
+ if spec == nil || spec.Pids == nil || spec.Pids.Limit <= 0 {
return nil
}
val := strconv.FormatInt(spec.Pids.Limit, 10)
@@ -583,6 +582,9 @@ func (*pids) set(spec *specs.LinuxResources, path string) error {
type hugeTLB struct{}
func (*hugeTLB) set(spec *specs.LinuxResources, path string) error {
+ if spec == nil {
+ return nil
+ }
for _, limit := range spec.HugepageLimits {
name := fmt.Sprintf("hugetlb.%s.limit_in_bytes", limit.Pagesize)
val := strconv.FormatUint(limit.Limit, 10)
diff --git a/runsc/cli/BUILD b/runsc/cli/BUILD
new file mode 100644
index 000000000..32cce2a18
--- /dev/null
+++ b/runsc/cli/BUILD
@@ -0,0 +1,22 @@
+load("//tools:defs.bzl", "go_library")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "cli",
+ srcs = ["main.go"],
+ visibility = [
+ "//:__pkg__",
+ "//runsc:__pkg__",
+ ],
+ deps = [
+ "//pkg/log",
+ "//pkg/refs",
+ "//pkg/sentry/platform",
+ "//runsc/cmd",
+ "//runsc/config",
+ "//runsc/flag",
+ "//runsc/specutils",
+ "@com_github_google_subcommands//:go_default_library",
+ ],
+)
diff --git a/runsc/cli/main.go b/runsc/cli/main.go
new file mode 100644
index 000000000..bca015db5
--- /dev/null
+++ b/runsc/cli/main.go
@@ -0,0 +1,256 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package cli is the main entrypoint for runsc.
+package cli
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "github.com/google/subcommands"
+ "gvisor.dev/gvisor/pkg/log"
+ "gvisor.dev/gvisor/pkg/refs"
+ "gvisor.dev/gvisor/pkg/sentry/platform"
+ "gvisor.dev/gvisor/runsc/cmd"
+ "gvisor.dev/gvisor/runsc/config"
+ "gvisor.dev/gvisor/runsc/flag"
+ "gvisor.dev/gvisor/runsc/specutils"
+)
+
+var (
+ // Although these flags are not part of the OCI spec, they are used by
+ // Docker, and thus should not be changed.
+ // TODO(gvisor.dev/issue/193): support systemd cgroups
+ systemdCgroup = flag.Bool("systemd-cgroup", false, "Use systemd for cgroups. NOT SUPPORTED.")
+ showVersion = flag.Bool("version", false, "show version and exit.")
+
+ // These flags are unique to runsc, and are used to configure parts of the
+ // system that are not covered by the runtime spec.
+
+ // Debugging flags.
+ logFD = flag.Int("log-fd", -1, "file descriptor to log to. If set, the 'log' flag is ignored.")
+ debugLogFD = flag.Int("debug-log-fd", -1, "file descriptor to write debug logs to. If set, the 'debug-log-dir' flag is ignored.")
+ panicLogFD = flag.Int("panic-log-fd", -1, "file descriptor to write Go's runtime messages.")
+)
+
+// Main is the main entrypoint.
+func Main(version string) {
+ // Help and flags commands are generated automatically.
+ help := cmd.NewHelp(subcommands.DefaultCommander)
+ help.Register(new(cmd.Syscalls))
+ subcommands.Register(help, "")
+ subcommands.Register(subcommands.FlagsCommand(), "")
+
+ // Installation helpers.
+ const helperGroup = "helpers"
+ subcommands.Register(new(cmd.Install), helperGroup)
+ subcommands.Register(new(cmd.Uninstall), helperGroup)
+
+ // Register user-facing runsc commands.
+ subcommands.Register(new(cmd.Checkpoint), "")
+ subcommands.Register(new(cmd.Create), "")
+ subcommands.Register(new(cmd.Delete), "")
+ subcommands.Register(new(cmd.Do), "")
+ subcommands.Register(new(cmd.Events), "")
+ subcommands.Register(new(cmd.Exec), "")
+ subcommands.Register(new(cmd.Gofer), "")
+ subcommands.Register(new(cmd.Kill), "")
+ subcommands.Register(new(cmd.List), "")
+ subcommands.Register(new(cmd.Pause), "")
+ subcommands.Register(new(cmd.PS), "")
+ subcommands.Register(new(cmd.Restore), "")
+ subcommands.Register(new(cmd.Resume), "")
+ subcommands.Register(new(cmd.Run), "")
+ subcommands.Register(new(cmd.Spec), "")
+ subcommands.Register(new(cmd.State), "")
+ subcommands.Register(new(cmd.Start), "")
+ subcommands.Register(new(cmd.Wait), "")
+
+ // Register internal commands with the internal group name. This causes
+ // them to be sorted below the user-facing commands with empty group.
+ // The string below will be printed above the commands.
+ const internalGroup = "internal use only"
+ subcommands.Register(new(cmd.Boot), internalGroup)
+ subcommands.Register(new(cmd.Debug), internalGroup)
+ subcommands.Register(new(cmd.Gofer), internalGroup)
+ subcommands.Register(new(cmd.Statefile), internalGroup)
+
+ config.RegisterFlags()
+
+ // All subcommands must be registered before flag parsing.
+ flag.Parse()
+
+ // Are we showing the version?
+ if *showVersion {
+ // The format here is the same as runc.
+ fmt.Fprintf(os.Stdout, "runsc version %s\n", version)
+ fmt.Fprintf(os.Stdout, "spec: %s\n", specutils.Version)
+ os.Exit(0)
+ }
+
+ // Create a new Config from the flags.
+ conf, err := config.NewFromFlags()
+ if err != nil {
+ cmd.Fatalf(err.Error())
+ }
+
+ // TODO(gvisor.dev/issue/193): support systemd cgroups
+ if *systemdCgroup {
+ fmt.Fprintln(os.Stderr, "systemd cgroup flag passed, but systemd cgroups not supported. See gvisor.dev/issue/193")
+ os.Exit(1)
+ }
+
+ var errorLogger io.Writer
+ if *logFD > -1 {
+ errorLogger = os.NewFile(uintptr(*logFD), "error log file")
+
+ } else if conf.LogFilename != "" {
+ // We must set O_APPEND and not O_TRUNC because Docker passes
+ // the same log file for all commands (and also parses these
+ // log files), so we can't destroy them on each command.
+ var err error
+ errorLogger, err = os.OpenFile(conf.LogFilename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
+ if err != nil {
+ cmd.Fatalf("error opening log file %q: %v", conf.LogFilename, err)
+ }
+ }
+ cmd.ErrorLogger = errorLogger
+
+ if _, err := platform.Lookup(conf.Platform); err != nil {
+ cmd.Fatalf("%v", err)
+ }
+
+ // Sets the reference leak check mode. Also set it in config below to
+ // propagate it to child processes.
+ refs.SetLeakMode(conf.ReferenceLeak)
+
+ // Set up logging.
+ if conf.Debug {
+ log.SetLevel(log.Debug)
+ }
+
+ // Logging will include the local date and time via the time package.
+ //
+ // On first use, time.Local initializes the local time zone, which
+ // involves opening tzdata files on the host. Since this requires
+ // opening host files, it must be done before syscall filter
+ // installation.
+ //
+ // Generally there will be a log message before filter installation
+ // that will force initialization, but force initialization here in
+ // case that does not occur.
+ _ = time.Local.String()
+
+ subcommand := flag.CommandLine.Arg(0)
+
+ var e log.Emitter
+ if *debugLogFD > -1 {
+ f := os.NewFile(uintptr(*debugLogFD), "debug log file")
+
+ e = newEmitter(conf.DebugLogFormat, f)
+
+ } else if conf.DebugLog != "" {
+ f, err := specutils.DebugLogFile(conf.DebugLog, subcommand, "" /* name */)
+ if err != nil {
+ cmd.Fatalf("error opening debug log file in %q: %v", conf.DebugLog, err)
+ }
+ e = newEmitter(conf.DebugLogFormat, f)
+
+ } else {
+ // Stderr is reserved for the application, just discard the logs if no debug
+ // log is specified.
+ e = newEmitter("text", ioutil.Discard)
+ }
+
+ if *panicLogFD > -1 || *debugLogFD > -1 {
+ fd := *panicLogFD
+ if fd < 0 {
+ fd = *debugLogFD
+ }
+ // Quick sanity check to make sure no other commands get passed
+ // a log fd (they should use log dir instead).
+ if subcommand != "boot" && subcommand != "gofer" {
+ cmd.Fatalf("flags --debug-log-fd and --panic-log-fd should only be passed to 'boot' and 'gofer' command, but was passed to %q", subcommand)
+ }
+
+ // If we are the boot process, then we own our stdio FDs and can do what we
+ // want with them. Since Docker and Containerd both eat boot's stderr, we
+ // dup our stderr to the provided log FD so that panics will appear in the
+ // logs, rather than just disappear.
+ if err := syscall.Dup3(fd, int(os.Stderr.Fd()), 0); err != nil {
+ cmd.Fatalf("error dup'ing fd %d to stderr: %v", fd, err)
+ }
+ } else if conf.AlsoLogToStderr {
+ e = &log.MultiEmitter{e, newEmitter(conf.DebugLogFormat, os.Stderr)}
+ }
+
+ log.SetTarget(e)
+
+ log.Infof("***************************")
+ log.Infof("Args: %s", os.Args)
+ log.Infof("Version %s", version)
+ log.Infof("PID: %d", os.Getpid())
+ log.Infof("UID: %d, GID: %d", os.Getuid(), os.Getgid())
+ log.Infof("Configuration:")
+ log.Infof("\t\tRootDir: %s", conf.RootDir)
+ log.Infof("\t\tPlatform: %v", conf.Platform)
+ log.Infof("\t\tFileAccess: %v, overlay: %t", conf.FileAccess, conf.Overlay)
+ log.Infof("\t\tNetwork: %v, logging: %t", conf.Network, conf.LogPackets)
+ log.Infof("\t\tStrace: %t, max size: %d, syscalls: %s", conf.Strace, conf.StraceLogSize, conf.StraceSyscalls)
+ log.Infof("\t\tVFS2 enabled: %v", conf.VFS2)
+ log.Infof("***************************")
+
+ if conf.TestOnlyAllowRunAsCurrentUserWithoutChroot {
+ // SIGTERM is sent to all processes if a test exceeds its
+ // timeout and this case is handled by syscall_test_runner.
+ log.Warningf("Block the TERM signal. This is only safe in tests!")
+ signal.Ignore(syscall.SIGTERM)
+ }
+
+ // Call the subcommand and pass in the configuration.
+ var ws syscall.WaitStatus
+ subcmdCode := subcommands.Execute(context.Background(), conf, &ws)
+ if subcmdCode == subcommands.ExitSuccess {
+ log.Infof("Exiting with status: %v", ws)
+ if ws.Signaled() {
+ // No good way to return it, emulate what the shell does. Maybe raise
+ // signal to self?
+ os.Exit(128 + int(ws.Signal()))
+ }
+ os.Exit(ws.ExitStatus())
+ }
+ // Return an error that is unlikely to be used by the application.
+ log.Warningf("Failure to execute command, err: %v", subcmdCode)
+ os.Exit(128)
+}
+
+func newEmitter(format string, logFile io.Writer) log.Emitter {
+ switch format {
+ case "text":
+ return log.GoogleEmitter{&log.Writer{Next: logFile}}
+ case "json":
+ return log.JSONEmitter{&log.Writer{Next: logFile}}
+ case "json-k8s":
+ return log.K8sJSONEmitter{&log.Writer{Next: logFile}}
+ }
+ cmd.Fatalf("invalid log format %q, must be 'text', 'json', or 'json-k8s'", format)
+ panic("unreachable")
+}
diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go
index d1f2e9e6d..640de4c47 100644
--- a/runsc/cmd/do.go
+++ b/runsc/cmd/do.go
@@ -17,6 +17,7 @@ package cmd
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"io/ioutil"
"math/rand"
@@ -36,6 +37,8 @@ import (
"gvisor.dev/gvisor/runsc/specutils"
)
+var errNoDefaultInterface = errors.New("no default interface found")
+
// Do implements subcommands.Command for the "do" command. It sets up a simple
// sandbox and executes the command inside it. See Usage() for more details.
type Do struct {
@@ -126,26 +129,28 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su
cid := fmt.Sprintf("runsc-%06d", rand.Int31n(1000000))
if conf.Network == config.NetworkNone {
- netns := specs.LinuxNamespace{
- Type: specs.NetworkNamespace,
- }
- if spec.Linux != nil {
- panic("spec.Linux is not nil")
- }
- spec.Linux = &specs.Linux{Namespaces: []specs.LinuxNamespace{netns}}
+ addNamespace(spec, specs.LinuxNamespace{Type: specs.NetworkNamespace})
} else if conf.Rootless {
if conf.Network == config.NetworkSandbox {
- c.notifyUser("*** Warning: using host network due to --rootless ***")
+ c.notifyUser("*** Warning: sandbox network isn't supported with --rootless, switching to host ***")
conf.Network = config.NetworkHost
}
} else {
- clean, err := c.setupNet(cid, spec)
- if err != nil {
+ switch clean, err := c.setupNet(cid, spec); err {
+ case errNoDefaultInterface:
+ log.Warningf("Network interface not found, using internal network")
+ addNamespace(spec, specs.LinuxNamespace{Type: specs.NetworkNamespace})
+ conf.Network = config.NetworkHost
+
+ case nil:
+ // Setup successfull.
+ defer clean()
+
+ default:
return Errorf("Error setting up network: %v", err)
}
- defer clean()
}
out, err := json.Marshal(spec)
@@ -199,6 +204,13 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su
return subcommands.ExitSuccess
}
+func addNamespace(spec *specs.Spec, ns specs.LinuxNamespace) {
+ if spec.Linux == nil {
+ spec.Linux = &specs.Linux{}
+ }
+ spec.Linux.Namespaces = append(spec.Linux.Namespaces, ns)
+}
+
func (c *Do) notifyUser(format string, v ...interface{}) {
if !c.quiet {
fmt.Printf(format+"\n", v...)
@@ -219,10 +231,14 @@ func resolvePath(path string) (string, error) {
return path, nil
}
+// setupNet setups up the sandbox network, including the creation of a network
+// namespace, and iptable rules to redirect the traffic. Returns a cleanup
+// function to tear down the network. Returns errNoDefaultInterface when there
+// is no network interface available to setup the network.
func (c *Do) setupNet(cid string, spec *specs.Spec) (func(), error) {
dev, err := defaultDevice()
if err != nil {
- return nil, err
+ return nil, errNoDefaultInterface
}
peerIP, err := calculatePeerIP(c.ip)
if err != nil {
@@ -279,14 +295,11 @@ func (c *Do) setupNet(cid string, spec *specs.Spec) (func(), error) {
return nil, err
}
- if spec.Linux == nil {
- spec.Linux = &specs.Linux{}
- }
netns := specs.LinuxNamespace{
Type: specs.NetworkNamespace,
Path: filepath.Join("/var/run/netns", cid),
}
- spec.Linux.Namespaces = append(spec.Linux.Namespaces, netns)
+ addNamespace(spec, netns)
return func() { c.cleanupNet(cid, dev, resolvPath, hostnamePath, hostsPath) }, nil
}
diff --git a/runsc/container/container.go b/runsc/container/container.go
index 63478ba8c..63f64ce6e 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -985,7 +985,7 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *config.Config, bu
// Start the gofer in the given namespace.
log.Debugf("Starting gofer: %s %v", binPath, args)
if err := specutils.StartInNS(cmd, nss); err != nil {
- return nil, nil, fmt.Errorf("Gofer: %v", err)
+ return nil, nil, fmt.Errorf("gofer: %v", err)
}
log.Infof("Gofer started, PID: %d", cmd.Process.Pid)
c.GoferPid = cmd.Process.Pid
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 1f8e277cc..cc188f45b 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -2362,12 +2362,12 @@ func executeCombinedOutput(cont *Container, name string, arg ...string) ([]byte,
}
// executeSync synchronously executes a new process.
-func (cont *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) {
- pid, err := cont.Execute(args)
+func (c *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) {
+ pid, err := c.Execute(args)
if err != nil {
return 0, fmt.Errorf("error executing: %v", err)
}
- ws, err := cont.WaitPID(pid)
+ ws, err := c.WaitPID(pid)
if err != nil {
return 0, fmt.Errorf("error waiting: %v", err)
}
diff --git a/runsc/main.go b/runsc/main.go
index ed244c4ba..4ce5ebee9 100644
--- a/runsc/main.go
+++ b/runsc/main.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The gVisor Authors.
+// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,245 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Binary runsc is an implementation of the Open Container Initiative Runtime
-// that runs applications inside a sandbox.
+// Binary runsc implements the OCI runtime interface.
package main
import (
- "context"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/signal"
- "syscall"
- "time"
-
- "github.com/google/subcommands"
- "gvisor.dev/gvisor/pkg/log"
- "gvisor.dev/gvisor/pkg/refs"
- "gvisor.dev/gvisor/pkg/sentry/platform"
- "gvisor.dev/gvisor/runsc/cmd"
- "gvisor.dev/gvisor/runsc/config"
- "gvisor.dev/gvisor/runsc/flag"
- "gvisor.dev/gvisor/runsc/specutils"
-)
-
-var (
- // Although these flags are not part of the OCI spec, they are used by
- // Docker, and thus should not be changed.
- // TODO(gvisor.dev/issue/193): support systemd cgroups
- systemdCgroup = flag.Bool("systemd-cgroup", false, "Use systemd for cgroups. NOT SUPPORTED.")
- showVersion = flag.Bool("version", false, "show version and exit.")
-
- // These flags are unique to runsc, and are used to configure parts of the
- // system that are not covered by the runtime spec.
-
- // Debugging flags.
- logFD = flag.Int("log-fd", -1, "file descriptor to log to. If set, the 'log' flag is ignored.")
- debugLogFD = flag.Int("debug-log-fd", -1, "file descriptor to write debug logs to. If set, the 'debug-log-dir' flag is ignored.")
- panicLogFD = flag.Int("panic-log-fd", -1, "file descriptor to write Go's runtime messages.")
+ "gvisor.dev/gvisor/runsc/cli"
)
func main() {
- // Help and flags commands are generated automatically.
- help := cmd.NewHelp(subcommands.DefaultCommander)
- help.Register(new(cmd.Syscalls))
- subcommands.Register(help, "")
- subcommands.Register(subcommands.FlagsCommand(), "")
-
- // Installation helpers.
- const helperGroup = "helpers"
- subcommands.Register(new(cmd.Install), helperGroup)
- subcommands.Register(new(cmd.Uninstall), helperGroup)
-
- // Register user-facing runsc commands.
- subcommands.Register(new(cmd.Checkpoint), "")
- subcommands.Register(new(cmd.Create), "")
- subcommands.Register(new(cmd.Delete), "")
- subcommands.Register(new(cmd.Do), "")
- subcommands.Register(new(cmd.Events), "")
- subcommands.Register(new(cmd.Exec), "")
- subcommands.Register(new(cmd.Gofer), "")
- subcommands.Register(new(cmd.Kill), "")
- subcommands.Register(new(cmd.List), "")
- subcommands.Register(new(cmd.Pause), "")
- subcommands.Register(new(cmd.PS), "")
- subcommands.Register(new(cmd.Restore), "")
- subcommands.Register(new(cmd.Resume), "")
- subcommands.Register(new(cmd.Run), "")
- subcommands.Register(new(cmd.Spec), "")
- subcommands.Register(new(cmd.State), "")
- subcommands.Register(new(cmd.Start), "")
- subcommands.Register(new(cmd.Wait), "")
-
- // Register internal commands with the internal group name. This causes
- // them to be sorted below the user-facing commands with empty group.
- // The string below will be printed above the commands.
- const internalGroup = "internal use only"
- subcommands.Register(new(cmd.Boot), internalGroup)
- subcommands.Register(new(cmd.Debug), internalGroup)
- subcommands.Register(new(cmd.Gofer), internalGroup)
- subcommands.Register(new(cmd.Statefile), internalGroup)
-
- config.RegisterFlags()
-
- // All subcommands must be registered before flag parsing.
- flag.Parse()
-
- // Are we showing the version?
- if *showVersion {
- // The format here is the same as runc.
- fmt.Fprintf(os.Stdout, "runsc version %s\n", version)
- fmt.Fprintf(os.Stdout, "spec: %s\n", specutils.Version)
- os.Exit(0)
- }
-
- // Create a new Config from the flags.
- conf, err := config.NewFromFlags()
- if err != nil {
- cmd.Fatalf(err.Error())
- }
-
- // TODO(gvisor.dev/issue/193): support systemd cgroups
- if *systemdCgroup {
- fmt.Fprintln(os.Stderr, "systemd cgroup flag passed, but systemd cgroups not supported. See gvisor.dev/issue/193")
- os.Exit(1)
- }
-
- var errorLogger io.Writer
- if *logFD > -1 {
- errorLogger = os.NewFile(uintptr(*logFD), "error log file")
-
- } else if conf.LogFilename != "" {
- // We must set O_APPEND and not O_TRUNC because Docker passes
- // the same log file for all commands (and also parses these
- // log files), so we can't destroy them on each command.
- var err error
- errorLogger, err = os.OpenFile(conf.LogFilename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
- if err != nil {
- cmd.Fatalf("error opening log file %q: %v", conf.LogFilename, err)
- }
- }
- cmd.ErrorLogger = errorLogger
-
- if _, err := platform.Lookup(conf.Platform); err != nil {
- cmd.Fatalf("%v", err)
- }
-
- // Sets the reference leak check mode. Also set it in config below to
- // propagate it to child processes.
- refs.SetLeakMode(conf.ReferenceLeak)
-
- // Set up logging.
- if conf.Debug {
- log.SetLevel(log.Debug)
- }
-
- // Logging will include the local date and time via the time package.
- //
- // On first use, time.Local initializes the local time zone, which
- // involves opening tzdata files on the host. Since this requires
- // opening host files, it must be done before syscall filter
- // installation.
- //
- // Generally there will be a log message before filter installation
- // that will force initialization, but force initialization here in
- // case that does not occur.
- _ = time.Local.String()
-
- subcommand := flag.CommandLine.Arg(0)
-
- var e log.Emitter
- if *debugLogFD > -1 {
- f := os.NewFile(uintptr(*debugLogFD), "debug log file")
-
- e = newEmitter(conf.DebugLogFormat, f)
-
- } else if conf.DebugLog != "" {
- f, err := specutils.DebugLogFile(conf.DebugLog, subcommand, "" /* name */)
- if err != nil {
- cmd.Fatalf("error opening debug log file in %q: %v", conf.DebugLog, err)
- }
- e = newEmitter(conf.DebugLogFormat, f)
-
- } else {
- // Stderr is reserved for the application, just discard the logs if no debug
- // log is specified.
- e = newEmitter("text", ioutil.Discard)
- }
-
- if *panicLogFD > -1 || *debugLogFD > -1 {
- fd := *panicLogFD
- if fd < 0 {
- fd = *debugLogFD
- }
- // Quick sanity check to make sure no other commands get passed
- // a log fd (they should use log dir instead).
- if subcommand != "boot" && subcommand != "gofer" {
- cmd.Fatalf("flags --debug-log-fd and --panic-log-fd should only be passed to 'boot' and 'gofer' command, but was passed to %q", subcommand)
- }
-
- // If we are the boot process, then we own our stdio FDs and can do what we
- // want with them. Since Docker and Containerd both eat boot's stderr, we
- // dup our stderr to the provided log FD so that panics will appear in the
- // logs, rather than just disappear.
- if err := syscall.Dup3(fd, int(os.Stderr.Fd()), 0); err != nil {
- cmd.Fatalf("error dup'ing fd %d to stderr: %v", fd, err)
- }
- } else if conf.AlsoLogToStderr {
- e = &log.MultiEmitter{e, newEmitter(conf.DebugLogFormat, os.Stderr)}
- }
-
- log.SetTarget(e)
-
- log.Infof("***************************")
- log.Infof("Args: %s", os.Args)
- log.Infof("Version %s", version)
- log.Infof("PID: %d", os.Getpid())
- log.Infof("UID: %d, GID: %d", os.Getuid(), os.Getgid())
- log.Infof("Configuration:")
- log.Infof("\t\tRootDir: %s", conf.RootDir)
- log.Infof("\t\tPlatform: %v", conf.Platform)
- log.Infof("\t\tFileAccess: %v, overlay: %t", conf.FileAccess, conf.Overlay)
- log.Infof("\t\tNetwork: %v, logging: %t", conf.Network, conf.LogPackets)
- log.Infof("\t\tStrace: %t, max size: %d, syscalls: %s", conf.Strace, conf.StraceLogSize, conf.StraceSyscalls)
- log.Infof("\t\tVFS2 enabled: %v", conf.VFS2)
- log.Infof("***************************")
-
- if conf.TestOnlyAllowRunAsCurrentUserWithoutChroot {
- // SIGTERM is sent to all processes if a test exceeds its
- // timeout and this case is handled by syscall_test_runner.
- log.Warningf("Block the TERM signal. This is only safe in tests!")
- signal.Ignore(syscall.SIGTERM)
- }
-
- // Call the subcommand and pass in the configuration.
- var ws syscall.WaitStatus
- subcmdCode := subcommands.Execute(context.Background(), conf, &ws)
- if subcmdCode == subcommands.ExitSuccess {
- log.Infof("Exiting with status: %v", ws)
- if ws.Signaled() {
- // No good way to return it, emulate what the shell does. Maybe raise
- // signal to self?
- os.Exit(128 + int(ws.Signal()))
- }
- os.Exit(ws.ExitStatus())
- }
- // Return an error that is unlikely to be used by the application.
- log.Warningf("Failure to execute command, err: %v", subcmdCode)
- os.Exit(128)
-}
-
-func newEmitter(format string, logFile io.Writer) log.Emitter {
- switch format {
- case "text":
- return log.GoogleEmitter{&log.Writer{Next: logFile}}
- case "json":
- return log.JSONEmitter{&log.Writer{Next: logFile}}
- case "json-k8s":
- return log.K8sJSONEmitter{&log.Writer{Next: logFile}}
- }
- cmd.Fatalf("invalid log format %q, must be 'text', 'json', or 'json-k8s'", format)
- panic("unreachable")
+ cli.Main(version)
}