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