How to tell which files are entirely or partly within a band of a sparse bundle disk image?

I have an image with an error in one of its bands:

/Volumes/twoz/macbookpro08-centrim.sparsebundle/bands/3252

The file system personality of the image is Case-sensitive Journaled HFS+.

Question

At the HFS Plus level:

  • how can I tell which files are entirely or partly within that band?

Names and paths of files will be ideal.

Background

The error is detected by ZFS and is permanent (I chose no redundancy for the pool). File system twoz is mounted by ZEVO.

At the root of the bundle there's probably nothing to help us:

sh-3.2$ sudo ls -ahl /Volumes/twoz/macbookpro08-centrim.sparsebundle
total 8952
drwx------@ 3 root  wheel    10B  3 Mar 19:38 .
drwxr-xr-x  7 root  wheel     7B  9 Dec 17:16 ..
-rw-r--r--  1 root  wheel   499B 30 Dec 12:20 Info.bckup
-rw-r--r--  1 root  wheel   499B 30 Dec 12:20 Info.plist
drwx------  2 root  wheel    26K  3 Mar 08:16 bands
-rw-r--r--  1 root  wheel   445B  3 Mar 06:48 com.apple.TimeMachine.MachineID.bckup
-rw-r--r--  1 root  wheel   445B  3 Mar 06:48 com.apple.TimeMachine.MachineID.plist
-rw-r--r--  1 root  wheel   1.4K  3 Mar 08:20 com.apple.TimeMachine.Results.plist
-rw-r--r--  1 root  wheel    11K  3 Mar 08:20 com.apple.TimeMachine.SnapshotHistory.plist
-rwx------  1 root  wheel     0B  9 Dec 17:16 token
sh-3.2$ sudo defaults read /Volumes/twoz/macbookpro08-centrim.sparsebundle/Info
{
    CFBundleInfoDictionaryVersion = "6.0";
    "band-size" = 8388608;
    "bundle-backingstore-version" = 1;
    "diskimage-bundle-type" = "com.apple.diskimage.sparsebundle";
    size = 821820674048;
}

I expect the bands directory to contain nothing except bands.

An experiment with cat

sudo cat /Volumes/twoz/macbookpro08-centrim.sparsebundle/bands/3252

The result is mostly binary data and unsurprisingly, an I/O error seems to prevent completion of the command – so I aborted.

Interspersed with binary data, some content is human readable. Some of that readable content is almost certainly within one file (xulrunner) that suffered an I/O error when I attempted to restore data from the disk image.

I'd prefer a more direct approach – one that does not involve attempting to read all data from the volume.


Wonder whether an authoritative answer to this question will involve knowledge of the disk image driver or a private framework. In OS X 10.8.2 for example:

sh-3.2$ hdiutil info
framework       : 344
driver          : 10.7v344

/System/Library/PrivateFrameworks/DiskImages.framework


Solution 1:

Assuming you can attach the sparsebundle, you should be able to do this using fileXray, which is $79 for a personal use license and found at http://filexray.com

fileXray is able to "reverse map volume storage" meaning it can "determine which file a given block or byte offset on a volume belongs to." The relevant option is --who_owns_byte, explained on page 172 of the documentation, which can be found at http:// filexray.com / fileXray.pdf (link broken because it won't let me post more than two links).

Now according to page 54 of the documentation, "It is important to note that a device dump must be 'raw'—that is, it must not require any additional transformations such as decompression or decryption. In other words, for a disk image file to be used directly by fileXray, the image must not be compressed, encrypted, or sparse. fileXray will reject such an image." However, it goes on to say, "If you do have such an image that is compressed, encrypted, or sparse, either convert it using the Mac OS X hdiutil command-line program, or simply attach it (optionally without mounting it) using hdiutil and use fileXray on the resultant block device instead of the image file."

So once you have the sparsebundle attached or mounted, the question is what byte offset to provide to the --who_owns_byte option.

Apple provides "routines to manipulate a sparse bundle" at http://www.opensource.apple.com/source/hfs/hfs-191/CopyHFSMeta/SparseBundle.c

Based on that code, we can see in the routine doSparseRead that the bandName of the first band for a given offset is the bandNum as a hexadecimal number. In particular, asprintf(&bandName, "%s/bands/%x", ctx->pathname, bandNum).

The bandNum is the offset / blockSize, since off_t bandNum = (offset + nread) / blockSize and nread starts at 0, which will truncate the result of the division, so the offset should be bandNum * blockSize or bandNum * blockSize + blockSize. Note that the blockSize is the bandSize, since off_t blockSize = ctx->bandSize;

Looking at doSparseWrite seems to give the same answer, since off_t bandNum = (offset + written) / blockSize; with written initialized at 0 and asprintf(&bandName, "%s/bands/%x", ctx->pathname, bandNum);

It would be great if someone with access to fileXray could try this.