Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions storage/drivers/btrfs/btrfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (

"github.com/docker/go-units"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/sirupsen/logrus"
graphdriver "go.podman.io/storage/drivers"
"go.podman.io/storage/internal/tempdir"
"go.podman.io/storage/pkg/directory"
Expand Down Expand Up @@ -279,21 +278,6 @@ func subvolDelete(dirpath, name string, quotaEnabled bool) error {
return fmt.Errorf("recursively walking subvolumes for %s failed: %w", dirpath, err)
}

if quotaEnabled {
if qgroupid, err := subvolLookupQgroup(fullPath); err == nil {
var args C.struct_btrfs_ioctl_qgroup_create_args
args.qgroupid = C.__u64(qgroupid)

_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE,
uintptr(unsafe.Pointer(&args)))
if errno != 0 {
logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
}
} else {
logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
}
}

// all subvolumes have been removed
// now remove the one originally passed in
for i, c := range []byte(name) {
Expand Down
124 changes: 124 additions & 0 deletions storage/drivers/btrfs/btrfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
package btrfs

import (
"bytes"
"fmt"
"os"
"os/exec"
"path"
"strings"
"testing"

graphdriver "go.podman.io/storage/drivers"
Expand Down Expand Up @@ -77,3 +81,123 @@ func TestBtrfsListLayers(t *testing.T) {
func TestBtrfsTeardown(t *testing.T) {
graphtest.PutDriver(t)
}

// dependencies: btrfs-progs, libbtrfs-dev
// permission: root, loop device
func TestSubVolDelete(t *testing.T) {
if os.Getuid() != 0 {
t.Skip("test requires root")
}

// Helpers
runCmd := func(name string, arg ...string) error {
cmd := exec.Command(name, arg...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("command '%s %s' failed: %v, stderr: %s", name, strings.Join(arg, " "), err, stderr.String())
}
return nil
}
qgroupShow := func(mountPath string) string {
cmd := exec.Command("btrfs", "qgroup", "show", mountPath)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Run()
return out.String()
}

// parepare btrfs using loop device
baseDir, err := os.MkdirTemp("/mnt", "btrfs-test-")
if err != nil {
t.Fatalf("Failed to create base temp dir: %v", err)
}
defer os.RemoveAll(baseDir)

mnt := path.Join(baseDir, "mountpoint")
if err := os.Mkdir(mnt, 0o755); err != nil {
t.Fatalf("Failed to create mountpoint dir: %v", err)
}

blockFile := path.Join(baseDir, "btrfs.img")
if err := runCmd("dd", "if=/dev/zero", "of="+blockFile, "bs=1M", "count=120", "status=none"); err != nil {
t.Skipf("Failed to create block file: %v", err)
}

if err := runCmd("mkfs.btrfs", "-f", blockFile); err != nil {
t.Skipf("Failed to format to btrfs: %v", err)
}

findLoopCmd := exec.Command("losetup", "-f")
var loopDevBytes bytes.Buffer
findLoopCmd.Stdout = &loopDevBytes
if err := findLoopCmd.Run(); err != nil {
t.Skipf("Failed to find a loop device with 'losetup -f': %v", err)
}
loopDev := strings.TrimSpace(loopDevBytes.String())
if loopDev == "" {
t.Skip("No available loop devices found")
}

if err := runCmd("losetup", loopDev, blockFile); err != nil {
t.Skipf("Failed to setup loop device with 'losetup %s %s': %v", loopDev, blockFile, err)
}
defer runCmd("losetup", "-d", loopDev)

if err := runCmd("mount", loopDev, mnt); err != nil {
t.Skipf("Failed to mount loop device %s: %v", loopDev, err)
}
defer runCmd("umount", mnt)

d := &Driver{home: mnt}
if err := d.enableQuota(); err != nil {
t.Skipf("Failed to enable qgroup using API: %v", err)
}

t.Run("subVolDelete", func(t *testing.T) {
subvolName := "subvol1"
subvolPath := path.Join(mnt, subvolName)
if err := subvolCreate(mnt, subvolName); err != nil {
t.Fatalf("Failed to create subvolume using API: %v", err)
}

if err := d.subvolRescanQuota(); err != nil {
t.Fatalf("Failed to rescan quota using API: %v", err)
}

qtreeid, err := subvolLookupQgroup(subvolPath)
if err != nil {
t.Fatalf("Failed to lookup qgroup for subvolume using API: %v", err)
}
qgroupID := fmt.Sprintf("0/%d", qtreeid)
t.Logf("subvolume %s has qgroup ID %s", subvolPath, qgroupID)

if !strings.Contains(qgroupShow(mnt), qgroupID) {
t.Fatalf("qgroup %s was not created for subvolume %s", qgroupID, subvolPath)
}

if err := subvolDelete(mnt, subvolName, true); err != nil {
t.Fatalf("Failed to delete subvolume using API: %v", err)
}

if err := d.subvolRescanQuota(); err != nil {
t.Fatalf("Failed to rescan quota after delete using API: %v", err)
}

qgroupInfo := qgroupShow(mnt)
t.Logf("Current qgroup info:\n%s", qgroupInfo)

if strings.Contains(qgroupInfo, qgroupID) && !strings.Contains(qgroupInfo, "under deletion") {
t.Fatalf("qgroup %s was not marked as 'under deletion' for subvolume %s", qgroupID, subvolPath)
}

runCmd("btrfs", "qgroup", "clear-stale", mnt)

qgroupInfo = qgroupShow(mnt)
t.Logf("Current qgroup info after clearing stale:\n%s", qgroupInfo)

if strings.Contains(qgroupInfo, qgroupID) {
t.Fatalf("qgroup %s was not deleted for subvolume %s", qgroupID, subvolPath)
}
})
}