Search code examples
swiftmacoscocoaiokitdiskarbitration

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


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


Solution

  • 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.)