1#include <stdio.h> 2#include <stdlib.h> 3#include <unistd.h> 4#include <string.h> 5#include <fcntl.h> 6#include <err.h> 7#include <errno.h> 8#include <sys/stat.h> 9#include <sys/disk.h> 10#include <sys/sysctl.h> 11#include <hfs/hfs_mount.h> 12#include "hfsmeta.h" 13#include "Data.h" 14#include "Sparse.h" 15 16/* 17 * Used to automatically run a corruption program after the 18 * copying is done. Only used during development. Uncomment 19 * to use. 20 */ 21//#define TESTINJECT 1 22 23static const char *kAppleInternal = "/AppleInternal"; 24static const char *kTestProgram = "HC-Inject-Errors"; 25 26int verbose; 27int debug; 28int printProgress; 29 30/* 31 * Exit status values. We use some errno values because 32 * they are convenient. 33 * kGoodExit: we finished copying, and no problems. 34 * kNoSpaceExit: Not enough space for the skeleton copy. 35 * kCopyIOExit: An I/O error occurred. This may not be fatal, 36 * as it may just mean the source device went away. We 37 * can continue later, perhaps. 38 * kIntrExit: The copying was interrupted. As above, this may 39 * not be fatal. 40 * kBadExit: Any other problem. 41 */ 42enum { 43 kGoodExit = 0, 44 kNoSpaceExit = ENOSPC, 45 kCopyIOExit = EIO, 46 kIntrExit = EINTR, 47 kBadExit = 1, 48}; 49 50/* 51 * Open the source device. In addition to opening the device, 52 * this also attempts to flush the journal, and then sets up a 53 * DeviceInfo_t object that will be used when doing the actual 54 * reading. 55 */ 56static DeviceInfo_t * 57OpenDevice(const char *devname) 58{ 59 char *rawname; 60 DeviceInfo_t *retval = NULL; 61 int fd; 62 DeviceInfo_t dev = { 0 }; 63 struct stat sb; 64 struct vfsconf vfc; 65 66 if (stat(devname, &sb) == -1) { 67 err(kBadExit, "cannot open device %s", devname); 68 } 69 /* 70 * Attempt to flush the journal. If it fails, we just warn, but don't abort. 71 */ 72 if (getvfsbyname("hfs", &vfc) == 0) { 73 int rv; 74 int mib[4]; 75 char block_device[MAXPATHLEN+1]; 76 int jfd; 77 78 /* 79 * The journal replay code, sadly, requires a block device. 80 * So we need to go from the raw device to block device, if 81 * necessary. 82 */ 83 if (strncmp(devname, "/dev/rdisk", 10) == 0) { 84 snprintf(block_device, sizeof(block_device), "/dev/%s", devname+6); 85 } else { 86 snprintf(block_device, sizeof(block_device), "%s", devname); 87 } 88 jfd = open(block_device, O_RDWR); 89 if (jfd == -1) { 90 warn("Cannot open block device %s for read-write", block_device); 91 } else { 92 mib[0] = CTL_VFS; 93 mib[1] = vfc.vfc_typenum; 94 mib[2] = HFS_REPLAY_JOURNAL; 95 mib[3] = jfd; 96 if (debug) 97 fprintf(stderr, "about to replay journal\n"); 98 rv = sysctl(mib, 4, NULL, NULL, NULL, 0); 99 if (rv == -1) { 100 warn("cannot replay journal"); 101 } 102 /* This is probably not necessary, but we couldn't prove it. */ 103 (void)fcntl(jfd, F_FULLFSYNC, 0); 104 close(jfd); 105 } 106 } 107 /* 108 * We only allow a character device (e.g., /dev/rdisk1s2) 109 * If we're given a non-character device, we'll try to turn 110 * into a character device assuming a name pattern of /dev/rdisk* 111 */ 112 if ((sb.st_mode & S_IFMT) == S_IFCHR) { 113 dev.devname = strdup(devname); 114 } else if (strncmp(devname, "/dev/disk", 9) == 0) { 115 // Turn "/dev/diskFoo" into "/dev/rdiskFoo" 116 char tmpname[strlen(devname) + 2]; 117 (void)snprintf(tmpname, sizeof(tmpname), "/dev/rdisk%s", devname + sizeof("/dev/disk") - 1); 118 if (stat(tmpname, &sb) == -1) { 119 err(kBadExit, "cannot open raw device %s", tmpname); 120 } 121 if ((sb.st_mode & S_IFMT) != S_IFCHR) { 122 errx(kBadExit, "raw device %s is not a raw device", tmpname); 123 } 124 dev.devname = strdup(tmpname); 125 } else { 126 errx(kBadExit, "device name `%s' does not fit pattern", devname); 127 } 128 // Only use an exclusive open if we're not debugging. 129 fd = open(dev.devname, O_RDONLY | (debug ? 0 : O_EXLOCK)); 130 if (fd == -1) { 131 err(kBadExit, "cannot open raw device %s", dev.devname); 132 } 133 // Get the block size and counts for the device. 134 if (ioctl(fd, DKIOCGETBLOCKSIZE, &dev.blockSize) == -1) { 135 dev.blockSize = 512; // Sane default, I hope 136 } 137 if (ioctl(fd, DKIOCGETBLOCKCOUNT, &dev.blockCount) == -1) { 138 err(kBadExit, "cannot get size of device %s", dev.devname); 139 } 140 141 dev.size = dev.blockCount * dev.blockSize; 142 dev.fd = fd; 143 144 retval = malloc(sizeof(*retval)); 145 if (retval == NULL) { 146 err(kBadExit, "cannot allocate device info structure"); 147 } 148 *retval = dev; 149 return retval; 150} 151 152/* 153 * Get the header and alternate header for a device. 154 */ 155VolumeDescriptor_t * 156VolumeInfo(DeviceInfo_t *devp) 157{ 158 uint8_t buffer[devp->blockSize]; 159 VolumeDescriptor_t *vdp = NULL, vd = { 0 }; 160 ssize_t rv; 161 162 vd.priOffset = 1024; // primary volume header is at 1024 bytes 163 vd.altOffset = devp->size - 1024; // alternate header is 1024 bytes from the end 164 165 rv = GetBlock(devp, vd.priOffset, buffer); 166 if (rv == -1) { 167 err(kBadExit, "cannot get primary volume header for device %s", devp->devname); 168 } 169 vd.priHeader = *(HFSPlusVolumeHeader*)buffer; 170 171 rv = GetBlock(devp, vd.altOffset, buffer); 172 if (rv == -1) { 173 err(kBadExit, "cannot get alternate volume header for device %s", devp->devname); 174 } 175 vd.altHeader = *(HFSPlusVolumeHeader*)buffer; 176 177 vdp = malloc(sizeof(*vdp)); 178 *vdp = vd; 179 180 return vdp; 181} 182 183/* 184 * Compare two volume headers to see if they're the same. Some fields 185 * we may not care about, so we only compare specific fields. Note that 186 * since we're looking for equality, we don't need to byte swap. 187 */ 188int 189CompareVolumeHeaders(HFSPlusVolumeHeader *left, HFSPlusVolumeHeader *right) 190{ 191 if (left->signature != right->signature || 192 left->version != right->version || 193 left->modifyDate != right->modifyDate || 194 left->fileCount != right->fileCount || 195 left->folderCount != right->folderCount || 196 left->nextAllocation != right->nextAllocation || 197 left->nextCatalogID != right->nextCatalogID || 198 left->writeCount != right->writeCount) 199 return 0; 200 return 1; 201} 202 203/* 204 * Only two (currently) types of signatures are valid: H+ and HX. 205 */ 206static int 207IsValidSigWord(uint16_t word) { 208 if (word == kHFSPlusSigWord || 209 word == kHFSXSigWord) 210 return 1; 211 return 0; 212} 213 214/* 215 * Add the volume headers to the in-core volume information list. 216 */ 217int 218AddHeaders(VolumeObjects_t *vop) 219{ 220 int retval = 1; 221 HFSPlusVolumeHeader *hp; 222 uint8_t buffer[vop->devp->blockSize]; 223 ssize_t rv; 224 225 hp = &vop->vdp->priHeader; 226 227 if (IsValidSigWord(S16(hp->signature)) == 0) { 228 warnx("primary volume header signature = %x, invalid", S16(hp->signature)); 229 retval = 0; 230 } 231 AddExtent(vop, 1024, 512); 232 233 hp = &vop->vdp->altHeader; 234 235 if (IsValidSigWord(S16(hp->signature)) == 0) { 236 warnx("alternate volume header signature = %x, invalid", S16(hp->signature)); 237 retval = 0; 238 } 239 AddExtent(vop, vop->vdp->altOffset, 512); 240 241done: 242 return retval; 243} 244 245/* 246 * Add the journal information to the in-core volume list. 247 * This means the journal info block, the journal itself, and 248 * the contents of the same as described by the alternate volume 249 * header (if it's different from the primary volume header). 250 */ 251void 252AddJournal(VolumeObjects_t *vop) 253{ 254 DeviceInfo_t *devp = vop->devp; 255 uint8_t buffer[devp->blockSize]; 256 ssize_t rv; 257 HFSPlusVolumeHeader *php, *ahp; 258 JournalInfoBlock *jib; 259 260 php = &vop->vdp->priHeader; 261 ahp = &vop->vdp->altHeader; 262 263 if (php->journalInfoBlock) { 264 off_t jOffset = (off_t)S32(php->journalInfoBlock) * S32(php->blockSize); 265 rv = GetBlock(devp, jOffset, buffer); 266 if (rv == -1) { 267 err(kBadExit, "cannot get primary header's copy of journal info block"); 268 } 269 AddExtent(vop, jOffset, sizeof(buffer)); 270 jib = (JournalInfoBlock*)buffer; 271 if (S32(jib->flags) & kJIJournalInFSMask) { 272 AddExtent(vop, S64(jib->offset), S64(jib->size)); 273 } 274 } 275 276 if (ahp->journalInfoBlock && 277 ahp->journalInfoBlock != php->journalInfoBlock) { 278 off_t jOffset = (off_t)S32(ahp->journalInfoBlock) * S32(ahp->blockSize); 279 rv = GetBlock(devp, jOffset, buffer); 280 if (rv == -1) { 281 err(kBadExit, "cannot get alternate header's copy of journal info block"); 282 } 283 AddExtent(vop, jOffset, sizeof(buffer)); 284 jib = (JournalInfoBlock*)buffer; 285 if (S32(jib->flags) & kJIJournalInFSMask) { 286 AddExtent(vop, S64(jib->offset), S64(jib->size)); 287 } 288 } 289 290} 291 292/* 293 * Add the extents for the special files in the volume header. Compare 294 * them with the alternate volume header's versions, and if they're different, 295 * add that as well. 296 */ 297void 298AddFileExtents(VolumeObjects_t *vop) 299{ 300 int useAlt = 0; 301#define ADDEXTS(vop, file) \ 302 do { \ 303 off_t pSize = S32(vop->vdp->priHeader.blockSize); \ 304 off_t aSize = S32(vop->vdp->altHeader.blockSize); \ 305 int i; \ 306 if (debug) printf("Adding " #file " extents\n"); \ 307 for (i = 0; i < kHFSPlusExtentDensity; i++) { \ 308 HFSPlusExtentDescriptor *ep = &vop->vdp->priHeader. file .extents[i]; \ 309 HFSPlusExtentDescriptor *ap = &vop->vdp->altHeader. file .extents[i]; \ 310 if (debug) printf("\tExtent <%u, %u>\n", S32(ep->startBlock), S32(ep->blockCount)); \ 311 if (ep->startBlock && ep->blockCount) { \ 312 AddExtent(vop, S32(ep->startBlock) * pSize, S32(ep->blockCount) * pSize); \ 313 if (memcmp(ep, ap, sizeof(*ep)) != 0) { \ 314 AddExtent(vop, S32(ap->startBlock) * aSize, S32(ap->blockCount) * aSize); \ 315 useAlt = 1; \ 316 } \ 317 } \ 318 } \ 319 } while (0) 320 321 ADDEXTS(vop, allocationFile); 322 ADDEXTS(vop, extentsFile); 323 ADDEXTS(vop, catalogFile); 324 ADDEXTS(vop, attributesFile); 325 ADDEXTS(vop, startupFile); 326 327#undef ADDEXTS 328 329 ScanExtents(vop, 0); 330 if (useAlt) 331 ScanExtents(vop, useAlt); 332 333 return; 334} 335 336static void 337usage(const char *progname) 338{ 339 340 errx(kBadExit, "usage: %s [-vdpS] [-g gatherFile] [-r <bytes>] <src device> <destination>", progname); 341} 342 343 344main(int ac, char **av) 345{ 346 char *src = NULL; 347 char *dst = NULL; 348 DeviceInfo_t *devp = NULL; 349 VolumeDescriptor_t *vdp = NULL; 350 VolumeObjects_t *vop = NULL; 351 IOWrapper_t *wrapper = NULL; 352 int ch; 353 off_t restart = 0; 354 int printEstimate = 0; 355 const char *progname = av[0]; 356 char *gather = NULL; 357 int force = 0; 358 int retval = kGoodExit; 359 360 while ((ch = getopt(ac, av, "fvdg:Spr:")) != -1) { 361 switch (ch) { 362 case 'v': verbose++; break; 363 case 'd': debug++; verbose++; break; 364 case 'S': printEstimate = 1; break; 365 case 'p': printProgress = 1; break; 366 case 'r': restart = strtoull(optarg, NULL, 0); break; 367 case 'g': gather = strdup(optarg); break; 368 case 'f': force = 1; break; 369 default: usage(progname); 370 } 371 } 372 373 ac -= optind; 374 av += optind; 375 376 if (ac == 0 || ac > 2) { 377 usage(progname); 378 } 379 src = av[0]; 380 if (ac == 2) 381 dst = av[1]; 382 383 // Start by opening the input device 384 devp = OpenDevice(src); 385 if (devp == NULL) { 386 errx(kBadExit, "cannot get device information for %s", src); 387 } 388 389 // Get the volume information. 390 vdp = VolumeInfo(devp); 391 392 // Start creating the in-core volume list 393 vop = InitVolumeObject(devp, vdp); 394 395 // Add the volume headers 396 if (AddHeaders(vop) == 0) { 397 errx(kBadExit, "Invalid volume header(s) for %s", src); 398 } 399 // Add the journal and file extents 400 AddJournal(vop); 401 AddFileExtents(vop); 402 403 if (debug) 404 PrintVolumeObject(vop); 405 406 if (printEstimate) { 407 printf("Estimate %llu\n", vop->byteCount); 408 } 409 410 // Create a gatherHFS-compatible file, if requested. 411 if (gather) { 412 WriteGatheredData(gather, vop); 413 } 414 415 /* 416 * If we're given a destination, initialize it. 417 */ 418 if (dst) { 419 wrapper = InitSparseBundle(dst, devp); 420 } 421 422 if (wrapper) { 423 // See if we're picking up from a previous copy 424 if (restart == 0) { 425 restart = wrapper->getprog(wrapper); 426 if (debug) { 427 fprintf(stderr, "auto-restarting at offset %lld\n", restart); 428 } 429 } 430 // "force" in this case means try even if the space estimate says we won't succeed. 431 if (force == 0) { 432 struct statfs sfs; 433 if (statfs(dst, &sfs) != -1) { 434 off_t freeSpace = (off_t)sfs.f_bsize * (off_t)sfs.f_bfree; 435 if (freeSpace < (vop->byteCount - restart)) { 436 errx(kNoSpaceExit, "free space (%lld) < required space (%lld)", freeSpace, vop->byteCount - restart); 437 } 438 } 439 } 440 441 /* 442 * If we're restarting, we need to compare the volume headers and see if 443 * they're the same. If they're not, we need to start from the beginning. 444 */ 445 if (restart) { 446 HFSPlusVolumeHeader priHeader, altHeader; 447 448 if (wrapper->reader(wrapper, 1024, &priHeader, sizeof(priHeader)) != -1) { 449 if (CompareVolumeHeaders(&priHeader, &vop->vdp->priHeader) == 0) { 450 restart = 0; 451 } else { 452 if (wrapper->reader(wrapper, vop->vdp->altOffset, &altHeader, sizeof(altHeader)) != -1) { 453 if (CompareVolumeHeaders(&altHeader, &vop->vdp->altHeader) == 0) { 454 restart = 0; 455 } 456 } 457 } 458 } 459 if (restart == 0) { 460 if (verbose) 461 warnx("Destination volume does not match source, starting from beginning"); 462 } 463 } 464 465 // And start copying the objects. 466 if (CopyObjectsToDest(vop, wrapper, restart) == -1) { 467 if (errno == EIO) 468 retval = kCopyIOExit; 469 else if (errno == EINTR) 470 retval = kIntrExit; 471 else 472 retval = kBadExit; 473 err(retval, "CopyObjectsToDest failed"); 474 } else { 475#if TESTINJECT 476 // Copy finished, let's see if we should run a test program 477 if (access(kAppleInternal, 0) != -1) { 478 char *home = getenv("HOME"); 479 if (home) { 480 char *pName; 481 pName = malloc(strlen(home) + strlen(kTestProgram) + 2); // '/' and NUL 482 if (pName) { 483 sprintf(pName, "%s/%s", home, kTestProgram); 484 execl(pName, kTestProgram, dst, NULL); 485 } 486 } 487 } 488#endif 489 } 490 } 491 492 return retval; 493} 494 495