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 */
36struct SparseBundleContext {
37	char *pathname;
38	size_t bandSize;
39	int cfd;	// Cached file descriptor
40	int cBandNum;	// cached bandfile number
41};
42
43static const int kBandSize = 8388608;
44
45// Prototype bundle Info.plist file
46static 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 */
67static ssize_t
68UnalignedRead(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
99done:
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 */
108static ssize_t
109doSparseRead(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;
152done:
153	return retval;
154
155}
156
157/*
158 * Write a chunk of data to a bundle.
159 */
160static ssize_t
161doSparseWrite(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;
216done:
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 */
225static ssize_t
226WriteExtentToSparse(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);
263done:
264	if (buffer)
265		free(buffer);
266	return retval;
267}
268
269static const CFStringRef kBandSizeKey = CFSTR("band-size");
270static 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 */
278static int
279GetSizesFromPlist(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
339done:
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 */
358static off_t
359GetProgress(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);
375done:
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 */
383static void
384SetProgress(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 */
409int
410doCleanup(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 */
434struct IOWrapper *
435InitSparseBundle(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;
521done:
522	if (retval == NULL) {
523		if (ctx.pathname)
524			free(ctx.pathname);
525	}
526	return retval;
527}
528