Various macOS fixes for drive finding.
authorCarl Hetherington <cth@carlh.net>
Sat, 4 Apr 2020 19:10:34 +0000 (21:10 +0200)
committerCarl Hetherington <cth@carlh.net>
Sat, 4 Apr 2020 21:29:09 +0000 (23:29 +0200)
src/lib/cross_osx.cc
src/tools/dcpomatic_disk_writer.cc

index cbc30924684d659d46357a80fff815ec1da3ab13..3ffad02c2770a83f5b0ae0800c037a930124bc72 100644 (file)
@@ -39,6 +39,7 @@ extern "C" {
 #include <IOKit/storage/IOMedia.h>
 #include <DiskArbitration/DADisk.h>
 #include <DiskArbitration/DiskArbitration.h>
+#include <CoreFoundation/CFURL.h>
 #include <sys/types.h>
 #include <ifaddrs.h>
 #include <netinet/in.h>
@@ -133,7 +134,6 @@ openssl_path ()
 boost::filesystem::path
 disk_writer_path ()
 {
-       return "/Users/carl/dcpomatic/src/dcpomatic/build/src/tools/dcpomatic2_disk_writer";
        boost::filesystem::path path = app_contents();
        path /= "MacOS";
        path /= "dcpomatic2_disk_writer";
@@ -241,42 +241,72 @@ running_32_on_64 ()
        return false;
 }
 
-static void
-disk_appeared (DADiskRef disk, void* context)
+static optional<string>
+get_vendor (CFDictionaryRef& description)
 {
-       const char* name = DADiskGetBSDName (disk);
-       if (!name) {
-               return;
+       void const* str = CFDictionaryGetValue (description, kDADiskDescriptionDeviceVendorKey);
+       if (!str) {
+               return optional<string>();
        }
 
-       vector<Drive>* drives = reinterpret_cast<vector<Drive>*> (context);
+       string s = CFStringGetCStringPtr ((CFStringRef) str, kCFStringEncodingUTF8);
+       boost::algorithm::trim (s);
+       return s;
+}
 
-       CFDictionaryRef description = DADiskCopyDescription (disk);
+static optional<string>
+get_model (CFDictionaryRef& description)
+{
+       void const* str = CFDictionaryGetValue (description, kDADiskDescriptionDeviceModelKey);
+       if (!str) {
+               return optional<string>();
+       }
 
-       optional<string> vendor;
-       void const* str = CFDictionaryGetValue (description, kDADiskDescriptionDeviceVendorKey);
-       if (str) {
-               string s = CFStringGetCStringPtr ((CFStringRef) str, kCFStringEncodingUTF8);
-               boost::algorithm::trim (s);
-               vendor = s;
+       string s = CFStringGetCStringPtr ((CFStringRef) str, kCFStringEncodingUTF8);
+       boost::algorithm::trim (s);
+       return s;
+}
+
+struct MediaPath
+{
+       bool real;       ///< true for a "real" disk, false for a synthesized APFS one
+       std::string prt; ///< "PRT" entry from the media path
+};
+
+static optional<MediaPath>
+analyse_media_path (CFDictionaryRef& description)
+{
+       using namespace boost::algorithm;
+
+       void const* str = CFDictionaryGetValue (description, kDADiskDescriptionMediaPathKey);
+       if (!str) {
+               return optional<MediaPath>();
        }
 
-       optional<string> model;
-       str = CFDictionaryGetValue (description, kDADiskDescriptionDeviceModelKey);
-       if (str) {
-               string s = CFStringGetCStringPtr ((CFStringRef) str, kCFStringEncodingUTF8);
-               boost::algorithm::trim (s);
-               model = s;
+       string path(CFStringGetCStringPtr((CFStringRef) str, kCFStringEncodingUTF8));
+       MediaPath mp;
+       if (starts_with(path, "IODeviceTree:")) {
+               mp.real = true;
+       } else if (starts_with(path, "IOService:")) {
+               mp.real = false;
+       } else {
+               return optional<MediaPath>();
        }
 
-       str = CFDictionaryGetValue (description, kDADiskDescriptionMediaPathKey);
-       if (str) {
-               char const* path = CFStringGetCStringPtr((CFStringRef) str, kCFStringEncodingUTF8);
-               if (strncmp(path, "IODeviceTree:", 13) != 0) {
-                       return;
+       vector<string> bits;
+       split(bits, path, boost::is_any_of("/"));
+       BOOST_FOREACH (string i, bits) {
+               if (starts_with(i, "PRT")) {
+                       mp.prt = i;
                }
        }
 
+       return mp;
+}
+
+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;
@@ -285,36 +315,123 @@ disk_appeared (DADiskRef disk, void* context)
                 CFRelease (whole_media_ref);
         }
        IOObjectRelease (service);
-       if (!whole_media) {
+       return whole_media;
+}
+
+static bool
+is_mounted (CFDictionaryRef& description)
+{
+       CFURLRef volume_path_key = (CFURLRef) CFDictionaryGetValue (description, kDADiskDescriptionVolumePathKey);
+       char mount_path_buffer[1024];
+       return CFURLGetFileSystemRepresentation(volume_path_key, false, (UInt8 *) mount_path_buffer, sizeof(mount_path_buffer));
+}
+
+struct Disk
+{
+       string device;
+       optional<string> vendor;
+       optional<string> model;
+       bool real;
+       string prt;
+       bool whole;
+       bool mounted;
+       unsigned long size;
+};
+
+static void
+disk_appeared (DADiskRef disk, void* context)
+{
+       const char* bsd_name = DADiskGetBSDName (disk);
+       if (!bsd_name) {
                return;
        }
+       LOG_DISK("%1 appeared", bsd_name);
 
-       unsigned long size;
-       CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (description, kDADiskDescriptionMediaSizeKey), kCFNumberLongType, &size);
+       Disk this_disk;
+
+       this_disk.device = string("/dev/") + bsd_name;
+
+       CFDictionaryRef description = DADiskCopyDescription (disk);
+
+       this_disk.vendor = get_vendor (description);
+       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]"));
 
+       optional<MediaPath> media_path = analyse_media_path (description);
+       if (!media_path) {
+               LOG_DISK("Finding media path for %1 failed", bsd_name);
+               return;
+       }
+
+       this_disk.real = media_path->real;
+       this_disk.prt = media_path->prt;
+       this_disk.whole = is_whole_drive (disk);
+       this_disk.mounted = is_mounted (description);
+       LOG_DISK("%1 prt %2 whole %3 mounted %4", this_disk.real ? "Real" : "Synth", this_disk.prt, this_disk.whole ? "whole" : "part", this_disk.mounted ? "mounted" : "unmounted");
+
+       CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (description, kDADiskDescriptionMediaSizeKey), kCFNumberLongType, &this_disk.size);
        CFRelease (description);
 
-       drives->push_back (Drive(name, size, false, vendor, model));
+       reinterpret_cast<vector<Disk>*>(context)->push_back(this_disk);
 }
 
 vector<Drive>
 get_drives ()
-{
-       vector<Drive> drives;
+{      
+       using namespace boost::algorithm;
+       vector<Disk> disks;
 
        DASessionRef session = DASessionCreate(kCFAllocatorDefault);
        if (!session) {
-               return drives;
+               return vector<Drive>();
        }
 
-       DARegisterDiskAppearedCallback (session, NULL, disk_appeared, &drives);
+       DARegisterDiskAppearedCallback (session, NULL, disk_appeared, &disks);
        CFRunLoopRef run_loop = CFRunLoopGetCurrent ();
        DASessionScheduleWithRunLoop (session, run_loop, kCFRunLoopDefaultMode);
        CFRunLoopStop (run_loop);
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.05, 0);
-       DAUnregisterCallback(session, (void *) disk_appeared, &drives);
+       DAUnregisterCallback(session, (void *) disk_appeared, &disks);
        CFRelease(session);
 
+       /* Mark disks containing mounted partitions as themselves mounted */
+       BOOST_FOREACH (Disk& i, disks) {
+               if (!i.whole) {
+                       continue;
+               }
+               BOOST_FOREACH (Disk& j, disks) {
+                       if (j.mounted && starts_with(j.device, i.device)) {
+                               LOG_DISK("Marking %1 as mounted because %2 is", i.device, j.device);
+                               i.mounted = true;
+                       }
+               }
+       }
+
+       /* Make a list of the PRT codes of mounted, synthesized disks */
+       vector<string> mounted_synths;
+       BOOST_FOREACH (Disk& i, disks) {
+               if (!i.real && i.mounted) {
+                       LOG_DISK("Found a mounted synth %1 with %2", i.device, i.prt);
+                       mounted_synths.push_back (i.prt);
+               }
+       }
+
+       /* Mark containers of those mounted synths as themselves mounted */
+       BOOST_FOREACH (Disk& i, disks) {
+               if (i.real && find(mounted_synths.begin(), mounted_synths.end(), i.prt) != mounted_synths.end()) {
+                       LOG_DISK("Marking %1 (%2) as mounted because it contains a mounted synth", i.device, i.prt);
+                       i.mounted = true;
+               }
+       }
+
+       vector<Drive> drives;
+       BOOST_FOREACH (Disk& i, disks) {
+               if (i.whole) {
+                       /* A whole disk that is not a container for a mounted synth */
+                       LOG_DISK("Adding drive: %1 %2 %3 %4 %5", i.device, i.size, i.mounted ? "mounted" : "unmounted", i.vendor.get_value_or("[none]"), i.model.get_value_or("[none]"));
+                       drives.push_back(Drive(i.device, i.size, i.mounted, i.vendor, i.model));
+               }
+       }
        return drives;
 }
 
index 08f06ff02a6894e67745da6a372705140245112f..8618f64a041fbbd1df731b15e21ea6a5679c1bd9 100644 (file)
@@ -290,7 +290,7 @@ try
        close(fd);
 #endif
 
-#ifdef DCPOMATIC_POSIX
+#ifdef DCPOMATIC_LINUX
        string partition = device;
        /* XXX: don't know if this logic is sensible */
        if (partition.size() > 0 && isdigit(partition[partition.length() - 1])) {
@@ -302,6 +302,12 @@ try
        bd = file_dev_get ();
 #endif
 
+#ifdef DCPOMATIC_OSX
+       string partition = device + "s1";
+       file_dev_name_set (partition.c_str());
+       bd = file_dev_get ();
+#endif
+
        if (!bd) {
                throw CopyError ("Failed to open partition", 0);
        }