OSX: fix unmounting drives that have APFS volumes

- If a drive is formatted APFS it will have a seperate disk
devices for physical drive (e.g. /dev/disk2) and volumes
(e.g. /dev/disk3).
Need to unmount all, or opening the device for
writing will subsequently fail.
(User will see an "Error running authopen" error in Imager
in that case).

- Also do not show APFS volumes seperately in the disk
selection dialog. List mount points under physical drive
instead.

Closes #501
This commit is contained in:
Floris Bos 2022-11-07 03:36:32 +01:00
parent 9d4665dbca
commit 30225187bd
5 changed files with 100 additions and 4 deletions

View file

@ -267,7 +267,8 @@ elseif(APPLE)
find_library(CoreFoundation CoreFoundation)
find_library(DiskArbitration DiskArbitration)
find_library(Security Security)
set(EXTRALIBS ${EXTRALIBS} ${CoreFoundation} ${DiskArbitration} ${Security} ${Cocoa})
find_library(IOKit IOKit)
set(EXTRALIBS ${EXTRALIBS} ${CoreFoundation} ${DiskArbitration} ${Security} ${Cocoa} ${IOKit})
set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE YES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist.in)
find_program(MACDEPLOYQT "macdeployqt" PATHS "${Qt5_DIR}/../../../bin")

View file

@ -26,7 +26,7 @@
if (self) {
_disks = [[NSMutableArray alloc] init];
[self populateDisksBlocking];
[_disks sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
[(NSMutableArray *)_disks sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}
return self;

View file

@ -21,6 +21,9 @@
#import "REDiskList.h"
#import <Cocoa/Cocoa.h>
#import <DiskArbitration/DiskArbitration.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/storage/IOMedia.h>
#import <IOKit/IOBSD.h>
namespace Drivelist {
bool IsDiskPartition(NSString *disk) {
@ -54,6 +57,55 @@ namespace Drivelist {
return (NSNumber*)CFDictionaryGetValue(dict, key);
}
std::string GetParentOfAPFS(const char *diskBsdName) {
/* Inspired by: https://opensource.apple.com/source/bless/bless-152/libbless/APFS/BLAPFSUtilities.c.auto.html
Simplified, assumes APFS only has a single physical drive */
std::string result;
kern_return_t kret;
io_iterator_t psIter;
CFTypeRef data;
NSString *s;
io_service_t parent, p = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, diskBsdName));
if (p)
{
/* Go three levels up in hierarchy */
for (int i=0; i<3; i++)
{
kret = IORegistryEntryGetParentEntry(p, kIOServicePlane, &parent);
IOObjectRelease(p);
if (kret)
{
/* Error. Return empty string */
return result;
}
p = parent;
}
IORegistryEntryGetParentIterator(p, kIOServicePlane, &psIter);
parent = IOIteratorNext(psIter);
if (parent)
{
if (IOObjectConformsTo(parent, kIOMediaClass))
{
data = IORegistryEntryCreateCFProperty(parent, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
if (data && CFGetTypeID(data) == CFStringGetTypeID())
{
s = (NSString *) data;
result = std::string([s UTF8String]);
CFRelease(data);
}
}
IOObjectRelease(parent);
}
IOObjectRelease(psIter);
IOObjectRelease(p);
}
return result;
}
DeviceDescriptor CreateDeviceDescriptorFromDiskDescription(std::string diskBsdName, CFDictionaryRef diskDescription) {
NSString *deviceProtocol = (NSString*)CFDictionaryGetValue(diskDescription, kDADiskDescriptionDeviceProtocolKey);
NSNumber *blockSize = DictionaryGetNumber(diskDescription, kDADiskDescriptionMediaBlockSizeKey);
@ -126,6 +178,13 @@ namespace Drivelist {
}
DeviceDescriptor device = CreateDeviceDescriptorFromDiskDescription(diskBsdNameStr, diskDescription);
if (device.description == "AppleAPFSMedia")
{
device.isVirtual = true;
device.parentDevice = GetParentOfAPFS(diskBsdNameStr.c_str());
}
deviceList.push_back(device);
CFRelease(diskDescription);
@ -158,6 +217,20 @@ namespace Drivelist {
std::string partitionBsdName = std::string(bsdnameChar);
std::string diskBsdName = partitionBsdName.substr(0, partitionBsdName.find("s", 5));
std::string childDevice;
/* Check if it concerns APFS volume first, and if so attribute mountpoints to parent device instead */
for(std::vector<int>::size_type i = 0; i != deviceList.size(); i++) {
DeviceDescriptor *dd = &deviceList[i];
if (dd->device == "/dev/" + diskBsdName) {
if (!dd->parentDevice.empty()) {
childDevice = diskBsdName;
diskBsdName = dd->parentDevice;
}
break;
}
}
for(std::vector<int>::size_type i = 0; i != deviceList.size(); i++) {
DeviceDescriptor *dd = &deviceList[i];
@ -165,6 +238,9 @@ namespace Drivelist {
if (dd->device == "/dev/" + diskBsdName) {
dd->mountpoints.push_back([[path path] UTF8String]);
dd->mountpointLabels.push_back([volumeName UTF8String]);
if (!childDevice.empty()) {
dd->childDevices.push_back("/dev/"+childDevice);
}
break;
}
}

View file

@ -38,11 +38,13 @@ struct DeviceDescriptor {
std::string raw;
std::string description;
std::string error;
std::string parentDevice;
uint64_t size;
uint32_t blockSize = 512;
uint32_t logicalBlockSize = 512;
std::vector<std::string> mountpoints;
std::vector<std::string> mountpointLabels;
std::vector<std::string> childDevices;
bool isReadOnly; // Device is read-only
bool isSystem; // Device is a system drive
bool isVirtual; // Device is a virtual storage device

View file

@ -111,12 +111,29 @@ QByteArray DownloadThread::_fileGetContentsTrimmed(const QString &filename)
bool DownloadThread::_openAndPrepareDevice()
{
emit preparationStatusUpdate(tr("opening drive"));
if (_filename.startsWith("/dev/"))
{
emit preparationStatusUpdate(tr("unmounting drive"));
#ifdef Q_OS_DARWIN
/* Also unmount any APFS volumes using this physical disk */
auto l = Drivelist::ListStorageDevices();
for (const auto &i : l)
{
if (QByteArray::fromStdString(i.device) == _filename)
{
for (const auto &j : i.childDevices)
{
qDebug() << "Unmounting APFS volume:" << j.c_str();
unmount_disk(j.c_str());
}
break;
}
}
#endif
qDebug() << "Unmounting:" << _filename;
unmount_disk(_filename.constData());
}
emit preparationStatusUpdate(tr("opening drive"));
_file.setFileName(_filename);