Working with DiskArbitration
The DiskArbitration framework, and diskarbitrationd specifically, is the primary arbiter of volumes and disks under Mac OS X. Like Filesystem Bundles, there is no existing documentation of the commands DiskArbitration expects you to implement, and what information it expects you to return.
I’m here to explain it in gruesome detail.
NOTICE: it is assumed you have read the documents on Filesystem Bundles and are familiar with the terminology, at least as far as FSMediaTypes and FSPersonalities are concerned. The terms “media type” and “personality” will be used extensively, so please know them well.
In every media type, you specify a probe executable and arguments. In every personality, you specify formatting and repair executables. As of Mac OS X 10.4, diskarbitrationd chooses the media type and personality using calls to the internal function ___CFDictionaryGetAnyValue(). This function returns the first value as obtained using CFDictionaryGetKeysAndValues(), which is nondeterministic; it could be any media type, or any personality.
Thus, it is advisable that all of your media types and personalities use the same executables. Arguments may differ on a per-type or per-personality basis, as those are only respected by DiskManagement-based applications. The flags used by diskarbitrationd are hard-coded.
Without further ado, here are the major groups of commands in this document. Make certain you read through the terminology section first.
Terminology
Before diving into the actual specifications, it is crucial that you be familiar with the terminology I’ll be using. Many of the terms were discussed in my post about Filesystem Bundles. Here are the new terms I’ll use, with which you should make yourself familiar before continuing:
Executables
[PROBE]This refers to the probe executable referenced by the
FSProbeExecutableinside the “randomly”-selectedFSMediaTypesrecord chosen bydiskarbitrationd.[FORMAT]This refers to the formatting executable referenced by the
FSFormatExecutableinside the “randomly”-selectedFSPersonalitiesrecord chosen bydiskarbitrationd.[REPAIR]This refers to the repair executable referenced by the
FSRepairExecutableinside the sameFSPersonalitiesrecord as used to find the[FORMAT]command.
Arguments
[bsd_name]This refers to the basename of a disk device; this is my notation of the value that Mac OS X also calls the “BSD Name” of a device. Given
/dev/disk0s3, the[bsd_name]is justdisk0s3.[device]This refers to the block device for a given disk. This is the argument that people familiar with Linux and/or UNIX would expect. Given
/dev/disk0s3, the[device]is precisely/dev/disk0s3.[raw_device]This refers to the “raw” counterpart to a disk’s block device as referenced by
[device]. The effects of using one versus the other can be quite pronounced, anddiskarbitrationduses different variations in different places. Given/dev/disk0s3, the[raw_device]is/dev/rdisk0s3.
Probe commands
Since the concept of probing a disk is specific to diskarbitration and BSD’s idea of a loadable filesystem, the probe commands return statuses are expected to use the FSUR_* constants found in <sys/lodable_fs.h>. These commands should never be accessed by a shell, as they don’t use the “zero-as-success” exit code expected of command-line tools.
These probing commands will be first contact between your filesystem and diskarbitrationd. When a disk first appears, either at boot or when a device is attached to the system, diskarbitrationd goes through the following procedures to check if your filesystem is actually present. However, these checks are only performed if your filesystem has a matching record in FSMediaTypes. The checks are as follows:
Probe device
When diskarbitrationd finds a matching media type, it will decide to probe the disk using your filesystem. Since the checks to match a media type are only rudimentary, this is basically diskarbitrationd doing finer-grained checks to determine if the new device actually contains a filesystem you can mount. You will see the following invocation from diskarbitrationd:
[PROBE] -p [bsd_name] removable readonly
You’ll notice that the last two arguments, removable and readonly are hard-coded. They will always be passed by diskarbitrationd; they are two of the string constants defined in <sys/loadable_fs.h>. Their presence seems irrelevant and arcane; in most (if not all) cases, they can be safely ignored.
RETURNS: FSUR_RECOGNIZED.
Any other value is taken to indicate failure, even if it’s another FSUR_ constant such as FSUR_IO_SUCCESS. Also, avoid returning zero, as other tools may interpret that as success when an operation actually failed.
Get UUID
When the initial probe succeeds, diskarbitrationd will immediately invoke your probe executable again. This time, since you claimed the filesystem, it’s looking for the volume’s UUID. This may seem like a menial task you can overlook, but if at all possible, it is of great benefit to diskarbitrationd and other Mac OS X facilities if you can provide a UUID. Your tool will be called as such:
[PROBE] -k [bsd_name]
The UUID is returned to diskarbitrationd by printing it to stdout. You are expected to print the uppercase hexadecimal equivalent of your 64- or 128-bit identifier as text. A newline is not required, and your output should not be hyphenated.
If you only print a 64-bit value (16 characters), diskarbitrationd will automatically conver it into a full 128-bit UUID in a deterministic fashion. For example, the HFS+ utility only returns a 64-bit value. NTFS identifiers are also only 64 bits in length.
RETURNS: FSUR_IO_SUCCESS.
This value indicates that you successfully fetched and output the UUID for diskarbitrationd to parse. Any other value indicates failure. Be very careful with this. If you return success, but your output on stdout fails to parse into a UUID, diskarbitrationd will then ask you to set one via the Set UUID operation.
Set UUID
If your volume returns success for the Get UUID operation, yet fails to provide text that can be converted into a UUID, diskarbitrationd will ask you to set a valid UUID on the volume. The assumption is that the conversion failed because the on-disk data was not a valid ID—for example, if your on-disk value was all zeroes. The invocation is as follows:
[PROBE] -s [bsd_name]
You’ll note that diskarbitrationd doesn’t give you a UUID. It just politely asks that you generate a new identifier for the volume and store it on disk, wherever and however that is appropriately done. For something like NTFS, mkntfs just picks a new random 64-bit value and writes it to disk.
RETURNS: FSUR_IO_SUCCESS.
This indicates that you successfully wrote a new identifier to the disk. If you return success, the new UUID should be immediately readable via the Get UUID operation, as diskarbitrationd will try to fetch it immediately.
Repair commands
These repair commands use the same executable as given by its FSPersonalities, but the arguments are ignored, and diskarbitrationd uses its own set. In the context of diskarbitrationd, these are used for basic verficiation (and/or repair) prior to mounting a volume. Essentially, diskarbitrationd wants to make sure it doesn’t ask you to mount an unclean volume.
If a volume fails verification (and repair, if attempted), then diskarbitrationd WILL NOT fall through to the next media type in the probe order. Thus, if you try to override an internal filesystem, and choke somewhere in verification or repair, then diskarbitrationd will not revert to the old implementation—the user’s volumes will simply fail to mount.
From experience, I can say this confuses a lot of people. When overriding Apple-provided implementations, it is imperative that you only return success during the probe operation if you can actually mount the system; you should not postpone verification logic to this stage.
Here are the two ways (in order) in which diskarbitrationd will call your tools:
Verify volume
At this point, diskarbitrationd just wants to do basic verification on the volume. For reasons I cannot entirely explain, diskarbitrationd will actually pass you the raw device associated with the disk, as such:
[REPAIR] -q [raw_device]
This should do checks necessary to make sure the disk is in working order prior to mounting it. If these checks fail, diskarbitrationd WILL NOT fall through to the next filesystem in the probing order; it will just attempt repair.
RETURNS: 0 on success, non-zero on failure.
Repair volume
If your verification fails, then diskarbitrationd will request that you do a lightweight repair on the volume. If you cannot safely repair the volume (in an automatic, without-user-input way), then this should fail. Like the verification, the arguments are simple:
[REPAIR] -y [raw_device]
Again, it uses the raw device, for reasons I do not understand. If you can successfully repair and clean the volume without intervention, that is fantastic; otherwise, this should fail, lest the user will be using an inconsistent filesystem.
RETURNS: 0 on success, non-zero on failure.
Mounting
True to the BSD nature of Mac OS X, diskarbitrationd actually goes through the standard BSD disk-mounting channels. Though you can set a FSMountExecutable in your FSPersonalities records, those are completely ignored by diskarbitrationd. Once diskarbitrationd has successfully located the first filesystem claiming it can mount a volume, it will mount it using the following command:
/sbin/mount -t [type] (-o [options]) [device] [mountpoint]
Of course, since your filesystem is not one known to /sbin/mount, it will in turn launch the following command to actually mount your filesystem:
/sbin/mount_[type] (-o [options]) [device] [mountpoint]
The -o [options] argumenst will only be present if diskarbitrationd decides there are additional arguments that must be passed. If they are present, [options] will be precisely one argument, as a comma-separated list of flags. The [device] argument is described above, and the [mountpoint] flag is self-explanatory. This is how a UNIX mount command would normally look, so there’s nothing extraordinary here.
Here’s what you do need to know: the value of [type] as passed to /sbin/mount and translated into /sbin/mount_[type] is just the value of your filesystem bundle’s CFBundleName string.
RETURNS: 0 on success, non-zero on failure.
Formatting
This is almost entirely undocumented, and not actually handled by anything related to DiskArbitration. Only tools based on the private DiskManagement framework will actually be able to format volumes. Thus, they respect the argument strings you set in your FSPersonalities records. The format executable is invoked as follows:
[FORMAT] [options] (-v) [vol_name] [raw_device]
The [options] are all of the space-separated options you specify in the selected personality’s FSFormatArguments string. Make sure your utility knows how to parse any flags you use in your bundle’s personalities. Optionally, DiskManagement will pass the -v flag if it wants verbose output, but it’s not guaranteed to be there.
The [vol_name] argument is the volume name the user has requested, encoded as UTF-8 bytes. DiskManagement seems to restrict the length of this to 16 characters in some cases, but I cannot explain why—at least, when I used diskutil to create NTFS volumes using mkntfs, the operation would fail on names longer than 12 or 16 characters, even though mkntfs says it can take up to 128 UTF-8 bytes.
The [raw_device] is the raw disk device as described above.
RETURNS: 0 on success, non-zero on failure.