Subvolumes are nested. That is to say that removing subvolume foo will also remove subvolumes foo/bar and foo/bar/baz. --- blivet/devicelibs/btrfs.py | 12 +++++++++--- blivet/devices.py | 14 ++++++++++++-- blivet/devicetree.py | 17 ++++++++++++++++- 3 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/blivet/devicelibs/btrfs.py b/blivet/devicelibs/btrfs.py index 2f0cee4..bde165d 100644 --- a/blivet/devicelibs/btrfs.py +++ b/blivet/devicelibs/btrfs.py @@ -32,6 +32,9 @@ _ = lambda x: gettext.ldgettext("blivet", x) import logging log = logging.getLogger("blivet")
+# this is the volume id btrfs always assigns to the top-level volume/tree +MAIN_VOLUME_ID = 5 + def btrfs(args, capture=False): if capture: exec_func = util.capture_output @@ -98,15 +101,18 @@ def create_snapshot(source, dest): def scan_device(path): return btrfs(["device", "scan", path])
+_subvol_list_regex = r'ID (\d+) gen \d+ parent (\d+) top level \d+ path (.+)\n' + # get a list of subvolumes from a mounted btrfs filesystem def list_subvolumes(mountpoint): if not os.path.ismount(mountpoint): raise ValueError("volume not mounted")
- args = ["subvol", "list", mountpoint] + args = ["subvol", "list", "-p", mountpoint] buf = btrfs(args, capture=True) vols = [] - for group in re.findall(r'ID (\d+) gen \d+ top level \d+ path (.+)\n', buf): - vols.append({"id": int(group[0]), "path": group[1]}) + for group in re.findall(_subvol_list_regex, buf): + vols.append({"id": int(group[0]), "parent": int(group[1]), + "path": group[2]})
return vols diff --git a/blivet/devices.py b/blivet/devices.py index 54ece22..82f1872 100644 --- a/blivet/devices.py +++ b/blivet/devices.py @@ -4346,6 +4346,7 @@ class BTRFSDevice(StorageDevice):
class BTRFSVolumeDevice(BTRFSDevice): _type = "btrfs volume" + vol_id = btrfs.MAIN_VOLUME_ID
def __init__(self, *args, **kwargs): self.dataLevel = kwargs.pop("dataLevel", None) @@ -4373,7 +4374,7 @@ class BTRFSVolumeDevice(BTRFSDevice): label=label, volUUID=self.uuid, device=self.path, - mountopts="subvolid=0") + mountopts="subvolid=%d" % self.vol_id) self.originalFormat = copy.copy(self.format)
def _setFormat(self, format): @@ -4529,7 +4530,16 @@ class BTRFSSubVolumeDevice(BTRFSDevice):
@property def volume(self): - return self.parents[0] + parent = self.parents[0] + vol = None + while True: + if not isinstance(parent, BTRFSSubVolumeDevice): + vol = parent + break + + parent = parent.parents[0] + + return vol
def setupParents(self, orig=False): """ Run setup method of all parent devices. """ diff --git a/blivet/devicetree.py b/blivet/devicetree.py index bad135c..8ab1e5f 100644 --- a/blivet/devicetree.py +++ b/blivet/devicetree.py @@ -1590,14 +1590,29 @@ class DeviceTree(object): for subvol_dict in btrfs_dev.listSubVolumes(): vol_id = subvol_dict["id"] vol_path = subvol_dict["path"] + parent_id = subvol_dict["parent"] if vol_path in [sv.name for sv in btrfs_dev.subvolumes]: continue + + # look up the parent subvol + parent = None + subvols = [btrfs_dev] + btrfs_dev.subvolumes + for sv in subvols: + if sv.vol_id == parent_id: + parent = sv + break + + if parent is None: + log.error("failed to find parent (%d) for subvol %s" + % (parent_id, vol_path)) + raise DeviceTreeError("could not find parent for subvol") + fmt = getFormat("btrfs", device=btrfs_dev.path, exists=True, mountopts="subvol=%s" % vol_path) subvol = BTRFSSubVolumeDevice(vol_path, vol_id=vol_id, format=fmt, - parents=[btrfs_dev], + parents=[parent], exists=True) self._addDevice(subvol)