+#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);
+ }
+}