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