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