diff --git a/lxd/storage/drivers/driver_btrfs_utils.go b/lxd/storage/drivers/driver_btrfs_utils.go index b79abbed0632..791b3e7f2641 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,67 @@ 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+"/") - - // Make sure the path has a trailing slash. - if !strings.HasSuffix(path, "/") { - path = path + "/" - } + var result []string + // Attempt to run `btrfs subvolume list`. This may fail inside a nested container, where the container does not have + // sufficient permission to run the command. var stdout bytes.Buffer err := shared.RunCommandWithFds(d.state.ShutdownCtx, nil, &stdout, "btrfs", "subvolume", "list", poolMountPath) - if err != nil { - return nil, err + if err == nil { + 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)) + } + + return result, nil } - result := []string{} + // Revert to walking the directory tree looking for subvolumes. + err = filepath.Walk(path, func(fpath string, entry fs.FileInfo, err error) error { + if err != nil { + return err + } - scanner := bufio.NewScanner(&stdout) - for scanner.Scan() { - fields := strings.Fields(scanner.Text()) + // Ignore the base path. + if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") { + return nil + } - if len(fields) != 9 { - continue + // Subvolumes can only be directories. + if !entry.IsDir() { + return nil } - if !strings.HasPrefix(fields[8], path) { - continue + // Check if directory is a subvolume. + if d.isSubvolume(fpath) { + result = append(result, strings.TrimPrefix(fpath, path)) } - result = append(result, strings.TrimPrefix(fields[8], path)) + return nil + }) + if err != nil { + return nil, err } return result, nil