Get a filesystem UUID

Hello!

I have flash storage with exFAT filesystem and I need to get it filesystem UUID. How can I do it programmatically?

I attempt to get it via ioreg, and I think that Filesystem UUID is "Content" property of partition. Below we can think that filesystem UUID is EBD0A0A2-B9E5-4433-87C0-68B6B72699C7.


+-o IOBlockStorageServices <class IORegistryEntry:IOService:IOBlockStorageDevice:IOBlockStorageServices, id 0x10000129c, registered, matched, active, busy 0 (73 ms), retain 6>

| {

| "IOMinimumSegmentAlignmentByteCount" = 4

| "device-type" = "Generic"

| "Protocol Characteristics" = {"Physical Interconnect"="USB","Read Time Out Duration"=30000,"SCSI Logical Unit Number"=0,"Physical Interconnect Location"="External","Write Time Out Duration"=30000,"Retry Count"=20}

| "Device Characteristics" = {"Vendor Name"="Kingston","Product Name"="DataTraveler 2.0","Product Revision Level"="1.00"}

| }

|

+-o IOBlockStorageDriver <class IORegistryEntry:IOService:IOStorage:IOBlockStorageDriver, id 0x10000129d, registered, matched, active, busy 0 (72 ms), retain 8>

| {

| "IOPropertyMatch" = {"device-type"="Generic"}

| "CFBundleIdentifier" = "com.apple.iokit.IOStorageFamily"

| "IOProviderClass" = "IOBlockStorageDevice"

| "IOClass" = "IOBlockStorageDriver"

| "IOProbeScore" = 0

| "CFBundleIdentifierKernel" = "com.apple.iokit.IOStorageFamily"

| "Statistics" = {"Operations (Write)"=338,"Latency Time (Write)"=0,"Bytes (Read)"=532992,"Errors (Write)"=0,"Total Time (Read)"=147844763,"Latency Time (Read)"=0,"Retries (Read)"=0,"Errors (Read)"=0,"Total Time (Write)"=3660822621,"Bytes (Write)"=1882624,"Operations (Read)"=101,"Retries (Write)"=0}

| "IOMatchCategory" = "IODefaultMatchCategory"

| "IOGeneralInterest" = "IOCommand is not serializable"

| }

|

+-o Kingston DataTraveler 2.0 Media <class IORegistryEntry:IOService:IOStorage:IOMedia, id 0x1000012b3, registered, matched, active, busy 0 (72 ms), retain 12>

| {

| "Content" = "GUID_partition_scheme"

| "Removable" = Yes

| "Whole" = Yes

| "Leaf" = No

| "BSD Name" = "disk2"

| "Ejectable" = Yes

| "Preferred Block Size" = 512

| "IOMediaIcon" = {"IOBundleResourceFile"="Removable.icns","CFBundleIdentifier"="com.apple.iokit.IOStorageFamily"}

| "BSD Minor" = 9

| "IOGeneralInterest" = "IOCommand is not serializable"

| "Writable" = Yes

| "BSD Major" = 1

| "Size" = 15500574720

| "IOBusyInterest" = "IOCommand is not serializable"

| "Open" = Yes

| "Content Hint" = ""

| "BSD Unit" = 2

| }

|

+-o IOMediaBSDClient <class IORegistryEntry:IOService:IOMediaBSDClient, id 0x1000012b8, registered, matched, active, busy 0 (0 ms), retain 6>

| {

| "IOProbeScore" = 30000

| "CFBundleIdentifier" = "com.apple.iokit.IOStorageFamily"

| "IOMatchCategory" = "IOMediaBSDClient"

| "IOClass" = "IOMediaBSDClient"

| "IOProviderClass" = "IOMedia"

| "CFBundleIdentifierKernel" = "com.apple.iokit.IOStorageFamily"

| "IOResourceMatch" = "IOBSD"

| }

|

+-o IOGUIDPartitionScheme <class IORegistryEntry:IOService:IOStorage:IOPartitionScheme:IOGUIDPartitionScheme, id 0x1000012ba, !registered, !matched, active, busy 0 (0 ms), retain 6>

| {

| "IOProbeScore" = 4000

| "IOPropertyMatch" = {"Whole"=Yes}

| "IOMatchCategory" = "IOStorage"

| "IOClass" = "IOGUIDPartitionScheme"

| "IOProviderClass" = "IOMedia"

| "CFBundleIdentifier" = "com.apple.iokit.IOStorageFamily"

| "CFBundleIdentifierKernel" = "com.apple.iokit.IOStorageFamily"

| "UUID" = "DB6A5DED-C145-4F28-B80E-5DA726D76AF2"

| "Content Mask" = "GUID_partition_scheme"

| }

|

+-o Microsoft Basic Data@1 <class IORegistryEntry:IOService:IOStorage:IOMedia, id 0x1000012be, registered, matched, active, busy 0 (0 ms), retain 11>

| {

| "Open" = Yes

| "Preferred Block Size" = 512

| "Base" = 1048576

| "Writable" = Yes

| "IOBusyInterest" = "IOCommand is not serializable"

| "Size" = 15499509248

| "Content" = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"

| "BSD Minor" = 10

| "Whole" = No

| "Removable" = Yes

| "UUID" = "E704A750-5562-4578-9F97-D4D0E0326C7A"

| "BSD Unit" = 2

| "BSD Major" = 1

| "Ejectable" = Yes

| "BSD Name" = "disk2s1"

| "Partition ID" = 1

| "IOGeneralInterest" = "IOCommand is not serializable"

| "GPT Attributes" = 0

| "Content Hint" = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"

| "Leaf" = Yes

| }

|

+-o IOMediaBSDClient <class IORegistryEntry:IOService:IOMediaBSDClient, id 0x1000012bf, registered, matched, active, busy 0 (0 ms), retain 7>

{

"IOProbeScore" = 30000

"CFBundleIdentifier" = "com.apple.iokit.IOStorageFamily"

"IOMatchCategory" = "IOMediaBSDClient"

"IOClass" = "IOMediaBSDClient"

"IOProviderClass" = "IOMedia"

"CFBundleIdentifierKernel" = "com.apple.iokit.IOStorageFamily"

"IOResourceMatch" = "IOBSD"

}



BUT when I try to check it via diskutil, I've get two ID's with similiar names - volume, disk/partition:

diskutil info disk2s1 | grep UUID

Volume UUID: C1E39943-2F19-369A-A22F-11027887EBC8

Disk / Partition UUID: E704A750-5562-4578-9F97-D4D0E0326C7A


And one of these UUIDs is not present in ioreg output - C1E39943-2F19-369A-A22F-11027887EBC8. Very interesting. I'd check in Disk Utility (with GUI) and in information about partition I see that (Filesystem UUID: C1E39943-2F19-369A-A22F-11027887EBC8):

https://i.ibb.co/pKrqs3r/Screenshot-2020-02-11-at-10-45-34.png


And for more information here is output of blkid in Linux:

sudo blkid /dev/sdf1

/dev/sdf1: LABEL="exFAT" UUID="9CAD-0C6D" TYPE="exfat" PARTLABEL="Microsoft Basic Data" PARTUUID="e704a750-5562-4578-9f97-d4d0e0326c7a"

We can see that partition UUID is equal, but filesystem UUID - is not.


Someone can explain me:

1. how I can get Filesystem UUID of volume programmatically?

2. why results in Linux and macOS are different?

3. why in some places C1E39943-2F19-369A-A22F-11027887EBC8 is Volume UUID, but in some places - Filesystem UUID? After all It really different things!

Thanks!

Accepted Reply

To understand what’s going on here, you need to learn more about how GPT and exFAT work. I can cover some basics here but most of this stuff is not Apple-specific.

Anyway, with regards the UUIDs in the I/O Registry:

  • Content
    is the GPT partition type GUID. The value EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 is for a basic data partition. The will be the same for all FAT-like volumes.
  • UUID
    is the GPT unique partition GUID. As the name suggests, this should be globally unique.

The

Volume UUID
reported by
diskutil
matches the value returned by DiskArbitration for the
kDADiskDescriptionVolumeUUIDKey
property. For example:
import DiskArbitration

func uuidForDisk(_ bsdName: String) -> UUID? {
    guard
        let session = DASessionCreate(nil),
        let disk = DADiskCreateFromBSDName(nil, session, bsdName),
        let descCF = DADiskCopyDescription(disk),
        let desc = descCF as? [String:Any],
        let uuidCF = desc[kDADiskDescriptionVolumeUUIDKey as String] as CFTypeRef?,
        CFGetTypeID(uuidCF) == CFUUIDGetTypeID(),
        let uuidStr = CFUUIDCreateString(nil, (uuidCF as! CFUUID)) as String?,
        let uuid = UUID(uuidString: uuidStr)
    else {
        return nil
    }
    return uuid
}

For Apple file systems (like APFS and HFS Plus) this UUID is stored on the volume. The VFS plug-in makes it available to the rest of the system via

ATTR_VOL_UUID
(see the
getattrlist
man page).

I’m not an expert on exFAT, but I believe it has a similar affordance. Specifically, it looks like there is a Volume GUID directory entry to store this. If that entry is missing, I believe that our exFAT VFS plug-in will synthesise a UUID by hashing the volume serial number.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

I'm not sure what you mean by "filesystem UUID". Maybe you could start off by explaining what you are trying to do at a higher level. None of what you have mentioned is "programatically". You are calling and parsing system utilities. You can call IOKit directly, use DiskArbitration, or maybe even just get attributes from a filesystem URL.


Linux and macOS are completely different operating systems. What you've highlighed from Linux isn't even a UUID at all.


If I use Google, I see a handful of places where the phrase "filesystem uuid" occurs at apple.com. Is that a Linux thing or something? Apple has "DiskUUID" and "VolumeUUID" and a "Content" sometimes. That "File system UUID" you are seeing is just something from Disk Utility. Just ignore that whole app. Only use the command line tools. And even then, don't use the text versions if an xml/plist output format exists.

To understand what’s going on here, you need to learn more about how GPT and exFAT work. I can cover some basics here but most of this stuff is not Apple-specific.

Anyway, with regards the UUIDs in the I/O Registry:

  • Content
    is the GPT partition type GUID. The value EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 is for a basic data partition. The will be the same for all FAT-like volumes.
  • UUID
    is the GPT unique partition GUID. As the name suggests, this should be globally unique.

The

Volume UUID
reported by
diskutil
matches the value returned by DiskArbitration for the
kDADiskDescriptionVolumeUUIDKey
property. For example:
import DiskArbitration

func uuidForDisk(_ bsdName: String) -> UUID? {
    guard
        let session = DASessionCreate(nil),
        let disk = DADiskCreateFromBSDName(nil, session, bsdName),
        let descCF = DADiskCopyDescription(disk),
        let desc = descCF as? [String:Any],
        let uuidCF = desc[kDADiskDescriptionVolumeUUIDKey as String] as CFTypeRef?,
        CFGetTypeID(uuidCF) == CFUUIDGetTypeID(),
        let uuidStr = CFUUIDCreateString(nil, (uuidCF as! CFUUID)) as String?,
        let uuid = UUID(uuidString: uuidStr)
    else {
        return nil
    }
    return uuid
}

For Apple file systems (like APFS and HFS Plus) this UUID is stored on the volume. The VFS plug-in makes it available to the rest of the system via

ATTR_VOL_UUID
(see the
getattrlist
man page).

I’m not an expert on exFAT, but I believe it has a similar affordance. Specifically, it looks like there is a Volume GUID directory entry to store this. If that entry is missing, I believe that our exFAT VFS plug-in will synthesise a UUID by hashing the volume serial number.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I just want to build tree of block devices.


Most filesystems have UUID. It can be used for identifying what filesystem should be mounted (for example). Usually this UUID stored in filesystem. So, this UUID of in case exFAT is not depends on OS.


I understand that "9CAD-0C6D" is not true UUID, but it is some ID, right? I thinks this ID should be the same in Linux, macOS, Unix


When I write "programatically" I mean "via API" (preferably). I read IO Registry via IOKit framework and I post here output from ioreg utility just for inspecting results which I can take from IO Registry.


I understand that I can ignore it, but maybe somebody know more about these diferrences 🙂

Yes, I know how GPT works 🙂


The

Volume UUID
reported by
diskutil
matches the value returned by DiskArbitration for the
kDADiskDescriptionVolumeUUIDKey
property. For example:
  1. import DiskArbitration
  2. func uuidForDisk(_ bsdName: String) -> UUID? {
  3. guard
  4. let session = DASessionCreate(nil),
  5. let disk = DADiskCreateFromBSDName(nil, session, bsdName),
  6. let descCF = DADiskCopyDescription(disk),
  7. let desc = descCF as? [String:Any],
  8. let uuidCF = desc[kDADiskDescriptionVolumeUUIDKey as String] as CFTypeRef?,
  9. CFGetTypeID(uuidCF) == CFUUIDGetTypeID(),
  10. let uuidStr = CFUUIDCreateString(nil, (uuidCF as! CFUUID)) as String?,
  11. let uuid = UUID(uuidString: uuidStr)
  12. else {
  13. return nil
  14. }
  15. return uuid
  16. }

For Apple file systems (like APFS and HFS Plus) this UUID is stored on the volume. The VFS plug-in makes it available to the rest of the system via

ATTR_VOL_UUID
(see the
getattrlist
man page).


Thanks! I hope I also can make it in C 🙂


If that entry is missing, I believe that our exFAT VFS plug-in will synthesise a UUID by hashing the volume serial number.

Make sense, I thought so too


Thank you

It would probably be better to use the device ID for that. Granted, those device IDs can change. The UUIDs would be good for tracking devices that aren't currently connected.


I think it is just awkward to talk about a "filesystem UUID". The filesystem can be composed of multiple volumes and those volumes can change. You can use the Volume and Disk UUID to uniquely refer to these objects, but the filesystem is more of a dynamic structure.


My app has a tree of devices. It takes a snapshot of the current status, so I don't care about UUIDs, just device IDs. Sometimes, you don't even have that.

All can be be changed - Volume UUID, Filesystem UUID. Maybe Disk UUID will always the same, I don't know - it may depends on OS.


One thing is important - how this device tree will used and what elements of this tree most important. For me really important are filesystems, because volume (disk/partition) without filesystem can't present data as files and directories.


If you need to collect only devices - OK, it is true, you can don't care about filesystems. But when filesystem is missing a disk will present in system, but you can't mount it (because really you can mount filesystem - not a volume) and user will can't get data from it (can, but only binary blob).

Yes, I know that exist some filesystems which use few volumes - BTRFS for example. In this case managing of filesystems is more logical right too, IMHO. Because finally you need filesystems to get data from volumes. Volumes are intended just for storing filesystems 🙂 You can do "mount <filesystem UUID>" and you mount all volumes with this filesystem, but you can do "mount volume1_with_path_of_fs" and you mount only part filesystem which can be stored on few volumes (I didn't check it in macOS but it is true for Linux).


So, I still think that knowledge about filesystems of volumes is important (at least for me)