I have a task to implement disk formatting functionality in my code.
I am against the use of command line wrappers (e.g. diskutil
), as they are slow and unreliable.
I'm importing this private framework: /System/Library/PrivateFrameworks/DiskManagement.framework
And the following headers: DMManager.h, DMEraseDisk.h, DMFilesystem.h (GitHub Repo)
I have almost everything ready, but there is one problem that I can not overcome:
Calling the eraseDisk
method in DMEraseDisk
freezes the application.
At the same time, the disk is formatted successfully, I just need to mount it manually.
#import <Foundation/Foundation.h>
#import <DiskArbitration/DiskArbitration.h>
#import "DiskManagement/DMManager.h"
#import "DiskManagement/DMEraseDisk.h"
#import "DiskManagement/DMFilesystem.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
/* From the public DiskArbitration.h */
DASessionRef diskSession = DASessionCreate(nil);
DADiskRef currentDisk = DADiskCreateFromBSDName(NULL, diskSession, "disk9s1");
/* From DiskManagement.framework private headers (DMManager.h, DMEraseDisk.h, DMFilesystem.h) */
DMManager *dmManager = [DMManager sharedManager];
DMEraseDisk *diskEraser = [[DMEraseDisk alloc] initWithManager:dmManager];
/* Getting available file systems for a given device */
NSArray *availableFilesystems = [DMEraseDisk eraseTypesForDisk:currentDisk];
printf("Available File Systems for this device:\n");
for (DMFilesystem *availableFilesystem in availableFilesystems) {
printf("[Type:] %s\n", [[availableFilesystem filesystemType] UTF8String]);
printf("[Personality:] %s\n", [[availableFilesystem filesystemPersonality] UTF8String]);
printf("---\n");
}
/* (Type: msdos, Personality: MS-DOS FAT32) */
DMFilesystem *selectedFilesystem = [availableFilesystems objectAtIndex:2];
/*
(Formatting this device to MS-DOS FAT32)
Formats successfully, but stops here
and the code after this function is not executed further
*/
[diskEraser
eraseDisk: currentDisk
synchronous: YES // Won't work if set to NO (even with CFRunLoopRun())
filesystem: selectedFilesystem
bootable: YES
name: @"RESOPHIE"
doNewfs: YES
doBooterCleanup: NO
];
printf("I will never show up :(\n");
}
return 0;
}
How can I make the code continue to execute after calling eraseDisk
method?
I returned to solve the problem a year later.
I migrated to Swift for personal reasons. But the logic will be the same for Objective-C, if someone actually needs this.
The steps I have taken to solve the problem:
DiskManagement.framework
binary from Mac OS X Mavericks.eraseDisk:currentDisk:synchronous:filesystem:bootable:name:doNewfs:
method logic on DMManager
object.
- (bool)checkClientDelegate;
(if synchronous == NO
).SwiftDiskManager
.diskManager.setDelegate(self)
and diskManager.setClientDelegate(self)
in order to send all messages to the SwiftDiskManager
object.func responds(to aSelector: Selector!) -> Bool
method in order to track which methods DMManager
is trying to access and then implemented them.- (bool)checkClientDelegate;
method.SwiftDiskManager.swift
import Foundation
extension String: Error {}
class SwiftDiskManager {
typealias Callback = (CallbackData) -> Void
// Because I don't know what type to use ¯\_(ツ)_/¯. NSErrorPointer doesn't work as expected, so lets just ignore it.
typealias SuppressedDataType = AutoreleasingUnsafeMutablePointer<NSObject?>?
struct CallbackData {
let bsdName: String
let operationName: String
let isError: Bool
init(bsdName: String, operationName: String, isError: Bool = false) {
self.bsdName = bsdName
self.operationName = operationName.trimmingCharacters(in: .whitespacesAndNewlines);
self.isError = isError
}
}
private let bsdName: String
private let sessionDisk: DASession
private let currentDisk: DADisk
private let diskManager: DMManager
private let diskErase: DMEraseDisk
private let runLoop: CFRunLoop
private var callback: Callback?
static func frameworkLinkFailureString(className: String) -> String {
return "Can't initialize '\(className)' due to DiskManagement.framework private framework linking failure."
}
init(bsdName: String) throws {
self.bsdName = bsdName
runLoop = CFRunLoopGetCurrent()
if (!DMManager.responds(to: NSSelectorFromString("init"))) {
throw SwiftDiskManager.frameworkLinkFailureString(className: DMManager.className())
}
diskManager = .init()
if (!DMEraseDisk.responds(to: NSSelectorFromString("init"))) {
throw SwiftDiskManager.frameworkLinkFailureString(className: DMEraseDisk.className())
}
diskErase = .init(manager: diskManager)
guard let _sessionDisk = DASessionCreate(kCFAllocatorDefault) else {
throw "Can't create DASession."
}
sessionDisk = _sessionDisk
guard let _currentDisk = DADiskCreateFromBSDName(kCFAllocatorDefault, sessionDisk, bsdName) else {
throw "Can't create DADisk with '\(bsdName)' BSD name."
}
currentDisk = _currentDisk
// Checking if BSD device is accessible
guard let _ = DADiskCopyDescription(currentDisk) else {
throw "Can't get DADisk description."
}
diskManager.setDelegate(self)
diskManager.setClientDelegate(self)
}
@objc private func dmAsyncStartedForDisk(_ disk: DADisk) {
callback?(CallbackData(bsdName: bsdName, operationName: "Operation started"))
}
@objc private func dmAsyncProgressForDisk(_ disk: DADisk, barberPole: AnyObject, percent: String) {
callback?(CallbackData(bsdName: bsdName, operationName: "In progress"))
}
@objc private func dmAsyncMessageForDisk(_ disk: DADisk, string: String, dictionary: NSDictionary) {
callback?(CallbackData(bsdName: bsdName, operationName: string))
}
@objc private func dmAsyncFinishedForDisk(_ disk: DADisk, mainError: SuppressedDataType, detailError: SuppressedDataType, dictionary: NSDictionary) {
let errorEncountered: Bool = (mainError != nil) || (detailError != nil)
callback?(CallbackData(bsdName: bsdName, operationName: "Operation finished", isError: errorEncountered))
CFRunLoopStop(runLoop)
}
func eraseDisk(name: String, bootable: Bool, filesystem: DMFilesystem, callback: @escaping Callback) {
self.callback = callback
diskErase.eraseDisk(
currentDisk,
synchronous: false,
filesystem: filesystem,
bootable: bootable,
name: name,
doNewfs: true
)
CFRunLoopRun()
self.callback = nil
}
}
main.swift
import Foundation
let filesystem: DMFilesystem = DMFilesystem.filesystem(forPersonality: "HFS+") as! DMFilesystem
do {
let diskManager: SwiftDiskManager = try .init(bsdName: "disk4s1")
diskManager.eraseDisk(name: "HELLO_WRLD", bootable: true, filesystem: filesystem) { callbackData in
print(callbackData)
}
} catch {
print("[Error: \(error)]")
}
LanguageBridgingHeader.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "DMManager.h"
#import "DMEraseDisk.h"
#import "DMFilesystem.h"
Console Output
CallbackData(bsdName: "disk4s1", operationName: "Operation started", isError: false)
CallbackData(bsdName: "disk4s1", operationName: "Unmounting disk", isError: false)
CallbackData(bsdName: "disk4s1", operationName: "In progress", isError: false)
CallbackData(bsdName: "disk4s1", operationName: "Erasing", isError: false)
CallbackData(bsdName: "disk4s1", operationName: "In progress", isError: false)
CallbackData(bsdName: "disk4s1", operationName: "Initialized /dev/rdisk4s1 as a 7 GB case-insensitive HFS Plus volume", isError: false)
CallbackData(bsdName: "disk4s1", operationName: "Mounting disk", isError: false)
CallbackData(bsdName: "disk4s1", operationName: "In progress", isError: false)
CallbackData(bsdName: "disk4s1", operationName: "Operation finished", isError: false)
Program ended with exit code: 0
At the end, it works as expected, so the issue was solved.
Important notice: I made this class synchronous, but you're always free to remove the CFRunLoop
and add your corrections.