Adjust how macOS drives are analysed and add a couple of tests.
authorCarl Hetherington <cth@carlh.net>
Mon, 17 Jan 2022 23:09:36 +0000 (00:09 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 19 Jan 2022 19:28:19 +0000 (20:28 +0100)
src/lib/cross.h
src/lib/cross_common.cc
src/lib/cross_osx.cc
test/disk_writer_test.cc

index cc902232cf70c7e175448986e0ba073cbdc10e4e..f1d0943fa33e75acb1f767b65fd49d867f475f9d 100644 (file)
@@ -142,19 +142,17 @@ void disk_write_finished ();
 
 struct OSXMediaPath
 {
-       bool real;       ///< true for a "real" disk, false for a synthesized APFS one
-       std::string prt; ///< "PRT" entry from the media path
+       bool real; ///< true for a "real" disk, false for a synthesized APFS one
+       std::vector<std::string> parts; ///< parts of the media path after the :
 };
 
 
-
 struct OSXDisk
 {
-       std::string mount_point;
+       std::string device;
        boost::optional<std::string> vendor;
        boost::optional<std::string> model;
-       bool real;
-       std::string prt;
+       OSXMediaPath media_path;
        bool whole;
        std::vector<boost::filesystem::path> mount_points;
        unsigned long size;
index 250db3cd5a5ded8b0f98a3eb7799b214e2931d9a..bc1a543678964a8daeaf8e9c69a708649bd54cfc 100644 (file)
@@ -124,8 +124,6 @@ Drive::log_summary () const
 optional<OSXMediaPath>
 analyse_osx_media_path (string path)
 {
-       using namespace boost::algorithm;
-
        if (path.find("/IOHDIXController") != string::npos) {
                /* This is a disk image, so we completely ignore it */
                LOG_DISK_NC("Ignoring this as it seems to be a disk image");
@@ -133,27 +131,32 @@ analyse_osx_media_path (string path)
        }
 
        OSXMediaPath mp;
-       if (starts_with(path, "IODeviceTree:")) {
+       vector<string> parts;
+       split(parts, path, boost::is_any_of("/"));
+       std::copy(parts.begin() + 1, parts.end(), back_inserter(mp.parts));
+
+       if (!parts.empty() && parts[0] == "IODeviceTree:") {
                mp.real = true;
-       } else if (starts_with(path, "IOService:")) {
+               if (mp.parts.size() < 2) {
+                       /* Later we expect at least 2 parts in a IODeviceTree */
+                       LOG_DISK_NC("Ignoring this as it has a strange media path");
+                       return {};
+               }
+       } else if (!parts.empty() && parts[0] == "IOService:") {
                mp.real = false;
        } else {
                return {};
        }
 
-       vector<string> bits;
-       split(bits, path, boost::is_any_of("/"));
-       for (auto i: bits) {
-               if (starts_with(i, "PRT")) {
-                       mp.prt = i;
-               }
-       }
-
        return mp;
 }
 
 
-/* This is in _common so we can use it in unit tests */
+/* Take soem OSXDisk objects, representing disks that `DARegisterDiskAppearedCallback` told us about,
+ * and find those drives that we could write a DCP to.  The drives returned are "real" (not synthesized)
+ * and are whole disks (not partitions).  They may be mounted, or contain mounted partitions, in which
+ * their mounted() method will return true.
+ */
 vector<Drive>
 osx_disks_to_drives (vector<OSXDisk> disks)
 {
@@ -165,38 +168,37 @@ osx_disks_to_drives (vector<OSXDisk> disks)
                        continue;
                }
                for (auto& j: disks) {
-                       if (!j.mount_points.empty() && starts_with(j.mount_point, i.mount_point)) {
-                               LOG_DISK("Marking %1 as mounted because %2 is", i.mount_point, j.mount_point);
+                       if (!j.mount_points.empty() && starts_with(j.device, i.device)) {
+                               LOG_DISK("Marking %1 as mounted because %2 is", i.device, j.device);
                                std::copy(j.mount_points.begin(), j.mount_points.end(), back_inserter(i.mount_points));
                        }
                }
        }
 
-       /* Make a map of the PRT codes and mount points of mounted, synthesized disks */
-       map<string, vector<boost::filesystem::path>> mounted_synths;
-       for (auto const& i: disks) {
-               if (!i.real && !i.mount_points.empty()) {
-                       LOG_DISK("Found a mounted synth %1 with %2", i.mount_point, i.prt);
-                       mounted_synths[i.prt] = i.mount_points;
-               }
-       }
-
-       /* Mark containers of those mounted synths as themselves mounted */
+       /* Mark containers of mounted synths as themselves mounted */
        for (auto& i: disks) {
-               if (i.real) {
-                       auto j = mounted_synths.find(i.prt);
-                       if (j != mounted_synths.end()) {
-                               LOG_DISK("Marking %1 (%2) as mounted because it contains a mounted synth", i.mount_point, i.prt);
-                               std::copy(j->second.begin(), j->second.end(), back_inserter(i.mount_points));
+               if (i.media_path.real) {
+                       for (auto& j: disks) {
+                               if (!j.media_path.real && !j.mount_points.empty()) {
+                                       /* i is real, j is a mounted synth; if we see the first two parts
+                                        * of i anywhere in j we assume they are related and so i shares
+                                        * j's mount points.
+                                        */
+                                       if (
+                                               find(j.media_path.parts.begin(), j.media_path.parts.end(), i.media_path.parts[0]) != j.media_path.parts.end() &&
+                                               find(j.media_path.parts.begin(), j.media_path.parts.end(), i.media_path.parts[1]) != j.media_path.parts.end()) {
+                                               LOG_DISK("Marking %1 as mounted because %2 is (found %3 and %4)", i.device, j.device, i.media_path.parts[0], i.media_path.parts[1]);
+                                               std::copy(j.mount_points.begin(), j.mount_points.end(), back_inserter(i.mount_points));
+                                       }
+                               }
                        }
                }
        }
 
        vector<Drive> drives;
        for (auto const& i: disks) {
-               if (i.whole) {
-                       /* A whole disk that is not a container for a mounted synth */
-                       drives.push_back(Drive(i.mount_point, i.mount_points, i.size, i.vendor, i.model));
+               if (i.whole && i.media_path.real) {
+                       drives.push_back(Drive(i.device, i.mount_points, i.size, i.vendor, i.model));
                        LOG_DISK_NC(drives.back().log_summary());
                }
        }
index 018516abbf0a337e3eb319715f995629a860da3b..2dc9b2702b006044f8d384e450f6877753ccc4c3 100644 (file)
@@ -380,8 +380,8 @@ mount_point (CFDictionaryRef& description)
  * a partition /dev/disk2s2 whose content is made into a synthesized /dev/disk3, itself containing some partitions
  * which are mounted.  /dev/disk2s2 is not considered to be mounted, in this case.  So we need to know that
  * disk2s2 is related to disk3 so we can consider disk2s2 as mounted if any parts of disk3 are.  In order to do
- * this I am picking out what looks like a suitable identifier prefixed with PRT from the MediaContentKey.
- * If disk2s2 and disk3 have the same PRT code I am assuming they are linked.
+ * this I am taking the first two parts of the IODeviceTree and seeing if they exist anywhere in a
+ * IOService identifier.  If they do, I am assuming the IOService device is on the matching IODeviceTree device.
  *
  * Lots of this is guesswork and may be broken.  In my defence the documentation that I have been able to
  * unearth is, to put it impolitely, crap.
@@ -399,8 +399,8 @@ disk_appeared (DADiskRef disk, void* context)
 
        OSXDisk this_disk;
 
-       this_disk.mount_point = string("/dev/") + bsd_name;
-       LOG_DISK("Mount point is %1", this_disk.mount_point);
+       this_disk.device = string("/dev/") + bsd_name;
+       LOG_DISK("Device is %1", this_disk.device);
 
        CFDictionaryRef description = DADiskCopyDescription (disk);
 
@@ -414,8 +414,7 @@ disk_appeared (DADiskRef disk, void* context)
                return;
        }
 
-       this_disk.real = media_path->real;
-       this_disk.prt = media_path->prt;
+       this_disk.media_path = *media_path;
        this_disk.whole = is_whole_drive (disk);
        auto mp = mount_point (description);
        if (mp) {
@@ -423,11 +422,10 @@ disk_appeared (DADiskRef disk, void* context)
        }
 
        LOG_DISK(
-               "%1 prt=%2 %3 %4",
-                this_disk.real ? "Real" : "Synth",
-                this_disk.prt,
+               "%1 %2 mounted at %3",
+                this_disk.media_path.real ? "Real" : "Synth",
                 this_disk.whole ? "whole" : "part",
-                mp ? ("mounted at " + mp->string()) : "unmounted"
+                mp ? mp->string() : "[nowhere]"
                );
 
        auto media_size_cstr = CFDictionaryGetValue (description, kDADiskDescriptionMediaSizeKey);
index 60d7fe22aeff1f576a7e0caf45323bb0bb923e7c..9e5f88b808c249ea624063844f5df92e27a0d0f4 100644 (file)
@@ -138,3 +138,81 @@ BOOST_AUTO_TEST_CASE (disk_writer_test1)
        check_file ("build/test/disk_writer_test1/foo", "build/test/disk_writer_test1_foo_back");
 }
 
+
+BOOST_AUTO_TEST_CASE (osx_drive_identification_test)
+{
+       vector<OSXDisk> disks;
+
+       auto disk = [&disks](string mount_point, string media_path, bool whole, std::vector<boost::filesystem::path> mount_points)
+       {
+               auto mp = analyse_osx_media_path (media_path);
+               if (mp) {
+                       disks.push_back({mount_point, {}, {}, *mp, whole, mount_points, 0});
+               }
+       };
+
+       auto find_unmounted = [](vector<OSXDisk> disks) {
+               auto drives = osx_disks_to_drives (disks);
+               vector<Drive> unmounted;
+               std::copy_if (drives.begin(), drives.end(), std::back_inserter(unmounted), [](Drive const& drive) { return !drive.mounted(); });
+               return unmounted;
+       };
+
+       disk("/dev/disk4s1", "IODeviceTree:/arm-io@10F00000/apcie@90000000/pci-bridge1@1/pcie-xhci@0/@7:1", false, {});
+       disk("/dev/disk4", "IODeviceTree:/arm-io@10F00000/apcie@90000000/pci-bridge1@1/pcie-xhci@0/@7:0", true, {});
+       disk("/dev/disk0", "IODeviceTree:/arm-io@10F00000/ans@77400000/iop-ans-nub/AppleANS3NVMeController/@1:0", true, {});
+       disk("/dev/disk0s1", "IODeviceTree:/arm-io@10F00000/ans@77400000/iop-ans-nub/AppleANS3NVMeController/@1:1", false, {});
+       disk("/dev/disk0s2", "IODeviceTree:/arm-io@10F00000/ans@77400000/iop-ans-nub/AppleANS3NVMeController/@1:2", false, {});
+       disk("/dev/disk0s3", "IODeviceTree:/arm-io@10F00000/ans@77400000/iop-ans-nub/AppleANS3NVMeController/@1:3", false, {});
+       disk("/dev/disk1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/ APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia", true, {});
+       disk("/dev/disk2", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/RecoveryOSContainer@3/AppleAPFSContainerScheme/AppleAPFSMedia", true, {});
+       disk("/dev/disk3", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia", false, {});
+       disk("/dev/disk1s1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/iSCPreboot@1", false, {});
+       disk("/dev/disk1s2", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/xART@2", false, {});
+       disk("/dev/disk1s3", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Hardware@3", false, {});
+       disk("/dev/disk1s4", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/iBootSystemContainer@1/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@4", false, {});
+       disk("/dev/disk2s1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/RecoveryOSContainer@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@1", false, {});
+       disk("/dev/disk2s2", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/RecoveryOSContainer@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Update@2", false, {});
+       disk("/dev/disk3s1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Macintosh HD@1", false, {});
+       disk("/dev/disk3s4", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Update@4", false, {"/System/Volumes/Update"});
+       disk("/dev/disk3s5", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Data@5", false, {"/System/Volumes/Data"});
+       disk("/dev/disk3s2", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Preboot@2", false, {"/System/Volumes/Preboot"});
+       disk("/dev/disk3s3", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@3", false, {});
+       disk("/dev/disk3s6", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/VM@6", false, {"/System/Volumes/VM"});
+       disk("/dev/disk3s1s1", "IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/ans@77400000/AppleASCWrapV4/iop-ans-nub/RTBuddyV2/RTBuddyService/AppleANS3NVMeController/NS_01@1/IOBlockStorageDriver/APPLE SSD AP0512Q Media/IOGUIDPartitionScheme/Container@2/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Macintosh HD@1/com.apple.os.update-EA882DCA7A28EBA0A6E94689836BB10D77D84D1AEE2468E17775A447AA815278@1", false, {"/"});
+
+       vector<Drive> writeable = find_unmounted (disks);
+       BOOST_REQUIRE_EQUAL (writeable.size(), 1U);
+       BOOST_CHECK_EQUAL (writeable[0].device(), "/dev/disk4");
+
+       disks.clear ();
+       disk("/dev/disk4s1", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@0/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media/IOGUIDPartitionScheme/disk image@1", false, {});
+       disk("/dev/disk4", "IOService:/IOResources/IOHDIXController/IOHDIXHDDriveOutKernel@0/IODiskImageBlockStorageDeviceOutKernel/IOBlockStorageDriver/Apple UDIF read-only compressed (zlib) Media", true, {});
+       disk("/dev/disk3s1", "IODeviceTree:/PCI0@0/XHC1@14/@2:1", false, {});
+       disk("/dev/disk3", "IODeviceTree:/PCI0@0/XHC1@14/@2:0", true, {});
+       disk("/dev/disk0", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:0", true, {});
+       disk("/dev/disk0s1", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:1", false, {});
+       disk("/dev/disk0s2", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:2", false, {"/Volumes/Macintosh HD"});
+       disk("/dev/disk0s3", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:3", false, {});
+       disk("/dev/disk0s4", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:4", false, {});
+       disk("/dev/disk0s5", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:5", false, {"/Volumes/High Sierra"});
+       disk("/dev/disk0s6", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:6", false, {});
+       disk("/dev/disk0s7", "IODeviceTree:/PCI0@0/SATA@1F,2/PRT1@1/PMP@0/@0:7", false, {"/Volumes/Recovery HD"});
+       disk("/dev/disk1", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia", true, {});
+       disk("/dev/disk", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia", true, {});
+       disk("/dev/disk1s1", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Untitled - Data@1", false, {"/Volumes/Untitled - Data"});
+       disk("/dev/disk1s2", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Preboot@2", false, {});
+       disk("/dev/disk1s3", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@3", false, {});
+       disk("/dev/disk1s4", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/VM@4", false, {});
+       disk("/dev/disk1s5", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 3@3/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Untitled@5", false, {"/Volumes/Untitled"});
+       disk("/dev/disk2s1", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Catalina - Data@1", false, {});
+       disk("/dev/disk2s2", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Preboot@2", false, {});
+       disk("/dev/disk2s3", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Recovery@3", false, {});
+       disk("/dev/disk2s4", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/VM@4", false, {"/private/var/vm"});
+       disk("/dev/disk2s5", "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleIntelPchSeriesAHCI/PRT1@1/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice/IOBlockStorageDriver/APPLE HDD ST500LM012 Media/IOGUIDPartitionScheme/Untitled 4@4/AppleAPFSContainerScheme/AppleAPFSMedia/AppleAPFSContainer/Catalina@5", false, {"/"});
+
+       writeable = find_unmounted (disks);
+       BOOST_REQUIRE_EQUAL (writeable.size(), 1U);
+       BOOST_CHECK_EQUAL (writeable[0].device(), "/dev/disk3");
+}
+