#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 "hfsmeta.h"
#include "Data.h"
#include "Sparse.h"
static const char *kAppleInternal = "/AppleInternal";
static const char *kTestProgram = "HC-Inject-Errors";
int verbose;
int debug;
int printProgress;
enum {
kGoodExit = 0,
kNoSpaceExit = ENOSPC,
kCopyIOExit = EIO,
kIntrExit = EINTR,
kBadExit = 1,
};
static DeviceInfo_t *
OpenDevice(const char *devname)
{
char *rawname;
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 (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_RDWR | (debug ? 0 : O_EXLOCK));
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;
}
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;
}
int
CompareVolumeHeaders(HFSPlusVolumeHeader *left, HFSPlusVolumeHeader *right)
{
if (left->signature != right->signature ||
left->version != right->version ||
left->modifyDate != right->modifyDate ||
left->fileCount != right->fileCount ||
left->folderCount != right->folderCount ||
left->nextAllocation != right->nextAllocation ||
left->nextCatalogID != right->nextCatalogID ||
left->writeCount != right->writeCount)
return 0;
return 1;
}
static int
IsValidSigWord(uint16_t word) {
if (word == kHFSPlusSigWord ||
word == kHFSXSigWord)
return 1;
return 0;
}
int
AddHeaders(VolumeObjects_t *vop)
{
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;
}
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;
}
AddExtent(vop, vop->vdp->altOffset, 512);
done:
return retval;
}
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));
}
}
}
void
AddFileExtents(VolumeObjects_t *vop)
{
int useAlt = 0;
#define ADDEXTS(vop, file) \
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) { \
AddExtent(vop, S32(ep->startBlock) * pSize, S32(ep->blockCount) * pSize); \
if (memcmp(ep, ap, sizeof(*ep)) != 0) { \
AddExtent(vop, S32(ap->startBlock) * aSize, S32(ap->blockCount) * aSize); \
useAlt = 1; \
} \
} \
} \
} while (0)
ADDEXTS(vop, allocationFile);
ADDEXTS(vop, extentsFile);
ADDEXTS(vop, catalogFile);
ADDEXTS(vop, attributesFile);
ADDEXTS(vop, startupFile);
#undef ADDEXTS
ScanExtents(vop, 0);
if (useAlt)
ScanExtents(vop, useAlt);
return;
}
static void
usage(const char *progname)
{
errx(kBadExit, "usage: %s [-vdpS] [-g gatherFile] [-r <bytes>] <src device> <destination>", progname);
}
main(int ac, char **av)
{
char *src = NULL;
char *dst = NULL;
DeviceInfo_t *devp = NULL;
VolumeDescriptor_t *vdp = NULL;
VolumeObjects_t *vop = NULL;
IOWrapper_t *wrapper = NULL;
int ch;
off_t restart = 0;
int printEstimate = 0;
const char *progname = av[0];
char *gather = NULL;
int force = 0;
int retval = kGoodExit;
while ((ch = getopt(ac, av, "fvdg:Spr:")) != -1) {
switch (ch) {
case 'v': verbose++; break;
case 'd': debug++; verbose++; break;
case 'S': printEstimate = 1; break;
case 'p': printProgress = 1; break;
case 'r': restart = strtoull(optarg, NULL, 0); break;
case 'g': gather = strdup(optarg); break;
case 'f': force = 1; break;
default: usage(progname);
}
}
ac -= optind;
av += optind;
if (ac == 0 || ac > 2) {
usage(progname);
}
src = av[0];
if (ac == 2)
dst = av[1];
devp = OpenDevice(src);
if (devp == NULL) {
errx(kBadExit, "cannot get device information for %s", src);
}
vdp = VolumeInfo(devp);
vop = InitVolumeObject(devp, vdp);
if (AddHeaders(vop) == 0) {
errx(kBadExit, "Invalid volume header(s) for %s", src);
}
AddJournal(vop);
AddFileExtents(vop);
if (debug)
PrintVolumeObject(vop);
if (printEstimate) {
printf("Estimate %llu\n", vop->byteCount);
}
if (gather) {
WriteGatheredData(gather, vop);
}
if (dst) {
wrapper = InitSparseBundle(dst, devp);
}
if (wrapper) {
if (restart == 0) {
restart = wrapper->getprog(wrapper);
if (debug) {
fprintf(stderr, "auto-restarting at offset %lld\n", restart);
}
}
if (force == 0) {
struct statfs sfs;
if (statfs(dst, &sfs) != -1) {
off_t freeSpace = (off_t)sfs.f_bsize * (off_t)sfs.f_bfree;
if (freeSpace < (vop->byteCount - restart)) {
errx(kNoSpaceExit, "free space (%lld) < required space (%lld)", freeSpace, vop->byteCount - restart);
}
}
}
if (restart) {
HFSPlusVolumeHeader priHeader, altHeader;
if (wrapper->reader(wrapper, 1024, &priHeader, sizeof(priHeader)) != -1) {
if (CompareVolumeHeaders(&priHeader, &vop->vdp->priHeader) == 0) {
restart = 0;
} else {
if (wrapper->reader(wrapper, vop->vdp->altOffset, &altHeader, sizeof(altHeader)) != -1) {
if (CompareVolumeHeaders(&altHeader, &vop->vdp->altHeader) == 0) {
restart = 0;
}
}
}
}
if (restart == 0) {
if (verbose)
warnx("Destination volume does not match source, starting from beginning");
}
}
if (CopyObjectsToDest(vop, wrapper, restart) == -1) {
if (errno == EIO)
retval = kCopyIOExit;
else if (errno == EINTR)
retval = kIntrExit;
else
retval = kBadExit;
err(retval, "CopyObjectsToDest failed");
} else {
#if TESTINJECT
if (access(kAppleInternal, 0) != -1) {
char *home = getenv("HOME");
if (home) {
char *pName;
pName = malloc(strlen(home) + strlen(kTestProgram) + 2); if (pName) {
sprintf(pName, "%s/%s", home, kTestProgram);
execl(pName, kTestProgram, dst, NULL);
}
}
}
#endif
}
}
return retval;
}