From 3b357da49691a1b030d986c0b0306293fab19136 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Mon, 13 Apr 2026 17:26:08 +0800 Subject: [PATCH] snapshotter/erofs: avoid using overlay if fsmerge is enabled and no upperdir If fsmerge is enabled and no write is needed, it can return an overlay mount with a single lowerdir, which is illegal for overlayfs. For example, it can cause the following Nerdctl error: : I'm not sure why ctr works, but the issue is real. ```bash $ nerdctl run --runtime io.containerd.kata.v2 --snapshotter=erofs -it --rm nginx:latest /bin/bash FATA[0000] failed to mount {Type:overlay Source:overlay Target: Options:[lowerdir=/run/containerd/ io.containerd.mount-manager.v1.bolt/t/7/1]} on "/tmp/initialC2039543827": mount source: "overlay", target: "/tmp/initialC2039543827", fstype: overlay, flags: 0, data: "lowerdir=/run/containerd/io. containerd.mount-manager.v1.bolt/t/7/1", err: invalid argument ``` Switch to using a bind mount instead. Fixes: 9a7500a97438 ("Add support for EROFS fsmerge feature") Signed-off-by: Gao Xiang --- plugins/snapshots/erofs/erofs.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/plugins/snapshots/erofs/erofs.go b/plugins/snapshots/erofs/erofs.go index a0f50d647d..e51959fcb4 100644 --- a/plugins/snapshots/erofs/erofs.go +++ b/plugins/snapshots/erofs/erofs.go @@ -389,7 +389,10 @@ func (s *snapshotter) mounts(snap storage.Snapshot, info snapshots.Info) ([]moun } } - var mounts []mount.Mount + var ( + mounts []mount.Mount + writable bool + ) if snap.Kind == snapshots.KindActive { if s.blockMode { mounts = append(mounts, mount.Mount{ @@ -416,6 +419,7 @@ func (s *snapshotter) mounts(snap storage.Snapshot, info snapshots.Info) ([]moun fmt.Sprintf("upperdir=%s", s.upperPath(snap.ID)), ) } + writable = true } else if len(snap.ParentIDs) == 1 { layerBlob, err := s.lowerPath(snap.ParentIDs[0]) if err != nil { @@ -452,11 +456,6 @@ func (s *snapshotter) mounts(snap storage.Snapshot, info snapshots.Info) ([]moun mounts = append(mounts, m) } - if (len(mounts) - first) == 1 { - options = append(options, fmt.Sprintf("lowerdir={{ mount %d }}", first)) - } else { - options = append(options, fmt.Sprintf("lowerdir={{ overlay %d %d }}", first, len(mounts)-1)) - } if s.remapIDs { if v, ok := info.Labels[snapshots.LabelSnapshotUIDMapping]; ok { @@ -467,6 +466,20 @@ func (s *snapshotter) mounts(snap storage.Snapshot, info snapshots.Info) ([]moun } } + if (len(mounts) - first) == 1 { + // End up with one single lowerdir (e.g. fsmerge on): + // it's unsupported by overlayfs + if !writable { + return append(mounts, mount.Mount{ + Type: "format/bind", + Source: fmt.Sprintf("{{ mount %d }}", first), + Options: append(options, "ro", "rbind"), + }), nil + } + options = append(options, fmt.Sprintf("lowerdir={{ mount %d }}", first)) + } else { + options = append(options, fmt.Sprintf("lowerdir={{ overlay %d %d }}", first, len(mounts)-1)) + } options = append(options, s.ovlOptions...) return append(mounts, mount.Mount{