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