DeviceWrapper.c   [plain text]


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/disk.h>

#include "hfsmeta.h"

/*
 * Functions to wrap around a device.
 */
#define MIN(a, b) \
	({ __typeof(a) __a = (a); __typeof(b) __b = (b); \
		__a < __b ? __a : __b; })

struct DeviceWrapperContext {
	char *pathname;
	size_t blockSize;
	off_t devSize;
	int fd;
};

static int
noClean(struct IOWrapper *ctx)
{
	// Conceivably, we could erase the entire device
	return 0;
}

static ssize_t
doRead(struct IOWrapper *ctx, off_t start, void *buffer, off_t len)
{
	// For now, just do a pread
	struct DeviceWrapperContext *dctx = (struct DeviceWrapperContext*)ctx->context;

	return pread(dctx->fd, buffer, (size_t)len, start);
}

static ssize_t
writeExtent(struct IOWrapper *context, DeviceInfo_t *devp, off_t start, off_t len, void (^bp)(off_t))
{
	const size_t bufSize = 1024 * 1024;
	struct DeviceWrapperContext *ctx = (struct DeviceWrapperContext*)context->context;
	uint8_t buffer[bufSize];
	off_t total = 0;

	if (debug) printf("Writing extent <%lld, %lld> to device %s", start, len, ctx->pathname);

	while (total < len) {
		ssize_t nread;
		size_t amt = MIN(bufSize, len - total);
		nread = pread(devp->fd, buffer, amt, start + total);
		if (nread == -1) {
			warn("Cannot read from device at offset %lld", start + total);
			return -1;
		}
		(void)pwrite(ctx->fd, (char*)buffer + total, amt, start + total);
		bp(amt);
		total += amt;
	}
	return 0;
}

/*
 * Device files can't have progress information stored, so we don't do anything.
 */
static off_t
GetProgress(struct IOWrapper *context)
{
	return 0;
}
static void
SetProgress(struct IOWrapper *context, off_t progr)
{
	return;
}

struct IOWrapper *
InitDeviceWrapper(const char *path, DeviceInfo_t *devp)
{
	struct DeviceWrapperContext ctx = { 0 };
	struct DeviceWrapperContext *retctx = NULL;
	IOWrapper_t *retval = NULL;
	struct stat sb;
	uint64_t blockCount;
	char rawname[strlen(path) + 2];	// /dev/disk5 -> /dev/rdisk5

	if (strncmp(path, "/dev/disk", 9) == 0) {
		// Need to make it into a raw device name
		sprintf(rawname, "/dev/rdisk%s", path + 9);
	} else {
		strcpy(rawname, path);
	}

	if (lstat(rawname, &sb) == -1) {
		warn("cannot examine raw device %s", rawname);
		goto done;
	}
	if ((sb.st_mode & S_IFMT) != S_IFCHR) {
		warnx("device %s is not a raw device", rawname);
		goto done;
	}

	ctx.pathname = strdup(rawname);

	ctx.fd = open(rawname, O_RDWR);
	if (ctx.fd == -1) {
		warn("Cannot open device %s for reading and writing", rawname);
		goto done;
	}

	if (ioctl(ctx.fd, DKIOCGETBLOCKSIZE, &ctx.blockSize) == -1) {
		ctx.blockSize = 512;	// A reasonable default
	}
	if (ioctl(ctx.fd, DKIOCGETBLOCKCOUNT, &blockCount) == -1) {
		warn("Cannot block count for device %s", rawname);
		goto done;
	}
	ctx.devSize = ctx.blockSize * blockCount;

	if (ctx.devSize != devp->size) {
		warnx("Device %s is not the same size (%lld) as source device (%lld)", rawname, ctx.devSize, devp->size);
		goto done;
	}

	ctx.pathname = strdup(rawname);
	retctx = malloc(sizeof(ctx));
	if (retctx == NULL) {
		warn("Cannot allocate space for device context");
		goto done;
	}
	*retctx = ctx;
	retval = malloc(sizeof(*retval));
	if (retval == NULL) {
		warn("Cannot allocate space for device wrapper");
		goto done;
	}
	retval->context = retctx;
	retval->reader = &doRead;
	retval->writer = &writeExtent;
	retval->getprog = &GetProgress;
	retval->setprog = &SetProgress;
	retval->cleanup = &noClean;

done:
	return retval;
}