diff --git a/lxd/storage/drivers/driver_btrfs_utils.go b/lxd/storage/drivers/driver_btrfs_utils.go index b79abbed0632..508c3e0c601d 100644 --- a/lxd/storage/drivers/driver_btrfs_utils.go +++ b/lxd/storage/drivers/driver_btrfs_utils.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "io" + "io/fs" "os" "os/exec" "path/filepath" @@ -107,39 +108,72 @@ func (d *btrfs) hasSubvolumes(path string) (bool, error) { } func (d *btrfs) getSubvolumes(path string) ([]string, error) { + // Make sure the path has a trailing slash. + if !strings.HasSuffix(path, "/") { + path = path + "/" + } + poolMountPath := GetPoolMountPath(d.name) if !strings.HasPrefix(path, poolMountPath+"/") { return nil, fmt.Errorf("%q is outside pool mount path %q", path, poolMountPath) } - path = strings.TrimPrefix(path, poolMountPath+"/") + var result []string - // Make sure the path has a trailing slash. - if !strings.HasSuffix(path, "/") { - path = path + "/" - } + if d.state.OS.RunningInUserNS { + // If using BTRFS in a nested container we cannot use "btrfs subvolume list" due to a permission error. + // So instead walk the directory tree testing each directory to see if it is subvolume. + err := filepath.Walk(path, func(fpath string, entry fs.FileInfo, err error) error { + if err != nil { + return err + } - var stdout bytes.Buffer - err := shared.RunCommandWithFds(d.state.ShutdownCtx, nil, &stdout, "btrfs", "subvolume", "list", poolMountPath) - if err != nil { - return nil, err - } + // Ignore the base path. + if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") { + return nil + } - result := []string{} + // Subvolumes can only be directories. + if !entry.IsDir() { + return nil + } - scanner := bufio.NewScanner(&stdout) - for scanner.Scan() { - fields := strings.Fields(scanner.Text()) + // Check if directory is a subvolume. + if d.isSubvolume(fpath) { + result = append(result, strings.TrimPrefix(fpath, path)) + } - if len(fields) != 9 { - continue + return nil + }) + if err != nil { + return nil, err + } + } else { + // If not running inside a nested container we can use "btrfs subvolume list" to get subvolumes which is more + // performant than walking the directory tree. + var stdout bytes.Buffer + err := shared.RunCommandWithFds(d.state.ShutdownCtx, nil, &stdout, "btrfs", "subvolume", "list", poolMountPath) + if err != nil { + return nil, err } - if !strings.HasPrefix(fields[8], path) { - continue + path = strings.TrimPrefix(path, poolMountPath+"/") + scanner := bufio.NewScanner(&stdout) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + + if len(fields) != 9 { + continue + } + + if !strings.HasPrefix(fields[8], path) { + continue + } + + result = append(result, strings.TrimPrefix(fields[8], path)) } - result = append(result, strings.TrimPrefix(fields[8], path)) + return result, nil } return result, nil