diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index 237dfa9b3b..54c285d45d 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -812,10 +812,12 @@ func (c *containerMounter) prepareMounts() ([]mountInfo, error) { hint: c.hints.FindMount(c.mounts[i].Source), } specutils.MaybeConvertToBindMount(info.mount) - if specutils.IsGoferMount(*info.mount) { + if specutils.IsGoferMount(*info.mount) || specutils.IsErofsMount(*info.mount) { info.goferMountConf = c.goferMountConfs[goferMntIdx] if info.goferMountConf.ShouldUseLisafs() { info.goferFD = c.goferFDs.removeAsFD() + } else if info.goferMountConf.ShouldUseErofs() { + info.goferFD = c.goferFDs.removeAsFD() } if info.goferMountConf.IsFilestorePresent() { info.filestoreFD = c.goferFilestoreFDs.removeAsFD() @@ -988,6 +990,18 @@ func getMountNameAndOptions(spec *specs.Spec, conf *config.Config, m *mountInfo, return "", nil, err } + case erofs.Name: + if m.goferFD == nil { + return "", nil, fmt.Errorf("EROFS mount requires an image file FD") + } + data = []string{fmt.Sprintf("ifd=%d", m.goferFD.Release())} + internalData = erofs.InternalFilesystemOptions{ + UniqueID: vfs.RestoreID{ + ContainerName: containerName, + Path: m.mount.Destination, + }, + } + default: log.Warningf("ignoring unknown filesystem type %q", m.mount.Type) return "", nil, nil diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go index 629f38ca5e..cedcb0973e 100644 --- a/runsc/cmd/gofer.go +++ b/runsc/cmd/gofer.go @@ -342,6 +342,11 @@ func (g *Gofer) serve(spec *specs.Spec, conf *config.Config, root string, ruid i mountIdx := 1 // first one is the root for _, m := range spec.Mounts { + // EROFS mounts are in goferMountConfs but gofer doesn't serve them + if specutils.IsErofsMount(m) { + mountIdx++ + continue + } if !specutils.IsGoferMount(m) { continue } @@ -528,6 +533,11 @@ func (g *Gofer) setupRootFS(spec *specs.Spec, conf *config.Config, goferToHostRP func (g *Gofer) setupMounts(conf *config.Config, mounts []specs.Mount, root, procPath string, goferToHostRPC *urpc.Client) (retErr error) { mountIdx := 1 // First index is for rootfs. for _, m := range mounts { + // EROFS mounts are in goferMountConfs but gofer doesn't set them up + if specutils.IsErofsMount(m) { + mountIdx++ + continue + } if !specutils.IsGoferMount(m) { continue } @@ -690,6 +700,12 @@ func (g *Gofer) resolveMounts(conf *config.Config, mounts []specs.Mount, root st mountIdx := 1 // First index is for rootfs. cleanMounts := make([]specs.Mount, 0, len(mounts)) for _, m := range mounts { + // EROFS mounts are in goferMountConfs but gofer doesn't resolve them + if specutils.IsErofsMount(m) { + cleanMounts = append(cleanMounts, m) + mountIdx++ + continue + } if !specutils.IsGoferMount(m) { cleanMounts = append(cleanMounts, m) continue diff --git a/runsc/container/container.go b/runsc/container/container.go index c53911d2e8..f41bba2ffb 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -911,6 +911,11 @@ func (c *Container) forEachSelfMount(fn func(mountSrc string)) { } goferMntIdx := 1 // First index is for rootfs. for i := range c.Spec.Mounts { + // EROFS mounts are in goferMountConfs but don't have self-backed filestores + if specutils.IsErofsMount(c.Spec.Mounts[i]) { + goferMntIdx++ + continue + } if !specutils.IsGoferMount(c.Spec.Mounts[i]) { continue } @@ -985,12 +990,17 @@ func (c *Container) initGoferConfs(ovlConf config.Overlay2, mountHints *boot.Pod // Handle bind mounts. for i := range c.Spec.Mounts { - if !specutils.IsGoferMount(c.Spec.Mounts[i]) { + if !specutils.IsGoferMount(c.Spec.Mounts[i]) && !specutils.IsErofsMount(c.Spec.Mounts[i]) { continue } + // Determine mount type: Bind for gofer mounts, erofs.Name for EROFS mounts + mountType := boot.Bind + if specutils.IsErofsMount(c.Spec.Mounts[i]) { + mountType = erofs.Name + } + overlayMedium := ovlConf.SubMountOverlayMedium() overlaySize := ovlConf.SubMountOverlaySize() - mountType = boot.Bind if specutils.IsReadonlyMount(c.Spec.Mounts[i].Options) { overlayMedium = config.NoOverlay } @@ -1037,6 +1047,11 @@ func (c *Container) createGoferFilestores(ovlConf config.Overlay2, mountHints *b // Then handle all the bind mounts. mountIdx := 1 // first one is the root for _, m := range c.Spec.Mounts { + // EROFS mounts are in goferMountConfs but don't need filestore processing + if specutils.IsErofsMount(m) { + mountIdx++ + continue + } if !specutils.IsGoferMount(m) { continue } @@ -1377,7 +1392,19 @@ func (c *Container) createGoferProcess(conf *config.Config, mountHints *boot.Pod } sandEnds := make([]*os.File, 0, ioFileCount) + // Track which spec.Mount corresponds to which goferMountConf + mountIdx := 0 for i, cfg := range c.GoferMountConfs { + // Align spec mount with gofer mount conf by skipping non-gofer/non-erofs mounts + // Skip alignment for root mount (i == 0) because it is not present in spec.Mounts + if i > 0 { + for mountIdx < len(c.Spec.Mounts) && + !specutils.IsGoferMount(c.Spec.Mounts[mountIdx]) && + !specutils.IsErofsMount(c.Spec.Mounts[mountIdx]) { + mountIdx++ + } + } + switch { case cfg.ShouldUseLisafs(): fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) @@ -1390,15 +1417,26 @@ func (c *Container) createGoferProcess(conf *config.Config, mountHints *boot.Pod donations.DonateAndClose("io-fds", goferEnd) case cfg.ShouldUseErofs(): - if i > 0 { - return nil, nil, nil, nil, fmt.Errorf("EROFS lower layer is only supported for root mount") + // Get the source for the EROFS image + var mountSrc string + if i == 0 { + // Root mount + mountSrc = rootfsHint.Mount.Source + } else { + // Non-root mount: use the aligned mountIdx + mountSrc = c.Spec.Mounts[mountIdx].Source } - f, err := os.Open(rootfsHint.Mount.Source) + f, err := os.Open(mountSrc) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("opening rootfs image %q: %v", rootfsHint.Mount.Source, err) + return nil, nil, nil, nil, fmt.Errorf("opening EROFS image %q: %v", mountSrc, err) } sandEnds = append(sandEnds, f) } + + // Move to next mount in spec (for non-root mounts) + if i > 0 { + mountIdx++ + } } var devSandEnd *os.File if shouldCreateDeviceGofer(c.Spec, conf) { diff --git a/runsc/specutils/BUILD b/runsc/specutils/BUILD index 9f0d3e52a6..fbe2ed1761 100644 --- a/runsc/specutils/BUILD +++ b/runsc/specutils/BUILD @@ -20,6 +20,7 @@ go_library( "//pkg/abi/linux", "//pkg/log", "//pkg/sentry/devices/nvproxy/nvconf", + "//pkg/sentry/fsimpl/erofs", "//pkg/sentry/kernel/auth", "//runsc/config", "//runsc/flag", diff --git a/runsc/specutils/specutils.go b/runsc/specutils/specutils.go index 499fd2ac12..a573e7c249 100644 --- a/runsc/specutils/specutils.go +++ b/runsc/specutils/specutils.go @@ -34,6 +34,7 @@ import ( "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/log" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/erofs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/flag" @@ -502,6 +503,11 @@ func IsGoferMount(m specs.Mount) bool { return m.Type == "bind" && m.Source != "" } +// IsErofsMount returns true if the given mount can be mounted as EROFS. +func IsErofsMount(m specs.Mount) bool { + return m.Type == erofs.Name +} + // MaybeConvertToBindMount converts mount type to "bind" in case any of the // mount options are either "bind" or "rbind" as required by the OCI spec. //