October 2008 Archives

C++ Operator Overloading

| No Comments

I came across this code recently:

level = kIOStorageAccessNone;
while ( ( object = objects->getNextObject( ) ) )
{
    if ( object != client )
    {
        level += _openClients->getObject( ( OSSymbol * ) object );
    }
}

Basically, it’s going through a list of clients that it services and works out what the aggregate access level is for all the clients. Here are the possible access levels:

enum
{
    kIOStorageAccessNone          = 0x00,
    kIOStorageAccessReader        = 0x01,
    kIOStorageAccessReaderWriter  = 0x03,
    kIOStorageAccessSharedLock    = 0x04,
    kIOStorageAccessExclusiveLock = 0x08
};

Now, from looking at this code, it looks wrong because of the use of the +=. Surely |= is more appropriate. It’s obvious to me now, but it took me some time before I found the bit where they’d overridden the += operator:

inline void operator+=( IOStorageAccess access )
{
    _access = ( ( _access - 1 ) >> 1 ) & 7;
    _access = gIOMediaAccessTable[ ( ( access - 1 ) >> 1 ) & 7 ][ _access ];
    _access = ( ( _access & 7 ) << 1 ) + 1;
}

Anyway, this, in my opinion, is a classic example of an abuse of C++ operator overloading. The rule when writing code should be to make it as readable and as obvious as possible first before you start trying to be clever. If the code had been something like this:

    clientLevel = _openClients->getObject( ( OSSymbol * ) object )->unsigned32BitValue( );
    level = aggregateLevel( level, clientLevel );

it wouldn’t have cost me any time as it would have been clear what was going on.

It’s for similar reasons that I always put brackets round expressions that combine && and || clauses: not everybody knows the order of precedence of these two and it’s easy to forget; adding brackets means you don’t need to remember.

For example, I would always do:

    if ((a && b) || (c && d))

rather than

    if (a && b || c && d)

I know it’s tempting to use that clever bit of the language that you just learnt about but in doing so you might make it harder for the person that follows you. The general rule should be: unless it is a common idiom, make it clear what’s going on and if a better known alternative exists, use that instead.

Disk Arbitration

| No Comments

Disk Arbitration is what is used on the Mac to handle automatic mounting of volumes and various other disk related things. When you insert a disk, the kernel will instantiate drivers for it and notify the Disk Arbitration daemon. It will then ask filesystem plugins if they recognise the volume and if they do it will usually proceed to mount them.

Using Disk Arbitration, you can┬ásend requests to eject disks, mount and unmount volumes, rename volumes and you can claim a disk for exclusive use. For each of those operations you can also approve or reject requests that other applications make. It’s not actually enforced: you could still force a volume to be unmounted and claiming a disk for exclusive use doesn’t stop anyone else from using it (like advisory file locks). You can see some of these notifications flying around by using disktool with the undocumented -y flag. You can get more debug by hacking the com.apple.diskarbitration.plist file in /System/Library/LaunchDaemons and adding the -d flag to the argument list. You’ll then get /var/log/diskarbitrationd.log.

diskarbitrationd is the main daemon that’s responsible for all this stuff. The source code for it and related things can be found on the Open Source part of Apple’s site. To use Disk Arbitration from within your application, you need to link to the Disk Arbitration framework. It was a private framework in Panther (10.3) but was made public in Tiger (10.4). Things like Spotlight use it to know when to stop indexing a volume when you want to unmount a volume and you should use it too if you’re writing a similar application.

There are a couple of files that it looks at to tell it what to do. /etc/fstab is one of them. The shipped version (in Leopard) has some examples in the comments of /etc/fstab and the man page looks pretty good. By making the appropriate change to this file, you can, amongst other things, stop a volume from being automatically mounted. The other file it looks at is /var/db/volinfo.database. This text file stores the setting of the “Ignore ownership of this volume” flag that you see in Finder.

Disk Arbitration will also give you information about all the disks and volumes on the system. Here’s the kind of information it might have about your startup volume:


{
    DAAppearanceTime = 244372661.099552;
    DABusName = PMP;
    DABusPath = "IODeviceTree:/PCI0@0/SATA@1F,2/PRT2@2/PMP@0";
    DADeviceInternal = 1;
    DADeviceModel = "WDC WD1600JS-40NGB2                     ";
    DADevicePath = "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/SATA@1F,2/AppleAHCI/PRT2@2/IOAHCIDevice@0/AppleAHCIDiskDriver/IOAHCIBlockStorageDevice";
    DADeviceProtocol = SATA;
    DADeviceRevision = "10.02E04";
    DADeviceUnit = 0;
    DAMediaBSDMajor = 14;
    DAMediaBSDMinor = 3;
    DAMediaBSDName = disk0s3;
    DAMediaBSDUnit = 0;
    DAMediaBlockSize = 512;
    DAMediaContent = "48465300-0000-11AA-AA11-00306543ECAC";
    DAMediaEjectable = 0;
    DAMediaIcon =     {
        CFBundleIdentifier = "com.apple.iokit.IOStorageFamily";
        IOBundleResourceFile = "Internal.icns";
    };
    DAMediaKind = IOMedia;
    DAMediaLeaf = 1;
    DAMediaName = "Macintosh HD";
    DAMediaPath = "IODeviceTree:/PCI0@0/SATA@1F,2/PRT2@2/PMP@0/@0:3";
    DAMediaRemovable = 0;
    DAMediaSize = 138155196416;
    DAMediaUUID = <NSCFType: 0xd32f150>;
    DAMediaWhole = 0;
    DAMediaWritable = 1;
    DAVolumeKind = hfs;
    DAVolumeMountable = 1;
    DAVolumeName = "Macintosh HD";
    DAVolumeNetwork = 0;
    DAVolumePath = file://localhost/;
    DAVolumeUUID = <NSCFType: 0xd3316b0>;
}

That’s obviously for a specific partition on a disk. There’s another entry that represents the whole disk.

Something to note regarding disk/volume UUIDs: GUID partition schemes have a UUID that is used for the partition but in addition to that, there is a UUID that is stored as part of the file system (in the case of HFS+, it’s not actually a UUID but eight bytes that get turned into a UUID), so if you do end up using them, make sure you know which one you’re dealing with. The UUID reported by Disk Arbitration happens to be the file system UUID but the UUID property of IOMedia objects is from the partition table.

One thing that isn’t ideal is there’s no actual notification indicating when the system wants to eject a disk. You can use the eject approval notification, but then there’s no way to find out if the eject was ultimately approved. This would be a problem for, say, an indexing application, where it might want to release all resources for the disk (so that the eject will actually succeed) when an eject is requested. Spotlight probably uses the eject approval callback, but the only way it would be able to restart indexing on a volume that failed to eject, is by checking again after a certain amount of time (I don’t know if it does that).

Another problem I found recently is that bad clients can cause it to eat memory and this in turn can cause kernel memory leaks (of IOMedia objects). For example, on the currently shipping version of Leopard, the WindowServer process doesn’t appear to be properly processing Disk Arbitration notifications (probably because it uses a funny run loop) which is ultimately causing IOMedia objects to hang around in the kernel. It’s not a big deal but it does need fixing.

Oh, and one other thing, the system does not like it if Disk Arbitration crashes. Even though launchd will restart it, you still need to reboot as Finder and other processes don’t connect to the new version.

Lock Free Data Structures

| No Comments

Lock free data structures are interesting I think. It’s usually much simpler to keep things simple and use regular locks but if you have a need for speed, lock free data structures can give that to you.

I was curious to know what difference it would make for a simple FIFO queue so I knocked up a mostly lock free based implementation and a conventional lock based version. I chose to make things slightly harder by choosing a fixed size queue and having it block when the queue was full.

The lock free version was nearly thirty times faster on the machine I tested it on. Disclaimer: I did this pretty quickly and I wouldn’t be surprised if there are bugs (I haven’t tested it on a multiprocessor weakly ordered architecture machine).

Here’s the code.

App Store Rejections

| No Comments

Now that the iPhone NDA has been lifted, all whining is now focused on App Store rejections. I’m sure you’ve all just read Gruber’s post. I’d just like to throw another theory in: what if Apple is rejecting these applications because they offer features that they are planning to add to their applications. If they allow these third party applications, then a) the fanfare when they announce their equivalent feature isn’t as good and b) they’re going to piss people off who’ve developed the third party application and also those that bought the application. By rejecting these applications early, they’re doing the developers a favour: better rejected now than find their application obsolete in a few months time. Obviously, it would be better if you could get rejected even earlier as Gruber and others have suggested.