Home > Software engineering >  What is the best way to get BSD drive names on macOS (Swift)?
What is the best way to get BSD drive names on macOS (Swift)?

Time:10-23

What's the best way I can get a list of BSD names of all USB devices (and maybe including internal Mac drives) without using a diskutil CLI wrapper?

I don't want to use any wrappers that interact with the CLI interface, as this way of interacting is quite slow and unreliable:

This is an example of why I'm not happy with using CLI wrappers
(Compare 'Time elapsed for DiskUtil CLI Wrapper.' and 'Time elapsed for Disk Arbitration')

What is the best way to implement the solution for my problem?
Use the data from IOReg?
If yes, how can I get a list of BSD names of connected devices using it?

Here is an example what I want to get:

["disk0", "disk0s1", "disk0s2", "disk0s3", "disk1", "disk1s1", "disk1s2", "disk1s3", "disk1s4", "disk2", "disk2s1", "disk2s2", "disk3", "disk3s1", "disk3s1s1", "disk3s2", "disk3s3", "disk3s4", "disk3s5", "disk3s6", "disk4", "disk4s1", "disk4s2", "disk5", "disk5s1", "disk5s2", "disk6", "disk6s1", "disk6s2", "disk10", "disk10s1", "disk10s2", "disk11", "disk11s1"]

At the moment, I have the following:

static func getMountedBSDNames() -> [String] {
    guard let session = DASessionCreate(nil) else { return [] }
    guard let mountedVolumeURLs = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: nil) else { return [] }
    
    var BSDNames: [String] = []
    
    for volumeURL in mountedVolumeURLs {
        if let disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, volumeURL as CFURL), let BSDName = DADiskGetBSDName(disk) {
            BSDNames.append(
                String(cString: BSDName)
            )
        }
    }
    
    return BSDNames
}

But in this case, only mounted are returning.
I want there to have even those, that were ejected

CodePudding user response:

I achieved the desired result using the IOReg lookup method:

Elapsed Time

func getDriveBSDNames() -> [String] {
    var iterator: io_iterator_t = 0

    let matching: CFDictionary = IOServiceMatching(kIOServicePlane)

    // Use 'kIOMasterPortDefault' for macOS older than 12.0 Monterey
    IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iterator)
    var child: io_object_t = IOIteratorNext(iterator)
    
    var BSDNames: [String] = []
    while child > 0 {
        if let BSDNameAnyObject = IORegistryEntryCreateCFProperty(child, "BSD Name" as CFString, kCFAllocatorDefault, IOOptionBits(kIORegistryIterateRecursively)) {
            
            if let BSDNameString = (BSDNameAnyObject.takeRetainedValue() as? String), BSDNameString.starts(with: "disk") {
                BSDNames.append(
                    BSDNameString
                )
            }
        }
        
        child = IOIteratorNext(iterator)
    }
    
    return BSDNames
}

In this case, it was also necessary to filter the output of the results using:
BSDNameString.starts(with: "disk")
(otherwise, some unnecessary devices were added, such as en0, anpi0, llw0, etc.)

CodePudding user response:

Note that while the Disk Arbitration framework doesn't have a function for synchronously enumerating all disks, it does effectively support asynchronous enumeration by registering a callback for disk appearance. This may or may not be useful depending on your use case - when providing the user with an interactive list of devices, this is usually exactly what you want though, as you'll automatically be notified of newly added devices.

I don't do Swift, sorry, but the following C code should be easy enough to understand to come up with something similar in other languages.

#include <DiskArbitration/DiskArbitration.h>
#include <stdio.h>

static void disk_appeared(DADiskRef disk, void* context)
{
    printf("%s\n", DADiskGetBSDName(disk) ?: "(null)");
}

int main()
{
    DASessionRef session = DASessionCreate(kCFAllocatorDefault);
    DASessionSetDispatchQueue(session, dispatch_get_main_queue());
    DARegisterDiskAppearedCallback(session, NULL, disk_appeared, NULL /*context*/);
    dispatch_main();
}

Note that the callback will also be called for APFS snapshots, which don't have a BSD name, so DADiskGetBSDName returns NULL and you'll have to do a little bit of filtering.

  • Related