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