summaryrefslogtreecommitdiffhomepage
path: root/runsc/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/cmd')
-rw-r--r--runsc/cmd/BUILD3
-rw-r--r--runsc/cmd/boot.go64
-rw-r--r--runsc/cmd/capability_test.go5
-rw-r--r--runsc/cmd/chroot.go33
-rw-r--r--runsc/cmd/debug.go105
-rw-r--r--runsc/cmd/do.go13
-rw-r--r--runsc/cmd/error.go2
-rw-r--r--runsc/cmd/events.go17
-rw-r--r--runsc/cmd/exec.go6
-rw-r--r--runsc/cmd/gofer.go44
-rw-r--r--runsc/cmd/help.go2
-rw-r--r--runsc/cmd/install.go4
-rw-r--r--runsc/cmd/list.go2
-rw-r--r--runsc/cmd/mitigate.go153
-rw-r--r--runsc/cmd/mitigate_extras.go3
-rw-r--r--runsc/cmd/mitigate_test.go210
-rw-r--r--runsc/cmd/pause.go2
-rw-r--r--runsc/cmd/resume.go2
-rw-r--r--runsc/cmd/run.go9
-rw-r--r--runsc/cmd/start.go2
-rw-r--r--runsc/cmd/state.go6
-rw-r--r--runsc/cmd/syscalls.go2
-rw-r--r--runsc/cmd/usage.go93
-rw-r--r--runsc/cmd/verity_prepare.go9
24 files changed, 495 insertions, 296 deletions
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD
index 39c8ff603..c5e32807d 100644
--- a/runsc/cmd/BUILD
+++ b/runsc/cmd/BUILD
@@ -36,6 +36,7 @@ go_library(
"statefile.go",
"symbolize.go",
"syscalls.go",
+ "usage.go",
"verity_prepare.go",
"wait.go",
],
@@ -95,10 +96,10 @@ go_test(
"//runsc/config",
"//runsc/container",
"//runsc/mitigate",
- "//runsc/mitigate/mock",
"//runsc/specutils",
"@com_github_google_go_cmp//cmp:go_default_library",
"@com_github_google_go_cmp//cmp/cmpopts:go_default_library",
+ "@com_github_google_subcommands//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@com_github_syndtr_gocapability//capability:go_default_library",
],
diff --git a/runsc/cmd/boot.go b/runsc/cmd/boot.go
index a14249641..e33a7f3cb 100644
--- a/runsc/cmd/boot.go
+++ b/runsc/cmd/boot.go
@@ -79,6 +79,26 @@ type Boot struct {
// sandbox (e.g. gofer) and sent through this FD.
mountsFD int
+ // profileBlockFD is the file descriptor to write a block profile to.
+ // Valid if >= 0.
+ profileBlockFD int
+
+ // profileCPUFD is the file descriptor to write a CPU profile to.
+ // Valid if >= 0.
+ profileCPUFD int
+
+ // profileHeapFD is the file descriptor to write a heap profile to.
+ // Valid if >= 0.
+ profileHeapFD int
+
+ // profileMutexFD is the file descriptor to write a mutex profile to.
+ // Valid if >= 0.
+ profileMutexFD int
+
+ // traceFD is the file descriptor to write a Go execution trace to.
+ // Valid if >= 0.
+ traceFD int
+
// pidns is set if the sandbox is in its own pid namespace.
pidns bool
@@ -119,6 +139,11 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
f.IntVar(&b.userLogFD, "user-log-fd", 0, "file descriptor to write user logs to. 0 means no logging.")
f.IntVar(&b.startSyncFD, "start-sync-fd", -1, "required FD to used to synchronize sandbox startup")
f.IntVar(&b.mountsFD, "mounts-fd", -1, "mountsFD is the file descriptor to read list of mounts after they have been resolved (direct paths, no symlinks).")
+ f.IntVar(&b.profileBlockFD, "profile-block-fd", -1, "file descriptor to write block profile to. -1 disables profiling.")
+ f.IntVar(&b.profileCPUFD, "profile-cpu-fd", -1, "file descriptor to write CPU profile to. -1 disables profiling.")
+ f.IntVar(&b.profileHeapFD, "profile-heap-fd", -1, "file descriptor to write heap profile to. -1 disables profiling.")
+ f.IntVar(&b.profileMutexFD, "profile-mutex-fd", -1, "file descriptor to write mutex profile to. -1 disables profiling.")
+ f.IntVar(&b.traceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.")
f.BoolVar(&b.attached, "attached", false, "if attached is true, kills the sandbox process when the parent process terminates")
}
@@ -157,10 +182,8 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
// we will read it again after the exec call. This works
// because the ReadSpecFromFile function seeks to the beginning
// of the file before reading.
- if err := callSelfAsNobody(args); err != nil {
- Fatalf("%v", err)
- }
- panic("callSelfAsNobody must never return success")
+ Fatalf("callSelfAsNobody(%v): %v", args, callSelfAsNobody(args))
+ panic("unreachable")
}
}
@@ -199,10 +222,8 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
// we will read it again after the exec call. This works
// because the ReadSpecFromFile function seeks to the beginning
// of the file before reading.
- if err := setCapsAndCallSelf(args, caps); err != nil {
- Fatalf("%v", err)
- }
- panic("setCapsAndCallSelf must never return success")
+ Fatalf("setCapsAndCallSelf(%v, %v): %v", args, caps, setCapsAndCallSelf(args, caps))
+ panic("unreachable")
}
// Read resolved mount list and replace the original one from the spec.
@@ -217,16 +238,21 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
// Create the loader.
bootArgs := boot.Args{
- ID: f.Arg(0),
- Spec: spec,
- Conf: conf,
- ControllerFD: b.controllerFD,
- Device: os.NewFile(uintptr(b.deviceFD), "platform device"),
- GoferFDs: b.ioFDs.GetArray(),
- StdioFDs: b.stdioFDs.GetArray(),
- NumCPU: b.cpuNum,
- TotalMem: b.totalMem,
- UserLogFD: b.userLogFD,
+ ID: f.Arg(0),
+ Spec: spec,
+ Conf: conf,
+ ControllerFD: b.controllerFD,
+ Device: os.NewFile(uintptr(b.deviceFD), "platform device"),
+ GoferFDs: b.ioFDs.GetArray(),
+ StdioFDs: b.stdioFDs.GetArray(),
+ NumCPU: b.cpuNum,
+ TotalMem: b.totalMem,
+ UserLogFD: b.userLogFD,
+ ProfileBlockFD: b.profileBlockFD,
+ ProfileCPUFD: b.profileCPUFD,
+ ProfileHeapFD: b.profileHeapFD,
+ ProfileMutexFD: b.profileMutexFD,
+ TraceFD: b.traceFD,
}
l, err := boot.New(bootArgs)
if err != nil {
@@ -259,7 +285,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
ws := l.WaitExit()
log.Infof("application exiting with %+v", ws)
waitStatus := args[1].(*unix.WaitStatus)
- *waitStatus = unix.WaitStatus(ws.Status())
+ *waitStatus = unix.WaitStatus(ws)
l.Destroy()
return subcommands.ExitSuccess
}
diff --git a/runsc/cmd/capability_test.go b/runsc/cmd/capability_test.go
index e13a94486..99075d82d 100644
--- a/runsc/cmd/capability_test.go
+++ b/runsc/cmd/capability_test.go
@@ -122,6 +122,9 @@ func TestCapabilities(t *testing.T) {
func TestMain(m *testing.M) {
flag.Parse()
- specutils.MaybeRunAsRoot()
+ if err := specutils.MaybeRunAsRoot(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error running as root: %v", err)
+ os.Exit(123)
+ }
os.Exit(m.Run())
}
diff --git a/runsc/cmd/chroot.go b/runsc/cmd/chroot.go
index e988247da..1fe9c6435 100644
--- a/runsc/cmd/chroot.go
+++ b/runsc/cmd/chroot.go
@@ -30,7 +30,7 @@ func mountInChroot(chroot, src, dst, typ string, flags uint32) error {
chrootDst := filepath.Join(chroot, dst)
log.Infof("Mounting %q at %q", src, chrootDst)
- if err := specutils.Mount(src, chrootDst, typ, flags); err != nil {
+ if err := specutils.SafeSetupAndMount(src, chrootDst, typ, flags, "/proc"); err != nil {
return fmt.Errorf("error mounting %q at %q: %v", src, chrootDst, err)
}
return nil
@@ -59,6 +59,23 @@ func pivotRoot(root string) error {
return nil
}
+func copyFile(dst, src string) error {
+ in, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+
+ out, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ _, err = out.ReadFrom(in)
+ return err
+}
+
// setUpChroot creates an empty directory with runsc mounted at /runsc and proc
// mounted at /proc.
func setUpChroot(pidns bool) error {
@@ -70,14 +87,22 @@ func setUpChroot(pidns bool) error {
// Convert all shared mounts into slave to be sure that nothing will be
// propagated outside of our namespace.
- if err := unix.Mount("", "/", "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil {
+ if err := specutils.SafeMount("", "/", "", unix.MS_SLAVE|unix.MS_REC, "", "/proc"); err != nil {
return fmt.Errorf("error converting mounts: %v", err)
}
- if err := unix.Mount("runsc-root", chroot, "tmpfs", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC, ""); err != nil {
+ if err := specutils.SafeMount("runsc-root", chroot, "tmpfs", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC, "", "/proc"); err != nil {
return fmt.Errorf("error mounting tmpfs in choot: %v", err)
}
+ if err := os.Mkdir(filepath.Join(chroot, "etc"), 0755); err != nil {
+ return fmt.Errorf("error creating /etc in chroot: %v", err)
+ }
+
+ if err := copyFile(filepath.Join(chroot, "etc/localtime"), "/etc/localtime"); err != nil {
+ log.Warningf("Failed to copy /etc/localtime: %v. UTC timezone will be used.", err)
+ }
+
if pidns {
flags := uint32(unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC | unix.MS_RDONLY)
if err := mountInChroot(chroot, "proc", "/proc", "proc", flags); err != nil {
@@ -89,7 +114,7 @@ func setUpChroot(pidns bool) error {
}
}
- if err := unix.Mount("", chroot, "", unix.MS_REMOUNT|unix.MS_RDONLY|unix.MS_BIND, ""); err != nil {
+ if err := specutils.SafeMount("", chroot, "", unix.MS_REMOUNT|unix.MS_RDONLY|unix.MS_BIND, "", "/proc"); err != nil {
return fmt.Errorf("error remounting chroot in read-only: %v", err)
}
diff --git a/runsc/cmd/debug.go b/runsc/cmd/debug.go
index 6212ffb2e..318753728 100644
--- a/runsc/cmd/debug.go
+++ b/runsc/cmd/debug.go
@@ -37,9 +37,9 @@ type Debug struct {
pid int
stacks bool
signal int
- profileHeap string
- profileCPU string
profileBlock string
+ profileCPU string
+ profileHeap string
profileMutex string
trace string
strace string
@@ -48,6 +48,7 @@ type Debug struct {
delay time.Duration
duration time.Duration
ps bool
+ cat stringSlice
}
// Name implements subcommands.Command.
@@ -69,9 +70,9 @@ func (*Debug) Usage() string {
func (d *Debug) SetFlags(f *flag.FlagSet) {
f.IntVar(&d.pid, "pid", 0, "sandbox process ID. Container ID is not necessary if this is set")
f.BoolVar(&d.stacks, "stacks", false, "if true, dumps all sandbox stacks to the log")
- f.StringVar(&d.profileHeap, "profile-heap", "", "writes heap profile to the given file.")
- f.StringVar(&d.profileCPU, "profile-cpu", "", "writes CPU profile to the given file.")
f.StringVar(&d.profileBlock, "profile-block", "", "writes block profile to the given file.")
+ f.StringVar(&d.profileCPU, "profile-cpu", "", "writes CPU profile to the given file.")
+ f.StringVar(&d.profileHeap, "profile-heap", "", "writes heap profile to the given file.")
f.StringVar(&d.profileMutex, "profile-mutex", "", "writes mutex profile to the given file.")
f.DurationVar(&d.delay, "delay", time.Hour, "amount of time to delay for collecting heap and goroutine profiles.")
f.DurationVar(&d.duration, "duration", time.Hour, "amount of time to wait for CPU and trace profiles.")
@@ -81,6 +82,7 @@ func (d *Debug) SetFlags(f *flag.FlagSet) {
f.StringVar(&d.logLevel, "log-level", "", "The log level to set: warning (0), info (1), or debug (2).")
f.StringVar(&d.logPackets, "log-packets", "", "A boolean value to enable or disable packet logging: true or false.")
f.BoolVar(&d.ps, "ps", false, "lists processes")
+ f.Var(&d.cat, "cat", "reads files and print to standard output")
}
// Execute implements subcommands.Command.Execute.
@@ -88,6 +90,13 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
var c *container.Container
conf := args[0].(*config.Config)
+ if conf.ProfileBlock != "" || conf.ProfileCPU != "" || conf.ProfileHeap != "" || conf.ProfileMutex != "" {
+ return Errorf("global -profile-{block,cpu,heap,mutex} flags have no effect on runsc debug. Pass runsc debug -profile-{block,cpu,heap,mutex} instead")
+ }
+ if conf.TraceFile != "" {
+ return Errorf("global -trace flag has no effect on runsc debug. Pass runsc debug -trace instead")
+ }
+
if d.pid == 0 {
// No pid, container ID must have been provided.
if f.NArg() != 1 {
@@ -166,7 +175,7 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
log.Infof("Enabling strace for syscalls: %s", d.strace)
args.SetStrace = true
args.EnableStrace = true
- args.StraceWhitelist = strings.Split(d.strace, ",")
+ args.StraceAllowlist = strings.Split(d.strace, ",")
}
if len(d.logLevel) != 0 {
@@ -217,19 +226,19 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
// Open profiling files.
var (
- heapFile *os.File
- cpuFile *os.File
- traceFile *os.File
blockFile *os.File
+ cpuFile *os.File
+ heapFile *os.File
mutexFile *os.File
+ traceFile *os.File
)
- if d.profileHeap != "" {
- f, err := os.OpenFile(d.profileHeap, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+ if d.profileBlock != "" {
+ f, err := os.OpenFile(d.profileBlock, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
- return Errorf("error opening heap profile output: %v", err)
+ return Errorf("error opening blocking profile output: %v", err)
}
defer f.Close()
- heapFile = f
+ blockFile = f
}
if d.profileCPU != "" {
f, err := os.OpenFile(d.profileCPU, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
@@ -239,20 +248,13 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
defer f.Close()
cpuFile = f
}
- if d.trace != "" {
- f, err := os.OpenFile(d.trace, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
- if err != nil {
- return Errorf("error opening trace profile output: %v", err)
- }
- traceFile = f
- }
- if d.profileBlock != "" {
- f, err := os.OpenFile(d.profileBlock, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+ if d.profileHeap != "" {
+ f, err := os.OpenFile(d.profileHeap, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
- return Errorf("error opening blocking profile output: %v", err)
+ return Errorf("error opening heap profile output: %v", err)
}
defer f.Close()
- blockFile = f
+ heapFile = f
}
if d.profileMutex != "" {
f, err := os.OpenFile(d.profileMutex, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
@@ -262,21 +264,28 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
defer f.Close()
mutexFile = f
}
+ if d.trace != "" {
+ f, err := os.OpenFile(d.trace, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+ if err != nil {
+ return Errorf("error opening trace profile output: %v", err)
+ }
+ traceFile = f
+ }
// Collect profiles.
var (
wg sync.WaitGroup
- heapErr error
- cpuErr error
- traceErr error
blockErr error
+ cpuErr error
+ heapErr error
mutexErr error
+ traceErr error
)
- if heapFile != nil {
+ if blockFile != nil {
wg.Add(1)
go func() {
defer wg.Done()
- heapErr = c.Sandbox.HeapProfile(heapFile, d.delay)
+ blockErr = c.Sandbox.BlockProfile(blockFile, d.duration)
}()
}
if cpuFile != nil {
@@ -286,25 +295,25 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
cpuErr = c.Sandbox.CPUProfile(cpuFile, d.duration)
}()
}
- if traceFile != nil {
+ if heapFile != nil {
wg.Add(1)
go func() {
defer wg.Done()
- traceErr = c.Sandbox.Trace(traceFile, d.duration)
+ heapErr = c.Sandbox.HeapProfile(heapFile, d.delay)
}()
}
- if blockFile != nil {
+ if mutexFile != nil {
wg.Add(1)
go func() {
defer wg.Done()
- blockErr = c.Sandbox.BlockProfile(blockFile, d.duration)
+ mutexErr = c.Sandbox.MutexProfile(mutexFile, d.duration)
}()
}
- if mutexFile != nil {
+ if traceFile != nil {
wg.Add(1)
go func() {
defer wg.Done()
- mutexErr = c.Sandbox.MutexProfile(mutexFile, d.duration)
+ traceErr = c.Sandbox.Trace(traceFile, d.duration)
}()
}
@@ -337,35 +346,41 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
// Collect all errors.
errorCount := 0
- if heapErr != nil {
+ if blockErr != nil {
errorCount++
- log.Infof("error collecting heap profile: %v", heapErr)
- os.Remove(heapFile.Name())
+ log.Infof("error collecting block profile: %v", blockErr)
+ os.Remove(blockFile.Name())
}
if cpuErr != nil {
errorCount++
log.Infof("error collecting cpu profile: %v", cpuErr)
os.Remove(cpuFile.Name())
}
- if traceErr != nil {
- errorCount++
- log.Infof("error collecting trace profile: %v", traceErr)
- os.Remove(traceFile.Name())
- }
- if blockErr != nil {
+ if heapErr != nil {
errorCount++
- log.Infof("error collecting block profile: %v", blockErr)
- os.Remove(blockFile.Name())
+ log.Infof("error collecting heap profile: %v", heapErr)
+ os.Remove(heapFile.Name())
}
if mutexErr != nil {
errorCount++
log.Infof("error collecting mutex profile: %v", mutexErr)
os.Remove(mutexFile.Name())
}
+ if traceErr != nil {
+ errorCount++
+ log.Infof("error collecting trace profile: %v", traceErr)
+ os.Remove(traceFile.Name())
+ }
if errorCount > 0 {
return subcommands.ExitFailure
}
+ if d.cat != nil {
+ if err := c.Cat(d.cat, os.Stdout); err != nil {
+ return Errorf("Cat failed: %v", err)
+ }
+ }
+
return subcommands.ExitSuccess
}
diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go
index 5485db149..4eb5a96f1 100644
--- a/runsc/cmd/do.go
+++ b/runsc/cmd/do.go
@@ -130,7 +130,6 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su
if conf.Network == config.NetworkNone {
addNamespace(spec, specs.LinuxNamespace{Type: specs.NetworkNamespace})
-
} else if conf.Rootless {
if conf.Network == config.NetworkSandbox {
c.notifyUser("*** Warning: sandbox network isn't supported with --rootless, switching to host ***")
@@ -225,25 +224,25 @@ func (c *Do) setupNet(cid string, spec *specs.Spec) (func(), error) {
args := strings.Split(cmd, " ")
cmd := exec.Command(args[0], args[1:]...)
if err := cmd.Run(); err != nil {
- c.cleanupNet(cid, dev, "", "", "")
+ c.cleanupNet(cid, "", "", "")
return nil, fmt.Errorf("failed to run %q: %v", cmd, err)
}
}
resolvPath, err := makeFile("/etc/resolv.conf", "nameserver 8.8.8.8\n", spec)
if err != nil {
- c.cleanupNet(cid, dev, "", "", "")
+ c.cleanupNet(cid, "", "", "")
return nil, err
}
hostnamePath, err := makeFile("/etc/hostname", cid+"\n", spec)
if err != nil {
- c.cleanupNet(cid, dev, resolvPath, "", "")
+ c.cleanupNet(cid, resolvPath, "", "")
return nil, err
}
hosts := fmt.Sprintf("127.0.0.1\tlocalhost\n%s\t%s\n", c.ip, cid)
hostsPath, err := makeFile("/etc/hosts", hosts, spec)
if err != nil {
- c.cleanupNet(cid, dev, resolvPath, hostnamePath, "")
+ c.cleanupNet(cid, resolvPath, hostnamePath, "")
return nil, err
}
@@ -253,7 +252,7 @@ func (c *Do) setupNet(cid string, spec *specs.Spec) (func(), error) {
}
addNamespace(spec, netns)
- return func() { c.cleanupNet(cid, dev, resolvPath, hostnamePath, hostsPath) }, nil
+ return func() { c.cleanupNet(cid, resolvPath, hostnamePath, hostsPath) }, nil
}
// cleanupNet tries to cleanup the network setup in setupNet.
@@ -263,7 +262,7 @@ func (c *Do) setupNet(cid string, spec *specs.Spec) (func(), error) {
//
// Unfortunately none of this can be automatically cleaned up on process exit,
// we must do so explicitly.
-func (c *Do) cleanupNet(cid, dev, resolvPath, hostnamePath, hostsPath string) {
+func (c *Do) cleanupNet(cid, resolvPath, hostnamePath, hostsPath string) {
_, peer := deviceNames(cid)
cmds := []string{
diff --git a/runsc/cmd/error.go b/runsc/cmd/error.go
index 3585b5448..96c5c1e8d 100644
--- a/runsc/cmd/error.go
+++ b/runsc/cmd/error.go
@@ -58,7 +58,7 @@ func Errorf(format string, args ...interface{}) subcommands.ExitStatus {
panic(err)
}
if ErrorLogger != nil {
- ErrorLogger.Write(b)
+ _, _ = ErrorLogger.Write(b)
}
return subcommands.ExitFailure
diff --git a/runsc/cmd/events.go b/runsc/cmd/events.go
index 06f00e8e7..08246e543 100644
--- a/runsc/cmd/events.go
+++ b/runsc/cmd/events.go
@@ -33,6 +33,10 @@ type Events struct {
intervalSec int
// If true, events will print a single group of stats and exit.
stats bool
+ // If true, events will dump all filtered events to stdout.
+ stream bool
+ // filters for streamed events.
+ filters stringSlice
}
// Name implements subcommands.Command.Name.
@@ -62,6 +66,8 @@ OPTIONS:
func (evs *Events) SetFlags(f *flag.FlagSet) {
f.IntVar(&evs.intervalSec, "interval", 5, "set the stats collection interval, in seconds")
f.BoolVar(&evs.stats, "stats", false, "display the container's stats then exit")
+ f.BoolVar(&evs.stream, "stream", false, "dump all filtered events to stdout")
+ f.Var(&evs.filters, "filters", "only display matching events")
}
// Execute implements subcommands.Command.Execute.
@@ -79,6 +85,13 @@ func (evs *Events) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
Fatalf("loading sandbox: %v", err)
}
+ if evs.stream {
+ if err := c.Stream(evs.filters, os.Stdout); err != nil {
+ Fatalf("Stream failed: %v", err)
+ }
+ return subcommands.ExitSuccess
+ }
+
// Repeatedly get stats from the container.
for {
// Get the event and print it as JSON.
@@ -97,7 +110,9 @@ func (evs *Events) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
if err != nil {
log.Warningf("Error while marshalling event %v: %v", ev.Event, err)
} else {
- os.Stdout.Write(b)
+ if _, err := os.Stdout.Write(b); err != nil {
+ Fatalf("Error writing to stdout: %v", err)
+ }
}
// If we're only running once, break. If we're only running
diff --git a/runsc/cmd/exec.go b/runsc/cmd/exec.go
index 242d474b8..2139fdf53 100644
--- a/runsc/cmd/exec.go
+++ b/runsc/cmd/exec.go
@@ -146,12 +146,12 @@ func (ex *Exec) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
if ex.detach {
return ex.execChildAndWait(waitStatus)
}
- return ex.exec(c, e, waitStatus)
+ return ex.exec(conf, c, e, waitStatus)
}
-func (ex *Exec) exec(c *container.Container, e *control.ExecArgs, waitStatus *unix.WaitStatus) subcommands.ExitStatus {
+func (ex *Exec) exec(conf *config.Config, c *container.Container, e *control.ExecArgs, waitStatus *unix.WaitStatus) subcommands.ExitStatus {
// Start the new process and get its pid.
- pid, err := c.Execute(e)
+ pid, err := c.Execute(conf, e)
if err != nil {
return Errorf("executing processes for container: %v", err)
}
diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go
index 5ded7b946..2193e9040 100644
--- a/runsc/cmd/gofer.go
+++ b/runsc/cmd/gofer.go
@@ -116,9 +116,7 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
// Note: minimal argument handling for the default case to keep it simple.
args := os.Args
args = append(args, "--apply-caps=false", "--setup-root=false")
- if err := setCapsAndCallSelf(args, goferCaps); err != nil {
- Fatalf("Unable to apply caps: %v", err)
- }
+ Fatalf("setCapsAndCallSelf(%v, %v): %v", args, goferCaps, setCapsAndCallSelf(args, goferCaps))
panic("unreachable")
}
@@ -267,7 +265,8 @@ func isReadonlyMount(opts []string) bool {
func setupRootFS(spec *specs.Spec, conf *config.Config) error {
// Convert all shared mounts into slaves to be sure that nothing will be
// propagated outside of our namespace.
- if err := unix.Mount("", "/", "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil {
+ procPath := "/proc"
+ if err := specutils.SafeMount("", "/", "", unix.MS_SLAVE|unix.MS_REC, "", procPath); err != nil {
Fatalf("error converting mounts: %v", err)
}
@@ -280,21 +279,34 @@ func setupRootFS(spec *specs.Spec, conf *config.Config) error {
// We need a directory to construct a new root and we know that
// runsc can't start without /proc, so we can use it for this.
flags := uintptr(unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC)
- if err := unix.Mount("runsc-root", "/proc", "tmpfs", flags, ""); err != nil {
+ if err := specutils.SafeMount("runsc-root", "/proc", "tmpfs", flags, "", procPath); err != nil {
Fatalf("error mounting tmpfs: %v", err)
}
// Prepare tree structure for pivot_root(2).
- os.Mkdir("/proc/proc", 0755)
- os.Mkdir("/proc/root", 0755)
+ if err := os.Mkdir("/proc/proc", 0755); err != nil {
+ Fatalf("error creating /proc/proc: %v", err)
+ }
+ if err := os.Mkdir("/proc/root", 0755); err != nil {
+ Fatalf("error creating /proc/root: %v", err)
+ }
+ if err := os.Mkdir("/proc/etc", 0755); err != nil {
+ Fatalf("error creating /proc/etc: %v", err)
+ }
+ // This cannot use SafeMount because there's no available procfs. But we
+ // know that /proc is an empty tmpfs mount, so this is safe.
if err := unix.Mount("runsc-proc", "/proc/proc", "proc", flags|unix.MS_RDONLY, ""); err != nil {
Fatalf("error mounting proc: %v", err)
}
+ if err := copyFile("/proc/etc/localtime", "/etc/localtime"); err != nil {
+ log.Warningf("Failed to copy /etc/localtime: %v. UTC timezone will be used.", err)
+ }
root = "/proc/root"
+ procPath = "/proc/proc"
}
// Mount root path followed by submounts.
- if err := unix.Mount(spec.Root.Path, root, "bind", unix.MS_BIND|unix.MS_REC, ""); err != nil {
+ if err := specutils.SafeMount(spec.Root.Path, root, "bind", unix.MS_BIND|unix.MS_REC, "", procPath); err != nil {
return fmt.Errorf("mounting root on root (%q) err: %v", root, err)
}
@@ -302,12 +314,12 @@ func setupRootFS(spec *specs.Spec, conf *config.Config) error {
if spec.Linux != nil && spec.Linux.RootfsPropagation != "" {
flags = specutils.PropOptionsToFlags([]string{spec.Linux.RootfsPropagation})
}
- if err := unix.Mount("", root, "", uintptr(flags), ""); err != nil {
+ if err := specutils.SafeMount("", root, "", uintptr(flags), "", procPath); err != nil {
return fmt.Errorf("mounting root (%q) with flags: %#x, err: %v", root, flags, err)
}
// Replace the current spec, with the clean spec with symlinks resolved.
- if err := setupMounts(conf, spec.Mounts, root); err != nil {
+ if err := setupMounts(conf, spec.Mounts, root, procPath); err != nil {
Fatalf("error setting up FS: %v", err)
}
@@ -329,7 +341,7 @@ func setupRootFS(spec *specs.Spec, conf *config.Config) error {
// to make it read-only for extra safety.
log.Infof("Remounting root as readonly: %q", root)
flags := uintptr(unix.MS_BIND | unix.MS_REMOUNT | unix.MS_RDONLY | unix.MS_REC)
- if err := unix.Mount(root, root, "bind", flags, ""); err != nil {
+ if err := specutils.SafeMount(root, root, "bind", flags, "", procPath); err != nil {
return fmt.Errorf("remounting root as read-only with source: %q, target: %q, flags: %#x, err: %v", root, root, flags, err)
}
}
@@ -345,10 +357,10 @@ func setupRootFS(spec *specs.Spec, conf *config.Config) error {
return nil
}
-// setupMounts binds mount all mounts specified in the spec in their correct
+// setupMounts bind mounts all mounts specified in the spec in their correct
// location inside root. It will resolve relative paths and symlinks. It also
// creates directories as needed.
-func setupMounts(conf *config.Config, mounts []specs.Mount, root string) error {
+func setupMounts(conf *config.Config, mounts []specs.Mount, root, procPath string) error {
for _, m := range mounts {
if !specutils.Is9PMount(m, conf.VFS2) {
continue
@@ -366,14 +378,14 @@ func setupMounts(conf *config.Config, mounts []specs.Mount, root string) error {
}
log.Infof("Mounting src: %q, dst: %q, flags: %#x", m.Source, dst, flags)
- if err := specutils.Mount(m.Source, dst, m.Type, flags); err != nil {
- return fmt.Errorf("mounting %v: %v", m, err)
+ if err := specutils.SafeSetupAndMount(m.Source, dst, m.Type, flags, procPath); err != nil {
+ return fmt.Errorf("mounting %+v: %v", m, err)
}
// Set propagation options that cannot be set together with other options.
flags = specutils.PropOptionsToFlags(m.Options)
if flags != 0 {
- if err := unix.Mount("", dst, "", uintptr(flags), ""); err != nil {
+ if err := specutils.SafeMount("", dst, "", uintptr(flags), "", procPath); err != nil {
return fmt.Errorf("mount dst: %q, flags: %#x, err: %v", dst, flags, err)
}
}
diff --git a/runsc/cmd/help.go b/runsc/cmd/help.go
index cd85dabbb..35545e938 100644
--- a/runsc/cmd/help.go
+++ b/runsc/cmd/help.go
@@ -58,7 +58,7 @@ func (*Help) Usage() string {
}
// SetFlags implements subcommands.Command.SetFlags.
-func (h *Help) SetFlags(f *flag.FlagSet) {}
+func (h *Help) SetFlags(*flag.FlagSet) {}
// Execute implements subcommands.Command.Execute.
func (h *Help) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
diff --git a/runsc/cmd/install.go b/runsc/cmd/install.go
index 2e223e3be..dc9e01d95 100644
--- a/runsc/cmd/install.go
+++ b/runsc/cmd/install.go
@@ -58,7 +58,7 @@ func (i *Install) SetFlags(fs *flag.FlagSet) {
}
// Execute implements subcommands.Command.Execute.
-func (i *Install) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+func (i *Install) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// Grab the name and arguments.
runtimeArgs := f.Args()
@@ -134,7 +134,7 @@ func (u *Uninstall) SetFlags(fs *flag.FlagSet) {
}
// Execute implements subcommands.Command.Execute.
-func (u *Uninstall) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+func (u *Uninstall) Execute(context.Context, *flag.FlagSet, ...interface{}) subcommands.ExitStatus {
log.Printf("Removing runtime %q from %q.", u.Runtime, u.ConfigFile)
c, err := readConfig(u.ConfigFile)
diff --git a/runsc/cmd/list.go b/runsc/cmd/list.go
index 9f9a47bd8..2adfcced7 100644
--- a/runsc/cmd/list.go
+++ b/runsc/cmd/list.go
@@ -102,7 +102,7 @@ func (l *List) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
c.CreatedAt.Format(time.RFC3339Nano),
c.Owner)
}
- w.Flush()
+ _ = w.Flush()
case "json":
// Print just the states.
var states []specs.State
diff --git a/runsc/cmd/mitigate.go b/runsc/cmd/mitigate.go
index f4e65adb8..1aada5968 100644
--- a/runsc/cmd/mitigate.go
+++ b/runsc/cmd/mitigate.go
@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"io/ioutil"
+ "os"
"runtime"
"github.com/google/subcommands"
@@ -29,8 +30,8 @@ import (
const (
// cpuInfo is the path used to parse CPU info.
cpuInfo = "/proc/cpuinfo"
- // allPossibleCPUs is the path used to enable CPUs.
- allPossibleCPUs = "/sys/devices/system/cpu/possible"
+ // Path to enable/disable SMT.
+ smtPath = "/sys/devices/system/cpu/smt/control"
)
// Mitigate implements subcommands.Command for the "mitigate" command.
@@ -39,10 +40,10 @@ type Mitigate struct {
dryRun bool
// Reverse mitigate by turning on all CPU cores.
reverse bool
- // Path to file to read to create CPUSet.
- path string
// Extra data for post mitigate operations.
data string
+ // Control to mitigate/reverse smt.
+ control machineControl
}
// Name implements subcommands.command.name.
@@ -56,12 +57,12 @@ func (*Mitigate) Synopsis() string {
}
// Usage implements Usage for cmd.Mitigate.
-func (m Mitigate) Usage() string {
+func (m *Mitigate) Usage() string {
return fmt.Sprintf(`mitigate [flags]
-mitigate mitigates a system to the "MDS" vulnerability by implementing a manual shutdown of SMT. The command checks /proc/cpuinfo for cpus having the MDS vulnerability, and if found, shutdown all but one CPU per hyperthread pair via /sys/devices/system/cpu/cpu{N}/online. CPUs can be restored by writing "2" to each file in /sys/devices/system/cpu/cpu{N}/online or performing a system reboot.
+mitigate mitigates a system to the "MDS" vulnerability by writing "off" to %q. CPUs can be restored by writing "on" to the same file or rebooting your system.
-The command can be reversed with --reverse, which reads the total CPUs from /sys/devices/system/cpu/possible and enables all with /sys/devices/system/cpu/cpu{N}/online.%s`, m.usage())
+The command can be reversed with --reverse, which writes "on" to the file above.%s`, smtPath, m.usage())
}
// SetFlags sets flags for the command Mitigate.
@@ -74,104 +75,110 @@ func (m *Mitigate) SetFlags(f *flag.FlagSet) {
// Execute implements subcommands.Command.Execute.
func (m *Mitigate) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
- log.Warningf("As ARM is not affected by MDS, mitigate does not support")
- return subcommands.ExitFailure
+ log.Warningf("As ARM is not affected by MDS, mitigate does not support ARM machines.")
+ // Set reverse flag so that we still perform post mitigate operations. mitigate reverse is a noop in this case.
+ m.reverse = true
}
if f.NArg() != 0 {
f.Usage()
return subcommands.ExitUsageError
}
+ m.control = &machineControlImpl{}
+ return m.execute()
+}
- m.path = cpuInfo
- if m.reverse {
- m.path = allPossibleCPUs
+// execute executes mitigate operations. Seperate from Execute method for
+// easier mocking.
+func (m *Mitigate) execute() subcommands.ExitStatus {
+ beforeSet, err := m.control.getCPUs()
+ if err != nil {
+ return Errorf("Get before CPUSet failed: %v", err)
}
+ log.Infof("CPUs before: %s", beforeSet.String())
- set, err := m.doExecute()
- if err != nil {
- return Errorf("Execute failed: %v", err)
+ if err := m.doEnableDisable(beforeSet); err != nil {
+ return Errorf("Enabled/Disable action failed on %q: %v", smtPath, err)
}
- if m.data == "" {
- return subcommands.ExitSuccess
+ afterSet, err := m.control.getCPUs()
+ if err != nil {
+ return Errorf("Get after CPUSet failed: %v", err)
}
+ log.Infof("CPUs after: %s", afterSet.String())
- if err = m.postMitigate(set); err != nil {
+ if err = m.postMitigate(afterSet); err != nil {
return Errorf("Post Mitigate failed: %v", err)
}
return subcommands.ExitSuccess
}
-// Execute executes the Mitigate command.
-func (m *Mitigate) doExecute() (mitigate.CPUSet, error) {
- if m.dryRun {
- log.Infof("Running with DryRun. No cpu settings will be changed.")
- }
- data, err := ioutil.ReadFile(m.path)
- if err != nil {
- return nil, fmt.Errorf("failed to read %s: %w", m.path, err)
- }
+// doEnableDisable does either enable or disable operation based on flags.
+func (m *Mitigate) doEnableDisable(set mitigate.CPUSet) error {
if m.reverse {
- set, err := m.doReverse(data)
- if err != nil {
- return nil, fmt.Errorf("reverse operation failed: %w", err)
+ if m.dryRun {
+ log.Infof("Skipping reverse action because dryrun is set.")
+ return nil
}
- return set, nil
+ return m.control.enable()
}
- set, err := m.doMitigate(data)
- if err != nil {
- return nil, fmt.Errorf("mitigate operation failed: %w", err)
+ if m.dryRun {
+ log.Infof("Skipping mitigate action because dryrun is set.")
+ return nil
}
- return set, nil
+ if set.IsVulnerable() {
+ return m.control.disable()
+ }
+ log.Infof("CPUs not vulnerable. Skipping disable call.")
+ return nil
}
-func (m *Mitigate) doMitigate(data []byte) (mitigate.CPUSet, error) {
- set, err := mitigate.NewCPUSet(data)
- if err != nil {
- return nil, err
- }
+// Interface to wrap interactions with underlying machine. Done
+// so testing with mocks can be done hermetically.
+type machineControl interface {
+ enable() error
+ disable() error
+ isEnabled() (bool, error)
+ getCPUs() (mitigate.CPUSet, error)
+}
- log.Infof("Mitigate found the following CPUs...")
- log.Infof("%s", set)
+// Implementation of SMT control interaction with the underlying machine.
+type machineControlImpl struct{}
- disableList := set.GetShutdownList()
- log.Infof("Disabling threads on thread pairs.")
- for _, t := range disableList {
- log.Infof("Disable thread: %s", t)
- if m.dryRun {
- continue
- }
- if err := t.Disable(); err != nil {
- return nil, fmt.Errorf("error disabling thread: %s err: %w", t, err)
- }
- }
- log.Infof("Shutdown successful.")
- return set, nil
+func (*machineControlImpl) enable() error {
+ return checkFileExistsOnWrite("enable", "on")
}
-func (m *Mitigate) doReverse(data []byte) (mitigate.CPUSet, error) {
- set, err := mitigate.NewCPUSetFromPossible(data)
- if err != nil {
- return nil, err
- }
+func (*machineControlImpl) disable() error {
+ return checkFileExistsOnWrite("disable", "off")
+}
- log.Infof("Reverse mitigate found the following CPUs...")
- log.Infof("%s", set)
+// Writes data to SMT control. If file not found, logs file not exist error and returns nil
+// error, which is done because machines without the file pointed to by smtPath only have one
+// thread per core in the first place. Otherwise returns error from ioutil.WriteFile.
+func checkFileExistsOnWrite(op, data string) error {
+ err := ioutil.WriteFile(smtPath, []byte(data), 0644)
+ if err != nil && os.IsExist(err) {
+ log.Infof("File %q does not exist for operation %s. This machine probably has no smt control.", smtPath, op)
+ return nil
+ }
+ return err
+}
- enableList := set.GetRemainingList()
+func (*machineControlImpl) isEnabled() (bool, error) {
+ data, err := ioutil.ReadFile(cpuInfo)
+ return string(data) == "on", err
+}
- log.Infof("Enabling all CPUs...")
- for _, t := range enableList {
- log.Infof("Enabling thread: %s", t)
- if m.dryRun {
- continue
- }
- if err := t.Enable(); err != nil {
- return nil, fmt.Errorf("error enabling thread: %s err: %w", t, err)
- }
+func (*machineControlImpl) getCPUs() (mitigate.CPUSet, error) {
+ data, err := ioutil.ReadFile(cpuInfo)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read %s: %w", cpuInfo, err)
+ }
+ set, err := mitigate.NewCPUSet(string(data))
+ if err != nil {
+ return nil, fmt.Errorf("getCPUs: %v", err)
}
- log.Infof("Enable successful.")
return set, nil
}
diff --git a/runsc/cmd/mitigate_extras.go b/runsc/cmd/mitigate_extras.go
index 2cb2833f0..2c3e17cd6 100644
--- a/runsc/cmd/mitigate_extras.go
+++ b/runsc/cmd/mitigate_extras.go
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+//go:build go1.1
+// +build go1.1
+
package cmd
import (
diff --git a/runsc/cmd/mitigate_test.go b/runsc/cmd/mitigate_test.go
index 2d3fef7c1..294fc645c 100644
--- a/runsc/cmd/mitigate_test.go
+++ b/runsc/cmd/mitigate_test.go
@@ -12,153 +12,139 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+//go:build amd64
// +build amd64
package cmd
import (
- "fmt"
- "io/ioutil"
- "os"
- "strings"
"testing"
- "gvisor.dev/gvisor/runsc/mitigate/mock"
+ "github.com/google/subcommands"
+ "gvisor.dev/gvisor/pkg/log"
+ "gvisor.dev/gvisor/runsc/mitigate"
)
-type executeTestCase struct {
- name string
- mitigateData string
- mitigateError error
- mitigateCPU int
- reverseData string
- reverseError error
- reverseCPU int
+type mockMachineControl struct {
+ enabled bool
+ cpus mitigate.CPUSet
}
-func TestExecute(t *testing.T) {
+func (m *mockMachineControl) enable() error {
+ m.enabled = true
+ return nil
+}
- partial := `processor : 1
-vendor_id : AuthenticAMD
-cpu family : 23
-model : 49
-model name : AMD EPYC 7B12
-physical id : 0
-bugs : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass
-power management:
-`
+func (m *mockMachineControl) disable() error {
+ if m.cpus.IsVulnerable() {
+ m.enabled = false
+ }
+ return nil
+}
+func (m *mockMachineControl) isEnabled() (bool, error) {
+ return m.enabled, nil
+}
+
+func (m *mockMachineControl) getCPUs() (mitigate.CPUSet, error) {
+ set := m.cpus
+ if !m.enabled {
+ set = m.cpus[:len(m.cpus)/2]
+ }
+
+ // Instead of just returning the created CPU set stored in this struct, call
+ // NewCPUSet to exercise that code path as the machineControlImpl would.
+ return mitigate.NewCPUSet(set.String())
+}
+
+type executeTestCase struct {
+ name string
+ cpu mitigate.MockCPU
+ mitigateWantCPUs int
+ mitigateError subcommands.ExitStatus
+ mitigateWantEnabled bool
+ reverseWantCPUs int
+ reverseError subcommands.ExitStatus
+ reverseWantEnabled bool
+ dryrun bool
+}
+
+func TestExecute(t *testing.T) {
for _, tc := range []executeTestCase{
{
- name: "CascadeLake4",
- mitigateData: mock.CascadeLake4.MakeCPUString(),
- mitigateCPU: 2,
- reverseData: mock.CascadeLake4.MakeSysPossibleString(),
- reverseCPU: 4,
+ name: "CascadeLake4",
+ cpu: mitigate.CascadeLake4,
+ mitigateWantCPUs: 2,
+ mitigateWantEnabled: false,
+ reverseWantCPUs: 4,
+ reverseWantEnabled: true,
},
{
- name: "Empty",
- mitigateData: "",
- mitigateError: fmt.Errorf(`mitigate operation failed: no cpus found for: ""`),
- reverseData: "",
- reverseError: fmt.Errorf(`reverse operation failed: mismatch regex from possible: ""`),
+ name: "CascadeLake4DryRun",
+ cpu: mitigate.CascadeLake4,
+ mitigateWantCPUs: 4,
+ mitigateWantEnabled: true,
+ reverseWantCPUs: 4,
+ reverseWantEnabled: true,
+ dryrun: true,
},
{
- name: "Partial",
- mitigateData: `processor : 0
-vendor_id : AuthenticAMD
-cpu family : 23
-model : 49
-model name : AMD EPYC 7B12
-physical id : 0
-core id : 0
-cpu cores : 1
-bugs : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass
-power management::84
-
-` + partial,
- mitigateError: fmt.Errorf(`mitigate operation failed: failed to match key "core id": %q`, partial),
- reverseData: "1-",
- reverseError: fmt.Errorf(`reverse operation failed: mismatch regex from possible: %q`, "1-"),
+ name: "AMD8",
+ cpu: mitigate.AMD8,
+ mitigateWantCPUs: 8,
+ mitigateWantEnabled: true,
+ reverseWantCPUs: 8,
+ reverseWantEnabled: true,
+ },
+ {
+ name: "Empty",
+ cpu: mitigate.Empty,
+ mitigateError: Errorf(`mitigate operation failed: no cpus found for: ""`),
+ reverseError: Errorf(`mitigate operation failed: no cpus found for: ""`),
},
} {
t.Run(tc.name, func(t *testing.T) {
+ set := tc.cpu.MakeCPUSet()
m := &Mitigate{
- dryRun: true,
+ control: &mockMachineControl{
+ enabled: true,
+ cpus: set,
+ },
+ dryRun: tc.dryrun,
}
- m.doExecuteTest(t, "Mitigate", tc.mitigateData, tc.mitigateCPU, tc.mitigateError)
+ t.Run("Mitigate", func(t *testing.T) {
+ m.doExecuteTest(t, tc.mitigateWantEnabled, tc.mitigateWantCPUs, tc.mitigateError)
+ })
m.reverse = true
- m.doExecuteTest(t, "Reverse", tc.reverseData, tc.reverseCPU, tc.reverseError)
+ t.Run("Reverse", func(t *testing.T) {
+ m.doExecuteTest(t, tc.reverseWantEnabled, tc.reverseWantCPUs, tc.reverseError)
+ })
})
}
}
-func TestExecuteSmoke(t *testing.T) {
- smokeMitigate, err := ioutil.ReadFile(cpuInfo)
- if err != nil {
- t.Fatalf("Failed to read %s: %v", cpuInfo, err)
+// doExecuteTest runs Execute with the mitigate operation and reverse operation.
+func (m *Mitigate) doExecuteTest(t *testing.T, wantEnabled bool, wantCPUs int, wantErr subcommands.ExitStatus) {
+ subError := m.execute()
+ if subError != wantErr {
+ t.Fatalf("Mitigate error mismatch: want: %v got: %v", wantErr, subError)
}
- m := &Mitigate{
- dryRun: true,
+ // case where test should end in error or we don't care
+ // about how many cpus are returned.
+ if wantErr != subcommands.ExitSuccess {
+ log.Infof("return")
+ return
}
- m.doExecuteTest(t, "Mitigate", string(smokeMitigate), 0, nil)
-
- smokeReverse, err := ioutil.ReadFile(allPossibleCPUs)
- if err != nil {
- t.Fatalf("Failed to read %s: %v", allPossibleCPUs, err)
+ gotEnabled, _ := m.control.isEnabled()
+ if wantEnabled != gotEnabled {
+ t.Fatalf("Incorrect enabled state: want: %t got: %t", wantEnabled, gotEnabled)
}
- m.reverse = true
- m.doExecuteTest(t, "Reverse", string(smokeReverse), 0, nil)
-}
-
-// doExecuteTest runs Execute with the mitigate operation and reverse operation.
-func (m *Mitigate) doExecuteTest(t *testing.T, name, data string, want int, wantErr error) {
- t.Run(name, func(t *testing.T) {
- file, err := ioutil.TempFile("", "outfile.txt")
- if err != nil {
- t.Fatalf("Failed to create tmpfile: %v", err)
- }
- defer os.Remove(file.Name())
-
- if _, err := file.WriteString(data); err != nil {
- t.Fatalf("Failed to write to file: %v", err)
- }
-
- // Set fields for mitigate and dryrun to keep test hermetic.
- m.path = file.Name()
-
- set, err := m.doExecute()
- if err = checkErr(wantErr, err); err != nil {
- t.Fatalf("Mitigate error mismatch: %v", err)
- }
-
- // case where test should end in error or we don't care
- // about how many cpus are returned.
- if wantErr != nil || want < 1 {
- return
- }
- got := len(set.GetRemainingList())
- if want != got {
- t.Fatalf("Failed wrong number of remaining CPUs: want %d, got %d", want, got)
- }
-
- })
-}
-
-// checkErr checks error for equality.
-func checkErr(want, got error) error {
- switch {
- case want == nil && got == nil:
- case want != nil && got == nil:
- fallthrough
- case want == nil && got != nil:
- fallthrough
- case want.Error() != strings.Trim(got.Error(), " "):
- return fmt.Errorf("got: %v want: %v", got, want)
+ gotCPUs, _ := m.control.getCPUs()
+ if len(gotCPUs) != wantCPUs {
+ t.Fatalf("Incorrect number of CPUs: want: %d got: %d", wantCPUs, len(gotCPUs))
}
- return nil
}
diff --git a/runsc/cmd/pause.go b/runsc/cmd/pause.go
index 15ef7b577..9768f1cfb 100644
--- a/runsc/cmd/pause.go
+++ b/runsc/cmd/pause.go
@@ -42,7 +42,7 @@ func (*Pause) Usage() string {
}
// SetFlags implements subcommands.Command.SetFlags.
-func (*Pause) SetFlags(f *flag.FlagSet) {
+func (*Pause) SetFlags(*flag.FlagSet) {
}
// Execute implements subcommands.Command.Execute.
diff --git a/runsc/cmd/resume.go b/runsc/cmd/resume.go
index 856469252..d62e89e80 100644
--- a/runsc/cmd/resume.go
+++ b/runsc/cmd/resume.go
@@ -43,7 +43,7 @@ func (*Resume) Usage() string {
}
// SetFlags implements subcommands.Command.SetFlags.
-func (r *Resume) SetFlags(f *flag.FlagSet) {
+func (r *Resume) SetFlags(*flag.FlagSet) {
}
// Execute implements subcommands.Command.Execute.
diff --git a/runsc/cmd/run.go b/runsc/cmd/run.go
index 722181aff..da11c9d06 100644
--- a/runsc/cmd/run.go
+++ b/runsc/cmd/run.go
@@ -68,7 +68,14 @@ func (r *Run) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) s
waitStatus := args[1].(*unix.WaitStatus)
if conf.Rootless {
- return Errorf("Rootless mode not supported with %q", r.Name())
+ if conf.Network == config.NetworkSandbox {
+ return Errorf("sandbox network isn't supported with --rootless, use --network=none or --network=host")
+ }
+
+ if err := specutils.MaybeRunAsRoot(); err != nil {
+ return Errorf("Error executing inside namespace: %v", err)
+ }
+ // Execution will continue here if no more capabilities are needed...
}
bundleDir := r.bundleDir
diff --git a/runsc/cmd/start.go b/runsc/cmd/start.go
index 964a65064..7c395d722 100644
--- a/runsc/cmd/start.go
+++ b/runsc/cmd/start.go
@@ -43,7 +43,7 @@ func (*Start) Usage() string {
}
// SetFlags implements subcommands.Command.SetFlags.
-func (*Start) SetFlags(f *flag.FlagSet) {}
+func (*Start) SetFlags(*flag.FlagSet) {}
// Execute implements subcommands.Command.Execute.
func (*Start) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
diff --git a/runsc/cmd/state.go b/runsc/cmd/state.go
index 1f7913d5a..061003bab 100644
--- a/runsc/cmd/state.go
+++ b/runsc/cmd/state.go
@@ -45,7 +45,7 @@ func (*State) Usage() string {
}
// SetFlags implements subcommands.Command.SetFlags.
-func (*State) SetFlags(f *flag.FlagSet) {}
+func (*State) SetFlags(*flag.FlagSet) {}
// Execute implements subcommands.Command.Execute.
func (*State) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
@@ -71,6 +71,8 @@ func (*State) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) s
if err != nil {
Fatalf("marshaling container state: %v", err)
}
- os.Stdout.Write(b)
+ if _, err := os.Stdout.Write(b); err != nil {
+ Fatalf("Error writing to stdout: %v", err)
+ }
return subcommands.ExitSuccess
}
diff --git a/runsc/cmd/syscalls.go b/runsc/cmd/syscalls.go
index a8c83d662..608be9bb4 100644
--- a/runsc/cmd/syscalls.go
+++ b/runsc/cmd/syscalls.go
@@ -103,7 +103,7 @@ func (s *Syscalls) SetFlags(f *flag.FlagSet) {
}
// Execute implements subcommands.Command.Execute.
-func (s *Syscalls) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+func (s *Syscalls) Execute(context.Context, *flag.FlagSet, ...interface{}) subcommands.ExitStatus {
out, ok := outputMap[s.format]
if !ok {
Fatalf("Unsupported output format %q", s.format)
diff --git a/runsc/cmd/usage.go b/runsc/cmd/usage.go
new file mode 100644
index 000000000..d2aeafa28
--- /dev/null
+++ b/runsc/cmd/usage.go
@@ -0,0 +1,93 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/google/subcommands"
+ "gvisor.dev/gvisor/runsc/config"
+ "gvisor.dev/gvisor/runsc/container"
+ "gvisor.dev/gvisor/runsc/flag"
+)
+
+// Usage implements subcommands.Command for the "usage" command.
+type Usage struct {
+ full bool
+ fd bool
+}
+
+// Name implements subcommands.Command.Name.
+func (*Usage) Name() string {
+ return "usage"
+}
+
+// Synopsis implements subcommands.Command.Synopsis.
+func (*Usage) Synopsis() string {
+ return "Usage shows application memory usage across various categories in bytes."
+}
+
+// Usage implements subcommands.Command.Usage.
+func (*Usage) Usage() string {
+ return `usage [flags] <container id> - print memory usages to standard output.`
+}
+
+// SetFlags implements subcommands.Command.SetFlags.
+func (u *Usage) SetFlags(f *flag.FlagSet) {
+ f.BoolVar(&u.full, "full", false, "enumerate all usage by categories")
+ f.BoolVar(&u.fd, "fd", false, "retrieves a subset of usage through the established usage FD")
+}
+
+// Execute implements subcommands.Command.Execute.
+func (u *Usage) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+ if f.NArg() < 1 {
+ f.Usage()
+ return subcommands.ExitUsageError
+ }
+
+ id := f.Arg(0)
+ conf := args[0].(*config.Config)
+
+ cont, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{})
+ if err != nil {
+ Fatalf("loading container: %v", err)
+ }
+
+ if !u.fd {
+ m, err := cont.Usage(u.full)
+ if err != nil {
+ Fatalf("usage failed: %v", err)
+ }
+ if err := json.NewEncoder(os.Stdout).Encode(m); err != nil {
+ Fatalf("Encode MemoryUsage failed: %v", err)
+ }
+ } else {
+ m, err := cont.UsageFD()
+ if err != nil {
+ Fatalf("usagefd failed: %v", err)
+ }
+
+ mapped, unknown, total, err := m.Fetch()
+ if err != nil {
+ Fatalf("Fetch memory usage failed: %v", err)
+ }
+
+ fmt.Printf("Mapped %v, Unknown %v, Total %v\n", mapped, unknown, total)
+ }
+ return subcommands.ExitSuccess
+}
diff --git a/runsc/cmd/verity_prepare.go b/runsc/cmd/verity_prepare.go
index 66128b2a3..44c1d05db 100644
--- a/runsc/cmd/verity_prepare.go
+++ b/runsc/cmd/verity_prepare.go
@@ -82,18 +82,23 @@ func (c *VerityPrepare) Execute(_ context.Context, f *flag.FlagSet, args ...inte
},
Process: &specs.Process{
Cwd: absRoot,
- Args: []string{c.tool, "--path", "/verityroot"},
+ Args: []string{c.tool, "--path", "/verityroot", "--rawpath", "/rawroot"},
Env: os.Environ(),
Capabilities: specutils.AllCapabilities(),
},
Hostname: hostname,
Mounts: []specs.Mount{
- specs.Mount{
+ {
Source: c.dir,
Destination: "/verityroot",
Type: "bind",
Options: []string{"verity.roothash="},
},
+ {
+ Source: c.dir,
+ Destination: "/rawroot",
+ Type: "bind",
+ },
},
}