diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go index 52bb09c25..b755f5c53 100644 --- a/runsc/boot/controller.go +++ b/runsc/boot/controller.go @@ -18,14 +18,19 @@ import ( "errors" "fmt" "os" + "path" gtime "time" specs "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/cleanup" + "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/control/server" "gvisor.dev/gvisor/pkg/fd" + "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/control" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/erofs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/seccheck" "gvisor.dev/gvisor/pkg/sentry/socket/netstack" @@ -95,6 +100,9 @@ const ( // ContMgrProcfsDump dumps sandbox procfs state. ContMgrProcfsDump = "containerManager.ProcfsDump" + + // ContMgrMount mounts a filesystem in a container. + ContMgrMount = "containerManager.Mount" ) const ( @@ -673,3 +681,95 @@ func (cm *containerManager) ProcfsDump(_ *struct{}, out *[]procfs.ProcessProcfsD } return nil } + +// MountArgs contains arguments to the Mount method. +type MountArgs struct { + // ContainerID is the container in which we will mount the filesystem. + ContainerID string + + // Source is the mount source. + Source string + + // Destination is the mount target. + Destination string + + // FsType is the filesystem type. + FsType string + + // FilePayload contains the source image FD, if required by the filesystem. + urpc.FilePayload +} + +const initTID kernel.ThreadID = 1 + +// Mount mounts a filesystem in a container. +func (cm *containerManager) Mount(args *MountArgs, _ *struct{}) error { + log.Debugf("containerManager.Mount, cid: %s, args: %+v", args.ContainerID, args) + + var cu cleanup.Cleanup + defer cu.Clean() + + eid := execID{cid: args.ContainerID} + ep, ok := cm.l.processes[eid] + if !ok { + return fmt.Errorf("container %v is deleted", args.ContainerID) + } + if ep.tg == nil { + return fmt.Errorf("container %v isn't started", args.ContainerID) + } + + t := ep.tg.PIDNamespace().TaskWithID(initTID) + if t == nil { + return fmt.Errorf("failed to find init process") + } + + source := args.Source + dest := path.Clean(args.Destination) + fstype := args.FsType + + if dest[0] != '/' { + return fmt.Errorf("absolute path must be provided for destination") + } + + var opts vfs.MountOptions + switch fstype { + case erofs.Name: + if len(args.FilePayload.Files) != 1 { + return fmt.Errorf("exactly one image file must be provided") + } + + imageFD, err := unix.Dup(int(args.FilePayload.Files[0].Fd())) + if err != nil { + return fmt.Errorf("failed to dup image FD: %v", err) + } + cu.Add(func() { unix.Close(imageFD) }) + + opts = vfs.MountOptions{ + ReadOnly: true, + GetFilesystemOptions: vfs.GetFilesystemOptions{ + Data: fmt.Sprintf("ifd=%d", imageFD), + }, + InternalMount: true, + } + + default: + return fmt.Errorf("unsupported filesystem type: %v", fstype) + } + + ctx := context.Background() + root := t.FSContext().RootDirectory() + defer root.DecRef(ctx) + + pop := vfs.PathOperation{ + Root: root, + Start: root, + Path: fspath.Parse(dest), + } + + if _, err := t.Kernel().VFS().MountAt(ctx, t.Credentials(), source, &pop, fstype, &opts); err != nil { + return err + } + log.Infof("Mounted %q to %q type: %s, internal-options: %q, in container %q", source, dest, fstype, opts.GetFilesystemOptions.Data, args.ContainerID) + cu.Release() + return nil +} diff --git a/runsc/cmd/debug.go b/runsc/cmd/debug.go index 82babd94f..3ccc4f68c 100644 --- a/runsc/cmd/debug.go +++ b/runsc/cmd/debug.go @@ -49,6 +49,7 @@ type Debug struct { delay time.Duration duration time.Duration ps bool + mount string } // Name implements subcommands.Command. @@ -82,6 +83,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.StringVar(&d.mount, "mount", "", "Mount a filesystem (-mount fstype:source:destination).") } // Execute implements subcommands.Command.Execute. @@ -224,6 +226,18 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomm } util.Infof("%s", o) } + if d.mount != "" { + opts := strings.Split(d.mount, ":") + if len(opts) != 3 { + util.Fatalf("Mount failed: invalid option: %v", d.mount) + } + fstype := opts[0] + src := opts[1] + dest := opts[2] + if err := c.Sandbox.Mount(c.ID, fstype, src, dest); err != nil { + util.Fatalf(err.Error()) + } + } // Open profiling files. var ( diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD index c1989d913..f0d034c22 100644 --- a/runsc/sandbox/BUILD +++ b/runsc/sandbox/BUILD @@ -28,6 +28,7 @@ go_library( "//pkg/metric:metric_go_proto", "//pkg/prometheus", "//pkg/sentry/control", + "//pkg/sentry/fsimpl/erofs", "//pkg/sentry/platform", "//pkg/sentry/seccheck", "//pkg/state/statefile", diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index fe90bc1a3..734ca3836 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -44,6 +44,7 @@ import ( metricpb "gvisor.dev/gvisor/pkg/metric/metric_go_proto" "gvisor.dev/gvisor/pkg/prometheus" "gvisor.dev/gvisor/pkg/sentry/control" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/erofs" "gvisor.dev/gvisor/pkg/sentry/platform" "gvisor.dev/gvisor/pkg/sentry/seccheck" "gvisor.dev/gvisor/pkg/state/statefile" @@ -1669,3 +1670,28 @@ func SetUserMappings(spec *specs.Spec, pid int) error { } return nil } + +// Mount mounts a filesystem in a container. +func (s *Sandbox) Mount(cid, fstype, src, dest string) error { + var files []*os.File + switch fstype { + case erofs.Name: + if imageFile, err := os.Open(src); err != nil { + return fmt.Errorf("opening %s: %v", src, err) + } else { + files = append(files, imageFile) + } + + default: + return fmt.Errorf("unsupported filesystem type: %v", fstype) + } + + args := boot.MountArgs{ + ContainerID: cid, + Source: src, + Destination: dest, + FsType: fstype, + FilePayload: urpc.FilePayload{Files: files}, + } + return s.call(boot.ContMgrMount, &args, nil) +}