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 */
38struct SparseBundleContext {
39	char *pathname;
40	size_t bandSize;
41	int cfd;	// Cached file descriptor
42	int cBandNum;	// cached bandfile number
43};
44
45static const int kBandSize = 8388608;
46
47// Prototype bundle Info.plist file
48static 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 */
71static ssize_t
72doSparseRead(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;
115done:
116	return retval;
117
118}
119
120/*
121 * Write a chunk of data to a bundle.
122 */
123static ssize_t
124doSparseWrite(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;
179done:
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 */
188static ssize_t
189WriteExtentToSparse(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);
226done:
227	if (buffer)
228		free(buffer);
229	return retval;
230}
231
232static const CFStringRef kBandSizeKey = CFSTR("band-size");
233static 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 */
241static int
242GetSizesFromPlist(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
302done:
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 */
321static off_t
322GetProgress(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);
338done:
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 */
346static void
347SetProgress(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 */
372int
373doCleanup(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 */
397struct IOWrapper *
398InitSparseBundle(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;
484done:
485	if (retval == NULL) {
486		if (ctx.pathname)
487			free(ctx.pathname);
488	}
489	return retval;
490}
491