#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"
__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);
}
if (flushJournal && getvfsbyname("hfs", &vfc) == 0) {
int rv;
int mib[4];
char block_device[MAXPATHLEN+1];
int jfd;
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");
}
(void)fcntl(jfd, F_FULLFSYNC, 0);
close(jfd);
}
}
if ((sb.st_mode & S_IFMT) == S_IFCHR) {
dev.devname = strdup(devname);
} else if (strncmp(devname, "/dev/disk", 9) == 0) {
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);
}
fd = open(dev.devname, O_RDONLY | (debug ? 0 : O_SHLOCK));
if (fd == -1) {
err(kBadExit, "cannot open raw device %s", dev.devname);
}
if (ioctl(fd, DKIOCGETBLOCKSIZE, &dev.blockSize) == -1) {
dev.blockSize = 512; }
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;
}
__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; vd.altOffset = devp->size - 1024;
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;
}
__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;
}
static int
IsValidSigWord(uint16_t word) {
if (word == kHFSPlusSigWord ||
word == kHFSXSigWord)
return 1;
return 0;
}
__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;
}
__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));
}
}
}
__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) return 0;
if (debug)
fprintf(stderr, "%s: scanning catalog node\n", __FUNCTION__);
for (counter = 1; counter <= S16(ndp->numRecords); counter++) {
uint16_t recOffset = S16(indices[-counter]);
HFSPlusCatalogKey *keyp = (HFSPlusCatalogKey*)(buffer + recOffset);
size_t keyLength = S16(keyp->keyLength);
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;
for (counter = 1; counter <= S16(ndp->numRecords); counter++) {
unsigned int fid;
HFSPlusAttrKey *keyp = (HFSPlusAttrKey*)(buffer + S16(indices[-counter]));
size_t keyLength = S16(keyp->keyLength);
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;
}
__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;
}
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);
}
}
}
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);
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; } 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); 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;
}
__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)) {
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); 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);
}
}