From 413c5678d0a9ba572e804f6ac0b37b62d015d7f0 Mon Sep 17 00:00:00 2001 From: Omer Preminger Date: Tue, 6 Jun 2023 12:38:45 -0400 Subject: [PATCH] oci: support for writable extfs img overlay via fuse-overlayfs --- CHANGELOG.md | 11 ++++---- .../pkg/util/fs/overlay/overlay_item_linux.go | 28 ++++++++++++------- .../pkg/util/fs/overlay/overlay_set_linux.go | 20 ++++++++++--- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2573d5275b..04b9052b05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,11 +38,12 @@ `--fakeroot`, for example). - The `remote status` command will now print the username, realname, and email of the logged-in user, if available. -- OCI-mode now supports an `--overlay ` flag. `` can be a writable - directory, in which case changes to the filesystem will persist across runs of - the OCI container. Alternatively, `` can be `:ro` or the path of a - squashfs or extfs image, to be mounted as a read-only overlay. Multiple - overlays can be specified, but all but one must be read-only. +- OCI-mode now supports the `--overlay ` flag. `` can be the path to a + writable directory or writable extfs image, in which case changes to the + filesystem will persist across runs of the OCI container. Alternatively, + `--overlay :ro` can be used, where `` is the path to a directory, to + a squashfs image, or to an extfs image, to be mounted as a read-only overlay. + Multiple overlays can be specified, but all but one must be read-only. - The `tap` CNI plugin, new to github.com/containernetworking/plugins v1.3.0, is now provided. - OCI-mode now supports the `--workdir ` option. If this option is diff --git a/internal/pkg/util/fs/overlay/overlay_item_linux.go b/internal/pkg/util/fs/overlay/overlay_item_linux.go index 128035ff7d..61b8a437fe 100644 --- a/internal/pkg/util/fs/overlay/overlay_item_linux.go +++ b/internal/pkg/util/fs/overlay/overlay_item_linux.go @@ -128,25 +128,31 @@ func (i *Item) GetParentDir() (string, error) { // this method does not mount the assembled overlay itself. That happens in // Set.Mount(). func (i *Item) Mount() error { - if i.Writable { - if err := i.prepareWritableOverlay(); err != nil { - return err - } - } - + var err error switch i.Type { case image.SANDBOX: - return i.mountDir() + err = i.mountDir() case image.SQUASHFS: - return i.mountWithFuse("squashfuse") + err = i.mountWithFuse("squashfuse") case image.EXT3: if i.Writable { - return fmt.Errorf("mounting writable extfs images is not currently supported, please use :ro suffix on image specification for read-only mode") + err = i.mountWithFuse("fuse2fs", "-o", "rw") + } else { + err = i.mountWithFuse("fuse2fs", "-o", "ro") } - return i.mountWithFuse("fuse2fs", "-o", "ro") default: return fmt.Errorf("internal error: unrecognized image type in overlay.Item.Mount() (type: %v)", i.Type) } + + if err != nil { + return err + } + + if i.Writable { + return i.prepareWritableOverlay() + } + + return nil } // mountDir mounts directory-based Items. This involves bind-mounting followed @@ -277,6 +283,8 @@ func (i *Item) prepareWritableOverlay() error { switch i.Type { case image.SANDBOX: i.StagingDir = i.SourcePath + fallthrough + case image.EXT3: if err := EnsureOverlayDir(i.StagingDir, true, 0o755); err != nil { return err } diff --git a/internal/pkg/util/fs/overlay/overlay_set_linux.go b/internal/pkg/util/fs/overlay/overlay_set_linux.go index 7804f7df1d..2a6c4a73d6 100644 --- a/internal/pkg/util/fs/overlay/overlay_set_linux.go +++ b/internal/pkg/util/fs/overlay/overlay_set_linux.go @@ -14,6 +14,7 @@ import ( "github.com/samber/lo" "github.com/sylabs/singularity/internal/pkg/util/bin" + "github.com/sylabs/singularity/pkg/image" "github.com/sylabs/singularity/pkg/sylog" ) @@ -66,7 +67,8 @@ func (s Set) Unmount(rootFsDir string) error { return fmt.Errorf("while checking for unprivileged overlay support in kernel: %w", err) } - if unprivOls { + useKernelMount := unprivOls && !s.hasWritableExtfsImg() + if useKernelMount { err = DetachMount(rootFsDir) } else { err = UnmountWithFuse(rootFsDir) @@ -108,7 +110,9 @@ func (s Set) performFinalMount(rootFsDir string) error { return fmt.Errorf("while checking for unprivileged overlay support in kernel: %w", err) } - if unprivOls { + useKernelMount := unprivOls && !s.hasWritableExtfsImg() + + if useKernelMount { sylog.Debugf("Mounting overlay (via syscall) with rootFsDir %q, options: %q", rootFsDir, options) if err := syscall.Mount("overlay", rootFsDir, "overlay", syscall.MS_NODEV, options); err != nil { return fmt.Errorf("failed to mount %s: %w", rootFsDir, err) @@ -116,14 +120,14 @@ func (s Set) performFinalMount(rootFsDir string) error { } else { fuseOlFsCmd, err := bin.FindBin("fuse-overlayfs") if err != nil { - return fmt.Errorf("kernel does not support unprivileged overlays, and fuse-overlayfs not available: %w", err) + return fmt.Errorf("'fuse-overlayfs' must be used for this overlay specification, but is not available: %w", err) } // Even though fusermount is not needed for this step, we shouldn't perform // the mount unless we have the necessary tools to eventually unmount it _, err = bin.FindBin("fusermount") if err != nil { - return fmt.Errorf("kernel does not support unprivileged overlays, and using fuse-overlayfs fallback requires fusermount to be installed: %w", err) + return fmt.Errorf("'fuse-overlayfs' must be used for this overlay specification, and this also requires 'fusermount' to be installed: %w", err) } sylog.Debugf("Mounting overlay (via fuse-overlayfs) with rootFsDir %q, options: %q", rootFsDir, options) @@ -156,6 +160,14 @@ func (s Set) options(rootFsDir string) string { lowerDirJoined, s.WritableOverlay.Upper(), s.WritableOverlay.Work()) } +func (s Set) hasWritableExtfsImg() bool { + if (s.WritableOverlay != nil) && (s.WritableOverlay.Type == image.EXT3) { + return true + } + + return false +} + // detachIndividualMounts detaches the bind mounts & remounts created by // performIndividualMounts, above. func (s Set) detachIndividualMounts() error {