github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/runsc/cli/main.go (about) 1 // Copyright 2018 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 cli is the main entrypoint for runsc. 16 package cli 17 18 import ( 19 "context" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "os/signal" 25 "runtime" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/google/subcommands" 31 "golang.org/x/sys/unix" 32 "github.com/metacubex/gvisor/pkg/coverage" 33 "github.com/metacubex/gvisor/pkg/log" 34 "github.com/metacubex/gvisor/pkg/metric" 35 "github.com/metacubex/gvisor/pkg/refs" 36 "github.com/metacubex/gvisor/pkg/sentry/platform" 37 "github.com/metacubex/gvisor/pkg/sentry/syscalls/linux" 38 "github.com/metacubex/gvisor/runsc/cmd" 39 "github.com/metacubex/gvisor/runsc/cmd/nvproxy" 40 "github.com/metacubex/gvisor/runsc/cmd/trace" 41 "github.com/metacubex/gvisor/runsc/cmd/util" 42 "github.com/metacubex/gvisor/runsc/config" 43 "github.com/metacubex/gvisor/runsc/flag" 44 "github.com/metacubex/gvisor/runsc/specutils" 45 "github.com/metacubex/gvisor/runsc/version" 46 ) 47 48 // versionFlagName is the name of a flag that triggers printing the version. 49 // Although this flags is not part of the OCI spec, it is used by 50 // Docker, and thus should not be removed. 51 const versionFlagName = "version" 52 53 var ( 54 // These flags are unique to runsc, and are used to configure parts of the 55 // system that are not covered by the runtime spec. 56 57 // Debugging flags. 58 logFD = flag.Int("log-fd", -1, "file descriptor to log to. If set, the 'log' flag is ignored.") 59 debugLogFD = flag.Int("debug-log-fd", -1, "file descriptor to write debug logs to. If set, the 'debug-log-dir' flag is ignored.") 60 panicLogFD = flag.Int("panic-log-fd", -1, "file descriptor to write Go's runtime messages.") 61 coverageFD = flag.Int("coverage-fd", -1, "file descriptor to write Go coverage output.") 62 profilingMetricsFD = flag.Int("profiling-metrics-fd", -1, "file descriptor to write sentry profiling metrics.") 63 ) 64 65 // Main is the main entrypoint. 66 func Main() { 67 // Register all commands. 68 forEachCmd(subcommands.Register) 69 70 // Register with the main command line. 71 config.RegisterFlags(flag.CommandLine) 72 73 // Register version flag if it is not already defined. 74 if flag.Lookup(versionFlagName) == nil { 75 flag.Bool(versionFlagName, false, "show version and exit.") 76 } 77 78 // All subcommands must be registered before flag parsing. 79 flag.Parse() 80 81 // Are we showing the version? 82 if flag.Get(flag.Lookup(versionFlagName).Value).(bool) { 83 // The format here is the same as runc. 84 fmt.Fprintf(os.Stdout, "runsc version %s\n", version.Version()) 85 fmt.Fprintf(os.Stdout, "spec: %s\n", specutils.Version) 86 os.Exit(0) 87 } 88 89 // Create a new Config from the flags. 90 conf, err := config.NewFromFlags(flag.CommandLine) 91 if err != nil { 92 util.Fatalf(err.Error()) 93 } 94 95 var errorLogger io.Writer 96 if *logFD > -1 { 97 errorLogger = os.NewFile(uintptr(*logFD), "error log file") 98 99 } else if conf.LogFilename != "" { 100 // We must set O_APPEND and not O_TRUNC because Docker passes 101 // the same log file for all commands (and also parses these 102 // log files), so we can't destroy them on each command. 103 var err error 104 errorLogger, err = os.OpenFile(conf.LogFilename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 105 if err != nil { 106 util.Fatalf("error opening log file %q: %v", conf.LogFilename, err) 107 } 108 } 109 util.ErrorLogger = errorLogger 110 111 if _, err := platform.Lookup(conf.Platform); err != nil { 112 util.Fatalf("%v", err) 113 } 114 115 // Sets the reference leak check mode. Also set it in config below to 116 // propagate it to child processes. 117 refs.SetLeakMode(conf.ReferenceLeak) 118 119 subcommand := flag.CommandLine.Arg(0) 120 121 // Set up logging. 122 if conf.Debug && specutils.IsDebugCommand(conf, subcommand) { 123 log.SetLevel(log.Debug) 124 } 125 126 // Logging will include the local date and time via the time package. 127 // 128 // On first use, time.Local initializes the local time zone, which 129 // involves opening tzdata files on the host. Since this requires 130 // opening host files, it must be done before syscall filter 131 // installation. 132 // 133 // Generally there will be a log message before filter installation 134 // that will force initialization, but force initialization here in 135 // case that does not occur. 136 _ = time.Local.String() 137 138 var emitters log.MultiEmitter 139 if *debugLogFD > -1 { 140 f := os.NewFile(uintptr(*debugLogFD), "debug log file") 141 142 emitters = append(emitters, newEmitter(conf.DebugLogFormat, f)) 143 144 } else if len(conf.DebugLog) > 0 && specutils.IsDebugCommand(conf, subcommand) { 145 f, err := specutils.DebugLogFile(conf.DebugLog, subcommand, "" /* name */) 146 if err != nil { 147 util.Fatalf("error opening debug log file in %q: %v", conf.DebugLog, err) 148 } 149 emitters = append(emitters, newEmitter(conf.DebugLogFormat, f)) 150 151 } else { 152 // Stderr is reserved for the application, just discard the logs if no debug 153 // log is specified. 154 emitters = append(emitters, newEmitter("text", ioutil.Discard)) 155 } 156 157 if *panicLogFD > -1 || *debugLogFD > -1 { 158 fd := *panicLogFD 159 if fd < 0 { 160 fd = *debugLogFD 161 } 162 // Quick sanity check to make sure no other commands get passed 163 // a log fd (they should use log dir instead). 164 if subcommand != "boot" && subcommand != "gofer" { 165 util.Fatalf("flags --debug-log-fd and --panic-log-fd should only be passed to 'boot' and 'gofer' command, but was passed to %q", subcommand) 166 } 167 168 // If we are the boot process, then we own our stdio FDs and can do what we 169 // want with them. Since Docker and Containerd both eat boot's stderr, we 170 // dup our stderr to the provided log FD so that panics will appear in the 171 // logs, rather than just disappear. 172 if err := unix.Dup3(fd, int(os.Stderr.Fd()), 0); err != nil { 173 util.Fatalf("error dup'ing fd %d to stderr: %v", fd, err) 174 } 175 } else if conf.AlsoLogToStderr { 176 emitters = append(emitters, newEmitter(conf.DebugLogFormat, os.Stderr)) 177 } 178 if ulEmittter, add := userLogEmitter(conf, subcommand); add { 179 emitters = append(emitters, ulEmittter) 180 } 181 182 switch len(emitters) { 183 case 0: 184 // Do nothing. 185 case 1: 186 // Use the singular emitter to avoid needless 187 // `for` loop overhead when logging to a single place. 188 log.SetTarget(emitters[0]) 189 default: 190 log.SetTarget(&emitters) 191 } 192 193 const delimString = `**************** gVisor ****************` 194 log.Infof(delimString) 195 log.Infof("Version %s, %s, %s, %d CPUs, %s, PID %d, PPID %d, UID %d, GID %d", version.Version(), runtime.Version(), runtime.GOARCH, runtime.NumCPU(), runtime.GOOS, os.Getpid(), os.Getppid(), os.Getuid(), os.Getgid()) 196 log.Debugf("Page size: 0x%x (%d bytes)", os.Getpagesize(), os.Getpagesize()) 197 log.Infof("Args: %v", os.Args) 198 conf.Log() 199 log.Infof(delimString) 200 201 if *coverageFD >= 0 { 202 f := os.NewFile(uintptr(*coverageFD), "coverage file") 203 coverage.EnableReport(f) 204 } 205 if *profilingMetricsFD >= 0 { 206 metric.ProfilingMetricWriter = os.NewFile(uintptr(*profilingMetricsFD), "metrics file") 207 if metric.ProfilingMetricWriter == nil { 208 log.Warningf("Failed to use -profiling-metrics-fd") 209 } 210 } 211 if conf.TestOnlyAllowRunAsCurrentUserWithoutChroot { 212 // SIGTERM is sent to all processes if a test exceeds its 213 // timeout and this case is handled by syscall_test_runner. 214 log.Warningf("Block the TERM signal. This is only safe in tests!") 215 signal.Ignore(unix.SIGTERM) 216 } 217 linux.SetAFSSyscallPanic(conf.TestOnlyAFSSyscallPanic) 218 219 // Call the subcommand and pass in the configuration. 220 var ws unix.WaitStatus 221 subcmdCode := subcommands.Execute(context.Background(), conf, &ws) 222 // Check for leaks and write coverage report before os.Exit(). 223 refs.DoLeakCheck() 224 _ = coverage.Report() 225 if subcmdCode == subcommands.ExitSuccess { 226 log.Infof("Exiting with status: %v", ws) 227 if ws.Signaled() { 228 // No good way to return it, emulate what the shell does. Maybe raise 229 // signal to self? 230 os.Exit(128 + int(ws.Signal())) 231 } 232 os.Exit(ws.ExitStatus()) 233 } 234 // Return an error that is unlikely to be used by the application. 235 log.Warningf("Failure to execute command, err: %v", subcmdCode) 236 os.Exit(128) 237 } 238 239 // forEachCmd invokes the passed callback for each command supported by runsc. 240 func forEachCmd(cb func(cmd subcommands.Command, group string)) { 241 // Help and flags commands are generated automatically. 242 help := cmd.NewHelp(subcommands.DefaultCommander) 243 help.Register(new(cmd.Platforms)) 244 help.Register(new(cmd.Syscalls)) 245 cb(help, "") 246 cb(subcommands.FlagsCommand(), "") 247 248 // Register OCI user-facing runsc commands. 249 cb(new(cmd.Checkpoint), "") 250 cb(new(cmd.Create), "") 251 cb(new(cmd.Delete), "") 252 cb(new(cmd.Do), "") 253 cb(new(cmd.Events), "") 254 cb(new(cmd.Exec), "") 255 cb(new(cmd.Kill), "") 256 cb(new(cmd.List), "") 257 cb(new(cmd.PS), "") 258 cb(new(cmd.Pause), "") 259 cb(new(cmd.PortForward), "") 260 cb(new(cmd.Restore), "") 261 cb(new(cmd.Resume), "") 262 cb(new(cmd.Run), "") 263 cb(new(cmd.Spec), "") 264 cb(new(cmd.Start), "") 265 cb(new(cmd.State), "") 266 cb(new(cmd.Wait), "") 267 268 // Helpers. 269 const helperGroup = "helpers" 270 cb(new(cmd.Install), helperGroup) 271 cb(new(cmd.Mitigate), helperGroup) 272 cb(new(cmd.Uninstall), helperGroup) 273 cb(new(nvproxy.Nvproxy), helperGroup) 274 cb(new(trace.Trace), helperGroup) 275 276 const debugGroup = "debug" 277 cb(new(cmd.Debug), debugGroup) 278 cb(new(cmd.Statefile), debugGroup) 279 cb(new(cmd.Symbolize), debugGroup) 280 cb(new(cmd.Usage), debugGroup) 281 cb(new(cmd.ReadControl), debugGroup) 282 cb(new(cmd.WriteControl), debugGroup) 283 284 const metricGroup = "metrics" 285 cb(new(cmd.MetricMetadata), metricGroup) 286 cb(new(cmd.MetricExport), metricGroup) 287 cb(new(cmd.MetricServer), metricGroup) 288 289 // Internal commands. 290 const internalGroup = "internal use only" 291 cb(new(cmd.Boot), internalGroup) 292 cb(new(cmd.Gofer), internalGroup) 293 cb(new(cmd.Umount), internalGroup) 294 } 295 296 func newEmitter(format string, logFile io.Writer) log.Emitter { 297 switch format { 298 case "text": 299 return log.GoogleEmitter{&log.Writer{Next: logFile}} 300 case "json": 301 return log.JSONEmitter{&log.Writer{Next: logFile}} 302 case "json-k8s": 303 return log.K8sJSONEmitter{&log.Writer{Next: logFile}} 304 } 305 util.Fatalf("invalid log format %q, must be 'text', 'json', or 'json-k8s'", format) 306 panic("unreachable") 307 } 308 309 // userLogEmitter returns an emitter to add logs to user logs if requested. 310 func userLogEmitter(conf *config.Config, subcommand string) (log.Emitter, bool) { 311 if subcommand != "boot" || !conf.DebugToUserLog { 312 return nil, false 313 } 314 // We need to manually scan for `--user-log-fd` since it is a flag of the 315 // `boot` subcommand. We know it is in `--user-log-fd=FD` format because 316 // we control how arguments to `runsc boot` are formatted. 317 const userLogFDFlagPrefix = "--user-log-fd=" 318 var userLog *os.File 319 for _, arg := range os.Args[1:] { 320 if !strings.HasPrefix(arg, userLogFDFlagPrefix) { 321 continue 322 } 323 if userLog != nil { 324 util.Fatalf("duplicate %q flag", userLogFDFlagPrefix) 325 } 326 userLogFD, err := strconv.Atoi(arg[len(userLogFDFlagPrefix):]) 327 if err != nil { 328 util.Fatalf("invalid user log FD flag %q: %v", arg, err) 329 } 330 userLog = os.NewFile(uintptr(userLogFD), "user log file") 331 } 332 if userLog == nil { 333 return nil, false 334 } 335 return log.K8sJSONEmitter{&log.Writer{Next: userLog}}, true 336 }