]> git.saurik.com Git - apple/hfs.git/blob - CopyHFSMeta/main.c
ba830fdec9528d0d0fc1009da5696493b882c450
[apple/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 * 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*
73 */
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);
82 }
83 if ((sb.st_mode & S_IFMT) != S_IFCHR) {
84 errx(kBadExit, "raw device %s is not a raw device", tmpname);
85 }
86 dev.devname = strdup(tmpname);
87 } else {
88 errx(kBadExit, "device name `%s' does not fit pattern", devname);
89 }
90 // Only use an exclusive open if we're not debugging.
91 fd = open(dev.devname, O_RDONLY | (debug ? 0 : O_EXLOCK));
92 if (fd == -1) {
93 err(kBadExit, "cannot open raw device %s", dev.devname);
94 }
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
98 }
99 if (ioctl(fd, DKIOCGETBLOCKCOUNT, &dev.blockCount) == -1) {
100 err(kBadExit, "cannot get size of device %s", dev.devname);
101 }
102 /*
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.
105 */
106 if (getvfsbyname("hfs", &vfc) == 0) {
107 int rv;
108 int mib[4];
109
110 mib[0] = CTL_VFS;
111 mib[1] = vfc.vfc_typenum;
112 mib[2] = HFS_REPLAY_JOURNAL;
113 mib[3] = fd;
114 if (debug)
115 fprintf(stderr, "about to replay journal\n");
116 rv = sysctl(mib, 4, NULL, NULL, NULL, 0);
117 if (rv == -1) {
118 warn("cannot replay journal");
119 }
120 }
121
122 dev.size = dev.blockCount * dev.blockSize;
123 dev.fd = fd;
124
125 retval = malloc(sizeof(*retval));
126 if (retval == NULL) {
127 err(kBadExit, "cannot allocate device info structure");
128 }
129 *retval = dev;
130 return retval;
131 }
132
133 /*
134 * Get the header and alternate header for a device.
135 */
136 VolumeDescriptor_t *
137 VolumeInfo(DeviceInfo_t *devp)
138 {
139 uint8_t buffer[devp->blockSize];
140 VolumeDescriptor_t *vdp = NULL, vd = { 0 };
141 ssize_t rv;
142
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
145
146 rv = GetBlock(devp, vd.priOffset, buffer);
147 if (rv == -1) {
148 err(kBadExit, "cannot get primary volume header for device %s", devp->devname);
149 }
150 vd.priHeader = *(HFSPlusVolumeHeader*)buffer;
151
152 rv = GetBlock(devp, vd.altOffset, buffer);
153 if (rv == -1) {
154 err(kBadExit, "cannot get alternate volume header for device %s", devp->devname);
155 }
156 vd.altHeader = *(HFSPlusVolumeHeader*)buffer;
157
158 vdp = malloc(sizeof(*vdp));
159 *vdp = vd;
160
161 return vdp;
162 }
163
164 /*
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.
168 */
169 int
170 CompareVolumeHeaders(HFSPlusVolumeHeader *left, HFSPlusVolumeHeader *right)
171 {
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)
180 return 0;
181 return 1;
182 }
183
184 /*
185 * Only two (currently) types of signatures are valid: H+ and HX.
186 */
187 static int
188 IsValidSigWord(uint16_t word) {
189 if (word == kHFSPlusSigWord ||
190 word == kHFSXSigWord)
191 return 1;
192 return 0;
193 }
194
195 /*
196 * Add the volume headers to the in-core volume information list.
197 */
198 int
199 AddHeaders(VolumeObjects_t *vop)
200 {
201 int retval = 1;
202 HFSPlusVolumeHeader *hp;
203 uint8_t buffer[vop->devp->blockSize];
204 ssize_t rv;
205
206 hp = &vop->vdp->priHeader;
207
208 if (IsValidSigWord(S16(hp->signature)) == 0) {
209 warnx("primary volume header signature = %x, invalid", S16(hp->signature));
210 retval = 0;
211 }
212 AddExtent(vop, 1024, 512);
213
214 hp = &vop->vdp->altHeader;
215
216 if (IsValidSigWord(S16(hp->signature)) == 0) {
217 warnx("alternate volume header signature = %x, invalid", S16(hp->signature));
218 retval = 0;
219 }
220 AddExtent(vop, vop->vdp->altOffset, 512);
221
222 done:
223 return retval;
224 }
225
226 /*
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).
231 */
232 void
233 AddJournal(VolumeObjects_t *vop)
234 {
235 DeviceInfo_t *devp = vop->devp;
236 uint8_t buffer[devp->blockSize];
237 ssize_t rv;
238 HFSPlusVolumeHeader *php, *ahp;
239 JournalInfoBlock *jib;
240
241 php = &vop->vdp->priHeader;
242 ahp = &vop->vdp->altHeader;
243
244 if (php->journalInfoBlock) {
245 off_t jOffset = (off_t)S32(php->journalInfoBlock) * S32(php->blockSize);
246 rv = GetBlock(devp, jOffset, buffer);
247 if (rv == -1) {
248 err(kBadExit, "cannot get primary header's copy of journal info block");
249 }
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));
254 }
255 }
256
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);
261 if (rv == -1) {
262 err(kBadExit, "cannot get alternate header's copy of journal info block");
263 }
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));
268 }
269 }
270
271 }
272
273 /*
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,
276 * add that as well.
277 */
278 void
279 AddFileExtents(VolumeObjects_t *vop)
280 {
281 int useAlt = 0;
282 #define ADDEXTS(vop, file) \
283 do { \
284 off_t pSize = S32(vop->vdp->priHeader.blockSize); \
285 off_t aSize = S32(vop->vdp->altHeader.blockSize); \
286 int i; \
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); \
296 useAlt = 1; \
297 } \
298 } \
299 } \
300 } while (0)
301
302 ADDEXTS(vop, allocationFile);
303 ADDEXTS(vop, extentsFile);
304 ADDEXTS(vop, catalogFile);
305 ADDEXTS(vop, attributesFile);
306 ADDEXTS(vop, startupFile);
307
308 #undef ADDEXTS
309
310 ScanExtents(vop, 0);
311 if (useAlt)
312 ScanExtents(vop, useAlt);
313
314 return;
315 }
316
317 static void
318 usage(const char *progname)
319 {
320
321 errx(kBadExit, "usage: %s [-vdpS] [-g gatherFile] [-r <bytes>] <src device> <destination>", progname);
322 }
323
324
325 main(int ac, char **av)
326 {
327 char *src = NULL;
328 char *dst = NULL;
329 DeviceInfo_t *devp = NULL;
330 VolumeDescriptor_t *vdp = NULL;
331 VolumeObjects_t *vop = NULL;
332 IOWrapper_t *wrapper = NULL;
333 int ch;
334 off_t restart = 0;
335 int printEstimate = 0;
336 const char *progname = av[0];
337 char *gather = NULL;
338 int force = 0;
339 int retval = kGoodExit;
340
341 while ((ch = getopt(ac, av, "fvdg:Spr:")) != -1) {
342 switch (ch) {
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);
351 }
352 }
353
354 ac -= optind;
355 av += optind;
356
357 if (ac == 0 || ac > 2) {
358 usage(progname);
359 }
360 src = av[0];
361 if (ac == 2)
362 dst = av[1];
363
364 // Start by opening the input device
365 devp = OpenDevice(src);
366 if (devp == NULL) {
367 errx(kBadExit, "cannot get device information for %s", src);
368 }
369
370 // Get the volume information.
371 vdp = VolumeInfo(devp);
372
373 // Start creating the in-core volume list
374 vop = InitVolumeObject(devp, vdp);
375
376 // Add the volume headers
377 if (AddHeaders(vop) == 0) {
378 errx(kBadExit, "Invalid volume header(s) for %s", src);
379 }
380 // Add the journal and file extents
381 AddJournal(vop);
382 AddFileExtents(vop);
383
384 if (debug)
385 PrintVolumeObject(vop);
386
387 if (printEstimate) {
388 printf("Estimate %llu\n", vop->byteCount);
389 }
390
391 // Create a gatherHFS-compatible file, if requested.
392 if (gather) {
393 WriteGatheredData(gather, vop);
394 }
395
396 /*
397 * If we're given a destination, initialize it.
398 */
399 if (dst) {
400 wrapper = InitSparseBundle(dst, devp);
401 }
402
403 if (wrapper) {
404 // See if we're picking up from a previous copy
405 if (restart == 0) {
406 restart = wrapper->getprog(wrapper);
407 if (debug) {
408 fprintf(stderr, "auto-restarting at offset %lld\n", restart);
409 }
410 }
411 // "force" in this case means try even if the space estimate says we won't succeed.
412 if (force == 0) {
413 struct statfs sfs;
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);
418 }
419 }
420 }
421
422 /*
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.
425 */
426 if (restart) {
427 HFSPlusVolumeHeader priHeader, altHeader;
428
429 if (wrapper->reader(wrapper, 1024, &priHeader, sizeof(priHeader)) != -1) {
430 if (CompareVolumeHeaders(&priHeader, &vop->vdp->priHeader) == 0) {
431 restart = 0;
432 } else {
433 if (wrapper->reader(wrapper, vop->vdp->altOffset, &altHeader, sizeof(altHeader)) != -1) {
434 if (CompareVolumeHeaders(&altHeader, &vop->vdp->altHeader) == 0) {
435 restart = 0;
436 }
437 }
438 }
439 }
440 if (restart == 0) {
441 if (verbose)
442 warnx("Destination volume does not match source, starting from beginning");
443 }
444 }
445
446 // And start copying the objects.
447 if (CopyObjectsToDest(vop, wrapper, restart) == -1) {
448 if (errno == EIO)
449 retval = kCopyIOExit;
450 else if (errno == EINTR)
451 retval = kIntrExit;
452 else
453 retval = kBadExit;
454 err(retval, "CopyObjectsToDest failed");
455 } else {
456 #if TESTINJECT
457 // Copy finished, let's see if we should run a test program
458 if (access(kAppleInternal, 0) != -1) {
459 char *home = getenv("HOME");
460 if (home) {
461 char *pName;
462 pName = malloc(strlen(home) + strlen(kTestProgram) + 2); // '/' and NUL
463 if (pName) {
464 sprintf(pName, "%s/%s", home, kTestProgram);
465 execl(pName, kTestProgram, dst, NULL);
466 }
467 }
468 }
469 #endif
470 }
471 }
472
473 return retval;
474 }
475