util.c   [plain text]


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/disk.h>
#include <sys/sysctl.h>
#include <hfs/hfs_mount.h>
#include <Block.h>
#include "hfsmeta.h"
#include "Data.h"

/*
 * Open the source device.  In addition to opening the device,
 * this also attempts to flush the journal, and then sets up a
 * DeviceInfo_t object that will be used when doing the actual
 * reading.
 */
__private_extern__
DeviceInfo_t *
OpenDevice(const char *devname, int flushJournal)
{
	DeviceInfo_t *retval = NULL;
	int fd;
	DeviceInfo_t dev = { 0 };
	struct stat sb;
	struct vfsconf vfc;

	if (stat(devname, &sb) == -1) {
		err(kBadExit, "cannot open device %s", devname);
	}
	/*
	 * Attempt to flush the journal if requested.  If it fails, we just warn, but don't abort.
	 */
	if (flushJournal && getvfsbyname("hfs", &vfc) == 0) {
		int rv;
		int mib[4];
		char block_device[MAXPATHLEN+1];
		int jfd;

		/*
		 * The journal replay code, sadly, requires a block device.
		 * So we need to go from the raw device to block device, if
		 * necessary.
		 */
		if (strncmp(devname, "/dev/rdisk", 10) == 0) {
			snprintf(block_device, sizeof(block_device), "/dev/%s", devname+6);
		} else {
			snprintf(block_device, sizeof(block_device), "%s", devname);
		}
		jfd = open(block_device, O_RDWR);
		if (jfd == -1) {
			warn("Cannot open block device %s for read-write", block_device);
		} else {
			mib[0] = CTL_VFS;
			mib[1] = vfc.vfc_typenum;
			mib[2] = HFS_REPLAY_JOURNAL;
			mib[3] = jfd;
			if (debug)
				fprintf(stderr, "about to replay journal\n");
			rv = sysctl(mib, 4, NULL, NULL, NULL, 0);
			if (rv == -1) {
				warn("cannot replay journal");
			}
			/* This is probably not necessary, but we couldn't prove it. */
			(void)fcntl(jfd, F_FULLFSYNC, 0);
			close(jfd);
		}
	}
	/*
	 * We only allow a character device (e.g., /dev/rdisk1s2)
	 * If we're given a non-character device, we'll try to turn
	 * into a character device assuming a name pattern of /dev/rdisk*
	 */
	if ((sb.st_mode & S_IFMT) == S_IFCHR) {
		dev.devname = strdup(devname);
	} else if (strncmp(devname, "/dev/disk", 9) == 0) {
		// Turn "/dev/diskFoo" into "/dev/rdiskFoo"
		char tmpname[strlen(devname) + 2];
		(void)snprintf(tmpname, sizeof(tmpname), "/dev/rdisk%s", devname + sizeof("/dev/disk") - 1);
		if (stat(tmpname, &sb) == -1) {
			err(kBadExit, "cannot open raw device %s", tmpname);
		}
		if ((sb.st_mode & S_IFMT) != S_IFCHR) {
			errx(kBadExit, "raw device %s is not a raw device", tmpname);
		}
		dev.devname = strdup(tmpname);
	} else {
		errx(kBadExit, "device name `%s' does not fit pattern", devname);
	}
	// Only use an exclusive open if we're not debugging.
	fd = open(dev.devname, O_RDONLY | (debug ? 0 : O_EXLOCK));
	if (fd == -1) {
		err(kBadExit, "cannot open raw device %s", dev.devname);
	}
	// Get the block size and counts for the device.
	if (ioctl(fd, DKIOCGETBLOCKSIZE, &dev.blockSize) == -1) {
		dev.blockSize = 512;	// Sane default, I hope
	}
	if (ioctl(fd, DKIOCGETBLOCKCOUNT, &dev.blockCount) == -1) {
		err(kBadExit, "cannot get size of device %s", dev.devname);
	}

	dev.size = dev.blockCount * dev.blockSize;
	dev.fd = fd;

	retval = malloc(sizeof(*retval));
	if (retval == NULL) {
		err(kBadExit, "cannot allocate device info structure");
	}
	*retval = dev;
	return retval;
}

/*
 * Get the header and alternate header for a device.
 */
__private_extern__
VolumeDescriptor_t *
VolumeInfo(DeviceInfo_t *devp)
{
	uint8_t buffer[devp->blockSize];
	VolumeDescriptor_t *vdp = NULL, vd = { 0 };
	ssize_t rv;

	vd.priOffset = 1024;	// primary volume header is at 1024 bytes
	vd.altOffset = devp->size - 1024;	// alternate header is 1024 bytes from the end

	rv = GetBlock(devp, vd.priOffset, buffer);
	if (rv == -1) {
		err(kBadExit, "cannot get primary volume header for device %s", devp->devname);
	}
	vd.priHeader = *(HFSPlusVolumeHeader*)buffer;

	rv = GetBlock(devp, vd.altOffset, buffer);
	if (rv == -1) {
		err(kBadExit, "cannot get alternate volume header for device %s", devp->devname);
	}
	vd.altHeader = *(HFSPlusVolumeHeader*)buffer;

	vdp = malloc(sizeof(*vdp));
	*vdp = vd;

	return vdp;
}

/*
 * Compare the primary and alternate volume headers.
 * We only care about the "important" bits (namely, the
 * portions related to extents).
 */
__private_extern__
int
CompareVolumeHeaders(VolumeDescriptor_t *vdp)
{
	int retval = -1;

#define CMP_FILE(v, f) memcmp(&(v)->priHeader.f, &(v)->altHeader.f, sizeof(v->priHeader.f))

	if (vdp &&
	    vdp->priHeader.journalInfoBlock == vdp->altHeader.journalInfoBlock &&
	    CMP_FILE(vdp, allocationFile) == 0 &&
	    CMP_FILE(vdp, extentsFile) == 0 &&
	    CMP_FILE(vdp, catalogFile) == 0 &&
	    CMP_FILE(vdp, attributesFile) == 0 &&
	    CMP_FILE(vdp, startupFile) == 0)
		retval = 0;
#undef CMP_FILE
	return retval;
}

/*
 * Only two (currently) types of signatures are valid: H+ and HX.
 */
static int
IsValidSigWord(uint16_t word) {
	if (word == kHFSPlusSigWord ||
	    word == kHFSXSigWord)
		return 1;
	return 0;
}

/*
 * Add the volume headers to the in-core volume information list.
 */
__private_extern__
int
AddHeaders(VolumeObjects_t *vop, int roundBlock)
{
	int retval = 1;
	HFSPlusVolumeHeader *hp;
	uint8_t buffer[vop->devp->blockSize];
	ssize_t rv;

	hp = &vop->vdp->priHeader;

	if (IsValidSigWord(S16(hp->signature)) == 0) {
		warnx("primary volume header signature = %x, invalid", S16(hp->signature));
		retval = 0;
	}
	if (roundBlock) {
		AddExtent(vop, 1024 / vop->devp->blockSize, vop->devp->blockSize);
	} else {
		AddExtent(vop, 1024, 512);
	}

	hp = &vop->vdp->altHeader;

	if (IsValidSigWord(S16(hp->signature)) == 0) {
		warnx("alternate volume header signature = %x, invalid", S16(hp->signature));
		retval = 0;
	}
	if (roundBlock) {
		AddExtent(vop, (vop->vdp->altOffset / vop->devp->blockSize) * vop->devp->blockSize, vop->devp->blockSize);
	} else {
		AddExtent(vop, vop->vdp->altOffset, 512);
	}

done:
	return retval;
}

/*
 * Add the journal information to the in-core volume list.
 * This means the journal info block, the journal itself, and
 * the contents of the same as described by the alternate volume
 * header (if it's different from the primary volume header).
 */
__private_extern__
void
AddJournal(VolumeObjects_t *vop)
{
	DeviceInfo_t *devp = vop->devp;
	uint8_t buffer[devp->blockSize];
	ssize_t rv;
	HFSPlusVolumeHeader *php, *ahp;
	JournalInfoBlock *jib;

	php = &vop->vdp->priHeader;
	ahp = &vop->vdp->altHeader;

	if (php->journalInfoBlock) {
		off_t jOffset = (off_t)S32(php->journalInfoBlock) * S32(php->blockSize);
		rv = GetBlock(devp, jOffset, buffer);
		if (rv == -1) {
			err(kBadExit, "cannot get primary header's copy of journal info block");
		}
		AddExtent(vop, jOffset, sizeof(buffer));
		jib = (JournalInfoBlock*)buffer;
		if (S32(jib->flags) & kJIJournalInFSMask) {
			AddExtent(vop, S64(jib->offset), S64(jib->size));
		}
	}

	if (ahp->journalInfoBlock &&
	    ahp->journalInfoBlock != php->journalInfoBlock) {
		off_t jOffset = (off_t)S32(ahp->journalInfoBlock) * S32(ahp->blockSize);
		rv = GetBlock(devp, jOffset, buffer);
		if (rv == -1) {
			err(kBadExit, "cannot get alternate header's copy of journal info block");
		}
		AddExtent(vop, jOffset, sizeof(buffer));
		jib = (JournalInfoBlock*)buffer;
		if (S32(jib->flags) & kJIJournalInFSMask) {
			AddExtent(vop, S64(jib->offset), S64(jib->size));
		}
	}

}

/*
 * Add the extents for the special files in the volume header.  Compare
 * them with the alternate volume header's versions, and if they're different,
 * add that as well.
 */
__private_extern__
void
AddFileExtents(VolumeObjects_t *vop)
{
	int useAlt = 0;
#define ADDEXTS(vop, file, fid)			\
	do { \
		off_t pSize = S32(vop->vdp->priHeader.blockSize);	\
		off_t aSize = S32(vop->vdp->altHeader.blockSize);	\
		int i;							\
		if (debug) printf("Adding " #file " extents\n");		\
		for (i = 0; i < kHFSPlusExtentDensity; i++) {		\
			HFSPlusExtentDescriptor *ep = &vop->vdp->priHeader. file .extents[i]; \
			HFSPlusExtentDescriptor *ap = &vop->vdp->altHeader. file .extents[i]; \
			if (debug) printf("\tExtent <%u, %u>\n", S32(ep->startBlock), S32(ep->blockCount)); \
			if (ep->startBlock && ep->blockCount) {		\
				AddExtentForFile(vop, S32(ep->startBlock) * pSize, S32(ep->blockCount) * pSize, fid); \
				if (memcmp(ep, ap, sizeof(*ep)) != 0) { \
					AddExtentForFile(vop, S32(ap->startBlock) * aSize, S32(ap->blockCount) * aSize, fid); \
					useAlt = 1;			\
				}					\
			}						\
		}							\
	} while (0)

	ADDEXTS(vop, allocationFile, kHFSAllocationFileID);
	ADDEXTS(vop, extentsFile, kHFSExtentsFileID);
	ADDEXTS(vop, catalogFile, kHFSCatalogFileID);
	ADDEXTS(vop, attributesFile, kHFSAttributesFileID);
	ADDEXTS(vop, startupFile, kHFSStartupFileID);

#undef ADDEXTS

	ScanExtents(vop, 0);
	if (useAlt)
		ScanExtents(vop, useAlt);

	return;
}

static int
ScanCatalogNode(VolumeObjects_t *vop, uint8_t *buffer, size_t nodeSize, extent_handler_t handler)
{
	BTNodeDescriptor *ndp = (BTNodeDescriptor *)buffer;
	uint16_t *indices = (uint16_t*)(buffer + nodeSize);
	size_t counter;
	off_t blockSize = S32(vop->vdp->priHeader.blockSize);
	int retval = 0;

	if (ndp->kind != kBTLeafNode)	// Skip if it's not a leaf node
		return 0;

	if (debug)
		fprintf(stderr, "%s:  scanning catalog node\n", __FUNCTION__);

	for (counter = 1; counter <= S16(ndp->numRecords); counter++) {
		// Need to get past the end of the key
		uint16_t recOffset = S16(indices[-counter]);
		HFSPlusCatalogKey *keyp = (HFSPlusCatalogKey*)(buffer + recOffset);
		size_t keyLength = S16(keyp->keyLength);
		// Add two because the keyLength field is not included.
		HFSPlusCatalogFile *fp = (HFSPlusCatalogFile*)(((uint8_t*)keyp) +  2 + keyLength + (keyLength & 1));

		if (S16(fp->recordType) != kHFSPlusFileRecord) {
			if (debug)
				fprintf(stderr, "%s:  skipping node record %zu because it is type %#x, at offset %u keyLength %zu\n", __FUNCTION__, counter, S16(fp->recordType), recOffset, keyLength);
			continue;
		}

		if (debug)
			fprintf(stderr, "%s:  node record %zu, file id = %u\n", __FUNCTION__, counter, S32(fp->fileID));
		if (S32(fp->userInfo.fdType) == kSymLinkFileType &&
		    S32(fp->userInfo.fdCreator) == kSymLinkCreator) {
			unsigned int fid = S32(fp->fileID);
			HFSPlusExtentDescriptor *extPtr = fp->dataFork.extents;
			int i;

			for (i = 0; i < 8; i++) {
				if (extPtr[i].startBlock &&
				    extPtr[i].blockCount) {
					off_t start = blockSize * S32(extPtr[i].startBlock);
					off_t length = blockSize * S32(extPtr[i].blockCount);
					retval = handler(fid, start, length);
					if (retval != 0)
						return retval;
				} else {
					break;
				}
			}
		}
	}
	return retval;
}

static int
ScanAttrNode(VolumeObjects_t *vop, uint8_t *buffer, size_t nodeSize, extent_handler_t handler)
{
	BTNodeDescriptor *ndp = (BTNodeDescriptor *)buffer;
	uint16_t *indices = (uint16_t*)(buffer + nodeSize);
	size_t counter;
	off_t blockSize = S32(vop->vdp->priHeader.blockSize);
	int retval = 0;

	if (ndp->kind != kBTLeafNode)
		return 0;	// Skip if it's not a leaf node

	/*
	 * Look for records of type kHFSPlusForkData and kHFSPlusAttrExtents
	 */
	for (counter = 1; counter <= S16(ndp->numRecords); counter++) {
		// Need to get past the end of the key
		unsigned int fid;
		HFSPlusAttrKey *keyp = (HFSPlusAttrKey*)(buffer + S16(indices[-counter]));
		size_t keyLength = S16(keyp->keyLength);
		// Add two because the keyLength field is not included.
		HFSPlusAttrRecord *ap = (HFSPlusAttrRecord*)(((uint8_t*)keyp) + 2 + keyLength + (keyLength & 1));
		HFSPlusExtentDescriptor *theExtents = NULL;
		switch (S32(ap->recordType)) {
		case kHFSPlusAttrForkData:
			theExtents = ap->forkData.theFork.extents;
			break;
		case kHFSPlusAttrExtents:
			theExtents = ap->overflowExtents.extents;
			break;
		default:
			break;
		}
		if (theExtents != NULL) {
			HFSPlusExtentDescriptor *extPtr = theExtents;
			int i;
			fid = S32(keyp->fileID);

			for (i = 0; i < 8; i++) {
				if (extPtr[i].startBlock &&
				    extPtr[i].blockCount) {
					off_t start = blockSize * S32(extPtr[i].startBlock);
					off_t length = blockSize * S32(extPtr[i].blockCount);
					retval = handler(fid, start, length);
					if (retval != 0)
						return retval;
				} else {
					break;
				}
			}
		}
	}
	return retval;
}


/*
 * Given a VolumeObject_t, search for the other metadata that
 * aren't described by the system files, but rather in the
 * system files.  This includes symbolic links, and large EA
 * extents.  We can do this at one of two times -- while copying
 * the data, or while setting up the list of extents.  The
 * former is going to be more efficient, but the latter will
 * mean the estimates and continuation will be less likely to
 * be wrong as we add extents to the list.
 */
__private_extern__
int
FindOtherMetadata(VolumeObjects_t *vop, extent_handler_t handler)
{
	size_t catNodeSize = 0, attrNodeSize = 0;
	off_t node0_location = 0;
	uint8_t *tBuffer;
	BTHeaderRec *hdp;
	BTNodeDescriptor *ndp;
	int retval = 0;

	tBuffer = calloc(1, vop->devp->blockSize);
	if (tBuffer == NULL) {
		warn("Could not allocate memory to collect extra metadata");
		goto done;
	}
	/*
	 * First, do the catalog file
	 */
	if (vop->vdp->priHeader.catalogFile.logicalSize) {

		node0_location = S32(vop->vdp->priHeader.catalogFile.extents[0].startBlock);
		node0_location = node0_location * S32(vop->vdp->priHeader.blockSize);
		if (GetBlock(vop->devp, node0_location, tBuffer) == -1) {
			warn("Could not read catalog header node");
		} else {
			ndp = (BTNodeDescriptor*)tBuffer;
			hdp = (BTHeaderRec*)(tBuffer + sizeof(BTNodeDescriptor));
			
			if (ndp->kind != kBTHeaderNode) {
				warnx("Did not read header node for catalog as expected");
			} else {
				catNodeSize = S16(hdp->nodeSize);
			}
		}
	}
	/*
	 * Now, the attributes file.
	 */
	if (vop->vdp->priHeader.attributesFile.logicalSize) {

		node0_location = S32(vop->vdp->priHeader.attributesFile.extents[0].startBlock);
		node0_location = node0_location * S32(vop->vdp->priHeader.blockSize);
		if (GetBlock(vop->devp, node0_location, tBuffer) == -1) {
			warn("Could not read attributes file header node");
		} else {
			ndp = (BTNodeDescriptor*)tBuffer;
			hdp = (BTHeaderRec*)(tBuffer + sizeof(BTNodeDescriptor));
			
			if (ndp->kind != kBTHeaderNode) {
				warnx("Did not read header node for attributes file as expected");
			} else {
				attrNodeSize = S16(hdp->nodeSize);
			}
		}
	}
	if (debug)
		fprintf(stderr, "Catalog node size = %zu, attributes node size = %zu\n", catNodeSize, attrNodeSize);

	/*
	 * We start reading the extents now.
	 *
	 * This is a lot of duplicated code, unfortunately.
	 */
	ExtentList_t *exts;
	for (exts = vop->list;
	     exts;
	     exts = exts->next) {
		size_t indx;
		
		for (indx = 0; indx < exts->count; indx++) {
			off_t start = exts->extents[indx].base;
			off_t len = exts->extents[indx].length;
			off_t nread = 0;
			if (exts->extents[indx].fid == 0) {
				continue;	// Unknown file, skip
			} else {
				if (debug) fprintf(stderr, "%s:  fid = %u, start = %llu, len = %llu\n", __FUNCTION__, exts->extents[indx].fid, start, len);
				while (nread < len) {
					size_t bufSize;
					uint8_t *buffer;
					bufSize = MIN(len - nread, 1024 * 1024);	// Read 1mbyte max
					buffer = calloc(1, bufSize);
					if (buffer == NULL) {
						warn("Cannot allocate %zu bytes for buffer, skipping node scan", bufSize);
					} else {
						ssize_t t = UnalignedRead(vop->devp, buffer, bufSize, start + nread);
						if (t != bufSize) {
							warn("Attempted to read %zu bytes, only read %zd, skipping node scan", bufSize, t);
						} else {
							uint8_t *curPtr = buffer, *endPtr = (buffer + bufSize);
							size_t nodeSize = 0;
							int (*func)(VolumeObjects_t *, uint8_t *, size_t, extent_handler_t) = NULL;
							if (exts->extents[indx].fid == kHFSCatalogFileID) {
								func = ScanCatalogNode;
								nodeSize = catNodeSize;
							} else if (exts->extents[indx].fid == kHFSAttributesFileID) {
								func = ScanAttrNode;
								nodeSize = attrNodeSize;
							}
							if (func) {
								while (curPtr < endPtr && retval == 0) {
									retval = (*func)(vop, curPtr, nodeSize, handler);
									curPtr += nodeSize;
								}
							}
						}
						free(buffer);
					}
					if (retval != 0)
						goto done;
					nread += bufSize;
				}
			}
		}
	}

done:
	if (tBuffer)
		free(tBuffer);
	return retval;
}

/*
 * Perform a (potentially) unaligned read from a given input device.
 */
__private_extern__
ssize_t
UnalignedRead(DeviceInfo_t *devp, void *buffer, size_t size, off_t offset)
{
	ssize_t nread = -1;
	size_t readSize = ((size + devp->blockSize - 1) / devp->blockSize) * devp->blockSize;
	off_t baseOffset = (offset / devp->blockSize) * devp->blockSize;
	size_t off = offset - baseOffset;
	char *tmpbuf = NULL;
	
	if ((baseOffset == offset) && (readSize == size)) {
		/*
		 * The read is already properly aligned, so call pread.
		 */
		return pread(devp->fd, buffer, size, offset);
	}
	
	tmpbuf = malloc(readSize);
	if (!tmpbuf) {
		goto done;
	}
	
	nread = pread(devp->fd, tmpbuf, readSize, baseOffset);
	if (nread == -1) {
		goto done;
	}
	
	nread -= off;
	if (nread > (ssize_t)size) {
		nread = size;
	}
	memcpy(buffer, tmpbuf + off, nread);
	
done:
	free(tmpbuf);
	return nread;
}

__private_extern__
void
ReleaseDeviceInfo(DeviceInfo_t *devp)
{
	if (devp) {
		if (devp->fd != -1) {
			close(devp->fd);
		}
		if (devp->devname)
			free(devp->devname);
		free(devp);
	}
	return;
}

__private_extern__
void
ReleaseVolumeDescriptor(VolumeDescriptor_t *vdp)
{
	if (vdp)
		free(vdp);	// No contained pointers!
	return;
}

__private_extern__
void
ReleaseVolumeObjects(VolumeObjects_t *vop)
{
	if (vop) {
		if (vop->devp) {
			ReleaseDeviceInfo(vop->devp);
		}
		if (vop->vdp) {
			ReleaseVolumeDescriptor(vop->vdp);
		}
		ExtentList_t *extList;
		for (extList = vop->list;
		     extList;
			) {
			ExtentList_t *next = extList->next;
			free(extList);
			extList = next;
		}
		free(vop);
	}
}