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  }