Add Fs controls

Add Fs controls and implement "cat" command.

PiperOrigin-RevId: 388812540
This commit is contained in:
Chong Cai
2021-08-04 16:44:11 -07:00
committed by gVisor bot
parent 681e541904
commit cbb99336ce
7 changed files with 204 additions and 12 deletions
+3
View File
@@ -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",
],
)
+93
View File
@@ -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
}
+6
View File
@@ -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))
+8
View File
@@ -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
}
+6
View File
@@ -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{
+70 -12
View File
@@ -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)
}
}
+18
View File
@@ -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 {