10 #include <sys/sysctl.h>
11 #include <hfs/hfs_mount.h>
17 * Used to automatically run a corruption program after the
18 * copying is done. Only used during development. Uncomment
21 //#define TESTINJECT 1
23 static const char *kAppleInternal
= "/AppleInternal";
24 static const char *kTestProgram
= "HC-Inject-Errors";
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
40 * kBadExit: Any other problem.
44 kNoSpaceExit
= ENOSPC
,
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
57 OpenDevice(const char *devname
)
60 DeviceInfo_t
*retval
= NULL
;
62 DeviceInfo_t dev
= { 0 };
66 if (stat(devname
, &sb
) == -1) {
67 err(kBadExit
, "cannot open device %s", devname
);
70 * Attempt to flush the journal. If it fails, we just warn, but don't abort.
72 if (getvfsbyname("hfs", &vfc
) == 0) {
75 char block_device
[MAXPATHLEN
+1];
79 * The journal replay code, sadly, requires a block device.
80 * So we need to go from the raw device to block device, if
83 if (strncmp(devname
, "/dev/rdisk", 10) == 0) {
84 snprintf(block_device
, sizeof(block_device
), "/dev/%s", devname
+6);
86 snprintf(block_device
, sizeof(block_device
), "%s", devname
);
88 jfd
= open(block_device
, O_RDWR
);
90 warn("Cannot open block device %s for read-write", block_device
);
93 mib
[1] = vfc
.vfc_typenum
;
94 mib
[2] = HFS_REPLAY_JOURNAL
;
97 fprintf(stderr
, "about to replay journal\n");
98 rv
= sysctl(mib
, 4, NULL
, NULL
, NULL
, 0);
100 warn("cannot replay journal");
102 /* This is probably not necessary, but we couldn't prove it. */
103 (void)fcntl(jfd
, F_FULLFSYNC
, 0);
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*
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
);
121 if ((sb
.st_mode
& S_IFMT
) != S_IFCHR
) {
122 errx(kBadExit
, "raw device %s is not a raw device", tmpname
);
124 dev
.devname
= strdup(tmpname
);
126 errx(kBadExit
, "device name `%s' does not fit pattern", devname
);
128 // Only use an exclusive open if we're not debugging.
129 fd
= open(dev
.devname
, O_RDONLY
| (debug
? 0 : O_EXLOCK
));
131 err(kBadExit
, "cannot open raw device %s", dev
.devname
);
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
137 if (ioctl(fd
, DKIOCGETBLOCKCOUNT
, &dev
.blockCount
) == -1) {
138 err(kBadExit
, "cannot get size of device %s", dev
.devname
);
141 dev
.size
= dev
.blockCount
* dev
.blockSize
;
144 retval
= malloc(sizeof(*retval
));
145 if (retval
== NULL
) {
146 err(kBadExit
, "cannot allocate device info structure");
153 * Get the header and alternate header for a device.
156 VolumeInfo(DeviceInfo_t
*devp
)
158 uint8_t buffer
[devp
->blockSize
];
159 VolumeDescriptor_t
*vdp
= NULL
, vd
= { 0 };
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
165 rv
= GetBlock(devp
, vd
.priOffset
, buffer
);
167 err(kBadExit
, "cannot get primary volume header for device %s", devp
->devname
);
169 vd
.priHeader
= *(HFSPlusVolumeHeader
*)buffer
;
171 rv
= GetBlock(devp
, vd
.altOffset
, buffer
);
173 err(kBadExit
, "cannot get alternate volume header for device %s", devp
->devname
);
175 vd
.altHeader
= *(HFSPlusVolumeHeader
*)buffer
;
177 vdp
= malloc(sizeof(*vdp
));
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.
189 CompareVolumeHeaders(HFSPlusVolumeHeader
*left
, HFSPlusVolumeHeader
*right
)
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
)
204 * Only two (currently) types of signatures are valid: H+ and HX.
207 IsValidSigWord(uint16_t word
) {
208 if (word
== kHFSPlusSigWord
||
209 word
== kHFSXSigWord
)
215 * Add the volume headers to the in-core volume information list.
218 AddHeaders(VolumeObjects_t
*vop
)
221 HFSPlusVolumeHeader
*hp
;
222 uint8_t buffer
[vop
->devp
->blockSize
];
225 hp
= &vop
->vdp
->priHeader
;
227 if (IsValidSigWord(S16(hp
->signature
)) == 0) {
228 warnx("primary volume header signature = %x, invalid", S16(hp
->signature
));
231 AddExtent(vop
, 1024, 512);
233 hp
= &vop
->vdp
->altHeader
;
235 if (IsValidSigWord(S16(hp
->signature
)) == 0) {
236 warnx("alternate volume header signature = %x, invalid", S16(hp
->signature
));
239 AddExtent(vop
, vop
->vdp
->altOffset
, 512);
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).
252 AddJournal(VolumeObjects_t
*vop
)
254 DeviceInfo_t
*devp
= vop
->devp
;
255 uint8_t buffer
[devp
->blockSize
];
257 HFSPlusVolumeHeader
*php
, *ahp
;
258 JournalInfoBlock
*jib
;
260 php
= &vop
->vdp
->priHeader
;
261 ahp
= &vop
->vdp
->altHeader
;
263 if (php
->journalInfoBlock
) {
264 off_t jOffset
= (off_t
)S32(php
->journalInfoBlock
) * S32(php
->blockSize
);
265 rv
= GetBlock(devp
, jOffset
, buffer
);
267 err(kBadExit
, "cannot get primary header's copy of journal info block");
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
));
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
);
281 err(kBadExit
, "cannot get alternate header's copy of journal info block");
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
));
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,
298 AddFileExtents(VolumeObjects_t
*vop
)
301 #define ADDEXTS(vop, file) \
303 off_t pSize = S32(vop->vdp->priHeader.blockSize); \
304 off_t aSize = S32(vop->vdp->altHeader.blockSize); \
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); \
321 ADDEXTS(vop
, allocationFile
);
322 ADDEXTS(vop
, extentsFile
);
323 ADDEXTS(vop
, catalogFile
);
324 ADDEXTS(vop
, attributesFile
);
325 ADDEXTS(vop
, startupFile
);
331 ScanExtents(vop
, useAlt
);
337 usage(const char *progname
)
340 errx(kBadExit
, "usage: %s [-vdpS] [-g gatherFile] [-r <bytes>] <src device> <destination>", progname
);
344 main(int ac
, char **av
)
348 DeviceInfo_t
*devp
= NULL
;
349 VolumeDescriptor_t
*vdp
= NULL
;
350 VolumeObjects_t
*vop
= NULL
;
351 IOWrapper_t
*wrapper
= NULL
;
354 int printEstimate
= 0;
355 const char *progname
= av
[0];
358 int retval
= kGoodExit
;
360 while ((ch
= getopt(ac
, av
, "fvdg:Spr:")) != -1) {
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
);
376 if (ac
== 0 || ac
> 2) {
383 // Start by opening the input device
384 devp
= OpenDevice(src
);
386 errx(kBadExit
, "cannot get device information for %s", src
);
389 // Get the volume information.
390 vdp
= VolumeInfo(devp
);
392 // Start creating the in-core volume list
393 vop
= InitVolumeObject(devp
, vdp
);
395 // Add the volume headers
396 if (AddHeaders(vop
) == 0) {
397 errx(kBadExit
, "Invalid volume header(s) for %s", src
);
399 // Add the journal and file extents
404 PrintVolumeObject(vop
);
407 printf("Estimate %llu\n", vop
->byteCount
);
410 // Create a gatherHFS-compatible file, if requested.
412 WriteGatheredData(gather
, vop
);
416 * If we're given a destination, initialize it.
419 wrapper
= InitSparseBundle(dst
, devp
);
423 // See if we're picking up from a previous copy
425 restart
= wrapper
->getprog(wrapper
);
427 fprintf(stderr
, "auto-restarting at offset %lld\n", restart
);
430 // "force" in this case means try even if the space estimate says we won't succeed.
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
);
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.
446 HFSPlusVolumeHeader priHeader
, altHeader
;
448 if (wrapper
->reader(wrapper
, 1024, &priHeader
, sizeof(priHeader
)) != -1) {
449 if (CompareVolumeHeaders(&priHeader
, &vop
->vdp
->priHeader
) == 0) {
452 if (wrapper
->reader(wrapper
, vop
->vdp
->altOffset
, &altHeader
, sizeof(altHeader
)) != -1) {
453 if (CompareVolumeHeaders(&altHeader
, &vop
->vdp
->altHeader
) == 0) {
461 warnx("Destination volume does not match source, starting from beginning");
465 // And start copying the objects.
466 if (CopyObjectsToDest(vop
, wrapper
, restart
) == -1) {
468 retval
= kCopyIOExit
;
469 else if (errno
== EINTR
)
473 err(retval
, "CopyObjectsToDest failed");
476 // Copy finished, let's see if we should run a test program
477 if (access(kAppleInternal
, 0) != -1) {
478 char *home
= getenv("HOME");
481 pName
= malloc(strlen(home
) + strlen(kTestProgram
) + 2); // '/' and NUL
483 sprintf(pName
, "%s/%s", home
, kTestProgram
);
484 execl(pName
, kTestProgram
, dst
, NULL
);