summaryrefslogtreecommitdiffhomepage
path: root/runsc
diff options
context:
space:
mode:
Diffstat (limited to 'runsc')
-rw-r--r--runsc/boot/BUILD2
-rw-r--r--runsc/boot/controller.go45
-rw-r--r--runsc/boot/loader.go3
-rw-r--r--runsc/boot/network.go3
-rw-r--r--runsc/boot/strace.go9
-rw-r--r--runsc/cmd/BUILD1
-rw-r--r--runsc/cmd/events.go13
-rw-r--r--runsc/cmd/usage.go93
-rw-r--r--runsc/cmd/verity_prepare.go7
-rw-r--r--runsc/config/BUILD6
-rw-r--r--runsc/config/config.go99
-rw-r--r--runsc/config/config_test.go23
-rw-r--r--runsc/config/flags.go2
-rw-r--r--runsc/container/container.go24
-rw-r--r--runsc/container/container_test.go174
-rw-r--r--runsc/fsgofer/fsgofer.go16
-rw-r--r--runsc/sandbox/BUILD2
-rw-r--r--runsc/sandbox/sandbox.go86
18 files changed, 584 insertions, 24 deletions
diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD
index c9d2b3eff..0ded907f0 100644
--- a/runsc/boot/BUILD
+++ b/runsc/boot/BUILD
@@ -45,6 +45,7 @@ go_library(
"//pkg/sentry/arch",
"//pkg/sentry/arch:registers_go_proto",
"//pkg/sentry/control",
+ "//pkg/sentry/control:control_go_proto",
"//pkg/sentry/devices/memdev",
"//pkg/sentry/devices/ttydev",
"//pkg/sentry/devices/tundev",
@@ -96,6 +97,7 @@ go_library(
"//pkg/sentry/watchdog",
"//pkg/sync",
"//pkg/tcpip",
+ "//pkg/tcpip/link/ethernet",
"//pkg/tcpip/link/fdbased",
"//pkg/tcpip/link/loopback",
"//pkg/tcpip/link/packetsocket",
diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go
index 60b532798..76e1f596b 100644
--- a/runsc/boot/controller.go
+++ b/runsc/boot/controller.go
@@ -26,6 +26,7 @@ import (
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/control"
+ controlpb "gvisor.dev/gvisor/pkg/sentry/control/control_go_proto"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket/netstack"
@@ -114,6 +115,18 @@ const (
FsCat = "Fs.Cat"
)
+// Usage related commands (see usage.go for more details).
+const (
+ UsageCollect = "Usage.Collect"
+ UsageUsageFD = "Usage.UsageFD"
+ UsageReduce = "Usage.Reduce"
+)
+
+// Events related commands (see events.go for more details).
+const (
+ EventsAttachDebugEmitter = "Events.AttachDebugEmitter"
+)
+
// ControlSocketAddr generates an abstract unix socket name for the given ID.
func ControlSocketAddr(id string) string {
return fmt.Sprintf("\x00runsc-sandbox.%s", id)
@@ -153,13 +166,31 @@ func newController(fd int, l *Loader) (*controller, error) {
ctrl.srv.Register(net)
}
- ctrl.srv.Register(&debug{})
- ctrl.srv.Register(&control.Logging{})
- ctrl.srv.Register(&control.Lifecycle{l.k})
- ctrl.srv.Register(&control.Fs{l.k})
-
- if l.root.conf.ProfileEnable {
- ctrl.srv.Register(control.NewProfile(l.k))
+ if l.root.conf.Controls.Controls != nil {
+ for _, c := range l.root.conf.Controls.Controls.AllowedControls {
+ switch c {
+ case controlpb.ControlConfig_EVENTS:
+ ctrl.srv.Register(&control.Events{})
+ case controlpb.ControlConfig_FS:
+ ctrl.srv.Register(&control.Fs{Kernel: l.k})
+ case controlpb.ControlConfig_LIFECYCLE:
+ ctrl.srv.Register(&control.Lifecycle{Kernel: l.k})
+ case controlpb.ControlConfig_LOGGING:
+ ctrl.srv.Register(&control.Logging{})
+ case controlpb.ControlConfig_PROFILE:
+ if l.root.conf.ProfileEnable {
+ ctrl.srv.Register(control.NewProfile(l.k))
+ }
+ case controlpb.ControlConfig_USAGE:
+ ctrl.srv.Register(&control.Usage{Kernel: l.k})
+ case controlpb.ControlConfig_PROC:
+ ctrl.srv.Register(&control.Proc{Kernel: l.k})
+ case controlpb.ControlConfig_STATE:
+ ctrl.srv.Register(&control.State{Kernel: l.k})
+ case controlpb.ControlConfig_DEBUG:
+ ctrl.srv.Register(&debug{})
+ }
+ }
}
return ctrl, nil
diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go
index ec9188021..3f667cd74 100644
--- a/runsc/boot/loader.go
+++ b/runsc/boot/loader.go
@@ -58,6 +58,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/watchdog"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/link/ethernet"
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
"gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
@@ -1174,7 +1175,7 @@ func (f *sandboxNetstackCreator) CreateStack() (inet.Stack, error) {
n := &Network{Stack: s.(*netstack.Stack).Stack}
nicID := tcpip.NICID(f.uniqueID.UniqueID())
link := DefaultLoopbackLink
- linkEP := loopback.New()
+ linkEP := ethernet.New(loopback.New())
if err := n.createNICWithAddrs(nicID, link.Name, linkEP, link.Addresses); err != nil {
return nil, err
}
diff --git a/runsc/boot/network.go b/runsc/boot/network.go
index 7e627e4c6..5c6879198 100644
--- a/runsc/boot/network.go
+++ b/runsc/boot/network.go
@@ -23,6 +23,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/link/ethernet"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
"gvisor.dev/gvisor/pkg/tcpip/link/packetsocket"
@@ -169,7 +170,7 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct
nicID++
nicids[link.Name] = nicID
- linkEP := loopback.New()
+ linkEP := ethernet.New(loopback.New())
log.Infof("Enabling loopback interface %q with id %d on addresses %+v", link.Name, nicID, link.Addresses)
if err := n.createNICWithAddrs(nicID, link.Name, linkEP, link.Addresses); err != nil {
diff --git a/runsc/boot/strace.go b/runsc/boot/strace.go
index c21648a32..cf5be34cd 100644
--- a/runsc/boot/strace.go
+++ b/runsc/boot/strace.go
@@ -35,9 +35,14 @@ func enableStrace(conf *config.Config) error {
}
strace.LogMaximumSize = max
+ sink := strace.SinkTypeLog
+ if conf.StraceEvent {
+ sink = strace.SinkTypeEvent
+ }
+
if len(conf.StraceSyscalls) == 0 {
- strace.EnableAll(strace.SinkTypeLog)
+ strace.EnableAll(sink)
return nil
}
- return strace.Enable(strings.Split(conf.StraceSyscalls, ","), strace.SinkTypeLog)
+ return strace.Enable(strings.Split(conf.StraceSyscalls, ","), sink)
}
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD
index 031ddd57e..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",
],
diff --git a/runsc/cmd/events.go b/runsc/cmd/events.go
index c1d029d7f..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.
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 85d762a51..44c1d05db 100644
--- a/runsc/cmd/verity_prepare.go
+++ b/runsc/cmd/verity_prepare.go
@@ -82,7 +82,7 @@ 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(),
},
@@ -94,6 +94,11 @@ func (c *VerityPrepare) Execute(_ context.Context, f *flag.FlagSet, args ...inte
Type: "bind",
Options: []string{"verity.roothash="},
},
+ {
+ Source: c.dir,
+ Destination: "/rawroot",
+ Type: "bind",
+ },
},
}
diff --git a/runsc/config/BUILD b/runsc/config/BUILD
index b1672bb9d..64295d283 100644
--- a/runsc/config/BUILD
+++ b/runsc/config/BUILD
@@ -11,6 +11,7 @@ go_library(
visibility = ["//:sandbox"],
deps = [
"//pkg/refs",
+ "//pkg/sentry/control:control_go_proto",
"//pkg/sentry/watchdog",
"//pkg/sync",
"//runsc/flag",
@@ -24,5 +25,8 @@ go_test(
"config_test.go",
],
library = ":config",
- deps = ["//runsc/flag"],
+ deps = [
+ "//pkg/sentry/control:control_go_proto",
+ "//runsc/flag",
+ ],
)
diff --git a/runsc/config/config.go b/runsc/config/config.go
index cc4650180..2f52863ff 100644
--- a/runsc/config/config.go
+++ b/runsc/config/config.go
@@ -19,8 +19,10 @@ package config
import (
"fmt"
+ "strings"
"gvisor.dev/gvisor/pkg/refs"
+ controlpb "gvisor.dev/gvisor/pkg/sentry/control/control_go_proto"
"gvisor.dev/gvisor/pkg/sentry/watchdog"
)
@@ -117,6 +119,10 @@ type Config struct {
// StraceLogSize is the max size of data blobs to display.
StraceLogSize uint `flag:"strace-log-size"`
+ // StraceEvent indicates sending strace to events if true. Strace is
+ // sent to log if false.
+ StraceEvent bool `flag:"strace-event"`
+
// DisableSeccomp indicates whether seccomp syscall filters should be
// disabled. Pardon the double negation, but default to enabled is important.
DisableSeccomp bool
@@ -131,6 +137,9 @@ type Config struct {
// ProfileEnable is set to prepare the sandbox to be profiled.
ProfileEnable bool `flag:"profile"`
+ // Controls defines the controls that may be enabled.
+ Controls controlConfig `flag:"controls"`
+
// RestoreFile is the path to the saved container image
RestoreFile string
@@ -347,6 +356,96 @@ func (q QueueingDiscipline) String() string {
panic(fmt.Sprintf("Invalid qdisc %d", q))
}
+// controlConfig represents control endpoints.
+type controlConfig struct {
+ Controls *controlpb.ControlConfig
+}
+
+// Set implements flag.Value.
+func (c *controlConfig) Set(v string) error {
+ controls := strings.Split(v, ",")
+ var controlList []controlpb.ControlConfig_Endpoint
+ for _, control := range controls {
+ switch control {
+ case "EVENTS":
+ controlList = append(controlList, controlpb.ControlConfig_EVENTS)
+ case "FS":
+ controlList = append(controlList, controlpb.ControlConfig_FS)
+ case "LIFECYCLE":
+ controlList = append(controlList, controlpb.ControlConfig_LIFECYCLE)
+ case "LOGGING":
+ controlList = append(controlList, controlpb.ControlConfig_LOGGING)
+ case "PROFILE":
+ controlList = append(controlList, controlpb.ControlConfig_PROFILE)
+ case "USAGE":
+ controlList = append(controlList, controlpb.ControlConfig_USAGE)
+ case "PROC":
+ controlList = append(controlList, controlpb.ControlConfig_PROC)
+ case "STATE":
+ controlList = append(controlList, controlpb.ControlConfig_STATE)
+ case "DEBUG":
+ controlList = append(controlList, controlpb.ControlConfig_DEBUG)
+ default:
+ return fmt.Errorf("invalid control %q", control)
+ }
+ }
+ c.Controls.AllowedControls = controlList
+ return nil
+}
+
+// Get implements flag.Value.
+func (c *controlConfig) Get() interface{} {
+ return *c
+}
+
+// String implements flag.Value.
+func (c *controlConfig) String() string {
+ v := ""
+ for _, control := range c.Controls.AllowedControls {
+ if len(v) > 0 {
+ v += ","
+ }
+ switch control {
+ case controlpb.ControlConfig_EVENTS:
+ v += "EVENTS"
+ case controlpb.ControlConfig_FS:
+ v += "FS"
+ case controlpb.ControlConfig_LIFECYCLE:
+ v += "LIFECYCLE"
+ case controlpb.ControlConfig_LOGGING:
+ v += "LOGGING"
+ case controlpb.ControlConfig_PROFILE:
+ v += "PROFILE"
+ case controlpb.ControlConfig_USAGE:
+ v += "USAGE"
+ case controlpb.ControlConfig_PROC:
+ v += "PROC"
+ case controlpb.ControlConfig_STATE:
+ v += "STATE"
+ case controlpb.ControlConfig_DEBUG:
+ v += "DEBUG"
+ default:
+ panic(fmt.Sprintf("Invalid control %d", control))
+ }
+ }
+ return v
+}
+
+func defaultControlConfig() *controlConfig {
+ c := controlConfig{}
+ c.Controls = &controlpb.ControlConfig{}
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_EVENTS)
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_FS)
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_LIFECYCLE)
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_LOGGING)
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_PROFILE)
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_USAGE)
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_PROC)
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_STATE)
+ c.Controls.AllowedControls = append(c.Controls.AllowedControls, controlpb.ControlConfig_DEBUG)
+ return &c
+}
+
func leakModePtr(v refs.LeakMode) *refs.LeakMode {
return &v
}
diff --git a/runsc/config/config_test.go b/runsc/config/config_test.go
index 80ff2c0a6..57c241c86 100644
--- a/runsc/config/config_test.go
+++ b/runsc/config/config_test.go
@@ -18,6 +18,7 @@ import (
"strings"
"testing"
+ controlpb "gvisor.dev/gvisor/pkg/sentry/control/control_go_proto"
"gvisor.dev/gvisor/runsc/flag"
)
@@ -59,6 +60,9 @@ func TestFromFlags(t *testing.T) {
if err := flag.CommandLine.Lookup("network").Value.Set("none"); err != nil {
t.Errorf("Flag set: %v", err)
}
+ if err := flag.CommandLine.Lookup("controls").Value.Set("EVENTS,FS"); err != nil {
+ t.Errorf("Flag set: %v", err)
+ }
defer func() {
if err := setDefault("root"); err != nil {
t.Errorf("Flag set: %v", err)
@@ -72,6 +76,9 @@ func TestFromFlags(t *testing.T) {
if err := setDefault("network"); err != nil {
t.Errorf("Flag set: %v", err)
}
+ if err := setDefault("controls"); err != nil {
+ t.Errorf("Flag set: %v", err)
+ }
}()
c, err := NewFromFlags()
@@ -90,6 +97,12 @@ func TestFromFlags(t *testing.T) {
if want := NetworkNone; c.Network != want {
t.Errorf("Network=%v, want: %v", c.Network, want)
}
+ wants := []controlpb.ControlConfig_Endpoint{controlpb.ControlConfig_EVENTS, controlpb.ControlConfig_FS}
+ for i, want := range wants {
+ if c.Controls.Controls.AllowedControls[i] != want {
+ t.Errorf("Controls.Controls.AllowedControls[%d]=%v, want: %v", i, c.Controls.Controls.AllowedControls[i], want)
+ }
+ }
}
func TestToFlags(t *testing.T) {
@@ -101,10 +114,15 @@ func TestToFlags(t *testing.T) {
c.Debug = true
c.NumNetworkChannels = 123
c.Network = NetworkNone
+ c.Controls = controlConfig{
+ Controls: &controlpb.ControlConfig{
+ AllowedControls: []controlpb.ControlConfig_Endpoint{controlpb.ControlConfig_EVENTS, controlpb.ControlConfig_FS},
+ },
+ }
flags := c.ToFlags()
- if len(flags) != 4 {
- t.Errorf("wrong number of flags set, want: 4, got: %d: %s", len(flags), flags)
+ if len(flags) != 5 {
+ t.Errorf("wrong number of flags set, want: 5, got: %d: %s", len(flags), flags)
}
t.Logf("Flags: %s", flags)
fm := map[string]string{}
@@ -117,6 +135,7 @@ func TestToFlags(t *testing.T) {
"--debug": "true",
"--num-network-channels": "123",
"--network": "none",
+ "--controls": "EVENTS,FS",
} {
if got, ok := fm[name]; ok {
if got != want {
diff --git a/runsc/config/flags.go b/runsc/config/flags.go
index 6f1b5927a..85507902a 100644
--- a/runsc/config/flags.go
+++ b/runsc/config/flags.go
@@ -56,6 +56,7 @@ func RegisterFlags() {
flag.Bool("strace", false, "enable strace.")
flag.String("strace-syscalls", "", "comma-separated list of syscalls to trace. If --strace is true and this list is empty, then all syscalls will be traced.")
flag.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs.")
+ flag.Bool("strace-event", false, "send strace to event.")
// Flags that control sandbox runtime behavior.
flag.String("platform", "ptrace", "specifies which platform to use: ptrace (default), kvm.")
@@ -66,6 +67,7 @@ func RegisterFlags() {
flag.Var(leakModePtr(refs.NoLeakChecking), "ref-leak-mode", "sets reference leak check mode: disabled (default), log-names, log-traces.")
flag.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value, but not less than 2)")
flag.Bool("oci-seccomp", false, "Enables loading OCI seccomp filters inside the sandbox.")
+ flag.Var(defaultControlConfig(), "controls", "Sentry control endpoints.")
// Flags that control sandbox runtime behavior: FS related.
flag.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.")
diff --git a/runsc/container/container.go b/runsc/container/container.go
index d1f979eb2..50b0dd5e7 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -652,6 +652,30 @@ func (c *Container) Cat(files []string, out *os.File) error {
return c.Sandbox.Cat(c.ID, files, out)
}
+// Usage displays memory used by the application.
+func (c *Container) Usage(full bool) (control.MemoryUsage, error) {
+ log.Debugf("Usage in container, cid: %s, full: %v", c.ID, full)
+ return c.Sandbox.Usage(c.ID, full)
+}
+
+// UsageFD shows application memory usage using two donated FDs.
+func (c *Container) UsageFD() (*control.MemoryUsageRecord, error) {
+ log.Debugf("UsageFD in container, cid: %s", c.ID)
+ return c.Sandbox.UsageFD(c.ID)
+}
+
+// Reduce requests that the sentry attempt to reduce its memory usage.
+func (c *Container) Reduce(wait bool) error {
+ log.Debugf("Reduce in container, cid: %s", c.ID)
+ return c.Sandbox.Reduce(c.ID, wait)
+}
+
+// Stream dumps all events to out.
+func (c *Container) Stream(filters []string, out *os.File) error {
+ log.Debugf("Stream in container, cid: %s", c.ID)
+ return c.Sandbox.Stream(c.ID, filters, out)
+}
+
// State returns the metadata of the container.
func (c *Container) State() specs.State {
return specs.State{
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 960c36946..681f5c1a9 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -2655,3 +2655,177 @@ func TestCat(t *testing.T) {
t.Errorf("out got %s, want include %s", buf, want)
}
}
+
+// TestUsage checks that usage generates the expected memory usage.
+func TestUsage(t *testing.T) {
+ spec, conf := sleepSpecConf(t)
+ _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer cleanup()
+
+ args := Args{
+ ID: testutil.RandomContainerID(),
+ Spec: spec,
+ BundleDir: bundleDir,
+ }
+
+ cont, err := New(conf, args)
+ if err != nil {
+ t.Fatalf("Creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ for _, full := range []bool{false, true} {
+ m, err := cont.Usage(full)
+ if err != nil {
+ t.Fatalf("error usage from container: %v", err)
+ }
+ if m.Mapped == 0 {
+ t.Errorf("Usage mapped got zero")
+ }
+ if m.Total == 0 {
+ t.Errorf("Usage total got zero")
+ }
+ if full {
+ if m.System == 0 {
+ t.Errorf("Usage system got zero")
+ }
+ if m.Anonymous == 0 {
+ t.Errorf("Usage anonymous got zero")
+ }
+ }
+ }
+}
+
+// TestUsageFD checks that usagefd generates the expected memory usage.
+func TestUsageFD(t *testing.T) {
+ spec, conf := sleepSpecConf(t)
+
+ _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer cleanup()
+
+ args := Args{
+ ID: testutil.RandomContainerID(),
+ Spec: spec,
+ BundleDir: bundleDir,
+ }
+
+ cont, err := New(conf, args)
+ if err != nil {
+ t.Fatalf("Creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ m, err := cont.UsageFD()
+ if err != nil {
+ t.Fatalf("error usageFD from container: %v", err)
+ }
+
+ mapped, unknown, total, err := m.Fetch()
+ if err != nil {
+ t.Fatalf("error Fetch memory usage: %v", err)
+ }
+
+ if mapped == 0 {
+ t.Errorf("UsageFD Mapped got zero")
+ }
+ if unknown == 0 {
+ t.Errorf("UsageFD unknown got zero")
+ }
+ if total == 0 {
+ t.Errorf("UsageFD total got zero")
+ }
+}
+
+// TestReduce checks that reduce call succeeds.
+func TestReduce(t *testing.T) {
+ spec, conf := sleepSpecConf(t)
+ _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer cleanup()
+
+ args := Args{
+ ID: testutil.RandomContainerID(),
+ Spec: spec,
+ BundleDir: bundleDir,
+ }
+
+ cont, err := New(conf, args)
+ if err != nil {
+ t.Fatalf("Creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ if err := cont.Reduce(false); err != nil {
+ t.Fatalf("error reduce from container: %v", err)
+ }
+}
+
+// TestStream checks that Stream dumps expected events.
+func TestStream(t *testing.T) {
+ spec, conf := sleepSpecConf(t)
+ conf.Strace = true
+ conf.StraceEvent = true
+ conf.StraceSyscalls = ""
+
+ _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer cleanup()
+
+ args := Args{
+ ID: testutil.RandomContainerID(),
+ Spec: spec,
+ BundleDir: bundleDir,
+ }
+
+ cont, err := New(conf, args)
+ if err != nil {
+ t.Fatalf("Creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatalf("os.Create(): %v", err)
+ }
+
+ // Spawn a new thread to Stream events as it blocks indefinitely.
+ go func() {
+ cont.Stream(nil, w)
+ }()
+
+ buf := make([]byte, 1024)
+ if _, err := r.Read(buf); err != nil {
+ t.Fatalf("Read out: %v", err)
+ }
+
+ // A syscall strace event includes "Strace".
+ if got, want := string(buf), "Strace"; !strings.Contains(got, want) {
+ t.Errorf("out got %s, want include %s", buf, want)
+ }
+}
diff --git a/runsc/fsgofer/fsgofer.go b/runsc/fsgofer/fsgofer.go
index 07497e47b..600b21189 100644
--- a/runsc/fsgofer/fsgofer.go
+++ b/runsc/fsgofer/fsgofer.go
@@ -1242,13 +1242,14 @@ func (l *localFile) MultiGetAttr(names []string) ([]p9.FullStat, error) {
}
parent := l.file.FD()
- for _, name := range names {
- child, err := unix.Openat(parent, name, openFlags|unix.O_PATH, 0)
+ closeParent := func() {
if parent != l.file.FD() {
- // Parent is no longer needed.
_ = unix.Close(parent)
- parent = -1
}
+ }
+ defer closeParent()
+ for _, name := range names {
+ child, err := unix.Openat(parent, name, openFlags|unix.O_PATH, 0)
if err != nil {
if errors.Is(err, unix.ENOENT) {
// No pont in continuing any further.
@@ -1256,10 +1257,11 @@ func (l *localFile) MultiGetAttr(names []string) ([]p9.FullStat, error) {
}
return nil, err
}
+ closeParent()
+ parent = child
var stat unix.Stat_t
if err := unix.Fstat(child, &stat); err != nil {
- _ = unix.Close(child)
return nil, err
}
valid, attr := l.fillAttr(&stat)
@@ -1271,13 +1273,9 @@ func (l *localFile) MultiGetAttr(names []string) ([]p9.FullStat, error) {
if (stat.Mode & unix.S_IFMT) != unix.S_IFDIR {
// Doesn't need to continue if entry is not a dir. Including symlinks
// that cannot be followed.
- _ = unix.Close(child)
break
}
parent = child
}
- if parent != -1 && parent != l.file.FD() {
- _ = unix.Close(parent)
- }
return stats, nil
}
diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD
index bc4a3fa32..d625230dd 100644
--- a/runsc/sandbox/BUILD
+++ b/runsc/sandbox/BUILD
@@ -17,12 +17,14 @@ go_library(
"//pkg/control/client",
"//pkg/control/server",
"//pkg/coverage",
+ "//pkg/eventchannel",
"//pkg/log",
"//pkg/sentry/control",
"//pkg/sentry/platform",
"//pkg/sync",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
+ "//pkg/unet",
"//pkg/urpc",
"//runsc/boot",
"//runsc/boot/platforms",
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index b15572a98..9fbce6bd6 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -35,10 +35,12 @@ import (
"gvisor.dev/gvisor/pkg/control/client"
"gvisor.dev/gvisor/pkg/control/server"
"gvisor.dev/gvisor/pkg/coverage"
+ "gvisor.dev/gvisor/pkg/eventchannel"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sync"
+ "gvisor.dev/gvisor/pkg/unet"
"gvisor.dev/gvisor/pkg/urpc"
"gvisor.dev/gvisor/runsc/boot"
"gvisor.dev/gvisor/runsc/boot/platforms"
@@ -1020,6 +1022,90 @@ func (s *Sandbox) Cat(cid string, files []string, out *os.File) error {
return nil
}
+// Usage sends the collect call for a container in the sandbox.
+func (s *Sandbox) Usage(cid string, Full bool) (control.MemoryUsage, error) {
+ log.Debugf("Usage sandbox %q", s.ID)
+ conn, err := s.sandboxConnect()
+ if err != nil {
+ return control.MemoryUsage{}, err
+ }
+ defer conn.Close()
+
+ var m control.MemoryUsage
+ err = conn.Call(boot.UsageCollect, &control.MemoryUsageOpts{
+ Full: Full,
+ }, &m)
+ return m, err
+}
+
+// UsageFD sends the usagefd call for a container in the sandbox.
+func (s *Sandbox) UsageFD(cid string) (*control.MemoryUsageRecord, error) {
+ log.Debugf("Usage sandbox %q", s.ID)
+ conn, err := s.sandboxConnect()
+ if err != nil {
+ return nil, err
+ }
+ defer conn.Close()
+
+ var m control.MemoryUsageFile
+ if err := conn.Call(boot.UsageUsageFD, &control.MemoryUsageFileOpts{
+ Version: 1,
+ }, &m); err != nil {
+ return nil, fmt.Errorf("UsageFD failed: %v", err)
+ }
+
+ if len(m.FilePayload.Files) != 2 {
+ return nil, fmt.Errorf("wants exactly two fds")
+ }
+
+ return control.NewMemoryUsageRecord(*m.FilePayload.Files[0], *m.FilePayload.Files[1])
+}
+
+// Reduce sends the reduce call for a container in the sandbox.
+func (s *Sandbox) Reduce(cid string, wait bool) error {
+ log.Debugf("Reduce sandbox %q", s.ID)
+ conn, err := s.sandboxConnect()
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ return conn.Call(boot.UsageReduce, &control.UsageReduceOpts{
+ Wait: wait,
+ }, nil)
+}
+
+// Stream sends the AttachDebugEmitter call for a container in the sandbox, and
+// dumps filtered events to out.
+func (s *Sandbox) Stream(cid string, filters []string, out *os.File) error {
+ log.Debugf("Stream sandbox %q", s.ID)
+ conn, err := s.sandboxConnect()
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ r, w, err := unet.SocketPair(false)
+ if err != nil {
+ return err
+ }
+
+ wfd, err := w.Release()
+ if err != nil {
+ return fmt.Errorf("failed to release write socket FD: %v", err)
+ }
+
+ if err := conn.Call(boot.EventsAttachDebugEmitter, &control.EventsOpts{
+ FilePayload: urpc.FilePayload{Files: []*os.File{
+ os.NewFile(uintptr(wfd), "event sink"),
+ }},
+ }, nil); err != nil {
+ return fmt.Errorf("AttachDebugEmitter failed: %v", err)
+ }
+
+ return eventchannel.ProcessAll(r, filters, out)
+}
+
// IsRunning returns true if the sandbox or gofer process is running.
func (s *Sandbox) IsRunning() bool {
if s.Pid != 0 {