About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Home » Learning Curve » Developers Workshop

Anatomy of a Makeover

Safe is when the user is in control. OS X / macOS users are not in control.

Get It

Try It

The advent of OS X 10.4 Tiger heralded the end of the HFS resource fork. HFS forks were used to store additional file data not found in the ordinary data fork - the 'fork' most people associate with file contents.

Starting with Tiger, Apple's HFS admitted of extended attributes (XAs). This data is stored in parallel with file data, but at a different location.

XAs can be found on many platforms, but Apple's HFS implementation is unique.

XAs are mostly anything anyone wants - save for the remnants of the resource fork and for Finder data. A new API was released with Tiger to provide the means to edit XAs.

BSD General Calls Manual

xattr -- display and manipulate extended attributes

BSD System Calls Manual

getxattr, fgetxattr -- get an extended attribute value
listxattr, flistxattr -- list extended attribute names
removexattr, fremovexattr -- remove an extended attribute value
setxattr, fsetxattr -- set an extended attribute value

Mac OS X    Oct 19, 2004                   Mac OS X

One of the major drawbacks of XAs (even more so with resource forks) is the difficulty for ordinary users to easily see what's going on in their systems. An early example of how deadly this could be: the Oompa Loompa worm.


Apple provide a difficult command line interface for dealing with XAs. We took it upon ourselves to develop a means of exerting further user control. This started with the command line tool xabatch ten years ago, designed for admins and power users.


The MO is simple. You either add an XA to a file, or you remove one. When removing an XA, one need only specify the XA identifier. When adding an XA, one specifies a path to the file containing the XA, and this XA is then added to the file's store.

A typical xabatch file might look like this.


The colon (':') is used as the DSV field separator. The backslash ('\') escapes colon characters. There are only two fields per xabatch record.

The initial '-' before the colon on each line in the above example means the XA in question is to be removed. A comparable command to add an XA might look like this.


xabatch can be run fortuitously with xargs to achieve blistering speeds. All is good. Then someone happened upon the idea of creating a GUI version. And the result was XaBatch.

XaBatch of course recognises the same file format. But an additional field was created, a field which the command line version ignores. The first record for the above xabatch file is actually the following.


The first byte is no longer '+' or '-', but 'P' or 'p'. The colon is followed by the path where an XaBatch run is to begin.

XaBatch can modify XAs for a single file, for all files dropped on one of its document windows, or recursively from the specified path. For this purpose, a checkbox is added to the interface. 'P' indicates XaBatch is to recurse, 'p' indicates it is not.

So much for file format and syntax.

The original version of XaBatch, released many years ago, didn't admit of a way to store the path and the recurse toggle through the GUI. Further: these new possibilities must acknowledge when a document file is 'dirty', to kick in the system document controller when needed. It is this document controller (developers on Windows, take note, sorry guys) which prompts the user to save changes when closing a document window or exiting an application.

The new XaBatch must also make sure document windows are not closed during runs. Those runs are necessarily executed through background threads, and any abrupt behaviour could cause an application crash. Further: options to close or save documents have to be disabled as well, this due to an anomaly in document controller logic.

Such improvements to an application seem trivial, and in fact they are, and they're all for the better, because XaBatch can now conform better to the MVC approach. But what's easy in theory can prove difficult in practice. This little travelogue will hopefully serve to explain why.


The first hurdle is working with Interface Builder. Interface Builder is used to edit NeXTSTEP Interface Builder (NIB) files. NIBs are a topic unto themselves, are pure genius, and are one of the reasons OS X / macOS remains light years ahead of other platforms.


Interface Builder was created for NeXT by Jean-Marie Hullot.


Steve Jobs had invited Jean-Marie up to demonstrate a new app called 'SOS Interface'. Jobs was so blown away that he scoured software shops in the area to buy up all available copies, telling Jean-Marie: 'I want that program on our computer!'

The original IB by Jean-Marie was a lot smarter than the version Apple have today. (So was the sibling Project Builder.) The original IB used the entire desktop as its working area, important for larger windows: if the window can't fit on screen... But the current version, baked into Xcode, is severely hampered: document windows are displayed in a 'child window' context. Not good. The original was a lot easier to use, and one of the good things (there were a few) with Aaron's original text was explaining how this interface worked. (Ctrl-drags the one way for actions, the other way for outlets, something Yachtman overlooked.) Today's IB, built into Xcode, is very arcane and not at all as intuitive as its predecessor.

The first task at hand for XaBatch was to adjust the window size to accommodate Jony's new tableview headers. These headers - at least the ones with directional options - are 6 pixels higher. (Jony wanted simpler graphics.) Fortunately, IB retained a lot of its original intuitive logic, and in this case it's a good thing, so heightening the overall window by 6 pixels will heighten the tableview by the same amount.

The next task is to see that the recurse click box automatically sends an 'action' to a designated method in the file's owner, this so the document can be 'dirtied'. The method is easy to write: all it needs is to trigger the document controller.

[self updateChangeCount:0];

The trick is getting IB to implement the new connection, and this no longer works as in the old IB. In this case it was necessary to 'hack' the designable.nib file.

Another snag further down the line comes with backward compatibility. Many of our clients (and their clients) have refused to update to Sierra. Certainly more valid reasons appear as time goes on. But earlier builds don't want the additional six pixels, so collaborating on different computers becomes a bit more dicey.

Another snag, immediately present, is how Xcode config files behave (and misbehave). Earlier versions of Xcode simply didn't admit of frameworks outside the project directory. The workaround was to create a symlink in the project area, then after exiting and restarting, the symlink could be removed. Naturally, the current Xcode doesn't understand those symlinks, and doesn't seem to understand earlier ordinary direct references to system frameworks either.

Meaning that cross-platform work must exclude not only affected NIBs but project config files as well. As to how Apple could have thought that projects could never include external frameworks, and/or how they managed in-house all those years: that can be left to the reader's consternation.

Disabling the menu items Close, Save, and Save As during background threads became vital. The procedure is straightforward, as the system always asks for approval before displaying a menu, but the Close item seems impervious. Retaining code to disallow closing a document window under the above circumstances remains essential.

The 'title' of the 'Run' button can be changed once a background thread's been dispatched, and the button itself can be configured in the NIB to be 'default' - to respond to Enter/Return. Previously, someone opted against this, for reasons not remembered anymore.


The executables. This wonderful thing's been noted before.


The exact same code compiled and linked in the exact same way bloats by a factor of almost 200% on Sierra. Changes to the XaBatch code upped the binary size by 8 bytes to 28168. The exact same code built on 10.12 results in a binary of 54 KB.

The suspect here - in LLVM - is availability for Swift. And it's inexcusable.


The final line is why such applications (and command line tools) should be necessary. The old guard at Apple - the so-called 'evangelists' - would insist they are not. For what does it hurt a user if said user is unaware what's going on?

At the very least of course: Oompa Loompa could hurt.

But users should be able to control all aspects of the computers they've purchased. The old resource forks were mostly inaccessible. That technology was often abused. Users couldn't see.

XAs are a step better (and immeasurably beneficial) but full user level access is necessary. Apple (and others) are today in the habit of dumping XAs on everything. Xcode dumps them on directories. Preview dumps 'quarantine' XAs on images it edits. Safari has a standard pack to put on anything it downloads. Now the 10.12 document controller's joined in on the game.

And this is the safest system going? Built on the Rock Solid Foundation™?

Safe is when the user is in control. OS X / macOS users are not in control.


The ACP utility to inspect and edit XAs on individual files is Xattr.

See Also
In the ACP: Xattr
In the ACP: XaBatch

Industry Watch: The Chocolate Tunnel: Oompa Loompa hits OS X
Industry Watch: xabatch: For admins to manage file system extended attributes

Learning Curve: The xargs Speed Boost

The NeXTonian: Interface Builder
The NeXTonian: People (Jean-Marie Hullot)

ACP Users: On Manicuring NIBs

About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Copyright © Rixstep. All rights reserved.