mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
a4b1c6f5a4
commit
32e7a54f7f
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user