Repository: containerd/containerd
File: plugins/mount/erofs/plugin_linux_test.go (Go)

Code before:
	}
	loopDev := loopFile.Name()
	defer func() {
		loopFile.Close()
		mount.DetachLoopDevice(loopDev)
	}()

	mountPoint := filepath.Join(tempDir, "mnt")
	if err := os.MkdirAll(mountPoint, 0755); err != nil {
		t.Fatalf("failed to create mount point: %v", err)
	}

	m := mount.Mount{
		Type:    "erofs",
		Source:  loopDev,
		Options: []string{"ro"},
	}

	err = mount.Fsmount(m, mountPoint)
	if err != nil {
		t.Fatalf("fsmount with loop device failed: %v", err)
	}
	defer mount.Unmount(mountPoint, 0)

	content, err := os.ReadFile(filepath.Join(mountPoint, "test.txt"))
	if err != nil {
		t.Fatalf("failed to read test file: %v", err)
	}
	if string(content) != "hello" {
		t.Fatalf("unexpected content: %s", content)
	}
}

// TestMountOptionsPageSizeLimit tests that when mount options exceed the kernel's
// PAGE_SIZE limit (typically 4KB), the traditional mount() syscall fails but
// fsmount API succeeds.
func TestMountOptionsPageSizeLimit(t *testing.T) {
	testutil.RequiresRoot(t)

	if _, err := exec.LookPath("mkfs.erofs"); err != nil {
		t.Skipf("mkfs.erofs not found: %v", err)
	}

	if !snapshotserofs.FindErofs() {
		t.Skip("erofs kernel support not available")
	}

	if !mount.SupportsFsmount() {
		t.Skip("fsmount syscall not available (requires Linux 5.2+)")
	}


Review comment (bug):
The idea looks good.

Just sharing some context: there is still a limitation here

I tried using fsopen 2–3 years ago for OverlayFS mounting, but ran into a restriction with key=value parsing in the kernel (see: https://elixir.bootlin.com/linux/v6.12.6/source/fs/fsopen.c#L429). When setting a string option, both the key and the value must be less than 256 bytes. However, OverlayFS options like lowerdir=xx:yy:zz:... can easily exceed this limit.

Maybe I used it incorrectly, but it does look like there is a hard limit on the key/value length.

Code after:

	cmd := exec.Command("mkfs.erofs", layerPath, layerDir)
	if output, err := cmd.CombinedOutput(); err != nil {
		t.Fatalf("failed to create erofs image: %v, output: %s", err, output)
	}

	loopFile, err := mount.SetupLoop(layerPath, mount.LoopParams{
		Readonly:  true,
		Autoclear: true,
	})
	if err != nil {
		t.Fatalf("failed to setup loop device: %v", err)
	}
	loopDev := loopFile.Name()
	defer func() {
		loopFile.Close()
		mount.DetachLoopDevice(loopDev)
	}()

	// Build mount options that exceed PAGE_SIZE (4096 bytes)
	options := []string{"ro"}
	var optionsBuilder strings.Builder
	optionsBuilder.WriteString("ro")

	for i := 0; i < 100; i++ {
		fakeDevice := filepath.Join(tempDir, strings.Repeat("x", 50), "fake_device_"+strings.Repeat("0", 10))
		opt := "device=" + fakeDevice
		options = append(options, opt)
		optionsBuilder.WriteString(",")
		optionsBuilder.WriteString(opt)
	}

	optionsLen := optionsBuilder.Len()
	if optionsLen <= 4096 {
		t.Fatalf("Test setup error: options string (%d bytes) should exceed PAGE_SIZE (4096)", optionsLen)
	}

	mountPoint := filepath.Join(tempDir, "mnt")
	if err := os.MkdirAll(mountPoint, 0755); err != nil {
		t.Fatalf("failed to create mount point: %v", err)
	}

	m := mount.Mount{
		Type:    "erofs",
		Source:  loopDev,
		Options: options,
	}

	// Test 1: Traditional mount should fail due to PAGE_SIZE limit
	t.Run("traditional_mount_fails", func(t *testing.T) {
		err := m.Mount(mountPoint)