summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChong Cai <chongc@google.com>2021-08-04 16:41:27 -0700
committergVisor bot <gvisor-bot@google.com>2021-08-04 16:44:11 -0700
commitcbb99336cee7d37f4050875a95946ca88b7ac690 (patch)
tree640779186c9c89f0fc47f0e6b442ca150b1d06c3
parent681e5419042ba6b00d50c82d64c0d556e28de0c7 (diff)
Add Fs controls
Add Fs controls and implement "cat" command. PiperOrigin-RevId: 388812540
-rw-r--r--pkg/sentry/control/BUILD3
-rw-r--r--pkg/sentry/control/fs.go93
-rw-r--r--runsc/boot/controller.go6
-rw-r--r--runsc/cmd/debug.go8
-rw-r--r--runsc/container/container.go6
-rw-r--r--runsc/container/container_test.go82
-rw-r--r--runsc/sandbox/sandbox.go18
7 files changed, 204 insertions, 12 deletions
diff --git a/pkg/sentry/control/BUILD b/pkg/sentry/control/BUILD
index 9fb8a054d..7ee237c9f 100644
--- a/pkg/sentry/control/BUILD
+++ b/pkg/sentry/control/BUILD
@@ -6,6 +6,7 @@ go_library(
name = "control",
srcs = [
"control.go",
+ "fs.go",
"lifecycle.go",
"logging.go",
"pprof.go",
@@ -17,6 +18,7 @@ go_library(
],
deps = [
"//pkg/abi/linux",
+ "//pkg/context",
"//pkg/fd",
"//pkg/log",
"//pkg/sentry/fdimport",
@@ -36,6 +38,7 @@ go_library(
"//pkg/sync",
"//pkg/tcpip/link/sniffer",
"//pkg/urpc",
+ "//pkg/usermem",
],
)
diff --git a/pkg/sentry/control/fs.go b/pkg/sentry/control/fs.go
new file mode 100644
index 000000000..d19b21f2d
--- /dev/null
+++ b/pkg/sentry/control/fs.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 control
+
+import (
+ "fmt"
+ "io"
+ "os"
+
+ "gvisor.dev/gvisor/pkg/context"
+ "gvisor.dev/gvisor/pkg/sentry/fs"
+ "gvisor.dev/gvisor/pkg/sentry/kernel"
+ "gvisor.dev/gvisor/pkg/urpc"
+ "gvisor.dev/gvisor/pkg/usermem"
+)
+
+// CatOpts contains options for the Cat RPC call.
+type CatOpts struct {
+ // Files are the filesystem paths for the files to cat.
+ Files []string `json:"files"`
+
+ // FilePayload contains the destination for output.
+ urpc.FilePayload
+}
+
+// Fs includes fs-related functions.
+type Fs struct {
+ Kernel *kernel.Kernel
+}
+
+// Cat is a RPC stub which prints out and returns the content of the files.
+func (f *Fs) Cat(o *CatOpts, _ *struct{}) error {
+ // Create an output stream.
+ if len(o.FilePayload.Files) != 1 {
+ return ErrInvalidFiles
+ }
+
+ output := o.FilePayload.Files[0]
+ for _, file := range o.Files {
+ if err := cat(f.Kernel, file, output); err != nil {
+ return fmt.Errorf("cannot read from file %s: %v", file, err)
+ }
+ }
+
+ return nil
+}
+
+// fileReader encapsulates a fs.File and provides an io.Reader interface.
+type fileReader struct {
+ ctx context.Context
+ file *fs.File
+}
+
+// Read implements io.Reader.Read.
+func (f *fileReader) Read(p []byte) (int, error) {
+ n, err := f.file.Readv(f.ctx, usermem.BytesIOSequence(p))
+ return int(n), err
+}
+
+func cat(k *kernel.Kernel, path string, output *os.File) error {
+ ctx := k.SupervisorContext()
+ mns := k.GlobalInit().Leader().MountNamespace()
+ root := mns.Root()
+ defer root.DecRef(ctx)
+
+ remainingTraversals := uint(fs.DefaultTraversalLimit)
+ d, err := mns.FindInode(ctx, root, nil, path, &remainingTraversals)
+ if err != nil {
+ return fmt.Errorf("cannot find file %s: %v", path, err)
+ }
+ defer d.DecRef(ctx)
+
+ file, err := d.Inode.GetFile(ctx, d, fs.FileFlags{Read: true})
+ if err != nil {
+ return fmt.Errorf("cannot get file for path %s: %v", path, err)
+ }
+ defer file.DecRef(ctx)
+
+ _, err = io.Copy(output, &fileReader{ctx: ctx, file: file})
+ return err
+}
diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go
index 548797788..60b532798 100644
--- a/runsc/boot/controller.go
+++ b/runsc/boot/controller.go
@@ -109,6 +109,11 @@ const (
LifecycleResume = "Lifecycle.Resume"
)
+// Filesystem related commands (see fs.go for more details).
+const (
+ FsCat = "Fs.Cat"
+)
+
// ControlSocketAddr generates an abstract unix socket name for the given ID.
func ControlSocketAddr(id string) string {
return fmt.Sprintf("\x00runsc-sandbox.%s", id)
@@ -151,6 +156,7 @@ func newController(fd int, l *Loader) (*controller, error) {
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))
diff --git a/runsc/cmd/debug.go b/runsc/cmd/debug.go
index da81cf048..f773ccca0 100644
--- a/runsc/cmd/debug.go
+++ b/runsc/cmd/debug.go
@@ -48,6 +48,7 @@ type Debug struct {
delay time.Duration
duration time.Duration
ps bool
+ cat stringSlice
}
// Name implements subcommands.Command.
@@ -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.
@@ -367,5 +369,11 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
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/container/container.go b/runsc/container/container.go
index 6a9a07afe..d1f979eb2 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -646,6 +646,12 @@ func (c *Container) Resume() error {
return c.saveLocked()
}
+// Cat prints out the content of the files.
+func (c *Container) Cat(files []string, out *os.File) error {
+ log.Debugf("Cat in container, cid: %s, files: %+v", c.ID, files)
+ return c.Sandbox.Cat(c.ID, files, 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 5fb4a3672..960c36946 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -442,6 +442,11 @@ func configs(t *testing.T, opts ...configOption) map[string]*config.Config {
return all
}
+// sleepSpec generates a spec with sleep 1000 and a conf.
+func sleepSpecConf(t *testing.T) (*specs.Spec, *config.Config) {
+ return testutil.NewSpecWithArgs("sleep", "1000"), testutil.TestConfig(t)
+}
+
// TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle.
// It verifies after each step that the container can be loaded from disk, and
// has the correct status.
@@ -455,7 +460,7 @@ func TestLifecycle(t *testing.T) {
t.Run(name, func(t *testing.T) {
// The container will just sleep for a long time. We will kill it before
// it finishes sleeping.
- spec := testutil.NewSpecWithArgs("sleep", "100")
+ spec, _ := sleepSpecConf(t)
rootDir, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
@@ -903,7 +908,7 @@ func TestExecProcList(t *testing.T) {
for name, conf := range configs(t, all...) {
t.Run(name, func(t *testing.T) {
const uid = 343
- spec := testutil.NewSpecWithArgs("sleep", "100")
+ spec, _ := sleepSpecConf(t)
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
@@ -1422,8 +1427,7 @@ func TestPauseResume(t *testing.T) {
// with calls to pause and resume and that pausing and resuming only
// occurs given the correct state.
func TestPauseResumeStatus(t *testing.T) {
- spec := testutil.NewSpecWithArgs("sleep", "20")
- conf := testutil.TestConfig(t)
+ spec, conf := sleepSpecConf(t)
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
@@ -1490,7 +1494,7 @@ func TestCapabilities(t *testing.T) {
for name, conf := range configs(t, all...) {
t.Run(name, func(t *testing.T) {
- spec := testutil.NewSpecWithArgs("sleep", "100")
+ spec, _ := sleepSpecConf(t)
rootDir, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
@@ -1640,7 +1644,7 @@ func TestMountNewDir(t *testing.T) {
func TestReadonlyRoot(t *testing.T) {
for name, conf := range configs(t, all...) {
t.Run(name, func(t *testing.T) {
- spec := testutil.NewSpecWithArgs("sleep", "100")
+ spec, _ := sleepSpecConf(t)
spec.Root.Readonly = true
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
@@ -1692,7 +1696,7 @@ func TestReadonlyMount(t *testing.T) {
if err != nil {
t.Fatalf("ioutil.TempDir() failed: %v", err)
}
- spec := testutil.NewSpecWithArgs("sleep", "100")
+ spec, _ := sleepSpecConf(t)
spec.Mounts = append(spec.Mounts, specs.Mount{
Destination: dir,
Source: dir,
@@ -1852,7 +1856,7 @@ func doAbbreviatedIDsTest(t *testing.T, vfs2 bool) {
"baz-" + testutil.RandomContainerID(),
}
for _, cid := range cids {
- spec := testutil.NewSpecWithArgs("sleep", "100")
+ spec, _ := sleepSpecConf(t)
bundleDir, cleanup, err := testutil.SetupBundleDir(spec)
if err != nil {
t.Fatalf("error setting up container: %v", err)
@@ -2229,7 +2233,7 @@ func TestMountPropagation(t *testing.T) {
t.Fatalf("mount(%q, MS_SHARED): %v", srcMnt, err)
}
- spec := testutil.NewSpecWithArgs("sleep", "1000")
+ spec, conf := sleepSpecConf(t)
priv := filepath.Join(tmpDir, "priv")
slave := filepath.Join(tmpDir, "slave")
@@ -2248,7 +2252,6 @@ func TestMountPropagation(t *testing.T) {
},
}
- conf := testutil.TestConfig(t)
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
@@ -2563,12 +2566,11 @@ func TestRlimits(t *testing.T) {
// TestRlimitsExec sets limit to number of open files and checks that the limit
// is propagated to exec'd processes.
func TestRlimitsExec(t *testing.T) {
- spec := testutil.NewSpecWithArgs("sleep", "100")
+ spec, conf := sleepSpecConf(t)
spec.Process.Rlimits = []specs.POSIXRlimit{
{Type: "RLIMIT_NOFILE", Hard: 1000, Soft: 100},
}
- conf := testutil.TestConfig(t)
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
@@ -2597,3 +2599,59 @@ func TestRlimitsExec(t *testing.T) {
t.Errorf("ulimit result, got: %q, want: %q", got, want)
}
}
+
+// TestCat creates a file and checks that cat generates the expected output.
+func TestCat(t *testing.T) {
+ f, err := ioutil.TempFile(testutil.TmpDir(), "test-case")
+ if err != nil {
+ t.Fatalf("ioutil.TempFile failed: %v", err)
+ }
+ defer os.RemoveAll(f.Name())
+
+ content := "test-cat"
+ if _, err := f.WriteString(content); err != nil {
+ t.Fatalf("f.WriteString(): %v", err)
+ }
+ f.Close()
+
+ 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)
+ }
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatalf("os.Create(): %v", err)
+ }
+
+ if err := cont.Cat([]string{f.Name()}, w); err != nil {
+ t.Fatalf("error cat from container: %v", err)
+ }
+
+ buf := make([]byte, 1024)
+ if _, err := r.Read(buf); err != nil {
+ t.Fatalf("Read out: %v", err)
+ }
+ if got, want := string(buf), content; !strings.Contains(got, want) {
+ t.Errorf("out got %s, want include %s", buf, want)
+ }
+}
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index 822da8c5e..b15572a98 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -1002,6 +1002,24 @@ func (s *Sandbox) Resume(cid string) error {
return nil
}
+// Cat sends the cat call for a container in the sandbox.
+func (s *Sandbox) Cat(cid string, files []string, out *os.File) error {
+ log.Debugf("Cat sandbox %q", s.ID)
+ conn, err := s.sandboxConnect()
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ if err := conn.Call(boot.FsCat, &control.CatOpts{
+ Files: files,
+ FilePayload: urpc.FilePayload{Files: []*os.File{out}},
+ }, nil); err != nil {
+ return fmt.Errorf("Cat container %q: %v", cid, err)
+ }
+ return nil
+}
+
// IsRunning returns true if the sandbox or gofer process is running.
func (s *Sandbox) IsRunning() bool {
if s.Pid != 0 {