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