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
This commit is contained in:
Fabricio Voznika
2020-08-26 20:24:41 -07:00
committed by gVisor bot
parent a4b1c6f5a4
commit 32e7a54f7f
12 changed files with 734 additions and 465 deletions
+33
View File
@@ -234,6 +234,39 @@ const (
LeaksLogTraces
)
// Set implements flag.Value.
func (l *LeakMode) Set(v string) error {
switch v {
case "disabled":
*l = NoLeakChecking
case "log-names":
*l = LeaksLogWarning
case "log-traces":
*l = LeaksLogTraces
default:
return fmt.Errorf("invalid ref leak mode %q", v)
}
return nil
}
// Get implements flag.Value.
func (l *LeakMode) Get() interface{} {
return *l
}
// String implements flag.Value.
func (l *LeakMode) String() string {
switch *l {
case NoLeakChecking:
return "disabled"
case LeaksLogWarning:
return "log-names"
case LeaksLogTraces:
return "log-traces"
}
panic(fmt.Sprintf("invalid ref leak mode %q", *l))
}
// leakMode stores the current mode for the reference leak checker.
//
// Values must be one of the LeakMode values.
+26 -8
View File
@@ -96,15 +96,33 @@ const (
Panic
)
// String returns Action's string representation.
func (a Action) String() string {
switch a {
case LogWarning:
return "LogWarning"
case Panic:
return "Panic"
// Set implements flag.Value.
func (a *Action) Set(v string) error {
switch v {
case "log", "logwarning":
*a = LogWarning
case "panic":
*a = Panic
default:
panic(fmt.Sprintf("Invalid action: %d", a))
return fmt.Errorf("invalid watchdog action %q", v)
}
return nil
}
// Get implements flag.Value.
func (a *Action) Get() interface{} {
return *a
}
// String returns Action's string representation.
func (a *Action) String() string {
switch *a {
case LogWarning:
return "logWarning"
case Panic:
return "panic"
default:
panic(fmt.Sprintf("Invalid watchdog action: %d", *a))
}
}
+15 -12
View File
@@ -138,20 +138,23 @@ func TestConfig(t *testing.T) *config.Config {
if dir, ok := os.LookupEnv("TEST_UNDECLARED_OUTPUTS_DIR"); ok {
logDir = dir + "/"
}
return &config.Config{
Debug: true,
DebugLog: path.Join(logDir, "runsc.log."+t.Name()+".%TIMESTAMP%.%COMMAND%"),
LogFormat: "text",
DebugLogFormat: "text",
LogPackets: true,
Network: config.NetworkNone,
Strace: true,
Platform: "ptrace",
FileAccess: config.FileAccessExclusive,
NumNetworkChannels: 1,
TestOnlyAllowRunAsCurrentUserWithoutChroot: true,
// Only register flags if config is being used. Otherwise anyone that uses
// testutil will get flags registered and they may conflict.
config.RegisterFlags()
conf, err := config.NewFromFlags()
if err != nil {
panic(err)
}
// Change test defaults.
conf.Debug = true
conf.DebugLog = path.Join(logDir, "runsc.log."+t.Name()+".%TIMESTAMP%.%COMMAND%")
conf.LogPackets = true
conf.Network = config.NetworkNone
conf.Strace = true
conf.TestOnlyAllowRunAsCurrentUserWithoutChroot = true
return conf
}
// NewSpecWithArgs creates a simple spec with the given args suitable for use
+12 -8
View File
@@ -44,15 +44,19 @@ func init() {
if err := fsgofer.OpenProcSelfFD(); err != nil {
panic(err)
}
config.RegisterFlags()
}
func testConfig() *config.Config {
return &config.Config{
RootDir: "unused_root_dir",
Network: config.NetworkNone,
DisableSeccomp: true,
Platform: "ptrace",
conf, err := config.NewFromFlags()
if err != nil {
panic(err)
}
// Change test defaults.
conf.RootDir = "unused_root_dir"
conf.Network = config.NetworkNone
conf.DisableSeccomp = true
return conf
}
// testSpec returns a simple spec that can be used in tests.
@@ -546,7 +550,7 @@ func TestRestoreEnvironment(t *testing.T) {
{
Dev: "9pfs-/",
Flags: fs.MountSourceFlags{ReadOnly: true},
DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating",
DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true",
},
},
"tmpfs": {
@@ -600,7 +604,7 @@ func TestRestoreEnvironment(t *testing.T) {
{
Dev: "9pfs-/",
Flags: fs.MountSourceFlags{ReadOnly: true},
DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating",
DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true",
},
{
Dev: "9pfs-/dev/fd-foo",
@@ -658,7 +662,7 @@ func TestRestoreEnvironment(t *testing.T) {
{
Dev: "9pfs-/",
Flags: fs.MountSourceFlags{ReadOnly: true},
DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating",
DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true",
},
},
"tmpfs": {
+3 -1
View File
@@ -15,6 +15,8 @@
package boot
import (
"strings"
"gvisor.dev/gvisor/pkg/sentry/strace"
"gvisor.dev/gvisor/runsc/config"
)
@@ -37,5 +39,5 @@ func enableStrace(conf *config.Config) error {
strace.EnableAll(strace.SinkTypeLog)
return nil
}
return strace.Enable(conf.StraceSyscalls, strace.SinkTypeLog)
return strace.Enable(strings.Split(conf.StraceSyscalls, ","), strace.SinkTypeLog)
}
+14 -1
View File
@@ -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"],
)
+241 -286
View File
File diff suppressed because it is too large Load Diff
+185
View File
@@ -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)
}
})
}
}
+168
View File
@@ -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())
}
}
+10 -4
View File
@@ -21,13 +21,19 @@ import (
type FlagSet = flag.FlagSet
var (
NewFlagSet = flag.NewFlagSet
String = flag.String
Bool = flag.Bool
Int = flag.Int
Uint = flag.Uint
CommandLine = flag.CommandLine
Int = flag.Int
NewFlagSet = flag.NewFlagSet
Parse = flag.Parse
String = flag.String
Uint = flag.Uint
Var = flag.Var
)
const ContinueOnError = flag.ContinueOnError
// Get returns the flag's underlying object.
func Get(v flag.Value) interface{} {
return v.(flag.Getter).Get()
}
+26 -144
View File
@@ -23,8 +23,6 @@ import (
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
@@ -41,58 +39,17 @@ import (
var (
// Although these flags are not part of the OCI spec, they are used by
// Docker, and thus should not be changed.
rootDir = flag.String("root", "", "root directory for storage of container state.")
logFilename = flag.String("log", "", "file path where internal debug information is written, default is stdout.")
logFormat = flag.String("log-format", "text", "log format: text (default), json, or json-k8s.")
debug = flag.Bool("debug", false, "enable debug logging.")
showVersion = flag.Bool("version", false, "show version and exit.")
// 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.
debugLog = 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%.")
panicLog = flag.String("panic-log", "", "file path were panic reports and other Go's runtime messages are written.")
logPackets = flag.Bool("log-packets", false, "enable network packet logging.")
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.")
debugLogFormat = flag.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.")
alsoLogToStderr = flag.Bool("alsologtostderr", false, "send log messages to stderr.")
// Debugging flags: strace related
strace = flag.Bool("strace", false, "enable strace.")
straceSyscalls = 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.")
straceLogSize = flag.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs.")
// Flags that control sandbox runtime behavior.
platformName = flag.String("platform", "ptrace", "specifies which platform to use: ptrace (default), kvm.")
network = flag.String("network", "sandbox", "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.")
hardwareGSO = flag.Bool("gso", true, "enable hardware segmentation offload if it is supported by a network device.")
softwareGSO = flag.Bool("software-gso", true, "enable software segmentation offload when hardware offload can't be enabled.")
txChecksumOffload = flag.Bool("tx-checksum-offload", false, "enable TX checksum offload.")
rxChecksumOffload = flag.Bool("rx-checksum-offload", true, "enable RX checksum offload.")
qDisc = flag.String("qdisc", "fifo", "specifies which queueing discipline to apply by default to the non loopback nics used by the sandbox.")
fileAccess = flag.String("file-access", "exclusive", "specifies which filesystem to use for the root mount: exclusive (default), shared. Volume mounts are always shared.")
fsGoferHostUDS = flag.Bool("fsgofer-host-uds", false, "allow the gofer to mount Unix Domain Sockets.")
overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable overlay. All modifications are stored in memory inside the sandbox.")
overlayfsStaleRead = flag.Bool("overlayfs-stale-read", true, "assume root mount is an overlay filesystem")
watchdogAction = flag.String("watchdog-action", "log", "sets what action the watchdog takes when triggered: log (default), panic.")
panicSignal = flag.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.")
profile = 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).")
netRaw = 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.")
numNetworkChannels = flag.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.")
rootless = 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.")
referenceLeakMode = flag.String("ref-leak-mode", "disabled", "sets reference leak check mode: disabled (default), log-names, log-traces.")
cpuNumFromQuota = 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)")
vfs2Enabled = flag.Bool("vfs2", false, "TEST ONLY; use while VFSv2 is landing. This uses the new experimental VFS layer.")
fuseEnabled = flag.Bool("fuse", false, "TEST ONLY; use while FUSE in VFSv2 is landing. This allows the use of the new experimental FUSE filesystem.")
// Test flags, not to be used outside tests, ever.
testOnlyAllowRunAsCurrentUserWithoutChroot = flag.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.")
testOnlyTestNameEnv = flag.String("TESTONLY-test-name-env", "", "TEST ONLY; do not ever use! Used for automated tests to improve logging.")
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.")
)
func main() {
@@ -136,6 +93,8 @@ func main() {
subcommands.Register(new(cmd.Gofer), internalGroup)
subcommands.Register(new(cmd.Statefile), internalGroup)
config.RegisterFlags()
// All subcommands must be registered before flag parsing.
flag.Parse()
@@ -147,6 +106,12 @@ func main() {
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")
@@ -157,103 +122,28 @@ func main() {
if *logFD > -1 {
errorLogger = os.NewFile(uintptr(*logFD), "error log file")
} else if *logFilename != "" {
} 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(*logFilename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
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", *logFilename, err)
cmd.Fatalf("error opening log file %q: %v", conf.LogFilename, err)
}
}
cmd.ErrorLogger = errorLogger
platformType := *platformName
if _, err := platform.Lookup(platformType); err != nil {
if _, err := platform.Lookup(conf.Platform); err != nil {
cmd.Fatalf("%v", err)
}
fsAccess, err := config.MakeFileAccessType(*fileAccess)
if err != nil {
cmd.Fatalf("%v", err)
}
if fsAccess == config.FileAccessShared && *overlay {
cmd.Fatalf("overlay flag is incompatible with shared file access")
}
netType, err := config.MakeNetworkType(*network)
if err != nil {
cmd.Fatalf("%v", err)
}
wa, err := config.MakeWatchdogAction(*watchdogAction)
if err != nil {
cmd.Fatalf("%v", err)
}
if *numNetworkChannels <= 0 {
cmd.Fatalf("num_network_channels must be > 0, got: %d", *numNetworkChannels)
}
refsLeakMode, err := config.MakeRefsLeakMode(*referenceLeakMode)
if err != nil {
cmd.Fatalf("%v", err)
}
queueingDiscipline, err := config.MakeQueueingDiscipline(*qDisc)
if err != nil {
cmd.Fatalf("%s", err)
}
// Sets the reference leak check mode. Also set it in config below to
// propagate it to child processes.
refs.SetLeakMode(refsLeakMode)
// Create a new Config from the flags.
conf := &config.Config{
RootDir: *rootDir,
Debug: *debug,
LogFilename: *logFilename,
LogFormat: *logFormat,
DebugLog: *debugLog,
PanicLog: *panicLog,
DebugLogFormat: *debugLogFormat,
FileAccess: fsAccess,
FSGoferHostUDS: *fsGoferHostUDS,
Overlay: *overlay,
Network: netType,
HardwareGSO: *hardwareGSO,
SoftwareGSO: *softwareGSO,
TXChecksumOffload: *txChecksumOffload,
RXChecksumOffload: *rxChecksumOffload,
LogPackets: *logPackets,
Platform: platformType,
Strace: *strace,
StraceLogSize: *straceLogSize,
WatchdogAction: wa,
PanicSignal: *panicSignal,
ProfileEnable: *profile,
EnableRaw: *netRaw,
NumNetworkChannels: *numNetworkChannels,
Rootless: *rootless,
AlsoLogToStderr: *alsoLogToStderr,
ReferenceLeakMode: refsLeakMode,
OverlayfsStaleRead: *overlayfsStaleRead,
CPUNumFromQuota: *cpuNumFromQuota,
VFS2: *vfs2Enabled,
FUSE: *fuseEnabled,
QDisc: queueingDiscipline,
TestOnlyAllowRunAsCurrentUserWithoutChroot: *testOnlyAllowRunAsCurrentUserWithoutChroot,
TestOnlyTestNameEnv: *testOnlyTestNameEnv,
}
if len(*straceSyscalls) != 0 {
conf.StraceSyscalls = strings.Split(*straceSyscalls, ",")
}
refs.SetLeakMode(conf.ReferenceLeak)
// Set up logging.
if *debug {
if conf.Debug {
log.SetLevel(log.Debug)
}
@@ -275,14 +165,14 @@ func main() {
if *debugLogFD > -1 {
f := os.NewFile(uintptr(*debugLogFD), "debug log file")
e = newEmitter(*debugLogFormat, f)
e = newEmitter(conf.DebugLogFormat, f)
} else if *debugLog != "" {
f, err := specutils.DebugLogFile(*debugLog, subcommand, "" /* name */)
} 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", *debugLog, err)
cmd.Fatalf("error opening debug log file in %q: %v", conf.DebugLog, err)
}
e = newEmitter(*debugLogFormat, f)
e = newEmitter(conf.DebugLogFormat, f)
} else {
// Stderr is reserved for the application, just discard the logs if no debug
@@ -308,8 +198,8 @@ func main() {
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 *alsoLogToStderr {
e = &log.MultiEmitter{e, newEmitter(*debugLogFormat, os.Stderr)}
} else if conf.AlsoLogToStderr {
e = &log.MultiEmitter{e, newEmitter(conf.DebugLogFormat, os.Stderr)}
}
log.SetTarget(e)
@@ -328,7 +218,7 @@ func main() {
log.Infof("\t\tVFS2 enabled: %v", conf.VFS2)
log.Infof("***************************")
if *testOnlyAllowRunAsCurrentUserWithoutChroot {
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!")
@@ -364,11 +254,3 @@ func newEmitter(format string, logFile io.Writer) log.Emitter {
cmd.Fatalf("invalid log format %q, must be 'text', 'json', or 'json-k8s'", format)
panic("unreachable")
}
func init() {
// Set default root dir to something (hopefully) user-writeable.
*rootDir = "/var/run/runsc"
if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
*rootDir = filepath.Join(runtimeDir, "runsc")
}
}
+1 -1
View File
@@ -69,7 +69,7 @@ func setupNetwork(conn *urpc.Client, pid int, spec *specs.Spec, conf *config.Con
case config.NetworkHost:
// Nothing to do here.
default:
return fmt.Errorf("invalid network type: %d", conf.Network)
return fmt.Errorf("invalid network type: %v", conf.Network)
}
return nil
}