From fef37b67033c22603471f47e7b0d9c0fa6f76c98 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Mon, 21 Nov 2011 09:50:25 -0600
Subject: [PATCH 10/12] Add support for btrfs to the devicetree.

Adds support for scanning existing btrfs, including subvolumes.

Also adds an optional arg to getDeviceByPath to accommodate the fact
that btrfs volumes don't have a separate device node to represent the
top-level volume.

btrfs format.uuid is the member device's UUID (ID_FS_UUID_SUB),
while format.volUUID (ID_FS_UUID) is the top-level volume's UUID.
This prevents some further madness in which multiple devices would
have the same UUID.
---
 pyanaconda/storage/devices.py      |   15 ++++---
 pyanaconda/storage/devicetree.py   |   80 ++++++++++++++++++++++++++++++------
 pyanaconda/storage/formats/fs.py   |    2 +-
 pyanaconda/storage/partitioning.py |    4 +-
 4 files changed, 79 insertions(+), 22 deletions(-)

diff --git a/pyanaconda/storage/devices.py b/pyanaconda/storage/devices.py
index de78602..0e641f3 100644
--- a/pyanaconda/storage/devices.py
+++ b/pyanaconda/storage/devices.py
@@ -3941,18 +3941,21 @@ class BTRFSVolumeDevice(BTRFSDevice):
         self.subvolumes = []
 
         for parent in self.parents:
+            if parent.format.type != "btrfs":
+                raise ValueError("member device %s is not BTRFS" % parent.name)
+
             if parent.format.exists and self.exists and \
-               parent.format.uuid != self.uuid:
+               parent.format.volUUID != self.uuid:
                 raise ValueError("BTRFS member device %s UUID %s does not "
                                  "match volume UUID %s" % (parent.name,
-                                                           parent.format.uuid,
-                                                           self.uuid))
+                                 parent.format.volUUID, self.uuid))
 
         if self.parents and not self.format.type:
             label = getattr(self.parents[0].format, "label", None)
-            self.format = getFormat("btrfs", exists=self.exists,
+            self.format = getFormat("btrfs",
+                                    exists=self.exists,
                                     label=label,
-                                    uuid=self.uuid,
+                                    volUUID=self.uuid,
                                     device=self.path)
 
         label = getattr(self.format, "label", None)
@@ -3980,7 +3983,7 @@ class BTRFSVolumeDevice(BTRFSDevice):
         if device.format.type != "btrfs":
             raise ValueError("addDevice requires a btrfs device as sole arg")
 
-        if device.format.uuid != self.uuid:
+        if device.format.volUUID != self.uuid:
             raise ValueError("device UUID does not match the volume UUID")
 
         if device in self.parents:
diff --git a/pyanaconda/storage/devicetree.py b/pyanaconda/storage/devicetree.py
index 6731743..c3e5973 100644
--- a/pyanaconda/storage/devicetree.py
+++ b/pyanaconda/storage/devicetree.py
@@ -336,7 +336,7 @@ class DeviceTree(object):
             Raise ValueError if the device's identifier is already
             in the list.
         """
-        if newdev.path in [d.path for d in self._devices] and \
+        if newdev.uuid and newdev.uuid in [d.uuid for d in self._devices] and \
            not isinstance(newdev, NoDevice):
             raise ValueError("device is already in tree")
 
@@ -1564,6 +1564,42 @@ class DeviceTree(object):
                 #device.format.raidmem = block.getMemFromRaidSet(dm_array,
                 #        major=major, minor=minor, uuid=uuid, name=name)
 
+    def handleBTRFSFormat(self, info, device):
+        log_method_call(self, name=device.name)
+        name = udev_device_get_name(info)
+        sysfs_path = udev_device_get_sysfs_path(info)
+        uuid = udev_device_get_uuid(info)
+
+        btrfs_dev = None
+        for d in self.devices:
+            if isinstance(d, BTRFSVolumeDevice) and d.uuid == uuid:
+                btrfs_dev = d
+                break
+
+        if btrfs_dev:
+            log.info("found btrfs volume %s" % btrfs_dev.name)
+            btrfs_dev._addDevice(device)
+        else:
+            label = udev_device_get_label(info)
+            log.info("creating btrfs volume btrfs.%s" % label)
+            btrfs_dev = BTRFSVolumeDevice(label, parents=[device], uuid=uuid,
+                                          exists=True)
+            self._addDevice(btrfs_dev)
+
+        if not btrfs_dev.subvolumes:
+            for subvol_dict in btrfs_dev.listSubVolumes():
+                vol_id = subvol_dict["id"]
+                vol_path = subvol_dict["path"]
+                if vol_path in [sv.name for sv in btrfs_dev.subvolumes]:
+                    continue
+                fmt = getFormat("btrfs", device=btrfs_dev.path, exists=True,
+                                mountopts="subvol=%d" % vol_id)
+                subvol = BTRFSSubVolumeDevice(vol_path,
+                                              vol_id=vol_id,
+                                              format=fmt,
+                                              parents=[btrfs_dev])
+                self._addDevice(subvol)
+
     def handleUdevDeviceFormat(self, info, device):
         log_method_call(self, name=getattr(device, "name", None))
         name = udev_device_get_name(info)
@@ -1642,6 +1678,11 @@ class DeviceTree(object):
                 apple = formats.getFormat("appleboot")
                 if apple.minSize <= device.size <= apple.maxSize:
                     args[0] = "appleboot"
+        elif format_type == "btrfs":
+            # the format's uuid attr will contain the UUID_SUB, while the
+            # overarching volume UUID will be stored as volUUID
+            kwargs["uuid"] = info["ID_FS_UUID_SUB"]
+            kwargs["volUUID"] = uuid
 
         try:
             log.info("type detected on '%s' is '%s'" % (name, format_type,))
@@ -1671,6 +1712,8 @@ class DeviceTree(object):
             self.handleUdevLVMPVFormat(info, device)
         elif device.format.type == "multipath_member":
             self.handleMultipathMemberFormat(info, device)
+        elif device.format.type == "btrfs":
+            self.handleBTRFSFormat(info, device)
 
     def updateDeviceFormat(self, device):
         log.info("updating format of device: %s" % device)
@@ -2057,21 +2100,30 @@ class DeviceTree(object):
         log_method_return(self, found)
         return found
 
-    def getDeviceByPath(self, path):
+    def getDeviceByPath(self, path, preferLeaves=True):
         log_method_call(self, path=path)
         if not path:
             log_method_return(self, None)
             return None
 
-        found = None
+        leaf = None
+        other = None
         for device in self._devices:
-            if device.path == path:
-                found = device
-                break
-            elif (device.type == "lvmlv" or device.type == "lvmvg") and \
-                    device.path == path.replace("--","-"):
-                found = device
-                break
+            if (device.path == path or
+                ((device.type == "lvmlv" or device.type == "lvmvg") and
+                 device.path == path.replace("--","-"))):
+                if device.isleaf and not leaf:
+                    leaf = device
+                elif not other:
+                    other = device
+
+        if preferLeaves:
+            all_devs = [leaf, other]
+        else:
+            all_devs = [other, leaf]
+        all_devs = [d for d in all_devs if d]
+        if all_devs:
+            found = all_devs[0]
 
         log_method_return(self, found)
         return found
@@ -2088,9 +2140,9 @@ class DeviceTree(object):
         """ List of device instances """
         devices = []
         for device in self._devices:
-            if device.path in [d.path for d in devices] and \
+            if device.uuid and device.uuid in [d.uuid for d in devices] and \
                not isinstance(device, NoDevice):
-                raise DeviceTreeError("duplicate paths in device tree")
+                raise DeviceTreeError("duplicate uuids in device tree")
 
             devices.append(device)
 
@@ -2138,7 +2190,9 @@ class DeviceTree(object):
         """
         labels = {}
         for dev in self._devices:
-            if dev.format and getattr(dev.format, "label", None):
+            # don't include btrfs member devices
+            if getattr(dev.format, "label", None) and \
+               (dev.format.type != "btrfs" or isinstance(dev, BTRFSDevice)):
                 labels[dev.format.label] = dev
 
         return labels
diff --git a/pyanaconda/storage/formats/fs.py b/pyanaconda/storage/formats/fs.py
index 3c3229f..0942402 100644
--- a/pyanaconda/storage/formats/fs.py
+++ b/pyanaconda/storage/formats/fs.py
@@ -1140,8 +1140,8 @@ class BTRFS(FS):
     # partedSystem = fileSystemType["btrfs"]
 
     def __init__(self, *args, **kwargs):
-        self.uuidSub = kwargs.pop("uuidSub", None)
         super(BTRFS, self).__init__(*args, **kwargs)
+        self.volUUID = kwargs.pop("volUUID", None)
 
     def create(self, *args, **kwargs):
         # filesystem creation is done in storage.devicelibs.btrfs.create_volume
diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py
index c9a4652..f5d4b0f 100644
--- a/pyanaconda/storage/partitioning.py
+++ b/pyanaconda/storage/partitioning.py
@@ -261,8 +261,8 @@ def _scheduleVolumes(storage, devs):
 def scheduleShrinkActions(storage):
     """ Schedule actions to shrink partitions as per autopart selection. """
     for (path, size) in storage.shrinkPartitions.items():
-        device = storage.devicetree.getDeviceByPath(path)
-        if not device:
+        device = storage.devicetree.getDeviceByPath(path, preferLeaves=False)
+        if not device or not isinstance(device, PartitionDevice):
             raise StorageError("device %s scheduled for shrink disappeared"
                                 % path)
         storage.devicetree.registerAction(ActionResizeFormat(device, size))
-- 
1.7.3.4

