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