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 * We only allow a character device (e.g., /dev/rdisk1s2)
71 * If we're given a non-character device, we'll try to turn
72 * into a character device assuming a name pattern of /dev/rdisk*
74 if ((sb
.st_mode
& S_IFMT
) == S_IFCHR
) {
75 dev
.devname
= strdup(devname
);
76 } else if (strncmp(devname
, "/dev/disk", 9) == 0) {
77 // Turn "/dev/diskFoo" into "/dev/rdiskFoo"
78 char tmpname
[strlen(devname
) + 2];
79 (void)snprintf(tmpname
, sizeof(tmpname
), "/dev/rdisk%s", devname
+ sizeof("/dev/disk") - 1);
80 if (stat(tmpname
, &sb
) == -1) {
81 err(kBadExit
, "cannot open raw device %s", tmpname
);
83 if ((sb
.st_mode
& S_IFMT
) != S_IFCHR
) {
84 errx(kBadExit
, "raw device %s is not a raw device", tmpname
);
86 dev
.devname
= strdup(tmpname
);
88 errx(kBadExit
, "device name `%s' does not fit pattern", devname
);
90 // Only use an exclusive open if we're not debugging.
91 fd
= open(dev
.devname
, O_RDONLY
| (debug
? 0 : O_EXLOCK
));
93 err(kBadExit
, "cannot open raw device %s", dev
.devname
);
95 // Get the block size and counts for the device.
96 if (ioctl(fd
, DKIOCGETBLOCKSIZE
, &dev
.blockSize
) == -1) {
97 dev
.blockSize
= 512; // Sane default, I hope
99 if (ioctl(fd
, DKIOCGETBLOCKCOUNT
, &dev
.blockCount
) == -1) {
100 err(kBadExit
, "cannot get size of device %s", dev
.devname
);
103 * Attempt to flush the buffer. This works even with a file descriptor
104 * opened for read-only. If it fails, we just warn, but don't abort.
106 if (getvfsbyname("hfs", &vfc
) == 0) {
111 mib
[1] = vfc
.vfc_typenum
;
112 mib
[2] = HFS_REPLAY_JOURNAL
;
115 fprintf(stderr
, "about to replay journal\n");
116 rv
= sysctl(mib
, 4, NULL
, NULL
, NULL
, 0);
118 warn("cannot replay journal");
122 dev
.size
= dev
.blockCount
* dev
.blockSize
;
125 retval
= malloc(sizeof(*retval
));
126 if (retval
== NULL
) {
127 err(kBadExit
, "cannot allocate device info structure");
134 * Get the header and alternate header for a device.
137 VolumeInfo(DeviceInfo_t
*devp
)
139 uint8_t buffer
[devp
->blockSize
];
140 VolumeDescriptor_t
*vdp
= NULL
, vd
= { 0 };
143 vd
.priOffset
= 1024; // primary volume header is at 1024 bytes
144 vd
.altOffset
= devp
->size
- 1024; // alternate header is 1024 bytes from the end
146 rv
= GetBlock(devp
, vd
.priOffset
, buffer
);
148 err(kBadExit
, "cannot get primary volume header for device %s", devp
->devname
);
150 vd
.priHeader
= *(HFSPlusVolumeHeader
*)buffer
;
152 rv
= GetBlock(devp
, vd
.altOffset
, buffer
);
154 err(kBadExit
, "cannot get alternate volume header for device %s", devp
->devname
);
156 vd
.altHeader
= *(HFSPlusVolumeHeader
*)buffer
;
158 vdp
= malloc(sizeof(*vdp
));
165 * Compare two volume headers to see if they're the same. Some fields
166 * we may not care about, so we only compare specific fields. Note that
167 * since we're looking for equality, we don't need to byte swap.
170 CompareVolumeHeaders(HFSPlusVolumeHeader
*left
, HFSPlusVolumeHeader
*right
)
172 if (left
->signature
!= right
->signature
||
173 left
->version
!= right
->version
||
174 left
->modifyDate
!= right
->modifyDate
||
175 left
->fileCount
!= right
->fileCount
||
176 left
->folderCount
!= right
->folderCount
||
177 left
->nextAllocation
!= right
->nextAllocation
||
178 left
->nextCatalogID
!= right
->nextCatalogID
||
179 left
->writeCount
!= right
->writeCount
)
185 * Only two (currently) types of signatures are valid: H+ and HX.
188 IsValidSigWord(uint16_t word
) {
189 if (word
== kHFSPlusSigWord
||
190 word
== kHFSXSigWord
)
196 * Add the volume headers to the in-core volume information list.
199 AddHeaders(VolumeObjects_t
*vop
)
202 HFSPlusVolumeHeader
*hp
;
203 uint8_t buffer
[vop
->devp
->blockSize
];
206 hp
= &vop
->vdp
->priHeader
;
208 if (IsValidSigWord(S16(hp
->signature
)) == 0) {
209 warnx("primary volume header signature = %x, invalid", S16(hp
->signature
));
212 AddExtent(vop
, 1024, 512);
214 hp
= &vop
->vdp
->altHeader
;
216 if (IsValidSigWord(S16(hp
->signature
)) == 0) {
217 warnx("alternate volume header signature = %x, invalid", S16(hp
->signature
));
220 AddExtent(vop
, vop
->vdp
->altOffset
, 512);
227 * Add the journal information to the in-core volume list.
228 * This means the journal info block, the journal itself, and
229 * the contents of the same as described by the alternate volume
230 * header (if it's different from the primary volume header).
233 AddJournal(VolumeObjects_t
*vop
)
235 DeviceInfo_t
*devp
= vop
->devp
;
236 uint8_t buffer
[devp
->blockSize
];
238 HFSPlusVolumeHeader
*php
, *ahp
;
239 JournalInfoBlock
*jib
;
241 php
= &vop
->vdp
->priHeader
;
242 ahp
= &vop
->vdp
->altHeader
;
244 if (php
->journalInfoBlock
) {
245 off_t jOffset
= (off_t
)S32(php
->journalInfoBlock
) * S32(php
->blockSize
);
246 rv
= GetBlock(devp
, jOffset
, buffer
);
248 err(kBadExit
, "cannot get primary header's copy of journal info block");
250 AddExtent(vop
, jOffset
, sizeof(buffer
));
251 jib
= (JournalInfoBlock
*)buffer
;
252 if (S32(jib
->flags
) & kJIJournalInFSMask
) {
253 AddExtent(vop
, S64(jib
->offset
), S64(jib
->size
));
257 if (ahp
->journalInfoBlock
&&
258 ahp
->journalInfoBlock
!= php
->journalInfoBlock
) {
259 off_t jOffset
= (off_t
)S32(ahp
->journalInfoBlock
) * S32(ahp
->blockSize
);
260 rv
= GetBlock(devp
, jOffset
, buffer
);
262 err(kBadExit
, "cannot get alternate header's copy of journal info block");
264 AddExtent(vop
, jOffset
, sizeof(buffer
));
265 jib
= (JournalInfoBlock
*)buffer
;
266 if (S32(jib
->flags
) & kJIJournalInFSMask
) {
267 AddExtent(vop
, S64(jib
->offset
), S64(jib
->size
));
274 * Add the extents for the special files in the volume header. Compare
275 * them with the alternate volume header's versions, and if they're different,
279 AddFileExtents(VolumeObjects_t
*vop
)
282 #define ADDEXTS(vop, file) \
284 off_t pSize = S32(vop->vdp->priHeader.blockSize); \
285 off_t aSize = S32(vop->vdp->altHeader.blockSize); \
287 if (debug) printf("Adding " #file " extents\n"); \
288 for (i = 0; i < kHFSPlusExtentDensity; i++) { \
289 HFSPlusExtentDescriptor *ep = &vop->vdp->priHeader. file .extents[i]; \
290 HFSPlusExtentDescriptor *ap = &vop->vdp->altHeader. file .extents[i]; \
291 if (debug) printf("\tExtent <%u, %u>\n", S32(ep->startBlock), S32(ep->blockCount)); \
292 if (ep->startBlock && ep->blockCount) { \
293 AddExtent(vop, S32(ep->startBlock) * pSize, S32(ep->blockCount) * pSize); \
294 if (memcmp(ep, ap, sizeof(*ep)) != 0) { \
295 AddExtent(vop, S32(ap->startBlock) * aSize, S32(ap->blockCount) * aSize); \
302 ADDEXTS(vop
, allocationFile
);
303 ADDEXTS(vop
, extentsFile
);
304 ADDEXTS(vop
, catalogFile
);
305 ADDEXTS(vop
, attributesFile
);
306 ADDEXTS(vop
, startupFile
);
312 ScanExtents(vop
, useAlt
);
318 usage(const char *progname
)
321 errx(kBadExit
, "usage: %s [-vdpS] [-g gatherFile] [-r <bytes>] <src device> <destination>", progname
);
325 main(int ac
, char **av
)
329 DeviceInfo_t
*devp
= NULL
;
330 VolumeDescriptor_t
*vdp
= NULL
;
331 VolumeObjects_t
*vop
= NULL
;
332 IOWrapper_t
*wrapper
= NULL
;
335 int printEstimate
= 0;
336 const char *progname
= av
[0];
339 int retval
= kGoodExit
;
341 while ((ch
= getopt(ac
, av
, "fvdg:Spr:")) != -1) {
343 case 'v': verbose
++; break;
344 case 'd': debug
++; verbose
++; break;
345 case 'S': printEstimate
= 1; break;
346 case 'p': printProgress
= 1; break;
347 case 'r': restart
= strtoull(optarg
, NULL
, 0); break;
348 case 'g': gather
= strdup(optarg
); break;
349 case 'f': force
= 1; break;
350 default: usage(progname
);
357 if (ac
== 0 || ac
> 2) {
364 // Start by opening the input device
365 devp
= OpenDevice(src
);
367 errx(kBadExit
, "cannot get device information for %s", src
);
370 // Get the volume information.
371 vdp
= VolumeInfo(devp
);
373 // Start creating the in-core volume list
374 vop
= InitVolumeObject(devp
, vdp
);
376 // Add the volume headers
377 if (AddHeaders(vop
) == 0) {
378 errx(kBadExit
, "Invalid volume header(s) for %s", src
);
380 // Add the journal and file extents
385 PrintVolumeObject(vop
);
388 printf("Estimate %llu\n", vop
->byteCount
);
391 // Create a gatherHFS-compatible file, if requested.
393 WriteGatheredData(gather
, vop
);
397 * If we're given a destination, initialize it.
400 wrapper
= InitSparseBundle(dst
, devp
);
404 // See if we're picking up from a previous copy
406 restart
= wrapper
->getprog(wrapper
);
408 fprintf(stderr
, "auto-restarting at offset %lld\n", restart
);
411 // "force" in this case means try even if the space estimate says we won't succeed.
414 if (statfs(dst
, &sfs
) != -1) {
415 off_t freeSpace
= (off_t
)sfs
.f_bsize
* (off_t
)sfs
.f_bfree
;
416 if (freeSpace
< (vop
->byteCount
- restart
)) {
417 errx(kNoSpaceExit
, "free space (%lld) < required space (%lld)", freeSpace
, vop
->byteCount
- restart
);
423 * If we're restarting, we need to compare the volume headers and see if
424 * they're the same. If they're not, we need to start from the beginning.
427 HFSPlusVolumeHeader priHeader
, altHeader
;
429 if (wrapper
->reader(wrapper
, 1024, &priHeader
, sizeof(priHeader
)) != -1) {
430 if (CompareVolumeHeaders(&priHeader
, &vop
->vdp
->priHeader
) == 0) {
433 if (wrapper
->reader(wrapper
, vop
->vdp
->altOffset
, &altHeader
, sizeof(altHeader
)) != -1) {
434 if (CompareVolumeHeaders(&altHeader
, &vop
->vdp
->altHeader
) == 0) {
442 warnx("Destination volume does not match source, starting from beginning");
446 // And start copying the objects.
447 if (CopyObjectsToDest(vop
, wrapper
, restart
) == -1) {
449 retval
= kCopyIOExit
;
450 else if (errno
== EINTR
)
454 err(retval
, "CopyObjectsToDest failed");
457 // Copy finished, let's see if we should run a test program
458 if (access(kAppleInternal
, 0) != -1) {
459 char *home
= getenv("HOME");
462 pName
= malloc(strlen(home
) + strlen(kTestProgram
) + 2); // '/' and NUL
464 sprintf(pName
, "%s/%s", home
, kTestProgram
);
465 execl(pName
, kTestProgram
, dst
, NULL
);