]> git.saurik.com Git - hfs.git/blob - CopyHFSMeta/SparseBundle.c
hfs-191.1.tar.gz
[hfs.git] / CopyHFSMeta / SparseBundle.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <fcntl.h>
6 #include <err.h>
7 #include <errno.h>
8 #include <sys/stat.h>
9 #include <sys/fcntl.h>
10 #include <removefile.h>
11
12 #include <CoreFoundation/CoreFoundation.h>
13
14 #include "hfsmeta.h"
15 #include "Sparse.h"
16
17 /*
18 * Routines to maniupulate a sparse bundle.
19 * N.B.: The sparse bundle format it uses is a subset of
20 * the real sparse bundle format: no partition map, and
21 * no encryption.
22 */
23
24 #define MIN(a, b) \
25 ({ __typeof(a) __a = (a); __typeof(b) __b = (b); \
26 __a < __b ? __a : __b; })
27
28 /*
29 * Context for the sparse bundle routines. The path name,
30 * size of the band files, and cached file descriptor and
31 * band numbers, to reduce the amount of pathname lookups
32 * required.
33 */
34 struct SparseBundleContext {
35 char *pathname;
36 size_t bandSize;
37 int cfd; // Cached file descriptor
38 int cBandNum; // cached bandfile number
39 };
40
41 static const int kBandSize = 8388608;
42
43 // Prototype bundle Info.plist file
44 static const char *bundlePrototype =
45 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
46 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
47 "<plist version=\"1.0\">\n"
48 "<dict>\n"
49 "\t<key>CFBundleInfoDictionaryVersion</key>\n"
50 "\t<string>6.0</string>\n"
51 "\t<key>band-size</key>\n"
52 "\t<integer>%d</integer>\n"
53 "\t<key>bundle-backingstore-version</key>\n"
54 "\t<integer>1</integer>\n"
55 "\t<key>diskimage-bundle-type</key>\n"
56 "\t<string>com.apple.diskimage.sparsebundle</string>\n"
57 "\t<key>size</key>\n"
58 "\t<integer>%llu</integer>\n"
59 "</dict>\n"
60 "</plist>\n";
61
62 /*
63 * Read from a sparse bundle. If the band file doesn't exist, or is shorter than
64 * what we need to get from it, we pad out with 0's.
65 */
66 static ssize_t
67 doSparseRead(struct IOWrapper *context, off_t offset, void *buffer, off_t len)
68 {
69 struct SparseBundleContext *ctx = context->context;
70 off_t blockSize = ctx->bandSize;
71 size_t nread = 0;
72 ssize_t retval = -1;
73
74 while (nread < len) {
75 off_t bandNum = (offset + nread) / blockSize; // Which band file to use
76 off_t bandOffset = (offset + nread) % blockSize; // how far to go into the file
77 size_t amount = MIN(len - nread, blockSize - bandOffset); // How many bytes to write in this band file
78 struct stat sbuf;
79 char *bandName;
80 ssize_t n;;
81 int fd;
82
83 asprintf(&bandName, "%s/bands/%x", ctx->pathname, bandNum);
84 fd = open(bandName, O_RDONLY);
85 if (fd == -1) {
86 if (errno == ENOENT) {
87 // Doesn't exist, so we just write zeroes
88 free(bandName);
89 memset(buffer + nread, 0, amount);
90 nread += amount;
91 continue;
92 }
93 warn("Cannot open band file %s for offset %llu", bandName, offset + nread);
94 retval = -1;
95 free(bandName);
96 goto done;
97 }
98 n = pread(fd, (char*)buffer + nread, amount, bandOffset);
99 if (n == -1) {
100 warn("Cannot write to band file %s/band/%x for offset %llu for amount %zu", ctx->pathname, bandNum, offset+nread, amount);
101 close(fd);
102 goto done;
103 }
104 if (n < amount) { // hit EOF, pad out with zeroes
105 memset(buffer + nread + amount, 0, amount - n);
106 }
107 nread += n;
108 }
109 retval = nread;
110 done:
111 return retval;
112
113 }
114
115 /*
116 * Write a chunk of data to a bundle.
117 */
118 static ssize_t
119 doSparseWrite(IOWrapper_t *context, off_t offset, void *buffer, size_t len)
120 {
121 struct SparseBundleContext *ctx = context->context;
122 off_t blockSize = ctx->bandSize;
123 size_t written = 0;
124 ssize_t retval = -1;
125
126 while (written < len) {
127 off_t bandNum = (offset + written) / blockSize; // Which band file to use
128 off_t bandOffset = (offset + written) % blockSize; // how far to go into the file
129 size_t amount = MIN(len - written, blockSize - bandOffset); // How many bytes to write in this band file
130 char *bandName;
131 ssize_t nwritten;
132 int fd;
133
134 if (ctx->cfd == -1 || ctx->cBandNum != bandNum) {
135 if (ctx->cfd != -1)
136 close(ctx->cfd);
137 asprintf(&bandName, "%s/bands/%x", ctx->pathname, bandNum);
138 fd = open(bandName, O_WRONLY | O_CREAT, 0666);
139 if (fd == -1) {
140 warn("Cannot open band file %s for offset %llu", bandName, offset + written);
141 retval = -1;
142 goto done;
143 }
144 free(bandName);
145 bandName = NULL;
146 ctx->cfd = fd;
147 ctx->cBandNum = bandNum;
148 } else {
149 fd = ctx->cfd;
150 }
151 nwritten = pwrite(fd, (char*)buffer + written, amount, bandOffset);
152 if (nwritten == -1) {
153 warn("Cannot write to band file %s/band/%x for offset %llu for amount %zu", ctx->pathname, bandNum, offset+written, amount);
154 close(fd);
155 ctx->cfd = -1;
156 retval = -1;
157 goto done;
158 }
159 (void)fcntl(fd, F_FULLFSYNC, 0);
160 written += nwritten;
161 }
162 retval = written;
163 done:
164 return retval;
165
166 }
167
168 /*
169 * Write a given extent (<start, length> pair) from an input device to the
170 * sparse bundle. We also use a block to update progress.
171 */
172 static ssize_t
173 WriteExtentToSparse(struct IOWrapper * context, DeviceInfo_t *devp, off_t start, off_t len, void (^bp)(off_t))
174 {
175 const size_t bufSize = 1024 * 1024;
176 uint8_t buffer[bufSize];
177 off_t total = 0;
178
179 if (debug) printf("Writing extent <%lld, %lld>\n", start, len);
180 while (total < len) {
181 ssize_t nread;
182 ssize_t nwritten;
183 size_t amt = MIN(bufSize, len - total);
184 nread = pread(devp->fd, buffer, amt, start + total);
185 if (nread == -1) {
186 warn("Cannot read from device at offset %lld", start + total);
187 return -1;
188 }
189 if (nread < amt) {
190 warnx("Short read from source device -- got %zd, expected %zd", nread, amt);
191 }
192 nwritten = doSparseWrite(context, start + total, buffer, nread);
193 if (nwritten == -1)
194 return -1;
195 bp(nread);
196 total += nread;
197 }
198 if (debug) printf("\twrote %lld\n", total);
199 return 0;
200 }
201
202 static const CFStringRef kBandSizeKey = CFSTR("band-size");
203 static const CFStringRef kDevSizeKey = CFSTR("size");
204
205 /*
206 * We need to be able to get the size of the "device" from a sparse bundle;
207 * we do this by using CF routines to parse the Info.plist file, and then
208 * get the two keys we care about: band-size (size of the band files), and
209 * size (size -- in bytes -- of the "disk").
210 */
211 static int
212 GetSizesFromPlist(const char *path, size_t *bandSize, off_t *devSize)
213 {
214 int retval = -1;
215 CFReadStreamRef inFile = NULL;
216 CFURLRef inFileURL = NULL;
217 CFStringRef cfPath = NULL;
218 CFPropertyListRef cfDict = NULL;
219 CFNumberRef cfVal = NULL;
220 int tmpInt;
221 long long tmpLL;
222
223
224 inFileURL = CFURLCreateFromFileSystemRepresentation(NULL, path, strlen(path), FALSE);
225 if (inFileURL == NULL) {
226 if (debug) warn("Cannot create url from pathname %s", path);
227 goto done;
228 }
229
230 inFile = CFReadStreamCreateWithFile(NULL, inFileURL);
231 if (inFile == NULL) {
232 if (debug) warn("cannot create read stream from path %s", path);
233 goto done;
234 }
235
236 if (CFReadStreamOpen(inFile) == FALSE) {
237 if (debug) warn("cannot open read stream");
238 goto done;
239 }
240
241 cfDict = CFPropertyListCreateWithStream(NULL, inFile, 0, 0, NULL, NULL);
242 if (cfDict == NULL) {
243 if (debug) warnx("cannot create propertly list from stream for path %s", path);
244 goto done;
245 }
246
247 cfVal = CFDictionaryGetValue(cfDict, kBandSizeKey);
248 if (cfVal == NULL) {
249 if (debug) warnx("cannot get bandsize key from plist");
250 goto done;
251 }
252
253 if (CFNumberGetValue(cfVal, kCFNumberIntType, &tmpInt) == false) {
254 if (debug) warnx("cannot get value from band size number");
255 goto done;
256 } else {
257 *bandSize = tmpInt;
258 }
259
260 cfVal = CFDictionaryGetValue(cfDict, kDevSizeKey);
261 if (cfVal == NULL) {
262 if (debug) warnx("cannot get dev size key from plist");
263 goto done;
264 }
265 if (CFNumberGetValue(cfVal, kCFNumberLongLongType, &tmpLL) == false) {
266 goto done;
267 } else {
268 *devSize = tmpLL;
269 }
270 retval = 0;
271
272 done:
273
274 if (cfPath)
275 CFRelease(cfPath);
276 if (inFileURL)
277 CFRelease(inFileURL);
278 if (inFile)
279 CFRelease(inFile);
280 if (cfDict)
281 CFRelease(cfDict);
282 return retval;
283 }
284
285 #define kProgressName "HC.progress.txt"
286
287 /*
288 * Get the progress state from a sparse bundle. If it's not there, then
289 * no progress.
290 */
291 static off_t
292 GetProgress(struct IOWrapper *context)
293 {
294 struct SparseBundleContext *ctx = context->context;
295 FILE *fp = NULL;
296 off_t retval = 0;
297 char progFile[strlen(ctx->pathname) + sizeof(kProgressName) + 2]; // '/' and NUL
298
299 sprintf(progFile, "%s/%s", ctx->pathname, kProgressName);
300 fp = fopen(progFile, "r");
301 if (fp == NULL) {
302 goto done;
303 }
304 if (fscanf(fp, "%llu", &retval) != 1) {
305 retval = 0;
306 }
307 fclose(fp);
308 done:
309 return retval;
310 }
311
312 /*
313 * Write the progress information out. This involves writing a file in
314 * the sparse bundle with the amount -- in bytes -- we've written so far.
315 */
316 static void
317 SetProgress(struct IOWrapper *context, off_t prog)
318 {
319 struct SparseBundleContext *ctx = context->context;
320 FILE *fp = NULL;
321 char progFile[strlen(ctx->pathname) + sizeof(kProgressName) + 2]; // '/' and NUL
322
323 sprintf(progFile, "%s/%s", ctx->pathname, kProgressName);
324 if (prog == 0) {
325 remove(progFile);
326 } else {
327 fp = fopen(progFile, "w");
328 if (fp) {
329 (void)fprintf(fp, "%llu\n", prog);
330 fclose(fp);
331 }
332 }
333 return;
334 }
335
336 /*
337 * Clean up. This is used when we have to initialize the bundle, but don't
338 * have any progress information -- in that case, we don't want to have any
339 * of the old band files laying around. We use removefile() to recursively
340 * remove them, but keep the bands directory.
341 */
342 int
343 doCleanup(struct IOWrapper *ctx)
344 {
345 struct SparseBundleContext *context = ctx->context;
346 int rv = 0;
347 char bandsDir[strlen(context->pathname) + sizeof("/bands") + 1]; // 1 for NUL
348
349 sprintf(bandsDir, "%s/bands", context->pathname);
350
351 if (debug)
352 fprintf(stderr, "Cleaning up, about to call removefile\n");
353 rv = removefile(bandsDir, NULL, REMOVEFILE_RECURSIVE | REMOVEFILE_KEEP_PARENT);
354 if (debug)
355 fprintf(stderr, "removefile returned %d\n", rv);
356
357 return (rv == 0) ? 0 : -1;
358 }
359
360 /*
361 * Initialize the IOWrapper structure for a sparse bundle. This will
362 * create the bundle directory (but not its parents!) if needed, and
363 * will populate it out. It checks to see if there is an existing bundle
364 * of the same name, and, if so, ensures that the izes are correct. Then
365 * it sets up all the function pointers.
366 */
367 struct IOWrapper *
368 InitSparseBundle(const char *path, DeviceInfo_t *devp)
369 {
370 struct SparseBundleContext ctx = { 0 };
371 struct SparseBundleContext *retctx = NULL;
372 IOWrapper_t *retval = NULL;
373 struct stat sb;
374 char tmpname[strlen(path) + sizeof("Info.plist") + 2]; // '/' + NUL
375
376 if (strstr(path, ".sparsebundle") == NULL) {
377 asprintf(&ctx.pathname, "%s.sparsebundle", path);
378 } else {
379 ctx.pathname = strdup(path);
380 }
381
382 if (lstat(ctx.pathname, &sb) == -1) {
383 if (errno != ENOENT) {
384 warn("cannot check sparse bundle %s", ctx.pathname);
385 goto done;
386 }
387 if (mkdir(ctx.pathname, 0777) == -1) {
388 warn("cannot create sparse bundle %s", ctx.pathname);
389 goto done;
390 }
391 } else if ((sb.st_mode & S_IFMT) != S_IFDIR) {
392 warnx("sparse bundle object %s is not a directory", ctx.pathname);
393 goto done;
394 }
395 sprintf(tmpname, "%s/Info.plist", ctx.pathname);
396 if (stat(tmpname, &sb) != -1) {
397 size_t bandSize = 0;
398 off_t devSize = 0;
399 if (GetSizesFromPlist(tmpname, &bandSize, &devSize) == -1) {
400 warnx("Existing sparse bundle can't be parsed");
401 goto done;
402 }
403 if (debug)
404 printf("Existing sparse bundle size = %lld, bandsize = %zu\n", devSize, bandSize);
405
406 if (devSize != devp->size) {
407 warnx("Existing sparse bundle size (%lld) != dev size (%lld)", devSize, devp->size);
408 goto done;
409 }
410 ctx.bandSize = bandSize;
411 } else {
412 FILE *fp = fopen(tmpname, "w");
413 if (fp == NULL) {
414 warn("cannot create sparse bundle info plist %s", tmpname);
415 goto done;
416 }
417 ctx.bandSize = kBandSize;
418 fprintf(fp, bundlePrototype, kBandSize, devp->size);
419 fclose(fp);
420 sprintf(tmpname, "%s/Info.bckup", ctx.pathname);
421 fp = fopen(tmpname, "w");
422 if (fp) {
423 fprintf(fp, bundlePrototype, kBandSize, devp->size);
424 fclose(fp);
425 }
426 sprintf(tmpname, "%s/bands", ctx.pathname);
427 if (mkdir(tmpname, 0777) == -1) {
428 warn("cannot create bands directory in sparse bundle %s", ctx.pathname);
429 goto done;
430 }
431 sprintf(tmpname, "%s/token", ctx.pathname);
432 close(open(tmpname, O_CREAT | O_TRUNC, 0666));
433 }
434
435 retval = malloc(sizeof(*retval));
436 if (retval == NULL) {
437 free(retval);
438 retval = NULL;
439 goto done;
440 }
441 retctx = malloc(sizeof(*retctx));
442 if (retctx) {
443 *retctx = ctx;
444 retctx->cfd = -1;
445
446 }
447 retval->writer = &WriteExtentToSparse;
448 retval->reader = &doSparseRead;
449 retval->getprog = &GetProgress;
450 retval->setprog = &SetProgress;
451 retval->cleanup = &doCleanup;
452
453 retval->context = retctx;
454 done:
455 if (retval == NULL) {
456 if (ctx.pathname)
457 free(ctx.pathname);
458 }
459 return retval;
460 }