diff options
author | Kevin Krakauer <krakauer@google.com> | 2019-06-12 15:21:22 -0700 |
---|---|---|
committer | Kevin Krakauer <krakauer@google.com> | 2019-06-12 15:21:22 -0700 |
commit | 0bbbcafd68154e7c7b46692b84a39fb6bb5f1568 (patch) | |
tree | d8fba01ad76900715665b0418a786de2d77e2a05 /runsc/cmd | |
parent | 06a83df533244dc2b3b8adfc1bf0608d3753c1d9 (diff) | |
parent | 70578806e8d3e01fae2249b3e602cd5b05d378a0 (diff) |
Merge branch 'master' into iptables-1-pkg
Change-Id: I7457a11de4725e1bf3811420c505d225b1cb6943
Diffstat (limited to 'runsc/cmd')
-rw-r--r-- | runsc/cmd/BUILD | 4 | ||||
-rw-r--r-- | runsc/cmd/boot.go | 22 | ||||
-rw-r--r-- | runsc/cmd/capability_test.go | 2 | ||||
-rw-r--r-- | runsc/cmd/cmd.go | 19 | ||||
-rw-r--r-- | runsc/cmd/create.go | 9 | ||||
-rw-r--r-- | runsc/cmd/do.go | 39 | ||||
-rw-r--r-- | runsc/cmd/error.go | 72 | ||||
-rw-r--r-- | runsc/cmd/exec.go | 62 | ||||
-rw-r--r-- | runsc/cmd/help.go | 126 | ||||
-rw-r--r-- | runsc/cmd/restore.go | 10 | ||||
-rw-r--r-- | runsc/cmd/run.go | 8 | ||||
-rw-r--r-- | runsc/cmd/start.go | 1 | ||||
-rw-r--r-- | runsc/cmd/syscalls.go | 347 | ||||
-rw-r--r-- | runsc/cmd/wait.go | 4 |
14 files changed, 636 insertions, 89 deletions
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD index b7551a5ab..df6af0ced 100644 --- a/runsc/cmd/BUILD +++ b/runsc/cmd/BUILD @@ -14,9 +14,11 @@ go_library( "debug.go", "delete.go", "do.go", + "error.go", "events.go", "exec.go", "gofer.go", + "help.go", "kill.go", "list.go", "path.go", @@ -28,6 +30,7 @@ go_library( "spec.go", "start.go", "state.go", + "syscalls.go", "wait.go", ], importpath = "gvisor.googlesource.com/gvisor/runsc/cmd", @@ -38,6 +41,7 @@ go_library( "//pkg/log", "//pkg/p9", "//pkg/sentry/control", + "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", "//pkg/unet", "//pkg/urpc", diff --git a/runsc/cmd/boot.go b/runsc/cmd/boot.go index 3a547d4aa..e0a950e9c 100644 --- a/runsc/cmd/boot.go +++ b/runsc/cmd/boot.go @@ -130,6 +130,8 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) // Ensure that if there is a panic, all goroutine stacks are printed. debug.SetTraceback("all") + conf := args[0].(*boot.Config) + if b.setUpRoot { if err := setUpChroot(b.pidns); err != nil { Fatalf("error setting up chroot: %v", err) @@ -143,14 +145,16 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) args = append(args, arg) } } - // Note that we've already read the spec from the spec FD, and - // we will read it again after the exec call. This works - // because the ReadSpecFromFile function seeks to the beginning - // of the file before reading. - if err := callSelfAsNobody(args); err != nil { - Fatalf("%v", err) + if !conf.Rootless { + // Note that we've already read the spec from the spec FD, and + // we will read it again after the exec call. This works + // because the ReadSpecFromFile function seeks to the beginning + // of the file before reading. + if err := callSelfAsNobody(args); err != nil { + Fatalf("%v", err) + } + panic("callSelfAsNobody must never return success") } - panic("callSelfAsNobody must never return success") } } @@ -163,9 +167,6 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) } specutils.LogSpec(spec) - conf := args[0].(*boot.Config) - waitStatus := args[1].(*syscall.WaitStatus) - if b.applyCaps { caps := spec.Process.Capabilities if caps == nil { @@ -251,6 +252,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) ws := l.WaitExit() log.Infof("application exiting with %+v", ws) + waitStatus := args[1].(*syscall.WaitStatus) *waitStatus = syscall.WaitStatus(ws.Status()) l.Destroy() return subcommands.ExitSuccess diff --git a/runsc/cmd/capability_test.go b/runsc/cmd/capability_test.go index ee74d33d8..2825dfaa5 100644 --- a/runsc/cmd/capability_test.go +++ b/runsc/cmd/capability_test.go @@ -116,6 +116,6 @@ func TestCapabilities(t *testing.T) { } func TestMain(m *testing.M) { - testutil.RunAsRoot() + specutils.MaybeRunAsRoot() os.Exit(m.Run()) } diff --git a/runsc/cmd/cmd.go b/runsc/cmd/cmd.go index a2fc377d1..5b4cc4a39 100644 --- a/runsc/cmd/cmd.go +++ b/runsc/cmd/cmd.go @@ -17,34 +17,15 @@ package cmd import ( "fmt" - "os" "runtime" "strconv" "syscall" - "github.com/google/subcommands" specs "github.com/opencontainers/runtime-spec/specs-go" "gvisor.googlesource.com/gvisor/pkg/log" "gvisor.googlesource.com/gvisor/runsc/specutils" ) -// Errorf logs to stderr and returns subcommands.ExitFailure. -func Errorf(s string, args ...interface{}) subcommands.ExitStatus { - // If runsc is being invoked by docker or cri-o, then we might not have - // access to stderr, so we log a serious-looking warning in addition to - // writing to stderr. - log.Warningf("FATAL ERROR: "+s, args...) - fmt.Fprintf(os.Stderr, s+"\n", args...) - // Return an error that is unlikely to be used by the application. - return subcommands.ExitFailure -} - -// Fatalf logs to stderr and exits with a failure status code. -func Fatalf(s string, args ...interface{}) { - Errorf(s, args...) - os.Exit(128) -} - // intFlags can be used with int flags that appear multiple times. type intFlags []int diff --git a/runsc/cmd/create.go b/runsc/cmd/create.go index 629c198fd..e82e8c667 100644 --- a/runsc/cmd/create.go +++ b/runsc/cmd/create.go @@ -16,7 +16,6 @@ package cmd import ( "context" - "flag" "github.com/google/subcommands" "gvisor.googlesource.com/gvisor/runsc/boot" @@ -83,13 +82,17 @@ func (c *Create) Execute(_ context.Context, f *flag.FlagSet, args ...interface{} id := f.Arg(0) conf := args[0].(*boot.Config) + if conf.Rootless { + return Errorf("Rootless mode not supported with %q", c.Name()) + } + bundleDir := c.bundleDir if bundleDir == "" { bundleDir = getwdOrDie() } spec, err := specutils.ReadSpec(bundleDir) if err != nil { - Fatalf("reading spec: %v", err) + return Errorf("reading spec: %v", err) } specutils.LogSpec(spec) @@ -97,7 +100,7 @@ func (c *Create) Execute(_ context.Context, f *flag.FlagSet, args ...interface{} // container unless the metadata specifies that it should be run in an // existing container. if _, err := container.Create(id, spec, conf, bundleDir, c.consoleSocket, c.pidFile, c.userLog); err != nil { - Fatalf("creating container: %v", err) + return Errorf("creating container: %v", err) } return subcommands.ExitSuccess } diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go index 8ea59046c..3f6e46fce 100644 --- a/runsc/cmd/do.go +++ b/runsc/cmd/do.go @@ -39,10 +39,9 @@ import ( // 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 { - root string - cwd string - ip string - networkNamespace bool + root string + cwd string + ip string } // Name implements subcommands.Command.Name. @@ -72,7 +71,6 @@ func (c *Do) SetFlags(f *flag.FlagSet) { f.StringVar(&c.root, "root", "/", `path to the root directory, defaults to "/"`) f.StringVar(&c.cwd, "cwd", ".", "path to the current directory, defaults to the current directory") f.StringVar(&c.ip, "ip", "192.168.10.2", "IPv4 address for the sandbox") - f.BoolVar(&c.networkNamespace, "netns", true, "run in a new network namespace") } // Execute implements subcommands.Command.Execute. @@ -85,15 +83,21 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su conf := args[0].(*boot.Config) waitStatus := args[1].(*syscall.WaitStatus) - // Map the entire host file system, but make it readonly with a writable - // overlay on top (ignore --overlay option). - conf.Overlay = true + if conf.Rootless { + if err := specutils.MaybeRunAsRoot(); err != nil { + return Errorf("Error executing inside namespace: %v", err) + } + // Execution will continue here if no more capabilities are needed... + } hostname, err := os.Hostname() if err != nil { return Errorf("Error to retrieve hostname: %v", err) } + // Map the entire host file system, but make it readonly with a writable + // overlay on top (ignore --overlay option). + conf.Overlay = true absRoot, err := resolvePath(c.root) if err != nil { return Errorf("Error resolving root: %v", err) @@ -119,11 +123,22 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su specutils.LogSpec(spec) cid := fmt.Sprintf("runsc-%06d", rand.Int31n(1000000)) - if !c.networkNamespace { - if conf.Network != boot.NetworkHost { - Fatalf("The current network namespace can be used only if --network=host is set", nil) + if conf.Network == boot.NetworkNone { + netns := specs.LinuxNamespace{ + Type: specs.NetworkNamespace, + } + if spec.Linux != nil { + panic("spec.Linux is not nil") } - } else if conf.Network != boot.NetworkNone { + spec.Linux = &specs.Linux{Namespaces: []specs.LinuxNamespace{netns}} + + } else if conf.Rootless { + if conf.Network == boot.NetworkSandbox { + fmt.Println("*** Rootless requires changing network type to host ***") + conf.Network = boot.NetworkHost + } + + } else { clean, err := c.setupNet(cid, spec) if err != nil { return Errorf("Error setting up network: %v", err) diff --git a/runsc/cmd/error.go b/runsc/cmd/error.go new file mode 100644 index 000000000..700b19f14 --- /dev/null +++ b/runsc/cmd/error.go @@ -0,0 +1,72 @@ +// Copyright 2019 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 cmd + +import ( + "encoding/json" + "fmt" + "io" + "os" + "time" + + "github.com/google/subcommands" + "gvisor.googlesource.com/gvisor/pkg/log" +) + +// ErrorLogger is where error messages should be written to. These messages are +// consumed by containerd and show up to users of command line tools, +// like docker/kubectl. +var ErrorLogger io.Writer + +type jsonError struct { + Msg string `json:"msg"` + Level string `json:"level"` + Time time.Time `json:"time"` +} + +// Errorf logs error to containerd log (--log), to stderr, and debug logs. It +// returns subcommands.ExitFailure for convenience with subcommand.Execute() +// methods: +// return Errorf("Danger! Danger!") +// +func Errorf(format string, args ...interface{}) subcommands.ExitStatus { + // If runsc is being invoked by docker or cri-o, then we might not have + // access to stderr, so we log a serious-looking warning in addition to + // writing to stderr. + log.Warningf("FATAL ERROR: "+format, args...) + fmt.Fprintf(os.Stderr, format+"\n", args...) + + j := jsonError{ + Msg: fmt.Sprintf(format, args...), + Level: "error", + Time: time.Now(), + } + b, err := json.Marshal(j) + if err != nil { + panic(err) + } + if ErrorLogger != nil { + ErrorLogger.Write(b) + } + + return subcommands.ExitFailure +} + +// Fatalf logs the same way as Errorf() does, plus *exits* the process. +func Fatalf(format string, args ...interface{}) { + Errorf(format, args...) + // Return an error that is unlikely to be used by the application. + os.Exit(128) +} diff --git a/runsc/cmd/exec.go b/runsc/cmd/exec.go index 52fd7ac4b..0eeaaadba 100644 --- a/runsc/cmd/exec.go +++ b/runsc/cmd/exec.go @@ -40,8 +40,6 @@ import ( "gvisor.googlesource.com/gvisor/runsc/specutils" ) -const privateClearStatusFlag = "private-clear-status" - // Exec implements subcommands.Command for the "exec" command. type Exec struct { cwd string @@ -51,7 +49,6 @@ type Exec struct { extraKGIDs stringSlice caps stringSlice detach bool - clearStatus bool processPath string pidFile string internalPidFile string @@ -103,10 +100,6 @@ func (ex *Exec) SetFlags(f *flag.FlagSet) { f.StringVar(&ex.pidFile, "pid-file", "", "filename that the container pid will be written to") f.StringVar(&ex.internalPidFile, "internal-pid-file", "", "filename that the container-internal pid will be written to") f.StringVar(&ex.consoleSocket, "console-socket", "", "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal") - - // This flag clears the status of the exec'd process upon completion. It is - // only used when we fork due to --detach being set on the parent. - f.BoolVar(&ex.clearStatus, privateClearStatusFlag, true, "private flag, do not use") } // Execute implements subcommands.Command.Execute. It starts a process in an @@ -150,13 +143,16 @@ func (ex *Exec) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) // write the child's PID to the pid file. So when the container returns, the // child process will also return and signal containerd. if ex.detach { - return ex.execAndWait(waitStatus) + return ex.execChildAndWait(waitStatus) } + return ex.exec(c, e, waitStatus) +} +func (ex *Exec) exec(c *container.Container, e *control.ExecArgs, waitStatus *syscall.WaitStatus) subcommands.ExitStatus { // Start the new process and get it pid. pid, err := c.Execute(e) if err != nil { - Fatalf("getting processes for container: %v", err) + return Errorf("executing processes for container: %v", err) } if e.StdioIsPty { @@ -170,33 +166,37 @@ func (ex *Exec) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) if ex.internalPidFile != "" { pidStr := []byte(strconv.Itoa(int(pid))) if err := ioutil.WriteFile(ex.internalPidFile, pidStr, 0644); err != nil { - Fatalf("writing internal pid file %q: %v", ex.internalPidFile, err) + return Errorf("writing internal pid file %q: %v", ex.internalPidFile, err) } } - // Generate the pid file after the internal pid file is generated, so that users - // can safely assume that the internal pid file is ready after `runsc exec -d` - // returns. + // Generate the pid file after the internal pid file is generated, so that + // users can safely assume that the internal pid file is ready after + // `runsc exec -d` returns. if ex.pidFile != "" { if err := ioutil.WriteFile(ex.pidFile, []byte(strconv.Itoa(os.Getpid())), 0644); err != nil { - Fatalf("writing pid file: %v", err) + return Errorf("writing pid file: %v", err) } } // Wait for the process to exit. - ws, err := c.WaitPID(pid, ex.clearStatus) + ws, err := c.WaitPID(pid) if err != nil { - Fatalf("waiting on pid %d: %v", pid, err) + return Errorf("waiting on pid %d: %v", pid, err) } *waitStatus = ws return subcommands.ExitSuccess } -func (ex *Exec) execAndWait(waitStatus *syscall.WaitStatus) subcommands.ExitStatus { - binPath := specutils.ExePath +func (ex *Exec) execChildAndWait(waitStatus *syscall.WaitStatus) subcommands.ExitStatus { var args []string + for _, a := range os.Args[1:] { + if !strings.Contains(a, "detach") { + args = append(args, a) + } + } - // The command needs to write a pid file so that execAndWait can tell + // The command needs to write a pid file so that execChildAndWait can tell // when it has started. If no pid-file was provided, we should use a // filename in a temp directory. pidFile := ex.pidFile @@ -210,19 +210,7 @@ func (ex *Exec) execAndWait(waitStatus *syscall.WaitStatus) subcommands.ExitStat args = append(args, "--pid-file="+pidFile) } - // Add the rest of the args, excluding the "detach" flag. - for _, a := range os.Args[1:] { - if strings.Contains(a, "detach") { - // Replace with the "private-clear-status" flag, which tells - // the new process it's a detached child and shouldn't - // clear the exit status of the sentry process. - args = append(args, fmt.Sprintf("--%s=false", privateClearStatusFlag)) - } else { - args = append(args, a) - } - } - - cmd := exec.Command(binPath, args...) + cmd := exec.Command(specutils.ExePath, args...) cmd.Args[0] = "runsc-exec" // Exec stdio defaults to current process stdio. @@ -233,8 +221,7 @@ func (ex *Exec) execAndWait(waitStatus *syscall.WaitStatus) subcommands.ExitStat // If the console control socket file is provided, then create a new // pty master/slave pair and set the TTY on the sandbox process. if ex.consoleSocket != "" { - // Create a new TTY pair and send the master on the provided - // socket. + // Create a new TTY pair and send the master on the provided socket. tty, err := console.NewWithSocket(ex.consoleSocket) if err != nil { Fatalf("setting up console with socket %q: %v", ex.consoleSocket, err) @@ -256,7 +243,7 @@ func (ex *Exec) execAndWait(waitStatus *syscall.WaitStatus) subcommands.ExitStat Fatalf("failure to start child exec process, err: %v", err) } - log.Infof("Started child (PID: %d) to exec and wait: %s %s", cmd.Process.Pid, binPath, args) + log.Infof("Started child (PID: %d) to exec and wait: %s %s", cmd.Process.Pid, specutils.ExePath, args) // Wait for PID file to ensure that child process has started. Otherwise, // '--process' file is deleted as soon as this process returns and the child @@ -278,7 +265,10 @@ func (ex *Exec) execAndWait(waitStatus *syscall.WaitStatus) subcommands.ExitStat return false, nil } if err := specutils.WaitForReady(cmd.Process.Pid, 10*time.Second, ready); err != nil { - Fatalf("unexpected error waiting for PID file, err: %v", err) + // Don't log fatal error here, otherwise it will override the error logged + // by the child process that has failed to start. + log.Warningf("Unexpected error waiting for PID file, err: %v", err) + return subcommands.ExitFailure } *waitStatus = 0 diff --git a/runsc/cmd/help.go b/runsc/cmd/help.go new file mode 100644 index 000000000..ff4f901cb --- /dev/null +++ b/runsc/cmd/help.go @@ -0,0 +1,126 @@ +// Copyright 2018 Google LLC +// +// 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 cmd + +import ( + "context" + "fmt" + + "flag" + "github.com/google/subcommands" +) + +// NewHelp returns a help command for the given commander. +func NewHelp(cdr *subcommands.Commander) *Help { + return &Help{ + cdr: cdr, + } +} + +// Help implements subcommands.Command for the "help" command. The 'help' +// command prints help for commands registered to a Commander but also allows for +// registering additional help commands that print other documentation. +type Help struct { + cdr *subcommands.Commander + commands []subcommands.Command + help bool +} + +// Name implements subcommands.Command.Name. +func (*Help) Name() string { + return "help" +} + +// Synopsis implements subcommands.Command.Synopsis. +func (*Help) Synopsis() string { + return "Print help documentation." +} + +// Usage implements subcommands.Command.Usage. +func (*Help) Usage() string { + return `help [<subcommand>]: + With an argument, prints detailed information on the use of + the specified topic or subcommand. With no argument, print a list of + all commands and a brief description of each. +` +} + +// SetFlags implements subcommands.Command.SetFlags. +func (h *Help) SetFlags(f *flag.FlagSet) {} + +// Execute implements subcommands.Command.Execute. +func (h *Help) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + switch f.NArg() { + case 0: + fmt.Fprintf(h.cdr.Output, "Usage: %s <flags> <subcommand> <subcommand args>\n\n", h.cdr.Name()) + fmt.Fprintf(h.cdr.Output, `runsc is a command line client for running applications packaged in the Open +Container Initiative (OCI) format. Applications run by runsc are run in an +isolated gVisor sandbox that emulates a Linux environment. + +gVisor is a user-space kernel, written in Go, that implements a substantial +portion of the Linux system call interface. It provides an additional layer +of isolation between running applications and the host operating system. + +Functionality is provided by subcommands. For additonal help on individual +subcommands use "%s %s <subcommand>". + +`, h.cdr.Name(), h.Name()) + h.cdr.VisitGroups(func(g *subcommands.CommandGroup) { + h.cdr.ExplainGroup(h.cdr.Output, g) + }) + + fmt.Fprintf(h.cdr.Output, "Additional help topics (Use \"%s %s <topic>\" to see help on the topic):\n", h.cdr.Name(), h.Name()) + for _, cmd := range h.commands { + fmt.Fprintf(h.cdr.Output, "\t%-15s %s\n", cmd.Name(), cmd.Synopsis()) + } + fmt.Fprintf(h.cdr.Output, "\nUse \"%s flags\" for a list of top-level flags\n", h.cdr.Name()) + return subcommands.ExitSuccess + default: + // Look for commands registered to the commander and print help explanation if found. + found := false + h.cdr.VisitCommands(func(g *subcommands.CommandGroup, cmd subcommands.Command) { + if f.Arg(0) == cmd.Name() { + h.cdr.ExplainCommand(h.cdr.Output, cmd) + found = true + } + }) + if found { + return subcommands.ExitSuccess + } + + // Next check commands registered to the help command. + for _, cmd := range h.commands { + if f.Arg(0) == cmd.Name() { + fs := flag.NewFlagSet(f.Arg(0), flag.ContinueOnError) + fs.Usage = func() { h.cdr.ExplainCommand(h.cdr.Error, cmd) } + cmd.SetFlags(fs) + if fs.Parse(f.Args()[1:]) != nil { + return subcommands.ExitUsageError + } + return cmd.Execute(ctx, f, args...) + } + } + + fmt.Fprintf(h.cdr.Error, "Subcommand %s not understood\n", f.Arg(0)) + } + + f.Usage() + return subcommands.ExitUsageError +} + +// Register registers a new help command. +func (h *Help) Register(cmd subcommands.Command) { + h.commands = append(h.commands, cmd) +} diff --git a/runsc/cmd/restore.go b/runsc/cmd/restore.go index 3ab2f5676..a78a0dce6 100644 --- a/runsc/cmd/restore.go +++ b/runsc/cmd/restore.go @@ -80,25 +80,29 @@ func (r *Restore) Execute(_ context.Context, f *flag.FlagSet, args ...interface{ conf := args[0].(*boot.Config) waitStatus := args[1].(*syscall.WaitStatus) + if conf.Rootless { + return Errorf("Rootless mode not supported with %q", r.Name()) + } + bundleDir := r.bundleDir if bundleDir == "" { bundleDir = getwdOrDie() } spec, err := specutils.ReadSpec(bundleDir) if err != nil { - Fatalf("reading spec: %v", err) + return Errorf("reading spec: %v", err) } specutils.LogSpec(spec) if r.imagePath == "" { - Fatalf("image-path flag must be provided") + return Errorf("image-path flag must be provided") } conf.RestoreFile = filepath.Join(r.imagePath, checkpointFileName) ws, err := container.Run(id, spec, conf, bundleDir, r.consoleSocket, r.pidFile, r.userLog, r.detach) if err != nil { - Fatalf("running container: %v", err) + return Errorf("running container: %v", err) } *waitStatus = ws diff --git a/runsc/cmd/run.go b/runsc/cmd/run.go index c228b4f93..abf602239 100644 --- a/runsc/cmd/run.go +++ b/runsc/cmd/run.go @@ -67,19 +67,23 @@ func (r *Run) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) s conf := args[0].(*boot.Config) waitStatus := args[1].(*syscall.WaitStatus) + if conf.Rootless { + return Errorf("Rootless mode not supported with %q", r.Name()) + } + bundleDir := r.bundleDir if bundleDir == "" { bundleDir = getwdOrDie() } spec, err := specutils.ReadSpec(bundleDir) if err != nil { - Fatalf("reading spec: %v", err) + return Errorf("reading spec: %v", err) } specutils.LogSpec(spec) ws, err := container.Run(id, spec, conf, bundleDir, r.consoleSocket, r.pidFile, r.userLog, r.detach) if err != nil { - Fatalf("running container: %v", err) + return Errorf("running container: %v", err) } *waitStatus = ws diff --git a/runsc/cmd/start.go b/runsc/cmd/start.go index 657726251..31e8f42bb 100644 --- a/runsc/cmd/start.go +++ b/runsc/cmd/start.go @@ -16,7 +16,6 @@ package cmd import ( "context" - "flag" "github.com/google/subcommands" "gvisor.googlesource.com/gvisor/runsc/boot" diff --git a/runsc/cmd/syscalls.go b/runsc/cmd/syscalls.go new file mode 100644 index 000000000..9c8a66490 --- /dev/null +++ b/runsc/cmd/syscalls.go @@ -0,0 +1,347 @@ +// Copyright 2019 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 cmd + +import ( + "context" + "encoding/csv" + "encoding/json" + "fmt" + "io" + "os" + "sort" + "strconv" + "text/tabwriter" + + "flag" + "github.com/google/subcommands" + "gvisor.googlesource.com/gvisor/pkg/sentry/kernel" +) + +// Syscalls implements subcommands.Command for the "syscalls" command. +type Syscalls struct { + output string + os string + arch string +} + +// CompatibilityInfo is a map of system and architecture to compatibility doc. +// Maps operating system to architecture to ArchInfo. +type CompatibilityInfo map[string]map[string]ArchInfo + +// ArchInfo is compatbility doc for an architecture. +type ArchInfo struct { + // Syscalls maps syscall number for the architecture to the doc. + Syscalls map[uintptr]SyscallDoc `json:"syscalls"` +} + +// SyscallDoc represents a single item of syscall documentation. +type SyscallDoc struct { + Name string `json:"name"` + num uintptr + + Support string `json:"support"` + Note string `json:"note,omitempty"` + URLs []string `json:"urls,omitempty"` +} + +type outputFunc func(io.Writer, CompatibilityInfo) error + +var ( + // The string name to use for printing compatibility for all OSes. + osAll = "all" + + // The string name to use for printing compatibility for all architectures. + archAll = "all" + + // A map of OS name to map of architecture name to syscall table. + syscallTableMap = make(map[string]map[string]*kernel.SyscallTable) + + // A map of output type names to output functions. + outputMap = map[string]outputFunc{ + "table": outputTable, + "json": outputJSON, + "csv": outputCSV, + } +) + +// Name implements subcommands.Command.Name. +func (*Syscalls) Name() string { + return "syscalls" +} + +// Synopsis implements subcommands.Command.Synopsis. +func (*Syscalls) Synopsis() string { + return "Print compatibility information for syscalls." +} + +// Usage implements subcommands.Command.Usage. +func (*Syscalls) Usage() string { + return `syscalls [options] - Print compatibility information for syscalls. +` +} + +// SetFlags implements subcommands.Command.SetFlags. +func (s *Syscalls) SetFlags(f *flag.FlagSet) { + f.StringVar(&s.output, "o", "table", "Output format (table, csv, json).") + f.StringVar(&s.os, "os", osAll, "The OS (e.g. linux)") + f.StringVar(&s.arch, "arch", archAll, "The CPU architecture (e.g. amd64).") +} + +// Execute implements subcommands.Command.Execute. +func (s *Syscalls) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + out, ok := outputMap[s.output] + if !ok { + Fatalf("Unsupported output format %q", s.output) + } + + // Build map of all supported architectures. + tables := kernel.SyscallTables() + for _, t := range tables { + osMap, ok := syscallTableMap[t.OS.String()] + if !ok { + osMap = make(map[string]*kernel.SyscallTable) + syscallTableMap[t.OS.String()] = osMap + } + osMap[t.Arch.String()] = t + } + + // Build a map of the architectures we want to output. + info, err := getCompatibilityInfo(s.os, s.arch) + if err != nil { + Fatalf("%v", err) + } + + if err := out(os.Stdout, info); err != nil { + Fatalf("Error writing output: %v", err) + } + + return subcommands.ExitSuccess +} + +// getCompatibilityInfo returns compatibility info for the given OS name and +// architecture name. Supports the special name 'all' for OS and architecture that +// specifies that all supported OSes or architectures should be included. +func getCompatibilityInfo(osName string, archName string) (CompatibilityInfo, error) { + info := CompatibilityInfo(make(map[string]map[string]ArchInfo)) + if osName == osAll { + // Special processing for the 'all' OS name. + for osName, _ := range syscallTableMap { + info[osName] = make(map[string]ArchInfo) + // osName is a specific OS name. + if err := addToCompatibilityInfo(info, osName, archName); err != nil { + return info, err + } + } + } else { + // osName is a specific OS name. + info[osName] = make(map[string]ArchInfo) + if err := addToCompatibilityInfo(info, osName, archName); err != nil { + return info, err + } + } + + return info, nil +} + +// addToCompatibilityInfo adds ArchInfo for the given specific OS name and +// architecture name. Supports the special architecture name 'all' to specify +// that all supported architectures for the OS should be included. +func addToCompatibilityInfo(info CompatibilityInfo, osName string, archName string) error { + if archName == archAll { + // Special processing for the 'all' architecture name. + for archName, _ := range syscallTableMap[osName] { + archInfo, err := getArchInfo(osName, archName) + if err != nil { + return err + } + info[osName][archName] = archInfo + } + } else { + // archName is a specific architecture name. + archInfo, err := getArchInfo(osName, archName) + if err != nil { + return err + } + info[osName][archName] = archInfo + } + + return nil +} + +// getArchInfo returns compatibility info for a specific OS and architecture. +func getArchInfo(osName string, archName string) (ArchInfo, error) { + info := ArchInfo{} + info.Syscalls = make(map[uintptr]SyscallDoc) + + t, ok := syscallTableMap[osName][archName] + if !ok { + return info, fmt.Errorf("syscall table for %s/%s not found", osName, archName) + } + + for num, sc := range t.Table { + info.Syscalls[num] = SyscallDoc{ + Name: sc.Name, + num: num, + Support: sc.SupportLevel.String(), + Note: sc.Note, + URLs: sc.URLs, + } + } + + return info, nil +} + +// outputTable outputs the syscall info in tabular format. +func outputTable(w io.Writer, info CompatibilityInfo) error { + tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) + + // Linux + for osName, osInfo := range info { + for archName, archInfo := range osInfo { + // Print the OS/arch + fmt.Fprintf(w, "%s/%s:\n\n", osName, archName) + + // Sort the syscalls for output in the table. + sortedCalls := []SyscallDoc{} + for _, sc := range archInfo.Syscalls { + sortedCalls = append(sortedCalls, sc) + } + sort.Slice(sortedCalls, func(i, j int) bool { + return sortedCalls[i].num < sortedCalls[j].num + }) + + // Write the header + _, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", + "NUM", + "NAME", + "SUPPORT", + "NOTE", + ) + if err != nil { + return err + } + + // Write each syscall entry + for _, sc := range sortedCalls { + _, err = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", + strconv.FormatInt(int64(sc.num), 10), + sc.Name, + sc.Support, + sc.Note, + ) + if err != nil { + return err + } + // Add issue urls to note. + for _, url := range sc.URLs { + _, err = fmt.Fprintf(tw, "%s\t%s\t%s\tSee: %s\t\n", + "", + "", + "", + url, + ) + if err != nil { + return err + } + } + } + + err = tw.Flush() + if err != nil { + return err + } + } + } + + return nil +} + +// outputJSON outputs the syscall info in JSON format. +func outputJSON(w io.Writer, info CompatibilityInfo) error { + e := json.NewEncoder(w) + e.SetIndent("", " ") + return e.Encode(info) +} + +// numberedRow is aCSV row annotated by syscall number (used for sorting) +type numberedRow struct { + num uintptr + row []string +} + +// outputCSV outputs the syscall info in tabular format. +func outputCSV(w io.Writer, info CompatibilityInfo) error { + csvWriter := csv.NewWriter(w) + + // Linux + for osName, osInfo := range info { + for archName, archInfo := range osInfo { + // Sort the syscalls for output in the table. + sortedCalls := []numberedRow{} + for _, sc := range archInfo.Syscalls { + // Add issue urls to note. + note := sc.Note + for _, url := range sc.URLs { + note = fmt.Sprintf("%s\nSee: %s", note, url) + } + + sortedCalls = append(sortedCalls, numberedRow{ + num: sc.num, + row: []string{ + osName, + archName, + strconv.FormatInt(int64(sc.num), 10), + sc.Name, + sc.Support, + note, + }, + }) + } + sort.Slice(sortedCalls, func(i, j int) bool { + return sortedCalls[i].num < sortedCalls[j].num + }) + + // Write the header + err := csvWriter.Write([]string{ + "OS", + "Arch", + "Num", + "Name", + "Support", + "Note", + }) + if err != nil { + return err + } + + // Write each syscall entry + for _, sc := range sortedCalls { + err = csvWriter.Write(sc.row) + if err != nil { + return err + } + } + + csvWriter.Flush() + err = csvWriter.Error() + if err != nil { + return err + } + } + } + + return nil +} diff --git a/runsc/cmd/wait.go b/runsc/cmd/wait.go index a55a682f3..58fd01974 100644 --- a/runsc/cmd/wait.go +++ b/runsc/cmd/wait.go @@ -88,14 +88,14 @@ func (wt *Wait) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) waitStatus = ws // Wait on a PID in the root PID namespace. case wt.rootPID != unsetPID: - ws, err := c.WaitRootPID(int32(wt.rootPID), true /* clearStatus */) + ws, err := c.WaitRootPID(int32(wt.rootPID)) if err != nil { Fatalf("waiting on PID in root PID namespace %d in container %q: %v", wt.rootPID, c.ID, err) } waitStatus = ws // Wait on a PID in the container's PID namespace. case wt.pid != unsetPID: - ws, err := c.WaitPID(int32(wt.pid), true /* clearStatus */) + ws, err := c.WaitPID(int32(wt.pid)) if err != nil { Fatalf("waiting on PID %d in container %q: %v", wt.pid, c.ID, err) } |