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