runtime: improve EROFS snapshotter support

To better support containerd 2.1 and later versions, remove the
hardcoded `layer.erofs` and instead parse `/proc/mounts` to obtain the
real mount source (and `/sys/block/loopX/loop/backing_file` if needed).

If the mount source doesn't end with `layer.erofs`, it should be marked
as unsupported, as it may be a filesystem meta file generated by later
containerd versions for the EROFS flattened filesystem feature.

Also check whether the filesystem type is `overlay` or not, since the
containerd mount manager [1] may change it after being introduced.

[1] https://github.com/containerd/containerd/issues/11303

Fixes: f63ec50ba3 ("runtime: Add EROFS snapshotter with block device support")
Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
This commit is contained in:
Gao Xiang
2025-06-19 13:14:11 +08:00
parent 9ff30c6aeb
commit 9079c8e598
5 changed files with 54 additions and 20 deletions

View File

@@ -821,12 +821,48 @@ func (c *Container) createMounts(ctx context.Context) error {
return c.createBlockDevices(ctx)
}
func findMountSource(mnt string) (string, error) {
output, err := os.ReadFile("/proc/mounts")
if err != nil {
return "", err
}
// /proc/mounts has 6 fields per line, one mount per line, e.g.
// /dev/loop0 /var/lib/containerd/io.containerd.snapshotter.v1.erofs/snapshots/1/fs erofs ro,relatime,user_xattr,acl,cache_strategy=readaround 0 0
for _, line := range strings.Split(string(output), "\n") {
parts := strings.Split(line, " ")
if len(parts) == 6 {
switch parts[2] {
case "erofs":
if parts[1] == mnt {
return parts[0], nil
}
}
}
}
return "", fmt.Errorf("erofs mount not found for %s", mnt)
}
func (c *Container) createErofsDevices() ([]config.DeviceInfo, error) {
var deviceInfos []config.DeviceInfo
if HasErofsOptions(c.rootFs.Options) {
parsedOptions := parseRootFsOptions(c.rootFs.Options)
for _, path := range parsedOptions {
di, err := c.createDeviceInfo(path+"/layer.erofs", path+"/layer.erofs", true, true)
if IsErofsRootFS(c.rootFs) {
lowerdirs := parseErofsRootFsOptions(c.rootFs.Options)
for _, path := range lowerdirs {
s, err := findMountSource(path)
if err != nil {
return nil, err
}
if strings.HasPrefix(s, "/dev/loop") {
b, err := os.ReadFile(fmt.Sprintf("/sys/block/loop%s/loop/backing_file", strings.TrimPrefix(s, "/dev/loop")))
if err != nil {
return nil, err
}
s = strings.TrimSuffix(string(b), "\n")
}
if filepath.Base(s) != "layer.erofs" {
return nil, fmt.Errorf("unsupported mount source %s for %s", s, path)
}
di, err := c.createDeviceInfo(s, s, true, true)
if err != nil {
return nil, err
}
@@ -1076,7 +1112,7 @@ func (c *Container) create(ctx context.Context) (err error) {
}
}()
if c.checkBlockDeviceSupport(ctx) && !IsNydusRootFSType(c.rootFs.Type) && !HasErofsOptions(c.rootFs.Options) {
if c.checkBlockDeviceSupport(ctx) && !IsNydusRootFSType(c.rootFs.Type) && !IsErofsRootFS(c.rootFs) {
// If the rootfs is backed by a block device, go ahead and hotplug it to the guest
if err = c.hotplugDrive(ctx); err != nil {
return

View File

@@ -626,7 +626,7 @@ func (f *FilesystemShare) ShareRootFilesystem(ctx context.Context, c *Container)
return f.shareRootFilesystemWithNydus(ctx, c)
}
if HasErofsOptions(c.rootFs.Options) {
if IsErofsRootFS(c.rootFs) {
return f.shareRootFilesystemWithErofs(ctx, c)
}

View File

@@ -2703,9 +2703,13 @@ func IsNydusRootFSType(s string) bool {
return strings.HasPrefix(path.Base(s), "nydus-overlayfs")
}
// HasErofsOptions checks if any of the options contain io.containerd.snapshotter.v1.erofs path
func HasErofsOptions(options []string) bool {
for _, opt := range options {
// IsErofsRootFS checks if any of the options contain io.containerd.snapshotter.v1.erofs path
func IsErofsRootFS(root RootFs) bool {
// TODO: support containerd mount manager: https://github.com/containerd/containerd/issues/11303
if root.Type != "overlay" {
return false
}
for _, opt := range root.Options {
if strings.Contains(opt, "io.containerd.snapshotter.v1.erofs") {
return true
}
@@ -2713,21 +2717,15 @@ func HasErofsOptions(options []string) bool {
return false
}
func parseRootFsOptions(options []string) []string {
func parseErofsRootFsOptions(options []string) []string {
lowerdirs := []string{}
for _, opt := range options {
if strings.HasPrefix(opt, "lowerdir=") {
lowerdirValue := strings.TrimPrefix(opt, "lowerdir=")
paths := strings.Split(lowerdirValue, ":")
for _, path := range paths {
path = strings.TrimSuffix(path, "/fs")
lowerdirs = append(lowerdirs, path)
}
lowerdirs = append(lowerdirs, strings.Split(lowerdirValue, ":")...)
}
}
return lowerdirs
}