]> git.saurik.com Git - hfs.git/blob - CopyHFSMeta/main.c
hfs-226.1.1.tar.gz
[hfs.git] / CopyHFSMeta / main.c
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
23 static const char *kAppleInternal = "/AppleInternal";
24 static const char *kTestProgram = "HC-Inject-Errors";
25
26 int verbose;
27 int debug;
28 int 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 */
42 enum {
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 */
56 static DeviceInfo_t *
57 OpenDevice(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 */
155 VolumeDescriptor_t *
156 VolumeInfo(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 */
188 int
189 CompareVolumeHeaders(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 */
206 static int
207 IsValidSigWord(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 */
217 int
218 AddHeaders(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
241 done:
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 */
251 void
252 AddJournal(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 */
297 void
298 AddFileExtents(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
336 static void
337 usage(const char *progname)
338 {
339
340 errx(kBadExit, "usage: %s [-vdpS] [-g gatherFile] [-r <bytes>] <src device> <destination>", progname);
341 }
342
343
344 main(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