Manipulating Disks and Volumes

In addition to watching for changes in volumes and refusing mount, unmount, and eject calls, the Disk Arbitration framework provides the ability to get various pieces of information about a volume, mount and unmount volumes, and so on. This chapter describes how to do this.

Obtaining a Disk Object

Before you can manipulate a disk or volume, you must obtain a DADiskRef object for that disk or volume. You can obtain a DADiskRef object in four ways:

If you have either an io_service_t object or a BSD device name, your app can create a DADiskRef object as follows:

  1. Create a DASessionRef object as described in Creating a Session.

  2. Schedule it as described in Scheduling the Session with the Run Loop or Dispatch Queue. Be sure your dispatch queue or run loop is running.

  3. Create the disk objects.

  4. Manipulate them as desired.

Obtaining Information About a Disk

Disk arbitration provides three functions to get additional information about disks and partitions: DADiskCopyDescription, DADiskGetBSDName, and DADiskCopyIOMedia. As a rule, you can obtain almost any information about a particular disk by calling DADiskCopyDescription. For some fairly esoteric pieces of information, however, you may have to obtain an IOMedia object for the disk and query that object.

Obtaining a Description Dictionary

The DADiskCopyDescription method returns a CFDictionaryRef object containing several dozen pieces of information about a disk or partition. Some commonly used data includes:

  • Mount point and volume name

  • BSD device node name and major and minor numbers

  • Information about the hardware (device ID, vendor ID, GUID, and so on)

  • Connection info (bus name and path)

You can find a complete list of properties in the DADisk.h header in the Disk Arbitration Framework, along with a description of the expected data types for the values of each key.

For example, to print the mount point path for a volume:

DADiskRef disk;
CFDictionaryRef *diskinfo;
 
...
 
diskinfo = DADiskCopyDescription(disk);
CFURLRef fspath = CFDictionaryGetValue(dict,
                kDADiskDescriptionVolumePathKey);
 
char buf[MAXPATHLEN];
if (CFURLGetFileSystemRepresentation(fspath, false, (UInt8 *)buf, sizeof(buf))) {
    printf("Disk %s mounted at %s\n",
        DADiskGetBSDName(disk),
        buf);
 
    /* Print the complete dictionary for debugging. */
    CFShow(diskinfo);
} else {
    /* Something is *really* wrong. */
}

For a complete list of dictionary keys, see the Constants section in DADisk.h Reference.

Obtaining Additional Information from I/O Kit

In some rare situations, you may need to obtain additional information about a disk beyond what is available from Disk Arbitration. If you do, you can call DADiskCopyIOMedia to obtain an io_service_t object, which is the user-space representation of an IOMedia object. You can manipulate this object just as you would any I/O Registry object.

For example, you can obtain a Core Foundation dictionary with the media’s I/O Registry properties by calling IORegistryEntryCreateCFProperties on the resulting object.

The properties in an I/O Registry dictionary are defined in the I/O Kit Framework. For more information, see I/O Kit Framework Reference.

Mounting and Unmounting Volumes

To mount or unmount a volume, call DADiskMount or DADiskUnmount. If you care whether the mount or unmount was successful, you must also provide a callback to handle the result.

Alternatively, you can call DADiskMountWithArguments if you need to pass additional options to the mount command. This command takes a null-terminated array of CFStringRef values, each of which becomes an argument to the mount command. You can find the most common options in the manual page for mount. For additional volume-format-specific options, see the manual page for specific mount commands, such as mount_hfs.

For each of these functions, the callback function takes three parameters: a DADiskRef object, a DADissenterRef object, and a context pointer (passed as a parameter when you register the callback). If the operation was successful, the dissenter object is NULL. Otherwise, it provides information about why the operation failed.

For example, the following snippet unmounts a volume:

void unmount_done(DADiskRef disk,
    DADissenterRef dissenter,
    void *context);
 
...
 
DADiskUnmount(disk, kDADiskUnmountOptionDefault,
    unmount_done, NULL);
 
...
 
void unmount_done(DADiskRef disk,
    DADissenterRef dissenter,
    void *context)
{
    if (dissenter) {
        /* Unmount failed. */
        char buf[MAXPATHLEN];
        if (CFURLGetFileSystemRepresentation(fspath, false, (UInt8 *)buf, sizeof(buf))) {
            fprintf(stderr, "Unmount failed (Error: 0x%x Reason: %s).  Retrying.\n",
                DADissenterGetStatus(dissenter),
                buf);
        } else {
            /* Something is *really* wrong. */
        }
    } else {
        /* Do something. */
    }
}

The most common error code values (errors specific to disk arbitration) are described in the DAReturn enumeration. However, this function can also potentially return any other error code in the mach_error_t space (UNIX errors, and so on).

Mounting a volume is similar except that you can specify a CFURLRef object with the mount point path (or pass NULL to mount the volume at the standard location). For example:

unsigned char *mppath = "/mnt/mydisk";
 
path = CFURLCreateFromFileSystemRepresentation(
    kCFAllocatorDefault,
    mppath,
    strlen(mppath),
    true);
 
DADiskMountWithArguments(disk, path, kDADiskMountOptionDefault,
    mount_complete_callback, NULL,
    NULL);

Ejecting a Disk

Ejecting a disk is similar to unmounting a volume. There are two key differences:

To get the whole disk partition that contains a given leaf partition, use DADiskCopyWholeDisk. For example:

DADiskRef wholedisk = DADiskCopyWholeDisk(disk);

The following snippet, given a DADiskRef object associated with a single partition (called partition), unmounts all volumes on the underlying disk, then ejects the disk:

void unmount_done(DADiskRef disk,
    DADissenterRef dissenter,
    void *context);
void eject_done(DADiskRef disk,
    DADissenterRef dissenter,
    void *context);
 
...
 
/* Unmount all volumes */
DADiskRef wholedisk = DADiskCopyWholeDisk(partition);
DADiskUnmount(wholedisk, kDADiskUnmountOptionWhole,
    unmount_done, NULL);
CFRelease(wholedisk);
 
...
 
/* In the unmount callback, eject the volume. */
void unmount_done(DADiskRef disk,
    DADissenterRef dissenter,
    void *context)
{
    if (dissenter) {
        ...
    } else {
        DADiskEject(disk, kDADiskEjectOptionDefault,
            eject_done, NULL);
    }
}
 
/* Eject callback. */
void eject_done(DADiskRef disk,
    DADissenterRef dissenter,
    void *context)
{
    if (dissenter) {
        ...
    } else {
        ...
    }
}