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