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