Get tree-like structure of all volumes using DiskArbitration and IOKit

329 views Asked by At

I am trying to get a tree like structure of all volumes of my computer using Swift.

The simplest way to get all the mounted volumes is this code:

FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: nil)

Using this URL, I can create a DADisk object to get the BSD name:

if let session = DASessionCreate(kCFAllocatorDefault) {
  if let disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, url as CFURL) {
    if let bsdName = DADiskGetBSDName(disk) {
      volume.bsdName = String(cString : bsdName) // Volume is my own class.
    }
  }
}

Now for the "main" disk, I get "disk0". I want to get additional information about this disk. If I take a look at the Disk Utility, disk0 does not seem to do much, so I want to get all of the "sub" volumes of disk0. I tried to gather all disks, cut "disk" and then parse the remains number. Sometimes this works (if disk1 has sub volumes disk1s1 and disk1s2), but sometimes there is a root disk (let's call it disk2) and there is one (virtual?) Container. Underneath this container, there are additional disks called disk3 and disk3s1 (this is where my approach fails).

I have tried using IOKit which prints all the disk names. However, I don't know how to build the tree like structure (I suppose I could hack something based on the path):

var iterator: io_iterator_t = 0
let matching: CFDictionary = IOServiceMatching(kIOServicePlane)
IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator)
var child: io_object_t = IOIteratorNext(iterator)

while child != 0
{
    if let bsdName = IORegistryEntryCreateCFProperty(child, self.bsdNameKey as CFString, kCFAllocatorDefault, IOOptionBits(kIORegistryIterateRecursively))
    {
        Swift.print(bsdName)
    }

    child = IOIteratorNext(iterator)
}

Is there a reliable way to get a main disk and all of its volumes in some sort of hierarchy?

1

There are 1 answers

0
Pietro Caruso On

I managed to make a similar structure using an hacky and for now not very Sandbox-friendly way (it could be sandbox-friendly, continue reading for more info).

It's all based on the fact that the diskutil command actually returns a tree-like structure with all the details you might need about the disks present in a system, when you do the following terminal command: diskutil list -plist

So to use it to get a disk tree structure in my code i did the following:

  1. I implemented a function using a Process Object to return the me the output of "diskutil list -plist" as a string (If you want to have your app sandbox-friendly, as far as i know, this part of the code needs to be inside an helper tool that needs to be embedded into your app's bundle)

  2. I created some Codable structures and some protocols to go with them to have the plist string deserialised with minimum effort

  3. I made my code perform some operations on the structure to have some stuff easyer to deal with like having APFS containers associated with their physical device into the tree-like structure

Copy-pasting all the code here will create a lot of mess (it's a lot of code), I prefer to link you the swift file in which I did most of this stuff in my open source project: https://github.com/ITzTravelInTime/TINU/blob/development/TINU/DiskutilListCodable.swift

Honestly i don't know what kind of dark magic the Diskutil executable uses to gather this info and generate the three structure, but for now it's the easyest way i foind to gather this info, i know that some developers (like the people working on some EFI Partition/ESP mounting tools) have managed to pull this off using system APIs, but i don't know how.