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