github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/runsc/config/flags.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "bytes" 19 "fmt" 20 "os" 21 "path/filepath" 22 "reflect" 23 "sort" 24 "strconv" 25 "strings" 26 "text/template" 27 28 "github.com/MerlinKodo/gvisor/pkg/log" 29 "github.com/MerlinKodo/gvisor/pkg/refs" 30 "github.com/MerlinKodo/gvisor/pkg/sentry/watchdog" 31 "github.com/MerlinKodo/gvisor/runsc/flag" 32 ) 33 34 // RegisterFlags registers flags used to populate Config. 35 func RegisterFlags(flagSet *flag.FlagSet) { 36 // Although these flags are not part of the OCI spec, they are used by 37 // Docker, and thus should not be changed. 38 flagSet.String("root", "", "root directory for storage of container state.") 39 flagSet.String("log", "", "file path where internal debug information is written, default is stdout.") 40 flagSet.String("log-format", "text", "log format: text (default), json, or json-k8s.") 41 flagSet.Bool("debug", false, "enable debug logging.") 42 flagSet.Bool("systemd-cgroup", false, "EXPERIMENTAL. Use systemd for cgroups.") 43 44 // These flags are unique to runsc, and are used to configure parts of the 45 // system that are not covered by the runtime spec. 46 47 // Debugging flags. 48 flagSet.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%.") 49 flagSet.String("debug-command", "", `comma-separated list of commands to be debugged if --debug-log is also set. Empty means debug all. "!" negates the expression. E.g. "create,start" or "!boot,events"`) 50 flagSet.String("panic-log", "", "file path where panic reports and other Go's runtime messages are written.") 51 flagSet.String("coverage-report", "", "file path where Go coverage reports are written. Reports will only be generated if runsc is built with --collect_code_coverage and --instrumentation_filter Bazel flags.") 52 flagSet.Bool("log-packets", false, "enable network packet logging.") 53 flagSet.String("pcap-log", "", "location of PCAP log file.") 54 flagSet.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.") 55 // Only register -alsologtostderr flag if it is not already defined on this flagSet. 56 if flagSet.Lookup("alsologtostderr") == nil { 57 flagSet.Bool("alsologtostderr", false, "send log messages to stderr.") 58 } 59 flagSet.Bool("allow-flag-override", false, "allow OCI annotations (dev.gvisor.flag.<name>) to override flags for debugging.") 60 flagSet.String("traceback", "system", "golang runtime's traceback level") 61 62 // Metrics flags. 63 flagSet.String("metric-server", "", "if set, export metrics on this address. This may either be 1) 'addr:port' to export metrics on a specific network interface address, 2) ':port' for exporting metrics on all interfaces, or 3) an absolute path to a Unix Domain Socket. The substring '%ID%' will be replaced by the container ID, and '%RUNTIME_ROOT%' by the root. This flag must be specified in both `runsc metric-server` and `runsc create`, and their values must match.") 64 flagSet.String("profiling-metrics", "", "comma separated list of metric names which are going to be written to the profiling-metrics-log file from within the sentry in CSV format. profiling-metrics will be snapshotted at a rate specified by profiling-metrics-rate-us. Requires profiling-metrics-log to be set. (DO NOT USE IN PRODUCTION).") 65 flagSet.String("profiling-metrics-log", "", "file name to use for profiling-metrics output. (DO NOT USE IN PRODUCTION)") 66 flagSet.Int("profiling-metrics-rate-us", 1000, "the target rate (in microseconds) at which profiling metrics will be snapshotted.") 67 68 // Debugging flags: strace related 69 flagSet.Bool("strace", false, "enable strace.") 70 flagSet.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.") 71 flagSet.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs.") 72 flagSet.Bool("strace-event", false, "send strace to event.") 73 74 // Flags that control sandbox runtime behavior. 75 flagSet.String("platform", "systrap", "specifies which platform to use: systrap (default), ptrace, kvm.") 76 flagSet.String("platform_device_path", "", "path to a platform-specific device file (e.g. /dev/kvm for KVM platform). If unset, will use a sane platform-specific default.") 77 flagSet.Var(watchdogActionPtr(watchdog.LogWarning), "watchdog-action", "sets what action the watchdog takes when triggered: log (default), panic.") 78 flagSet.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.") 79 flagSet.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).") 80 flagSet.String("profile-block", "", "collects a block profile to this file path for the duration of the container execution. Requires -profile=true.") 81 flagSet.String("profile-cpu", "", "collects a CPU profile to this file path for the duration of the container execution. Requires -profile=true.") 82 flagSet.String("profile-heap", "", "collects a heap profile to this file path for the duration of the container execution. Requires -profile=true.") 83 flagSet.String("profile-mutex", "", "collects a mutex profile to this file path for the duration of the container execution. Requires -profile=true.") 84 flagSet.String("trace", "", "collects a Go runtime execution trace to this file path for the duration of the container execution.") 85 flagSet.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.") 86 flagSet.Var(leakModePtr(refs.NoLeakChecking), "ref-leak-mode", "sets reference leak check mode: disabled (default), log-names, log-traces.") 87 flagSet.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)") 88 flagSet.Bool("oci-seccomp", false, "Enables loading OCI seccomp filters inside the sandbox.") 89 flagSet.Bool("enable-core-tags", false, "enables core tagging. Requires host linux kernel >= 5.14.") 90 flagSet.String("pod-init-config", "", "path to configuration file with additional steps to take during pod creation.") 91 92 // Flags that control sandbox runtime behavior: FS related. 93 flagSet.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.") 94 flagSet.Var(fileAccessTypePtr(FileAccessShared), "file-access-mounts", "specifies which filesystem validation to use for volumes other than the root mount: shared (default), exclusive.") 95 flagSet.Bool("overlay", false, "DEPRECATED: use --overlay2=all:memory to achieve the same effect") 96 flagSet.Var(defaultOverlay2(), "overlay2", "wrap mounts with overlayfs. Format is {mount}:{medium}, where 'mount' can be 'root' or 'all' and medium can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created. 'none' will turn overlay mode off.") 97 flagSet.Bool("fsgofer-host-uds", false, "DEPRECATED: use host-uds=all") 98 flagSet.Var(hostUDSPtr(HostUDSNone), "host-uds", "controls permission to access host Unix-domain sockets. Values: none|open|create|all, default: none") 99 flagSet.Var(hostFifoPtr(HostFifoNone), "host-fifo", "controls permission to access host FIFOs (or named pipes). Values: none|open, default: none") 100 101 flagSet.Bool("vfs2", true, "DEPRECATED: this flag has no effect.") 102 flagSet.Bool("fuse", true, "DEPRECATED: this flag has no effect.") 103 flagSet.Bool("lisafs", true, "DEPRECATED: this flag has no effect.") 104 flagSet.Bool("cgroupfs", false, "Automatically mount cgroupfs.") 105 flagSet.Bool("ignore-cgroups", false, "don't configure cgroups.") 106 flagSet.Int("fdlimit", -1, "Specifies a limit on the number of host file descriptors that can be open. Applies separately to the sentry and gofer. Note: each file in the sandbox holds more than one host FD open.") 107 flagSet.Int("dcache", -1, "Set the global dentry cache size. This acts as a coarse-grained control on the number of host FDs simultaneously open by the sentry. If negative, per-mount caches are used.") 108 flagSet.Bool("iouring", false, "TEST ONLY; Enables io_uring syscalls in the sentry. Support is experimental and very limited.") 109 flagSet.Bool("directfs", true, "directly access the container filesystems from the sentry. Sentry runs with higher privileges.") 110 111 // Flags that control sandbox runtime behavior: network related. 112 flagSet.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.") 113 flagSet.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.") 114 flagSet.Bool("gso", true, "enable host segmentation offload if it is supported by a network device.") 115 flagSet.Bool("software-gso", true, "enable gVisor segmentation offload when host offload can't be enabled.") 116 flagSet.Duration("gvisor-gro", 0, "(e.g. \"20000ns\" or \"1ms\") sets gVisor's generic receive offload timeout. Zero bypasses GRO.") 117 flagSet.Bool("tx-checksum-offload", false, "enable TX checksum offload.") 118 flagSet.Bool("rx-checksum-offload", true, "enable RX checksum offload.") 119 flagSet.Var(queueingDisciplinePtr(QDiscFIFO), "qdisc", "specifies which queueing discipline to apply by default to the non loopback nics used by the sandbox.") 120 flagSet.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.") 121 flagSet.Bool("buffer-pooling", true, "enable allocation of buffers from a shared pool instead of the heap.") 122 flagSet.Bool("EXPERIMENTAL-afxdp", false, "EXPERIMENTAL. Use an AF_XDP socket to receive packets.") 123 124 // Flags that control sandbox runtime behavior: accelerator related. 125 flagSet.Bool("nvproxy", false, "EXPERIMENTAL: enable support for Nvidia GPUs") 126 flagSet.Bool("nvproxy-docker", false, "Expose GPUs to containers based on NVIDIA_VISIBLE_DEVICES, as requested by the container or set by `docker --gpus`. Allows containers to self-serve GPU access and thus disabled by default for security. libnvidia-container must be installed on the host. No effect unless --nvproxy is enabled.") 127 flagSet.Bool("tpuproxy", false, "EXPERIMENTAL: enable support for TPU device passthrough.") 128 129 // Test flags, not to be used outside tests, ever. 130 flagSet.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.") 131 flagSet.String("TESTONLY-test-name-env", "", "TEST ONLY; do not ever use! Used for automated tests to improve logging.") 132 flagSet.Bool("TESTONLY-allow-packet-endpoint-write", false, "TEST ONLY; do not ever use! Used for tests to allow writes on packet sockets.") 133 flagSet.Bool("TESTONLY-afs-syscall-panic", false, "TEST ONLY; do not ever use! Used for tests exercising gVisor panic reporting.") 134 } 135 136 // overrideAllowlist lists all flags that can be changed using OCI 137 // annotations without an administrator setting `--allow-flag-override` on the 138 // runtime. Flags in this list can be set by container authors and should not 139 // make the sandbox less secure. 140 var overrideAllowlist = map[string]struct { 141 check func(name string, value string) error 142 }{ 143 "debug": {}, 144 "strace": {}, 145 "strace-syscalls": {}, 146 "strace-log-size": {}, 147 "host-uds": {}, 148 149 "oci-seccomp": {check: checkOciSeccomp}, 150 } 151 152 // checkOciSeccomp ensures that seccomp can be enabled but not disabled. 153 func checkOciSeccomp(name string, value string) error { 154 enable, err := strconv.ParseBool(value) 155 if err != nil { 156 return err 157 } 158 if !enable { 159 return fmt.Errorf("disabling %q requires flag %q to be enabled", name, "allow-flag-override") 160 } 161 return nil 162 } 163 164 // isFlagExplicitlySet returns whether the given flag name is explicitly set. 165 // Doesn't check for flag existence; returns `false` for flags that don't exist. 166 func isFlagExplicitlySet(flagSet *flag.FlagSet, name string) bool { 167 explicit := false 168 169 // The FlagSet.Visit function only visits flags that are explicitly set, as opposed to VisitAll. 170 flagSet.Visit(func(fl *flag.Flag) { 171 explicit = explicit || fl.Name == name 172 }) 173 174 return explicit 175 } 176 177 // NewFromFlags creates a new Config with values coming from command line flags. 178 func NewFromFlags(flagSet *flag.FlagSet) (*Config, error) { 179 conf := &Config{explicitlySet: map[string]struct{}{}} 180 181 obj := reflect.ValueOf(conf).Elem() 182 st := obj.Type() 183 for i := 0; i < st.NumField(); i++ { 184 f := st.Field(i) 185 name, ok := f.Tag.Lookup("flag") 186 if !ok { 187 // No flag set for this field. 188 continue 189 } 190 fl := flagSet.Lookup(name) 191 if fl == nil { 192 panic(fmt.Sprintf("Flag %q not found", name)) 193 } 194 x := reflect.ValueOf(flag.Get(fl.Value)) 195 obj.Field(i).Set(x) 196 if isFlagExplicitlySet(flagSet, name) { 197 conf.explicitlySet[name] = struct{}{} 198 } 199 } 200 201 if len(conf.RootDir) == 0 { 202 // If not set, set default root dir to something (hopefully) user-writeable. 203 conf.RootDir = "/var/run/runsc" 204 // NOTE: empty values for XDG_RUNTIME_DIR should be ignored. 205 if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" { 206 conf.RootDir = filepath.Join(runtimeDir, "runsc") 207 } 208 } 209 210 if err := conf.validate(); err != nil { 211 return nil, err 212 } 213 return conf, nil 214 } 215 216 // NewFromBundle makes a new config from a Bundle. 217 func NewFromBundle(bundle Bundle) (*Config, error) { 218 if err := bundle.Validate(); err != nil { 219 return nil, err 220 } 221 flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError) 222 RegisterFlags(flagSet) 223 conf := &Config{explicitlySet: map[string]struct{}{}} 224 225 obj := reflect.ValueOf(conf).Elem() 226 st := obj.Type() 227 for i := 0; i < st.NumField(); i++ { 228 f := st.Field(i) 229 name, ok := f.Tag.Lookup("flag") 230 if !ok { 231 continue 232 } 233 fl := flagSet.Lookup(name) 234 if fl == nil { 235 return nil, fmt.Errorf("flag %q not found", name) 236 } 237 val, ok := bundle[name] 238 if !ok { 239 continue 240 } 241 if err := flagSet.Set(name, val); err != nil { 242 return nil, fmt.Errorf("error setting flag %s=%q: %w", name, val, err) 243 } 244 conf.Override(flagSet, name, val, true) 245 246 conf.explicitlySet[name] = struct{}{} 247 } 248 return conf, nil 249 } 250 251 // ToFlags returns a slice of flags that correspond to the given Config. 252 func (c *Config) ToFlags() []string { 253 flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError) 254 RegisterFlags(flagSet) 255 256 var rv []string 257 keyVals := c.keyVals(flagSet, false /*onlyIfSet*/) 258 for name, val := range keyVals { 259 rv = append(rv, fmt.Sprintf("--%s=%s", name, val)) 260 } 261 262 // Construct a temporary set for default plumbing. 263 return rv 264 } 265 266 // KeyVal is a key value pair. It is used so ToContainerdConfigTOML returns 267 // predictable ordering for runsc flags. 268 type KeyVal struct { 269 Key string 270 Val string 271 } 272 273 // ContainerdConfigOptions contains arguments for ToContainerdConfigTOML. 274 type ContainerdConfigOptions struct { 275 BinaryPath string 276 RootPath string 277 Options map[string]string 278 RunscFlags []KeyVal 279 } 280 281 // ToContainerdConfigTOML turns a given config into a format for a k8s containerd config.toml file. 282 // See: https://gvisor.dev/docs/user_guide/containerd/quick_start/ 283 func (c *Config) ToContainerdConfigTOML(opts ContainerdConfigOptions) (string, error) { 284 flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError) 285 RegisterFlags(flagSet) 286 keyVals := c.keyVals(flagSet, true /*onlyIfSet*/) 287 keys := []string{} 288 for k := range keyVals { 289 keys = append(keys, k) 290 } 291 292 sort.Strings(keys) 293 294 for _, k := range keys { 295 opts.RunscFlags = append(opts.RunscFlags, KeyVal{k, keyVals[k]}) 296 } 297 298 const temp = `{{if .BinaryPath}}binary_name = "{{.BinaryPath}}"{{end}} 299 {{if .RootPath}}root = "{{.RootPath}}"{{end}} 300 {{if .Options}}{{ range $key, $value := .Options}}{{$key}} = "{{$value}}" 301 {{end}}{{end}}{{if .RunscFlags}}[runsc_config] 302 {{ range $fl:= .RunscFlags}} {{$fl.Key}} = "{{$fl.Val}}" 303 {{end}}{{end}}` 304 305 t := template.New("temp") 306 t, err := t.Parse(temp) 307 if err != nil { 308 return "", err 309 } 310 var buf bytes.Buffer 311 if err := t.Execute(&buf, opts); err != nil { 312 return "", err 313 } 314 return buf.String(), nil 315 } 316 317 func (c *Config) keyVals(flagSet *flag.FlagSet, onlyIfSet bool) map[string]string { 318 keyVals := make(map[string]string) 319 320 obj := reflect.ValueOf(c).Elem() 321 st := obj.Type() 322 for i := 0; i < st.NumField(); i++ { 323 f := st.Field(i) 324 name, ok := f.Tag.Lookup("flag") 325 if !ok { 326 // No flag set for this field. 327 continue 328 } 329 val := getVal(obj.Field(i)) 330 331 fl := flagSet.Lookup(name) 332 if fl == nil { 333 panic(fmt.Sprintf("Flag %q not found", name)) 334 } 335 if val == fl.DefValue || onlyIfSet { 336 // If this config wasn't populated from a FlagSet, don't plumb through default flags. 337 if c.explicitlySet == nil { 338 continue 339 } 340 // If this config was populated from a FlagSet, plumb through only default flags which were 341 // explicitly specified. 342 if _, explicit := c.explicitlySet[name]; !explicit { 343 continue 344 } 345 } 346 keyVals[fl.Name] = val 347 } 348 return keyVals 349 } 350 351 // Override writes a new value to a flag. 352 func (c *Config) Override(flagSet *flag.FlagSet, name string, value string, force bool) error { 353 obj := reflect.ValueOf(c).Elem() 354 st := obj.Type() 355 for i := 0; i < st.NumField(); i++ { 356 f := st.Field(i) 357 fieldName, ok := f.Tag.Lookup("flag") 358 if !ok || fieldName != name { 359 // Not a flag field, or flag name doesn't match. 360 continue 361 } 362 fl := flagSet.Lookup(name) 363 if fl == nil { 364 // Flag must exist if there is a field match above. 365 panic(fmt.Sprintf("Flag %q not found", name)) 366 } 367 if !force { 368 if err := c.isOverrideAllowed(name, value); err != nil { 369 return fmt.Errorf("error setting flag %s=%q: %w", name, value, err) 370 } 371 } 372 373 // Use flag to convert the string value to the underlying flag type, using 374 // the same rules as the command-line for consistency. 375 if err := fl.Value.Set(value); err != nil { 376 return fmt.Errorf("error setting flag %s=%q: %w", name, value, err) 377 } 378 x := reflect.ValueOf(flag.Get(fl.Value)) 379 obj.Field(i).Set(x) 380 381 // Validates the config again to ensure it's left in a consistent state. 382 return c.validate() 383 } 384 return fmt.Errorf("flag %q not found. Cannot set it to %q", name, value) 385 } 386 387 func (c *Config) isOverrideAllowed(name string, value string) error { 388 if c.AllowFlagOverride { 389 return nil 390 } 391 // If the global override flag is not enabled, check if the individual flag is 392 // safe to apply. 393 if allow, ok := overrideAllowlist[name]; ok { 394 if allow.check != nil { 395 if err := allow.check(name, value); err != nil { 396 return err 397 } 398 } 399 return nil 400 } 401 return fmt.Errorf("flag override disabled, use --allow-flag-override to enable it") 402 } 403 404 // ApplyBundles applies the given bundles by name. 405 // It returns an error if a bundle doesn't exist, or if the given 406 // bundles have conflicting flag values. 407 // Config values which are already specified prior to calling ApplyBundles are overridden. 408 func (c *Config) ApplyBundles(flagSet *flag.FlagSet, bundleNames ...BundleName) error { 409 // Populate a map from flag name to flag value to bundle name. 410 flagToValueToBundleName := make(map[string]map[string]BundleName) 411 for _, bundleName := range bundleNames { 412 b := Bundles[bundleName] 413 if b == nil { 414 return fmt.Errorf("no such bundle: %q", bundleName) 415 } 416 for flagName, val := range b { 417 valueToBundleName := flagToValueToBundleName[flagName] 418 if valueToBundleName == nil { 419 valueToBundleName = make(map[string]BundleName) 420 flagToValueToBundleName[flagName] = valueToBundleName 421 } 422 valueToBundleName[val] = bundleName 423 } 424 } 425 // Check for conflicting flag values between the bundles. 426 for flagName, valueToBundleName := range flagToValueToBundleName { 427 if len(valueToBundleName) == 1 { 428 continue 429 } 430 bundleNameToValue := make(map[string]string) 431 for val, bundleName := range valueToBundleName { 432 bundleNameToValue[string(bundleName)] = val 433 } 434 var sb strings.Builder 435 first := true 436 for _, bundleName := range bundleNames { 437 if val, ok := bundleNameToValue[string(bundleName)]; ok { 438 if !first { 439 sb.WriteString(", ") 440 } 441 sb.WriteString(fmt.Sprintf("bundle %q sets --%s=%q", bundleName, flagName, val)) 442 first = false 443 } 444 } 445 return fmt.Errorf("flag --%s is specified by multiple bundles: %s", flagName, sb.String()) 446 } 447 448 // Actually apply flag values. 449 for flagName, valueToBundleName := range flagToValueToBundleName { 450 fl := flagSet.Lookup(flagName) 451 if fl == nil { 452 return fmt.Errorf("flag --%s not found", flagName) 453 } 454 prevValue := fl.Value.String() 455 // Note: We verified earlier that valueToBundleName has length 1, 456 // so this loop executes exactly once per flag. 457 for val, bundleName := range valueToBundleName { 458 if prevValue == val { 459 continue 460 } 461 if isFlagExplicitlySet(flagSet, flagName) { 462 log.Infof("Flag --%s has explicitly-set value %q, but bundle %s takes precedence and is overriding its value to --%s=%q.", flagName, prevValue, bundleName, flagName, val) 463 } else { 464 log.Infof("Overriding flag --%s=%q from applying bundle %s.", flagName, val, bundleName) 465 } 466 if err := c.Override(flagSet, flagName, val /* force= */, true); err != nil { 467 return err 468 } 469 } 470 } 471 472 return c.validate() 473 } 474 475 func getVal(field reflect.Value) string { 476 if str, ok := field.Addr().Interface().(fmt.Stringer); ok { 477 return str.String() 478 } 479 switch field.Kind() { 480 case reflect.Bool: 481 return strconv.FormatBool(field.Bool()) 482 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 483 return strconv.FormatInt(field.Int(), 10) 484 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 485 return strconv.FormatUint(field.Uint(), 10) 486 case reflect.String: 487 return field.String() 488 default: 489 panic("unknown type " + field.Kind().String()) 490 } 491 }