mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add Fs controls
Add Fs controls and implement "cat" command. PiperOrigin-RevId: 388812540
This commit is contained in:
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user