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