summaryrefslogtreecommitdiffhomepage
path: root/runsc/config
diff options
context:
space:
mode:
authorFabricio Voznika <fvoznika@google.com>2020-08-26 20:22:39 -0700
committergVisor bot <gvisor-bot@google.com>2020-08-26 20:24:41 -0700
commit32e7a54f7f413ba83af8217b9bc0a367e3c30f94 (patch)
treeb6c6179d0e9a1633cb9e9aaf16d0d429e44e80f8 /runsc/config
parenta4b1c6f5a42e174ee9c1b286a3eade596507091c (diff)
Make flag propagation automatic
Use reflection and tags to provide automatic conversion from Config to flags. This makes adding new flags less error-prone, skips flags using default values (easier to read), and makes tests correctly use default flag values for test Configs. Updates #3494 PiperOrigin-RevId: 328662070
Diffstat (limited to 'runsc/config')
-rw-r--r--runsc/config/BUILD15
-rw-r--r--runsc/config/config.go443
-rw-r--r--runsc/config/config_test.go185
-rw-r--r--runsc/config/flags.go168
4 files changed, 566 insertions, 245 deletions
diff --git a/runsc/config/BUILD b/runsc/config/BUILD
index 3c8713d53..b1672bb9d 100644
--- a/runsc/config/BUILD
+++ b/runsc/config/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "go_library")
+load("//tools:defs.bzl", "go_library", "go_test")
package(licenses = ["notice"])
@@ -6,10 +6,23 @@ go_library(
name = "config",
srcs = [
"config.go",
+ "flags.go",
],
visibility = ["//:sandbox"],
deps = [
"//pkg/refs",
"//pkg/sentry/watchdog",
+ "//pkg/sync",
+ "//runsc/flag",
],
)
+
+go_test(
+ name = "config_test",
+ size = "small",
+ srcs = [
+ "config_test.go",
+ ],
+ library = ":config",
+ deps = ["//runsc/flag"],
+)
diff --git a/runsc/config/config.go b/runsc/config/config.go
index 8cf0378d5..bca27ebf1 100644
--- a/runsc/config/config.go
+++ b/runsc/config/config.go
@@ -19,254 +19,105 @@ package config
import (
"fmt"
- "strconv"
- "strings"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/sentry/watchdog"
)
-// FileAccessType tells how the filesystem is accessed.
-type FileAccessType int
-
-const (
- // FileAccessShared sends IO requests to a Gofer process that validates the
- // requests and forwards them to the host.
- FileAccessShared FileAccessType = iota
-
- // FileAccessExclusive is the same as FileAccessShared, but enables
- // extra caching for improved performance. It should only be used if
- // the sandbox has exclusive access to the filesystem.
- FileAccessExclusive
-)
-
-// MakeFileAccessType converts type from string.
-func MakeFileAccessType(s string) (FileAccessType, error) {
- switch s {
- case "shared":
- return FileAccessShared, nil
- case "exclusive":
- return FileAccessExclusive, nil
- default:
- return 0, fmt.Errorf("invalid file access type %q", s)
- }
-}
-
-func (f FileAccessType) String() string {
- switch f {
- case FileAccessShared:
- return "shared"
- case FileAccessExclusive:
- return "exclusive"
- default:
- return fmt.Sprintf("unknown(%d)", f)
- }
-}
-
-// NetworkType tells which network stack to use.
-type NetworkType int
-
-const (
- // NetworkSandbox uses internal network stack, isolated from the host.
- NetworkSandbox NetworkType = iota
-
- // NetworkHost redirects network related syscalls to the host network.
- NetworkHost
-
- // NetworkNone sets up just loopback using netstack.
- NetworkNone
-)
-
-// MakeNetworkType converts type from string.
-func MakeNetworkType(s string) (NetworkType, error) {
- switch s {
- case "sandbox":
- return NetworkSandbox, nil
- case "host":
- return NetworkHost, nil
- case "none":
- return NetworkNone, nil
- default:
- return 0, fmt.Errorf("invalid network type %q", s)
- }
-}
-
-func (n NetworkType) String() string {
- switch n {
- case NetworkSandbox:
- return "sandbox"
- case NetworkHost:
- return "host"
- case NetworkNone:
- return "none"
- default:
- return fmt.Sprintf("unknown(%d)", n)
- }
-}
-
-// MakeWatchdogAction converts type from string.
-func MakeWatchdogAction(s string) (watchdog.Action, error) {
- switch strings.ToLower(s) {
- case "log", "logwarning":
- return watchdog.LogWarning, nil
- case "panic":
- return watchdog.Panic, nil
- default:
- return 0, fmt.Errorf("invalid watchdog action %q", s)
- }
-}
-
-// MakeRefsLeakMode converts type from string.
-func MakeRefsLeakMode(s string) (refs.LeakMode, error) {
- switch strings.ToLower(s) {
- case "disabled":
- return refs.NoLeakChecking, nil
- case "log-names":
- return refs.LeaksLogWarning, nil
- case "log-traces":
- return refs.LeaksLogTraces, nil
- default:
- return 0, fmt.Errorf("invalid refs leakmode %q", s)
- }
-}
-
-func refsLeakModeToString(mode refs.LeakMode) string {
- switch mode {
- // If not set, default it to disabled.
- case refs.UninitializedLeakChecking, refs.NoLeakChecking:
- return "disabled"
- case refs.LeaksLogWarning:
- return "log-names"
- case refs.LeaksLogTraces:
- return "log-traces"
- default:
- panic(fmt.Sprintf("Invalid leakmode: %d", mode))
- }
-}
-
-// QueueingDiscipline is used to specify the kind of Queueing Discipline to
-// apply for a give FDBasedLink.
-type QueueingDiscipline int
-
-const (
- // QDiscNone disables any queueing for the underlying FD.
- QDiscNone QueueingDiscipline = iota
-
- // QDiscFIFO applies a simple fifo based queue to the underlying
- // FD.
- QDiscFIFO
-)
-
-// MakeQueueingDiscipline if possible the equivalent QueuingDiscipline for s
-// else returns an error.
-func MakeQueueingDiscipline(s string) (QueueingDiscipline, error) {
- switch s {
- case "none":
- return QDiscNone, nil
- case "fifo":
- return QDiscFIFO, nil
- default:
- return 0, fmt.Errorf("unsupported qdisc specified: %q", s)
- }
-}
-
-// String implements fmt.Stringer.
-func (q QueueingDiscipline) String() string {
- switch q {
- case QDiscNone:
- return "none"
- case QDiscFIFO:
- return "fifo"
- default:
- panic(fmt.Sprintf("Invalid queueing discipline: %d", q))
- }
-}
-
// Config holds configuration that is not part of the runtime spec.
+//
+// Follow these steps to add a new flag:
+// 1. Create a new field in Config.
+// 2. Add a field tag with the flag name
+// 3. Register a new flag in flags.go, with name and description
+// 4. Add any necessary validation into validate()
+// 5. If adding an enum, follow the same pattern as FileAccessType
+//
type Config struct {
// RootDir is the runtime root directory.
- RootDir string
+ RootDir string `flag:"root"`
// Debug indicates that debug logging should be enabled.
- Debug bool
+ Debug bool `flag:"debug"`
// LogFilename is the filename to log to, if not empty.
- LogFilename string
+ LogFilename string `flag:"log"`
// LogFormat is the log format.
- LogFormat string
+ LogFormat string `flag:"log-format"`
// DebugLog is the path to log debug information to, if not empty.
- DebugLog string
+ DebugLog string `flag:"debug-log"`
// PanicLog is the path to log GO's runtime messages, if not empty.
- PanicLog string
+ PanicLog string `flag:"panic-log"`
// DebugLogFormat is the log format for debug.
- DebugLogFormat string
+ DebugLogFormat string `flag:"debug-log-format"`
// FileAccess indicates how the filesystem is accessed.
- FileAccess FileAccessType
+ FileAccess FileAccessType `flag:"file-access"`
// Overlay is whether to wrap the root filesystem in an overlay.
- Overlay bool
+ Overlay bool `flag:"overlay"`
// FSGoferHostUDS enables the gofer to mount a host UDS.
- FSGoferHostUDS bool
+ FSGoferHostUDS bool `flag:"fsgofer-host-uds"`
// Network indicates what type of network to use.
- Network NetworkType
+ Network NetworkType `flag:"network"`
// EnableRaw indicates whether raw sockets should be enabled. Raw
// sockets are disabled by stripping CAP_NET_RAW from the list of
// capabilities.
- EnableRaw bool
+ EnableRaw bool `flag:"net-raw"`
// HardwareGSO indicates that hardware segmentation offload is enabled.
- HardwareGSO bool
+ HardwareGSO bool `flag:"gso"`
// SoftwareGSO indicates that software segmentation offload is enabled.
- SoftwareGSO bool
+ SoftwareGSO bool `flag:"software-gso"`
// TXChecksumOffload indicates that TX Checksum Offload is enabled.
- TXChecksumOffload bool
+ TXChecksumOffload bool `flag:"tx-checksum-offload"`
// RXChecksumOffload indicates that RX Checksum Offload is enabled.
- RXChecksumOffload bool
+ RXChecksumOffload bool `flag:"rx-checksum-offload"`
// QDisc indicates the type of queuening discipline to use by default
// for non-loopback interfaces.
- QDisc QueueingDiscipline
+ QDisc QueueingDiscipline `flag:"qdisc"`
// LogPackets indicates that all network packets should be logged.
- LogPackets bool
+ LogPackets bool `flag:"log-packets"`
// Platform is the platform to run on.
- Platform string
+ Platform string `flag:"platform"`
// Strace indicates that strace should be enabled.
- Strace bool
+ Strace bool `flag:"strace"`
- // StraceSyscalls is the set of syscalls to trace. If StraceEnable is
- // true and this list is empty, then all syscalls will be traced.
- StraceSyscalls []string
+ // StraceSyscalls is the set of syscalls to trace (comma-separated values).
+ // If StraceEnable is true and this string is empty, then all syscalls will
+ // be traced.
+ StraceSyscalls string `flag:"strace-syscalls"`
// StraceLogSize is the max size of data blobs to display.
- StraceLogSize uint
+ StraceLogSize uint `flag:"strace-log-size"`
// DisableSeccomp indicates whether seccomp syscall filters should be
// disabled. Pardon the double negation, but default to enabled is important.
DisableSeccomp bool
// WatchdogAction sets what action the watchdog takes when triggered.
- WatchdogAction watchdog.Action
+ WatchdogAction watchdog.Action `flag:"watchdog-action"`
// PanicSignal registers signal handling that panics. Usually set to
// SIGUSR2(12) to troubleshoot hangs. -1 disables it.
- PanicSignal int
+ PanicSignal int `flag:"panic-signal"`
// ProfileEnable is set to prepare the sandbox to be profiled.
- ProfileEnable bool
+ ProfileEnable bool `flag:"profile"`
// RestoreFile is the path to the saved container image
RestoreFile string
@@ -274,105 +125,209 @@ type Config struct {
// NumNetworkChannels controls the number of AF_PACKET sockets that map
// to the same underlying network device. This allows netstack to better
// scale for high throughput use cases.
- NumNetworkChannels int
+ NumNetworkChannels int `flag:"num-network-channels"`
// Rootless allows the sandbox to be started with a user that is not root.
// Defense is depth measures are weaker with rootless. Specifically, the
// sandbox and Gofer process run as root inside a user namespace with root
// mapped to the caller's user.
- Rootless bool
+ Rootless bool `flag:"rootless"`
// AlsoLogToStderr allows to send log messages to stderr.
- AlsoLogToStderr bool
+ AlsoLogToStderr bool `flag:"alsologtostderr"`
// ReferenceLeakMode sets reference leak check mode
- ReferenceLeakMode refs.LeakMode
+ ReferenceLeak refs.LeakMode `flag:"ref-leak-mode"`
// OverlayfsStaleRead instructs the sandbox to assume that the root mount
// is on a Linux overlayfs mount, which does not necessarily preserve
// coherence between read-only and subsequent writable file descriptors
// representing the "same" file.
- OverlayfsStaleRead bool
+ OverlayfsStaleRead bool `flag:"overlayfs-stale-read"`
// CPUNumFromQuota sets CPU number count to available CPU quota, using
// least integer value greater than or equal to quota.
//
// E.g. 0.2 CPU quota will result in 1, and 1.9 in 2.
- CPUNumFromQuota bool
+ CPUNumFromQuota bool `flag:"cpu-num-from-quota"`
- // Enables VFS2 (not plumbed through yet).
- VFS2 bool
+ // Enables VFS2.
+ VFS2 bool `flag:"vfs2"`
- // Enables FUSE usage (not plumbed through yet).
- FUSE bool
+ // Enables FUSE usage.
+ FUSE bool `flag:"fuse"`
// TestOnlyAllowRunAsCurrentUserWithoutChroot should only be used in
// tests. It allows runsc to start the sandbox process as the current
// user, and without chrooting the sandbox process. This can be
// necessary in test environments that have limited capabilities.
- TestOnlyAllowRunAsCurrentUserWithoutChroot bool
+ TestOnlyAllowRunAsCurrentUserWithoutChroot bool `flag:"TESTONLY-unsafe-nonroot"`
// TestOnlyTestNameEnv should only be used in tests. It looks up for the
// test name in the container environment variables and adds it to the debug
// log file name. This is done to help identify the log with the test when
// multiple tests are run in parallel, since there is no way to pass
// parameters to the runtime from docker.
- TestOnlyTestNameEnv string
+ TestOnlyTestNameEnv string `flag:"TESTONLY-test-name-env"`
+}
+
+func (c *Config) validate() error {
+ if c.FileAccess == FileAccessShared && c.Overlay {
+ return fmt.Errorf("overlay flag is incompatible with shared file access")
+ }
+ if c.NumNetworkChannels <= 0 {
+ return fmt.Errorf("num_network_channels must be > 0, got: %d", c.NumNetworkChannels)
+ }
+ return nil
}
-// ToFlags returns a slice of flags that correspond to the given Config.
-func (c *Config) ToFlags() []string {
- f := []string{
- "--root=" + c.RootDir,
- "--debug=" + strconv.FormatBool(c.Debug),
- "--log=" + c.LogFilename,
- "--log-format=" + c.LogFormat,
- "--debug-log=" + c.DebugLog,
- "--panic-log=" + c.PanicLog,
- "--debug-log-format=" + c.DebugLogFormat,
- "--file-access=" + c.FileAccess.String(),
- "--overlay=" + strconv.FormatBool(c.Overlay),
- "--fsgofer-host-uds=" + strconv.FormatBool(c.FSGoferHostUDS),
- "--network=" + c.Network.String(),
- "--log-packets=" + strconv.FormatBool(c.LogPackets),
- "--platform=" + c.Platform,
- "--strace=" + strconv.FormatBool(c.Strace),
- "--strace-syscalls=" + strings.Join(c.StraceSyscalls, ","),
- "--strace-log-size=" + strconv.Itoa(int(c.StraceLogSize)),
- "--watchdog-action=" + c.WatchdogAction.String(),
- "--panic-signal=" + strconv.Itoa(c.PanicSignal),
- "--profile=" + strconv.FormatBool(c.ProfileEnable),
- "--net-raw=" + strconv.FormatBool(c.EnableRaw),
- "--num-network-channels=" + strconv.Itoa(c.NumNetworkChannels),
- "--rootless=" + strconv.FormatBool(c.Rootless),
- "--alsologtostderr=" + strconv.FormatBool(c.AlsoLogToStderr),
- "--ref-leak-mode=" + refsLeakModeToString(c.ReferenceLeakMode),
- "--gso=" + strconv.FormatBool(c.HardwareGSO),
- "--software-gso=" + strconv.FormatBool(c.SoftwareGSO),
- "--rx-checksum-offload=" + strconv.FormatBool(c.RXChecksumOffload),
- "--tx-checksum-offload=" + strconv.FormatBool(c.TXChecksumOffload),
- "--overlayfs-stale-read=" + strconv.FormatBool(c.OverlayfsStaleRead),
- "--qdisc=" + c.QDisc.String(),
- "--vfs2=" + strconv.FormatBool(c.VFS2),
- "--fuse=" + strconv.FormatBool(c.FUSE),
+// FileAccessType tells how the filesystem is accessed.
+type FileAccessType int
+
+const (
+ // FileAccessExclusive is the same as FileAccessShared, but enables
+ // extra caching for improved performance. It should only be used if
+ // the sandbox has exclusive access to the filesystem.
+ FileAccessExclusive FileAccessType = iota
+
+ // FileAccessShared sends IO requests to a Gofer process that validates the
+ // requests and forwards them to the host.
+ FileAccessShared
+)
+
+func fileAccessTypePtr(v FileAccessType) *FileAccessType {
+ return &v
+}
+
+// Set implements flag.Value.
+func (f *FileAccessType) Set(v string) error {
+ switch v {
+ case "shared":
+ *f = FileAccessShared
+ case "exclusive":
+ *f = FileAccessExclusive
+ default:
+ return fmt.Errorf("invalid file access type %q", v)
}
- if c.CPUNumFromQuota {
- f = append(f, "--cpu-num-from-quota")
+ return nil
+}
+
+// Get implements flag.Value.
+func (f *FileAccessType) Get() interface{} {
+ return *f
+}
+
+// String implements flag.Value.
+func (f *FileAccessType) String() string {
+ switch *f {
+ case FileAccessShared:
+ return "shared"
+ case FileAccessExclusive:
+ return "exclusive"
}
- if c.VFS2 {
- f = append(f, "--vfs2=true")
+ panic(fmt.Sprintf("Invalid file access type %v", *f))
+}
+
+// NetworkType tells which network stack to use.
+type NetworkType int
+
+const (
+ // NetworkSandbox uses internal network stack, isolated from the host.
+ NetworkSandbox NetworkType = iota
+
+ // NetworkHost redirects network related syscalls to the host network.
+ NetworkHost
+
+ // NetworkNone sets up just loopback using netstack.
+ NetworkNone
+)
+
+func networkTypePtr(v NetworkType) *NetworkType {
+ return &v
+}
+
+// Set implements flag.Value.
+func (n *NetworkType) Set(v string) error {
+ switch v {
+ case "sandbox":
+ *n = NetworkSandbox
+ case "host":
+ *n = NetworkHost
+ case "none":
+ *n = NetworkNone
+ default:
+ return fmt.Errorf("invalid network type %q", v)
}
- if c.FUSE {
- f = append(f, "--fuse=true")
+ return nil
+}
+
+// Get implements flag.Value.
+func (n *NetworkType) Get() interface{} {
+ return *n
+}
+
+// String implements flag.Value.
+func (n *NetworkType) String() string {
+ switch *n {
+ case NetworkSandbox:
+ return "sandbox"
+ case NetworkHost:
+ return "host"
+ case NetworkNone:
+ return "none"
}
+ panic(fmt.Sprintf("Invalid network type %v", *n))
+}
+
+// QueueingDiscipline is used to specify the kind of Queueing Discipline to
+// apply for a give FDBasedLink.
+type QueueingDiscipline int
- // Only include these if set since it is never to be used by users.
- if c.TestOnlyAllowRunAsCurrentUserWithoutChroot {
- f = append(f, "--TESTONLY-unsafe-nonroot=true")
+const (
+ // QDiscNone disables any queueing for the underlying FD.
+ QDiscNone QueueingDiscipline = iota
+
+ // QDiscFIFO applies a simple fifo based queue to the underlying FD.
+ QDiscFIFO
+)
+
+func queueingDisciplinePtr(v QueueingDiscipline) *QueueingDiscipline {
+ return &v
+}
+
+// Set implements flag.Value.
+func (q *QueueingDiscipline) Set(v string) error {
+ switch v {
+ case "none":
+ *q = QDiscNone
+ case "fifo":
+ *q = QDiscFIFO
+ default:
+ return fmt.Errorf("invalid qdisc %q", v)
}
- if len(c.TestOnlyTestNameEnv) != 0 {
- f = append(f, "--TESTONLY-test-name-env="+c.TestOnlyTestNameEnv)
+ return nil
+}
+
+// Get implements flag.Value.
+func (q *QueueingDiscipline) Get() interface{} {
+ return *q
+}
+
+// String implements flag.Value.
+func (q *QueueingDiscipline) String() string {
+ switch *q {
+ case QDiscNone:
+ return "none"
+ case QDiscFIFO:
+ return "fifo"
}
+ panic(fmt.Sprintf("Invalid qdisc %v", *q))
+}
+
+func leakModePtr(v refs.LeakMode) *refs.LeakMode {
+ return &v
+}
- return f
+func watchdogActionPtr(v watchdog.Action) *watchdog.Action {
+ return &v
}
diff --git a/runsc/config/config_test.go b/runsc/config/config_test.go
new file mode 100644
index 000000000..af7867a2a
--- /dev/null
+++ b/runsc/config/config_test.go
@@ -0,0 +1,185 @@
+// 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.
+// 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 config
+
+import (
+ "strings"
+ "testing"
+
+ "gvisor.dev/gvisor/runsc/flag"
+)
+
+func init() {
+ RegisterFlags()
+}
+
+func TestDefault(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ // "--root" is always set to something different than the default. Reset it
+ // to make it easier to test that default values do not generate flags.
+ c.RootDir = ""
+
+ // All defaults doesn't require setting flags.
+ flags := c.ToFlags()
+ if len(flags) > 0 {
+ t.Errorf("default flags not set correctly for: %s", flags)
+ }
+}
+
+func setDefault(name string) {
+ fl := flag.CommandLine.Lookup(name)
+ fl.Value.Set(fl.DefValue)
+}
+
+func TestFromFlags(t *testing.T) {
+ flag.CommandLine.Lookup("root").Value.Set("some-path")
+ flag.CommandLine.Lookup("debug").Value.Set("true")
+ flag.CommandLine.Lookup("num-network-channels").Value.Set("123")
+ flag.CommandLine.Lookup("network").Value.Set("none")
+ defer func() {
+ setDefault("root")
+ setDefault("debug")
+ setDefault("num-network-channels")
+ setDefault("network")
+ }()
+
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if want := "some-path"; c.RootDir != want {
+ t.Errorf("RootDir=%v, want: %v", c.RootDir, want)
+ }
+ if want := true; c.Debug != want {
+ t.Errorf("Debug=%v, want: %v", c.Debug, want)
+ }
+ if want := 123; c.NumNetworkChannels != want {
+ t.Errorf("NumNetworkChannels=%v, want: %v", c.NumNetworkChannels, want)
+ }
+ if want := NetworkNone; c.Network != want {
+ t.Errorf("Network=%v, want: %v", c.Network, want)
+ }
+}
+
+func TestToFlags(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.RootDir = "some-path"
+ c.Debug = true
+ c.NumNetworkChannels = 123
+ c.Network = NetworkNone
+
+ flags := c.ToFlags()
+ if len(flags) != 4 {
+ t.Errorf("wrong number of flags set, want: 4, got: %d: %s", len(flags), flags)
+ }
+ t.Logf("Flags: %s", flags)
+ fm := map[string]string{}
+ for _, f := range flags {
+ kv := strings.Split(f, "=")
+ fm[kv[0]] = kv[1]
+ }
+ for name, want := range map[string]string{
+ "--root": "some-path",
+ "--debug": "true",
+ "--num-network-channels": "123",
+ "--network": "none",
+ } {
+ if got, ok := fm[name]; ok {
+ if got != want {
+ t.Errorf("flag %q, want: %q, got: %q", name, want, got)
+ }
+ } else {
+ t.Errorf("flag %q not set", name)
+ }
+ }
+}
+
+// TestInvalidFlags checks that enum flags fail when value is not in enum set.
+func TestInvalidFlags(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ error string
+ }{
+ {
+ name: "file-access",
+ error: "invalid file access type",
+ },
+ {
+ name: "network",
+ error: "invalid network type",
+ },
+ {
+ name: "qdisc",
+ error: "invalid qdisc",
+ },
+ {
+ name: "watchdog-action",
+ error: "invalid watchdog action",
+ },
+ {
+ name: "ref-leak-mode",
+ error: "invalid ref leak mode",
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ defer setDefault(tc.name)
+ if err := flag.CommandLine.Lookup(tc.name).Value.Set("invalid"); err == nil || !strings.Contains(err.Error(), tc.error) {
+ t.Errorf("flag.Value.Set(invalid) wrong error reported: %v", err)
+ }
+ })
+ }
+}
+
+func TestValidationFail(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ flags map[string]string
+ error string
+ }{
+ {
+ name: "shared+overlay",
+ flags: map[string]string{
+ "file-access": "shared",
+ "overlay": "true",
+ },
+ error: "overlay flag is incompatible",
+ },
+ {
+ name: "network-channels",
+ flags: map[string]string{
+ "num-network-channels": "-1",
+ },
+ error: "num_network_channels must be > 0",
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ for name, val := range tc.flags {
+ defer setDefault(name)
+ if err := flag.CommandLine.Lookup(name).Value.Set(val); err != nil {
+ t.Errorf("%s=%q: %v", name, val, err)
+ }
+ }
+ if _, err := NewFromFlags(); err == nil || !strings.Contains(err.Error(), tc.error) {
+ t.Errorf("NewFromFlags() wrong error reported: %v", err)
+ }
+ })
+ }
+}
diff --git a/runsc/config/flags.go b/runsc/config/flags.go
new file mode 100644
index 000000000..488a4b9fb
--- /dev/null
+++ b/runsc/config/flags.go
@@ -0,0 +1,168 @@
+// 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.
+// 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 config
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strconv"
+
+ "gvisor.dev/gvisor/pkg/refs"
+ "gvisor.dev/gvisor/pkg/sentry/watchdog"
+ "gvisor.dev/gvisor/pkg/sync"
+ "gvisor.dev/gvisor/runsc/flag"
+)
+
+var registration sync.Once
+
+// This is the set of flags used to populate Config.
+func RegisterFlags() {
+ registration.Do(func() {
+ // Although these flags are not part of the OCI spec, they are used by
+ // Docker, and thus should not be changed.
+ flag.String("root", "", "root directory for storage of container state.")
+ flag.String("log", "", "file path where internal debug information is written, default is stdout.")
+ flag.String("log-format", "text", "log format: text (default), json, or json-k8s.")
+ flag.Bool("debug", false, "enable debug logging.")
+
+ // 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.
+ flag.String("debug-log", "", "additional location for logs. If it ends with '/', log files are created inside the directory with default names. The following variables are available: %TIMESTAMP%, %COMMAND%.")
+ flag.String("panic-log", "", "file path were panic reports and other Go's runtime messages are written.")
+ flag.Bool("log-packets", false, "enable network packet logging.")
+ flag.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.")
+ flag.Bool("alsologtostderr", false, "send log messages to stderr.")
+
+ // Debugging flags: strace related
+ flag.Bool("strace", false, "enable strace.")
+ flag.String("strace-syscalls", "", "comma-separated list of syscalls to trace. If --strace is true and this list is empty, then all syscalls will be traced.")
+ flag.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs.")
+
+ // Flags that control sandbox runtime behavior.
+ flag.String("platform", "ptrace", "specifies which platform to use: ptrace (default), kvm.")
+ flag.Var(watchdogActionPtr(watchdog.LogWarning), "watchdog-action", "sets what action the watchdog takes when triggered: log (default), panic.")
+ flag.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.")
+ flag.Bool("profile", false, "prepares the sandbox to use Golang profiler. Note that enabling profiler loosens the seccomp protection added to the sandbox (DO NOT USE IN PRODUCTION).")
+ flag.Bool("rootless", false, "it allows the sandbox to be started with a user that is not root. Sandbox and Gofer processes may run with same privileges as current user.")
+ flag.Var(leakModePtr(refs.NoLeakChecking), "ref-leak-mode", "sets reference leak check mode: disabled (default), log-names, log-traces.")
+ flag.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value, but not less than 2)")
+
+ // Flags that control sandbox runtime behavior: FS related.
+ flag.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem to use for the root mount: exclusive (default), shared. Volume mounts are always shared.")
+ flag.Bool("overlay", false, "wrap filesystem mounts with writable overlay. All modifications are stored in memory inside the sandbox.")
+ flag.Bool("overlayfs-stale-read", true, "assume root mount is an overlay filesystem")
+ flag.Bool("fsgofer-host-uds", false, "allow the gofer to mount Unix Domain Sockets.")
+ flag.Bool("vfs2", false, "TEST ONLY; use while VFSv2 is landing. This uses the new experimental VFS layer.")
+ flag.Bool("fuse", false, "TEST ONLY; use while FUSE in VFSv2 is landing. This allows the use of the new experimental FUSE filesystem.")
+
+ // Flags that control sandbox runtime behavior: network related.
+ flag.Var(networkTypePtr(NetworkSandbox), "network", "specifies which network to use: sandbox (default), host, none. Using network inside the sandbox is more secure because it's isolated from the host network.")
+ flag.Bool("net-raw", false, "enable raw sockets. When false, raw sockets are disabled by removing CAP_NET_RAW from containers (`runsc exec` will still be able to utilize raw sockets). Raw sockets allow malicious containers to craft packets and potentially attack the network.")
+ flag.Bool("gso", true, "enable hardware segmentation offload if it is supported by a network device.")
+ flag.Bool("software-gso", true, "enable software segmentation offload when hardware offload can't be enabled.")
+ flag.Bool("tx-checksum-offload", false, "enable TX checksum offload.")
+ flag.Bool("rx-checksum-offload", true, "enable RX checksum offload.")
+ flag.Var(queueingDisciplinePtr(QDiscFIFO), "qdisc", "specifies which queueing discipline to apply by default to the non loopback nics used by the sandbox.")
+ flag.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.")
+
+ // Test flags, not to be used outside tests, ever.
+ flag.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.")
+ flag.String("TESTONLY-test-name-env", "", "TEST ONLY; do not ever use! Used for automated tests to improve logging.")
+ })
+}
+
+// NewFromFlags creates a new Config with values coming from command line flags.
+func NewFromFlags() (*Config, error) {
+ conf := &Config{}
+
+ obj := reflect.ValueOf(conf).Elem()
+ st := obj.Type()
+ for i := 0; i < st.NumField(); i++ {
+ f := st.Field(i)
+ name, ok := f.Tag.Lookup("flag")
+ if !ok {
+ // No flag set for this field.
+ continue
+ }
+ fl := flag.CommandLine.Lookup(name)
+ if fl == nil {
+ panic(fmt.Sprintf("Flag %q not found", name))
+ }
+ x := reflect.ValueOf(flag.Get(fl.Value))
+ obj.Field(i).Set(x)
+ }
+
+ if len(conf.RootDir) == 0 {
+ // If not set, set default root dir to something (hopefully) user-writeable.
+ conf.RootDir = "/var/run/runsc"
+ if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
+ conf.RootDir = filepath.Join(runtimeDir, "runsc")
+ }
+ }
+
+ if err := conf.validate(); err != nil {
+ return nil, err
+ }
+ return conf, nil
+}
+
+// ToFlags returns a slice of flags that correspond to the given Config.
+func (c *Config) ToFlags() []string {
+ var rv []string
+
+ obj := reflect.ValueOf(c).Elem()
+ st := obj.Type()
+ for i := 0; i < st.NumField(); i++ {
+ f := st.Field(i)
+ name, ok := f.Tag.Lookup("flag")
+ if !ok {
+ // No flag set for this field.
+ continue
+ }
+ val := getVal(obj.Field(i))
+
+ flag := flag.CommandLine.Lookup(name)
+ if flag == nil {
+ panic(fmt.Sprintf("Flag %q not found", name))
+ }
+ if val == flag.DefValue {
+ continue
+ }
+ rv = append(rv, fmt.Sprintf("--%s=%s", flag.Name, val))
+ }
+ return rv
+}
+
+func getVal(field reflect.Value) string {
+ if str, ok := field.Addr().Interface().(fmt.Stringer); ok {
+ return str.String()
+ }
+ switch field.Kind() {
+ case reflect.Bool:
+ return strconv.FormatBool(field.Bool())
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return strconv.FormatInt(field.Int(), 10)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return strconv.FormatUint(field.Uint(), 10)
+ case reflect.String:
+ return field.String()
+ default:
+ panic("unknown type " + field.Kind().String())
+ }
+}