Thursday, July 06, 2006

More on Disk Images, Resource Forks, Licenses

In a previous post I talked about disk images (dmg files) as a common mechanism for distributing software for OS X. I mentioned that dmg files can contain license agreements that must be accepted before the disk image can be mounted.

To understand the mechanism used for this, I'll need to talk about resource and data forks. In earlier versions of Apple's operating systems, each file actually had two parts, called a resource fork and a data fork. The file's data and metadata are split (in a sometimes complicated way) between the two forks. Often, the data fork is similar to what we'd normally think of as the file itself, and the resource fork carries metadata, in a binary format, about the file: an icon, a description, the name of an associated application, etc.. The resource fork is normally invisible to the user, and the filesystem takes care of keeping data and resource forks "connected". (When the file is moved to a new directory, for example, both forks are simultaneously moved.)

Resource forks are rarely used under OS X, but they're still supported by its HFS+ filesystem. You can get to a file's resource fork by appending "/rsrc" onto the file name. For example:
# ls -al /etc/bashrc
-rw-r--r-- 1 root wheel 250 Jun 15 10:48 /etc/bashrc
# ls -al /etc/bashrc/rsrc
-rw-r--r-- 1 root wheel 0 Jun 15 10:48 /etc/bashrc/rsrc
Weird, eh?

One problem with resource forks is that they can't easily be transported across the network (at least not to non-Apple servers). For example, If you ftp a file from an OS X computer to a Linux computer, only the data fork will be transported. Similarly, if you download a file from a web server, you'll only get the data fork, even if the file originally had a resource fork.

One way around this is to "flatten" the file by packing both resource and data forks into a new data file in such a way that they can be unpacked again later. This is often done with dmg files. A flattened dmg file can be unflattened with htiutil ("hdiutil unflatten file.dmg").

Licenses are often added to the resource forks of dmg files before they are flattened. Each component of a resource fork has a type (such as 'TEXT') and a numerical identifier. Before mounting a disk image, hdiutil looks in the dmg file's resource fork for a TEXT section with identifier 5002. This section holds the license agreement associated with the dmg. The perl script BuildDMG can be used to create dmg files, and it is capable of attaching a license as part of the process. BuildDMB hdiutil to unflatten the dmg file, then it uses the Rez command from the Xcode tools to compile the resource fork from a source-code description (here's an example). The dmg file is then re-flattened and ready for distribution.

To automate the mounting of a license-containing dmg file, you can either do something like this:
yes | hdiutil attach file.dmg
which will just answer "y" to any questions asked by hdiutil (e.g., "Do you accept this license?"), or you can unflatten the dmg file, clear its resource fork and reflatten it before trying to mount the image. One way to do this would be:
touch junk.rsrc
hdiutil unflatten file.dmg
/Developer/Tools/Rez junk.rsrc -o file.dmg/rsrc
hdiutil flatten file.dmg


Stephan said...

I tried the pipe command to automatically install dmg's, but it doesn't work. The Terminal shows the License Agreement and I have to manually agree to it.
Any idea why this doesn't work?
I don't know if it has any effect, but the license files are rather large, so they need scrolling.

catselbow said...

Hi Stephan,

Are you running Leopard? We've only started playing with Leopard here, and I've noticed the same problem. When I come up with a solution, I'll post an update here.

Stephan said...

Yes I'm on Leopard. Could the problem be that Leopard automatically redirects hdiutil's output to a pager like "less"? I'm quite new to the mac world, so I don't know if this was happening in Tiger as well.
Thanks for the quick reply!

catselbow said...

Hi Stephan,

My guess is that hdiutil isn't using stdin/stdout/stderr, but instead is doing
I/O directly to the user's TTY. I think this is what ssh does when it asks for a password. With ssh, you can use "expect" to fake the existence of a real TTY. This may be the solution in this case, too.