diff options
| author | Carl Hetherington <cth@carlh.net> | 2024-02-03 21:11:46 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2024-02-04 13:21:20 +0100 |
| commit | e0860839f664c6269261563d11be0495e756ff06 (patch) | |
| tree | 080257c5d9f1568c92542a4de8ee2ada983b9049 /src | |
| parent | b1231273ff0fbfd99e3d794f6d7ce5d9beed340b (diff) | |
Use a simpler way to decide what devices we could write to on macOS.
This basically involves the kDADiskDescriptionDeviceInternalKey,
kDADiskDescriptionMediaRemovableKey and kDADiskDescriptionMediaWritableKey
keys revealed by looking in
https://github.com/balena-io-modules/drivelist.git
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/cross.h | 16 | ||||
| -rw-r--r-- | src/lib/cross_common.cc | 97 | ||||
| -rw-r--r-- | src/lib/cross_osx.cc | 107 |
3 files changed, 33 insertions, 187 deletions
diff --git a/src/lib/cross.h b/src/lib/cross.h index 6904811b7..150199561 100644 --- a/src/lib/cross.h +++ b/src/lib/cross.h @@ -138,29 +138,19 @@ private: void disk_write_finished (); -struct OSXMediaPath -{ - 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 device; boost::optional<std::string> vendor; boost::optional<std::string> model; - OSXMediaPath media_path; - bool whole; std::vector<boost::filesystem::path> mount_points; unsigned long size; + bool system; + bool writeable; + bool partition; }; -boost::optional<OSXMediaPath> analyse_osx_media_path (std::string path); -std::vector<Drive> osx_disks_to_drives (std::vector<OSXDisk> disks); - - class ArgFixer { public: diff --git a/src/lib/cross_common.cc b/src/lib/cross_common.cc index b4d322096..b8f1d48f1 100644 --- a/src/lib/cross_common.cc +++ b/src/lib/cross_common.cc @@ -40,9 +40,6 @@ using std::vector; using boost::optional; -auto constexpr MEDIA_PATH_REQUIRED_MATCHES = 3; - - Drive::Drive (string xml) { cxml::Document doc; @@ -122,97 +119,3 @@ Drive::log_summary () const ); } - - -/* This is in _common so we can use it in unit tests */ -optional<OSXMediaPath> -analyse_osx_media_path (string path) -{ - 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"); - return {}; - } - - OSXMediaPath mp; - 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; - if (mp.parts.size() < MEDIA_PATH_REQUIRED_MATCHES) { - /* Later we expect at least MEDIA_PATH_REQUIRED_MATCHES 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 {}; - } - - return mp; -} - - -/* Take some 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) -{ - using namespace boost::algorithm; - - /* Mark disks containing mounted partitions as themselves mounted */ - for (auto& i: disks) { - if (!i.whole) { - continue; - } - for (auto& j: disks) { - if (&i != &j && !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)); - } - } - } - - /* Mark containers of mounted synths as themselves mounted */ - for (auto& i: disks) { - 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 MEDIA_PATH_REQUIRED_MATCHES parts - * of i anywhere in j we assume they are related and so i shares j's mount points. - */ - bool one_missing = false; - string all_parts; - DCPOMATIC_ASSERT (i.media_path.parts.size() >= MEDIA_PATH_REQUIRED_MATCHES); - for (auto k = 0; k < MEDIA_PATH_REQUIRED_MATCHES; ++k) { - if (find(j.media_path.parts.begin(), j.media_path.parts.end(), i.media_path.parts[k]) == j.media_path.parts.end()) { - one_missing = true; - } - all_parts += i.media_path.parts[k] + " "; - } - - if (!one_missing) { - LOG_DISK("Marking %1 as mounted because %2 is (found %3)", i.device, j.device, all_parts); - 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 && 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()); - } - } - - return drives; -} diff --git a/src/lib/cross_osx.cc b/src/lib/cross_osx.cc index 913b19103..20fe9bce8 100644 --- a/src/lib/cross_osx.cc +++ b/src/lib/cross_osx.cc @@ -240,44 +240,6 @@ get_model (CFDictionaryRef& description) } -static optional<OSXMediaPath> -analyse_media_path (CFDictionaryRef& description) -{ - using namespace boost::algorithm; - - void const* str = CFDictionaryGetValue (description, kDADiskDescriptionMediaPathKey); - if (!str) { - LOG_DISK_NC("There is no MediaPathKey (no dictionary value)"); - return {}; - } - - auto path_key_cstr = CFStringGetCStringPtr((CFStringRef) str, kCFStringEncodingUTF8); - if (!path_key_cstr) { - LOG_DISK_NC("There is no MediaPathKey (no cstring)"); - return {}; - } - - string path(path_key_cstr); - LOG_DISK("MediaPathKey is %1", path); - return analyse_osx_media_path (path); -} - - -static bool -is_whole_drive (DADiskRef& disk) -{ - io_service_t service = DADiskCopyIOMedia (disk); - CFTypeRef whole_media_ref = IORegistryEntryCreateCFProperty (service, CFSTR(kIOMediaWholeKey), kCFAllocatorDefault, 0); - bool whole_media = false; - if (whole_media_ref) { - whole_media = CFBooleanGetValue((CFBooleanRef) whole_media_ref); - CFRelease (whole_media_ref); - } - IOObjectRelease (service); - return whole_media; -} - - static optional<boost::filesystem::path> mount_point (CFDictionaryRef& description) { @@ -294,29 +256,17 @@ mount_point (CFDictionaryRef& description) } -/* Here follows some rather intricate and (probably) fragile code to find the list of available - * "real" drives on macOS that we might want to write a DCP to. - * - * We use the Disk Arbitration framework to give us a series of mount_points (/dev/disk0, /dev/disk1, - * /dev/disk1s1 and so on) and we use the API to gather useful information about these mount_points into - * a vector of Disk structs. - * - * Then we read the Disks that we found and try to derive a list of drives that we should offer to the - * user, with details of whether those drives are currently mounted or not. - * - * At the basic level we find the "disk"-level mount_points, looking at whether any of their partitions are mounted. - * - * This is complicated enormously by recent-ish macOS versions' habit of making `synthesized' volumes which - * reflect data in `real' partitions. So, for example, we might have a real (physical) drive /dev/disk2 with - * 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 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. - */ +static bool +get_bool(CFDictionaryRef& description, void const* key) +{ + auto value = CFDictionaryGetValue(description, key); + if (!value) { + return false; + } + + return CFBooleanGetValue(reinterpret_cast<CFBooleanRef>(value)); +} + static void disk_appeared (DADiskRef disk, void* context) @@ -339,32 +289,30 @@ disk_appeared (DADiskRef disk, void* context) this_disk.model = get_model (description); LOG_DISK("Vendor/model: %1 %2", this_disk.vendor.get_value_or("[none]"), this_disk.model.get_value_or("[none]")); - auto media_path = analyse_media_path (description); - if (!media_path) { - LOG_DISK("Finding media path for %1 failed", bsd_name); - return; - } - - this_disk.media_path = *media_path; - this_disk.whole = is_whole_drive (disk); auto mp = mount_point (description); if (mp) { this_disk.mount_points.push_back (*mp); } - LOG_DISK( - "%1 %2 mounted at %3", - this_disk.media_path.real ? "Real" : "Synth", - this_disk.whole ? "whole" : "part", - mp ? mp->string() : "[nowhere]" - ); - auto media_size_cstr = CFDictionaryGetValue (description, kDADiskDescriptionMediaSizeKey); if (!media_size_cstr) { LOG_DISK_NC("Could not read media size"); return; } + this_disk.system = get_bool(description, kDADiskDescriptionDeviceInternalKey) && !get_bool(description, kDADiskDescriptionMediaRemovableKey); + this_disk.writeable = get_bool(description, kDADiskDescriptionMediaWritableKey); + this_disk.partition = string(bsd_name).find("s", 5) != std::string::npos; + + LOG_DISK( + "%1 %2 %3 %4 mounted at %5", + bsd_name, + this_disk.system ? "system" : "non-system", + this_disk.writeable ? "writeable" : "read-only", + this_disk.partition ? "partition" : "drive", + mp ? mp->string() : "[nowhere]" + ); + CFNumberGetValue ((CFNumberRef) media_size_cstr, kCFNumberLongType, &this_disk.size); CFRelease (description); @@ -395,7 +343,12 @@ Drive::get () DAUnregisterCallback(session, (void *) disk_appeared, &disks); CFRelease(session); - auto drives = osx_disks_to_drives(disks); + vector<Drive> drives; + for (auto const& disk: disks) { + if (!disk.system && !disk.partition && disk.writeable) { + drives.push_back({disk.device, disk.mount_points, disk.size, disk.vendor, disk.model}); + } + } LOG_DISK("Drive::get() found %1 drives:", drives.size()); for (auto const& drive: drives) { |
