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_RDWR
| (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 journal. If it fails, we just warn, but don't abort.
105 if (getvfsbyname("hfs", &vfc
) == 0) {
110 mib
[1] = vfc
.vfc_typenum
;
111 mib
[2] = HFS_REPLAY_JOURNAL
;
114 fprintf(stderr
, "about to replay journal\n");
115 rv
= sysctl(mib
, 4, NULL
, NULL
, NULL
, 0);
117 warn("cannot replay journal");
119 /* This is probably not necessary, but we couldn't prove it. */
120 (void)fcntl(fd
, F_FULLFSYNC
, 0);
123 dev
.size
= dev
.blockCount
* dev
.blockSize
;
126 retval
= malloc(sizeof(*retval
));
127 if (retval
== NULL
) {
128 err(kBadExit
, "cannot allocate device info structure");
135 * Get the header and alternate header for a device.
138 VolumeInfo(DeviceInfo_t
*devp
)
140 uint8_t buffer
[devp
->blockSize
];
141 VolumeDescriptor_t
*vdp
= NULL
, vd
= { 0 };
144 vd
.priOffset
= 1024; // primary volume header is at 1024 bytes
145 vd
.altOffset
= devp
->size
- 1024; // alternate header is 1024 bytes from the end
147 rv
= GetBlock(devp
, vd
.priOffset
, buffer
);
149 err(kBadExit
, "cannot get primary volume header for device %s", devp
->devname
);
151 vd
.priHeader
= *(HFSPlusVolumeHeader
*)buffer
;
153 rv
= GetBlock(devp
, vd
.altOffset
, buffer
);
155 err(kBadExit
, "cannot get alternate volume header for device %s", devp
->devname
);
157 vd
.altHeader
= *(HFSPlusVolumeHeader
*)buffer
;
159 vdp
= malloc(sizeof(*vdp
));
166 * Compare two volume headers to see if they're the same. Some fields
167 * we may not care about, so we only compare specific fields. Note that
168 * since we're looking for equality, we don't need to byte swap.
171 CompareVolumeHeaders(HFSPlusVolumeHeader
*left
, HFSPlusVolumeHeader
*right
)
173 if (left
->signature
!= right
->signature
||
174 left
->version
!= right
->version
||
175 left
->modifyDate
!= right
->modifyDate
||
176 left
->fileCount
!= right
->fileCount
||
177 left
->folderCount
!= right
->folderCount
||
178 left
->nextAllocation
!= right
->nextAllocation
||
179 left
->nextCatalogID
!= right
->nextCatalogID
||
180 left
->writeCount
!= right
->writeCount
)
186 * Only two (currently) types of signatures are valid: H+ and HX.
189 IsValidSigWord(uint16_t word
) {
190 if (word
== kHFSPlusSigWord
||
191 word
== kHFSXSigWord
)
197 * Add the volume headers to the in-core volume information list.
200 AddHeaders(VolumeObjects_t
*vop
)
203 HFSPlusVolumeHeader
*hp
;
204 uint8_t buffer
[vop
->devp
->blockSize
];
207 hp
= &vop
->vdp
->priHeader
;
209 if (IsValidSigWord(S16(hp
->signature
)) == 0) {
210 warnx("primary volume header signature = %x, invalid", S16(hp
->signature
));
213 AddExtent(vop
, 1024, 512);
215 hp
= &vop
->vdp
->altHeader
;
217 if (IsValidSigWord(S16(hp
->signature
)) == 0) {
218 warnx("alternate volume header signature = %x, invalid", S16(hp
->signature
));
221 AddExtent(vop
, vop
->vdp
->altOffset
, 512);
228 * Add the journal information to the in-core volume list.
229 * This means the journal info block, the journal itself, and
230 * the contents of the same as described by the alternate volume
231 * header (if it's different from the primary volume header).
234 AddJournal(VolumeObjects_t
*vop
)
236 DeviceInfo_t
*devp
= vop
->devp
;
237 uint8_t buffer
[devp
->blockSize
];
239 HFSPlusVolumeHeader
*php
, *ahp
;
240 JournalInfoBlock
*jib
;
242 php
= &vop
->vdp
->priHeader
;
243 ahp
= &vop
->vdp
->altHeader
;
245 if (php
->journalInfoBlock
) {
246 off_t jOffset
= (off_t
)S32(php
->journalInfoBlock
) * S32(php
->blockSize
);
247 rv
= GetBlock(devp
, jOffset
, buffer
);
249 err(kBadExit
, "cannot get primary header's copy of journal info block");
251 AddExtent(vop
, jOffset
, sizeof(buffer
));
252 jib
= (JournalInfoBlock
*)buffer
;
253 if (S32(jib
->flags
) & kJIJournalInFSMask
) {
254 AddExtent(vop
, S64(jib
->offset
), S64(jib
->size
));
258 if (ahp
->journalInfoBlock
&&
259 ahp
->journalInfoBlock
!= php
->journalInfoBlock
) {
260 off_t jOffset
= (off_t
)S32(ahp
->journalInfoBlock
) * S32(ahp
->blockSize
);
261 rv
= GetBlock(devp
, jOffset
, buffer
);
263 err(kBadExit
, "cannot get alternate header's copy of journal info block");
265 AddExtent(vop
, jOffset
, sizeof(buffer
));
266 jib
= (JournalInfoBlock
*)buffer
;
267 if (S32(jib
->flags
) & kJIJournalInFSMask
) {
268 AddExtent(vop
, S64(jib
->offset
), S64(jib
->size
));
275 * Add the extents for the special files in the volume header. Compare
276 * them with the alternate volume header's versions, and if they're different,
280 AddFileExtents(VolumeObjects_t
*vop
)
283 #define ADDEXTS(vop, file) \
285 off_t pSize = S32(vop->vdp->priHeader.blockSize); \
286 off_t aSize = S32(vop->vdp->altHeader.blockSize); \
288 if (debug) printf("Adding " #file " extents\n"); \
289 for (i = 0; i < kHFSPlusExtentDensity; i++) { \
290 HFSPlusExtentDescriptor *ep = &vop->vdp->priHeader. file .extents[i]; \
291 HFSPlusExtentDescriptor *ap = &vop->vdp->altHeader. file .extents[i]; \
292 if (debug) printf("\tExtent <%u, %u>\n", S32(ep->startBlock), S32(ep->blockCount)); \
293 if (ep->startBlock && ep->blockCount) { \
294 AddExtent(vop, S32(ep->startBlock) * pSize, S32(ep->blockCount) * pSize); \
295 if (memcmp(ep, ap, sizeof(*ep)) != 0) { \
296 AddExtent(vop, S32(ap->startBlock) * aSize, S32(ap->blockCount) * aSize); \
303 ADDEXTS(vop
, allocationFile
);
304 ADDEXTS(vop
, extentsFile
);
305 ADDEXTS(vop
, catalogFile
);
306 ADDEXTS(vop
, attributesFile
);
307 ADDEXTS(vop
, startupFile
);
313 ScanExtents(vop
, useAlt
);
319 usage(const char *progname
)
322 errx(kBadExit
, "usage: %s [-vdpS] [-g gatherFile] [-r <bytes>] <src device> <destination>", progname
);
326 main(int ac
, char **av
)
330 DeviceInfo_t
*devp
= NULL
;
331 VolumeDescriptor_t
*vdp
= NULL
;
332 VolumeObjects_t
*vop
= NULL
;
333 IOWrapper_t
*wrapper
= NULL
;
336 int printEstimate
= 0;
337 const char *progname
= av
[0];
340 int retval
= kGoodExit
;
342 while ((ch
= getopt(ac
, av
, "fvdg:Spr:")) != -1) {
344 case 'v': verbose
++; break;
345 case 'd': debug
++; verbose
++; break;
346 case 'S': printEstimate
= 1; break;
347 case 'p': printProgress
= 1; break;
348 case 'r': restart
= strtoull(optarg
, NULL
, 0); break;
349 case 'g': gather
= strdup(optarg
); break;
350 case 'f': force
= 1; break;
351 default: usage(progname
);
358 if (ac
== 0 || ac
> 2) {
365 // Start by opening the input device
366 devp
= OpenDevice(src
);
368 errx(kBadExit
, "cannot get device information for %s", src
);
371 // Get the volume information.
372 vdp
= VolumeInfo(devp
);
374 // Start creating the in-core volume list
375 vop
= InitVolumeObject(devp
, vdp
);
377 // Add the volume headers
378 if (AddHeaders(vop
) == 0) {
379 errx(kBadExit
, "Invalid volume header(s) for %s", src
);
381 // Add the journal and file extents
386 PrintVolumeObject(vop
);
389 printf("Estimate %llu\n", vop
->byteCount
);
392 // Create a gatherHFS-compatible file, if requested.
394 WriteGatheredData(gather
, vop
);
398 * If we're given a destination, initialize it.
401 wrapper
= InitSparseBundle(dst
, devp
);
405 // See if we're picking up from a previous copy
407 restart
= wrapper
->getprog(wrapper
);
409 fprintf(stderr
, "auto-restarting at offset %lld\n", restart
);
412 // "force" in this case means try even if the space estimate says we won't succeed.
415 if (statfs(dst
, &sfs
) != -1) {
416 off_t freeSpace
= (off_t
)sfs
.f_bsize
* (off_t
)sfs
.f_bfree
;
417 if (freeSpace
< (vop
->byteCount
- restart
)) {
418 errx(kNoSpaceExit
, "free space (%lld) < required space (%lld)", freeSpace
, vop
->byteCount
- restart
);
424 * If we're restarting, we need to compare the volume headers and see if
425 * they're the same. If they're not, we need to start from the beginning.
428 HFSPlusVolumeHeader priHeader
, altHeader
;
430 if (wrapper
->reader(wrapper
, 1024, &priHeader
, sizeof(priHeader
)) != -1) {
431 if (CompareVolumeHeaders(&priHeader
, &vop
->vdp
->priHeader
) == 0) {
434 if (wrapper
->reader(wrapper
, vop
->vdp
->altOffset
, &altHeader
, sizeof(altHeader
)) != -1) {
435 if (CompareVolumeHeaders(&altHeader
, &vop
->vdp
->altHeader
) == 0) {
443 warnx("Destination volume does not match source, starting from beginning");
447 // And start copying the objects.
448 if (CopyObjectsToDest(vop
, wrapper
, restart
) == -1) {
450 retval
= kCopyIOExit
;
451 else if (errno
== EINTR
)
455 err(retval
, "CopyObjectsToDest failed");
458 // Copy finished, let's see if we should run a test program
459 if (access(kAppleInternal
, 0) != -1) {
460 char *home
= getenv("HOME");
463 pName
= malloc(strlen(home
) + strlen(kTestProgram
) + 2); // '/' and NUL
465 sprintf(pName
, "%s/%s", home
, kTestProgram
);
466 execl(pName
, kTestProgram
, dst
, NULL
);