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 <Block.h>
13#include "hfsmeta.h"
14#include "Data.h"
15#include "Sparse.h"
16
17/*
18 * Used to automatically run a corruption program after the
19 * copying is done.  Only used during development.  Uncomment
20 * to use.
21 */
22//#define TESTINJECT	1
23
24static const char *kAppleInternal = "/AppleInternal";
25static const char *kTestProgram = "HC-Inject-Errors";
26
27int verbose;
28int debug;
29int printProgress;
30
31
32/*
33 * Compare two volume headers to see if they're the same.  Some fields
34 * we may not care about, so we only compare specific fields.  Note that
35 * since we're looking for equality, we don't need to byte swap.
36 * (The function CompareVolumeHeaders() will compare the two volume
37 * headers to see if the extents they describe are the same.)
38 */
39static int
40CheckVolumeHeaders(HFSPlusVolumeHeader *left, HFSPlusVolumeHeader *right)
41{
42	if (left->signature != right->signature ||
43	    left->version != right->version ||
44	    left->modifyDate != right->modifyDate ||
45	    left->fileCount != right->fileCount ||
46	    left->folderCount != right->folderCount ||
47	    left->nextAllocation != right->nextAllocation ||
48	    left->nextCatalogID != right->nextCatalogID ||
49	    left->writeCount != right->writeCount)
50		return 0;
51	return 1;
52}
53
54static void
55usage(const char *progname)
56{
57
58	errx(kBadExit, "usage: %s [-vdpS] [-g gatherFile] [-C] [-r <bytes>] <src device> <destination>", progname);
59}
60
61
62main(int ac, char **av)
63{
64	char *src = NULL;
65	char *dst = NULL;
66	DeviceInfo_t *devp = NULL;
67	VolumeDescriptor_t *vdp = NULL;
68	VolumeObjects_t *vop = NULL;
69	IOWrapper_t *wrapper = NULL;
70	int ch;
71	off_t restart = 0;
72	int printEstimate = 0;
73	const char *progname = av[0];
74	char *gather = NULL;
75	int force = 0;
76	int retval = kGoodExit;
77	int find_all_metadata = 0;
78
79	while ((ch = getopt(ac, av, "fvdg:Spr:CA")) != -1) {
80		switch (ch) {
81		case 'A':	find_all_metadata = 1; break;
82		case 'v':	verbose++; break;
83		case 'd':	debug++; verbose++; break;
84		case 'S':	printEstimate = 1; break;
85		case 'p':	printProgress = 1; break;
86		case 'r':	restart = strtoull(optarg, NULL, 0); break;
87		case 'g':	gather = strdup(optarg); break;
88		case 'f':	force = 1; break;
89		default:	usage(progname);
90		}
91	}
92
93	ac -= optind;
94	av += optind;
95
96	if (ac == 0 || ac > 2) {
97		usage(progname);
98	}
99	src = av[0];
100	if (ac == 2)
101		dst = av[1];
102
103	// Start by opening the input device
104	devp = OpenDevice(src, 1);
105	if (devp == NULL) {
106		errx(kBadExit, "cannot get device information for %s", src);
107	}
108
109	// Get the volume information.
110	vdp = VolumeInfo(devp);
111
112	// Start creating the in-core volume list
113	vop = InitVolumeObject(devp, vdp);
114
115	// Add the volume headers
116	if (AddHeaders(vop, 0) == 0) {
117		errx(kBadExit, "Invalid volume header(s) for %s", src);
118	}
119	// Add the journal and file extents
120	AddJournal(vop);
121	AddFileExtents(vop);
122
123	/*
124	 * find_all_metadata requires scanning through
125	 * the catalog and attributes files, looking for
126	 * other bits treated as metadata.
127	 */
128	if (find_all_metadata)
129		FindOtherMetadata(vop, ^(int fid, off_t start, off_t len) {
130				AddExtentForFile(vop, start, len, fid);
131//				fprintf(stderr, "AddExtentForFile(%p, %llu, %llu, %u)\n", vop, start, len, fid);
132				return 0;
133			});
134
135	if (debug)
136		PrintVolumeObject(vop);
137
138	if (printEstimate) {
139		printf("Estimate %llu\n", vop->byteCount);
140	}
141
142	// Create a gatherHFS-compatible file, if requested.
143	if (gather) {
144		WriteGatheredData(gather, vop);
145	}
146
147	/*
148	 * If we're given a destination, initialize it.
149 	 */
150	if (dst) {
151		wrapper = InitSparseBundle(dst, devp);
152	}
153
154	if (wrapper) {
155		// See if we're picking up from a previous copy
156		if (restart == 0) {
157			restart = wrapper->getprog(wrapper);
158			if (debug) {
159				fprintf(stderr, "auto-restarting at offset %lld\n", restart);
160			}
161		}
162		// "force" in this case means try even if the space estimate says we won't succeed.
163		if (force == 0) {
164			struct statfs sfs;
165			if (statfs(dst, &sfs) != -1) {
166				off_t freeSpace = (off_t)sfs.f_bsize * (off_t)sfs.f_bfree;
167				if (freeSpace < (vop->byteCount - restart)) {
168					errx(kNoSpaceExit, "free space (%lld) < required space (%lld)", freeSpace, vop->byteCount - restart);
169				}
170			}
171		}
172
173		/*
174		 * If we're restarting, we need to compare the volume headers and see if
175		 * they're the same.  If they're not, we need to start from the beginning.
176		 */
177		if (restart) {
178			HFSPlusVolumeHeader priHeader, altHeader;
179
180			if (wrapper->reader(wrapper, 1024, &priHeader, sizeof(priHeader)) != -1) {
181				if (CheckVolumeHeaders(&priHeader, &vop->vdp->priHeader) == 0) {
182					restart = 0;
183				} else {
184					if (wrapper->reader(wrapper, vop->vdp->altOffset, &altHeader, sizeof(altHeader)) != -1) {
185						if (CheckVolumeHeaders(&altHeader, &vop->vdp->altHeader) == 0) {
186							restart = 0;
187						}
188					}
189				}
190			}
191			if (restart == 0) {
192				if (verbose)
193					warnx("Destination volume does not match source, starting from beginning");
194			}
195		}
196
197		// And start copying the objects.
198		if (CopyObjectsToDest(vop, wrapper, restart) == -1) {
199			if (errno == EIO)
200				retval = kCopyIOExit;
201			else if (errno == EINTR)
202				retval = kIntrExit;
203			else
204				retval = kBadExit;
205			err(retval, "CopyObjectsToDest failed");
206		} else {
207#if TESTINJECT
208			// Copy finished, let's see if we should run a test program
209			if (access(kAppleInternal, 0) != -1) {
210				char *home = getenv("HOME");
211				if (home) {
212					char *pName;
213					pName = malloc(strlen(home) + strlen(kTestProgram) + 2);	// '/' and NUL
214					if (pName) {
215						sprintf(pName, "%s/%s", home, kTestProgram);
216						execl(pName, kTestProgram, dst, NULL);
217					}
218				}
219			}
220#endif
221		}
222	}
223
224	return retval;
225}
226
227