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