#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <zlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/disk.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Kernel/libkern/OSByteOrder.h>
#include <IOKit/storage/IOAppleLabelScheme.h>
#include "util.h"
#define MIN(a, b) \
({ typeof (a) _x = (a); typeof (b) _y = (b); \
_x < _y ? _x : _y; })
static char *
RemoveQuotes(const char *s) {
char *retval;
int len;
if (*s != '"')
return strdup(s);
retval = strdup(s + 1);
len = strlen(retval);
if (len < 2 || retval[len - 1] != '"') {
warnx("Quoted string `%s' is mal-formed", s);
return NULL;
}
retval[len - 1] = 0;
return retval;
}
static int
CheckDeviceName(const char *name) {
int len = strlen(name);
char devname[len + 6];
struct stat sbuf;
if (strchr(name, '/') != NULL) {
warnx("Device name `%s' is invalid: cannot contain a '/'", name);
return -1;
}
snprintf(devname, sizeof(devname), "/dev/%s", name);
if (stat(devname, &sbuf) != -1) {
warnx("Device name `%s' already exists in /dev", name);
}
return 0;
}
static int
CheckUserName(const char *name, int asString, uid_t *uidp) {
unsigned long uid;
struct passwd *pwd;
if (!asString) {
char *endptr;
uid = strtoul(name, &endptr, 10);
if (*name != 0 && *endptr == 0) {
pwd = getpwuid(uid);
if (pwd == NULL) {
warnx("UID %d does not exist on this system", uid);
return -1;
}
*uidp = uid;
return 1;
}
}
pwd = getpwnam(name);
if (pwd == NULL) {
warnx("User-name `%s' does not exist on this system", name);
return -1;
}
*uidp = pwd->pw_uid;
return 1;
}
static int
CheckGroupName(const char *name, int asString, gid_t *gidp) {
unsigned long uid;
struct group *grp;
if (!asString) {
char *endptr;
uid = strtoul(name, &endptr, 10);
if (*name != 0 && *endptr == 0) {
grp = getgrgid(uid);
if (grp == NULL) {
warnx("GID %d does not exist on this system", uid);
return -1;
}
*gidp = uid;
return 1;
}
}
grp = getgrnam(name);
if (grp == NULL) {
warnx("Group-name `%s' does not exist on this system", name);
return -1;
}
*gidp = grp->gr_gid;
return 1;
}
static int
CheckDeviceMode(const char *mode, int asString) {
unsigned long m;
char *endptr;
if (asString) {
warnx("Device mode must be an integer");
return -1;
}
m = strtoul(mode, &endptr, 0);
if (*mode != 0 && *endptr == 0) {
return 0;
}
warnx("Device mode `%s' is invalid", mode);
return -1;
}
static struct applelabel *
getDeviceLabel(const char *dev) {
int fd;
struct applelabel *retval = NULL;
fd = open(dev, O_RDONLY);
if (fd == -1) {
warn("getDeviceLabel: unable to open device %s for reading", dev);
return NULL;
}
retval = malloc(sizeof(*retval));
if (retval == NULL) {
warnx("getDeviceLabel: unable to allocate memory for device label");
goto done;
}
if (read(fd, retval, sizeof(*retval)) != sizeof(*retval)) {
warnx("getDeviceLabel: unable to read a full label for device %s", dev);
free(retval);
retval = NULL;
goto done;
}
retval->al_magic = OSSwapBigToHostInt16(retval->al_magic);
retval->al_type = OSSwapBigToHostInt16(retval->al_type);
retval->al_flags = OSSwapBigToHostInt32(retval->al_flags);
retval->al_offset = OSSwapBigToHostInt64(retval->al_offset);
retval->al_size = OSSwapBigToHostInt32(retval->al_size);
retval->al_checksum = OSSwapBigToHostInt32(retval->al_checksum);
done:
close(fd);
return retval;
}
static int
setDeviceLabel(const char *dev, const struct applelabel *lbl) {
int fd;
int retval = -1;
struct applelabel tmp;
if (lbl == NULL)
return retval;
fd = open(dev, O_WRONLY);
if (fd == -1) {
warn("setDeviceLabel: unable to open device %s", dev);
return retval;
}
tmp = *lbl;
tmp.al_magic = OSSwapHostToBigInt16(tmp.al_magic);
tmp.al_type = OSSwapHostToBigInt16(tmp.al_type);
tmp.al_flags = OSSwapHostToBigInt32(tmp.al_flags);
tmp.al_offset = OSSwapHostToBigInt64(tmp.al_offset);
tmp.al_size = OSSwapHostToBigInt32(tmp.al_size);
tmp.al_checksum = OSSwapHostToBigInt32(tmp.al_checksum);
if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) {
warn("setDeviceLabel: unable to write a full label to device %s", dev);
goto done;
}
retval = 0;
done:
close(fd);
return retval;
}
static uint32_t
ChecksumData(CFDictionaryRef dict, int32_t size) {
uint32_t retval = 0;
unsigned char *bytes = NULL;
int len;
CFDataRef data = nil;
bytes = malloc(size);
if (bytes == NULL) {
warnx("ChecksumData: cannot allocate %d bytes of data\n", size);
return 0;
}
memset(bytes, 0, size);
data = CFPropertyListCreateXMLData(nil, (CFPropertyListRef)dict);
if (data == nil) {
warnx("ChecksumData: cannot create data from dictionary");
goto done;
}
len = CFDataGetLength(data);
memcpy(bytes, (void*)CFDataGetBytePtr(data), len);
if (gDebug && gVerbose) {
fprintf(stderr, "ChecksumData: calling crc32(0, %p, %d)\n", bytes, size);
}
retval = crc32(0, bytes, size);
done:
if (data)
CFRelease(data);
free(bytes);
return retval;
}
int
parseProperty(const char *term, CFStringRef *namePtr, CFTypeRef *valuePtr) {
CFStringRef k = nil;
CFTypeRef v = nil;
char *tag;
char *value = strdup(term);
char *endptr;
int tInt = -1;
char forceInt = 0;
char forceString = 0;
tag = strsep(&value, "=");
if (gDebug > 1)
fprintf(stderr, "parseProperty: property `%s' = value `%s'\n",
tag, value);
k = CFStringCreateWithCString(nil, tag, kCFStringEncodingASCII);
if (value) {
if (*value == '"') { endptr = RemoveQuotes(value);
free(tag);
tag = value = endptr;
forceString = 1;
}
if (!strcmp(tag, "dev-name")) {
forceString = 1; if (CheckDeviceName(value) == -1) {
goto bad;
}
}
if (!strcmp(tag, "owner-uid")) {
if (CheckUserName(value, forceString, &tInt) == -1) {
goto bad;
}
}
if (!strcmp(tag, "owner-gid")) {
if (CheckGroupName(value, forceString, &tInt) == -1) {
goto bad;
}
}
if (!strcmp(tag, "owner-mode")) {
if (CheckDeviceMode(value, forceString) == -1) {
goto bad;
}
}
if (tInt == -1) {
tInt = strtol(value, &endptr, 0);
} else {
forceInt = 1;
}
if (!forceInt && (forceString || (endptr == value ||
(*endptr != 0 && *value != 0)))) {
if (gDebug)
fprintf(stderr, "Property `%s' has value `%s'\n",
tag, value);
v = CFStringCreateWithCString(nil, value, kCFStringEncodingASCII);
} else {
if (gDebug)
fprintf(stderr, "Property `%s' has int value %d\n",
tag, tInt);
v = CFNumberCreate(nil, kCFNumberSInt32Type, &tInt);
}
}
if (k) {
if (namePtr) {
*namePtr = k;
} else {
CFRelease(k);
}
}
if (v) {
if (valuePtr) {
*valuePtr = v;
} else {
CFRelease(v);
}
}
free(tag);
return 1;
bad:
if (v)
CFRelease(v);
if (k)
CFRelease(k);
if (tag)
free(tag);
return 0;
}
CFDictionaryRef
ReadMetadata(const char *dev) {
int fd;
struct stat sbuf;
int64_t off;
struct applelabel *lbl = NULL;
CFDictionaryRef retval = nil;
CFStringRef errStr;
CFDataRef data = nil;
void *buf = NULL;
int len;
if ((fd = open(dev, O_RDONLY)) == -1) {
warn("cannot open device %s", dev);
return nil;
}
lbl = getDeviceLabel(dev);
if (lbl == NULL) {
warnx("ReadMetadata: cannot get label for device %s", dev);
goto done;
}
off = lbl->al_offset;
len = lbl->al_size;
fstat(fd, &sbuf);
if (lseek(fd, off, SEEK_SET) == -1) {
warn("ReadMetadata: cannot seek to metadata offset for device %s", dev);
goto done;
}
if (gDebug) {
fprintf(stderr, "For device %s, metadata len (max) = %d\n", dev, len);
}
buf = malloc(len);
if (buf == NULL) {
warnx("cannot allocate %d bytes to read metadata for device %s", len, dev);
goto done;
}
read(fd, buf, len);
data = CFDataCreate(nil, buf, len);
if (data == nil) {
warnx("cannot create CFData instance of XML");
goto done;
}
retval = (CFPropertyListRef)CFPropertyListCreateFromXMLData(nil,
data,
kCFPropertyListImmutable, &errStr);
if (retval == NULL) {
int l = CFStringGetLength(errStr);
char buf[l * 2];
if (CFStringGetCString(errStr, buf, sizeof(buf), kCFStringEncodingASCII)) {
warnx("cannot create property list: %s", buf);
}
CFRelease(errStr);
}
done:
if (buf)
free(buf);
if (data)
CFRelease(data);
close(fd);
if (lbl)
free(lbl);
return retval;
}
int
InitialMetadata(const char *dev, CFDictionaryRef dict, uint64_t size) {
int fd;
int retval = -1;
uint32_t bs;
uint64_t dSize;
struct applelabel lbl = { { 0 } };
if (gDebug && gVerbose) {
fprintf(stderr, "InitialMetadata(%s, dict, %qu)\n", dev, size);
}
fd = open(dev, O_RDWR);
if (fd == -1) {
warn("InitialMetadata: cannot open device file %s", dev);
return -1;
}
bs = GetBlockSize(dev);
if (bs == 0) {
warnx("InitialMetadata: cannot get block size for device %s", dev);
goto done;
}
dSize = GetDiskSize(dev);
if (dSize == 0) {
warnx("InitialMetadata: cannot get disk size for device %s", dev);
goto done;
}
if (dSize <= size) {
warnx("InitialMetadata: Disk device size (%qu) is not large enough "
"for metadata size (%qu) for device %s",
dSize, size, dev);
goto done;
}
lbl.al_magic = AL_MAGIC;
lbl.al_type = AL_TYPE_DEFAULT;
lbl.al_flags = AL_FLAG_DEFAULT;
lbl.al_offset = bs; lbl.al_size = (uint32_t)size - bs;
lbl.al_checksum = ChecksumData(dict, lbl.al_size);
if (gDebug) {
fprintf(stderr, "lbl = {\n");
fprintf(stderr, "\tal_magic = 0x%x\n", lbl.al_magic);
fprintf(stderr, "\tal_type = 0x%x\n", lbl.al_type);
fprintf(stderr, "\tal_flags = 0x%x\n", lbl.al_flags);
fprintf(stderr, "\tal_offset = %qu\n", lbl.al_offset);
fprintf(stderr, "\tal_size = %d\n", lbl.al_size);
fprintf(stderr, "\tal_checksum = 0x%x\n};\n", lbl.al_checksum);
}
if (setDeviceLabel(dev, &lbl) == -1) {
warnx("InitialMetadata: cannot write header for device %s", dev);
goto done;
}
if (WriteMetadata(dev, dict) == -1) {
warnx("InitialMetadata: cannot write metadata");
goto done;
}
retval = 0;
done:
close(fd);
return retval;
}
int
WriteMetadata(const char *dev, CFDictionaryRef dict) {
int fd;
int64_t off;
int len;
CFDataRef data = nil;
int retval = -1;
void *bytes;
uint32_t cksum = 0;
struct applelabel *lbl;
uint32_t mSize;
lbl = getDeviceLabel(dev);
if (lbl == NULL) {
warnx("cannot get label for device %s", dev);
return -1;
}
mSize = GetMetadataSize(dev);
bytes = malloc(mSize);
memset(bytes, 0, mSize);
if (bytes == NULL) {
warnx("WriteMetadata: cannot allocate %u bytes\n", mSize);
return -1;
}
fd = open(dev, O_RDWR);
if (fd == -1) {
warn("cannot open device %s", dev);
return -1;
}
if (GetDiskSize(dev) < lbl->al_size) {
warnx("device %s is too small for metadata size", dev);
return -1;
}
off = lbl->al_offset;
if (lseek(fd, off, SEEK_SET) == -1) {
warn("WriteMetadata: cannot seek to offset %qu for device %s", off, dev);
goto done;
}
data = CFPropertyListCreateXMLData(nil, (CFPropertyListRef)dict);
if (data == nil) {
warnx("cannot create CFData from dictionary");
goto done;
}
len = CFDataGetLength(data);
memcpy(bytes, CFDataGetBytePtr(data), len);
if (write(fd, bytes, mSize) != mSize) {
warn("cannot write %d bytes to metadata area", mSize);
goto done;
}
cksum = ChecksumData(dict, mSize);
lbl->al_checksum = cksum;
if (setDeviceLabel(dev, lbl) == -1) {
warnx("unable to update label for device %s", dev);
goto done;
}
retval = 1;
done:
close(fd);
if (data)
CFRelease(data);
return retval;
}
uint32_t
ChecksumMetadata(const char *dev) {
int fd = -1;
struct stat sbuf;
int64_t off;
int32_t cksum = 0;
unsigned char *buf = NULL;
int len;
struct applelabel *lbl = NULL;
if ((fd = open(dev, O_RDONLY)) == -1) {
warn("cannot open device %s", dev);
return 0;
}
lbl = getDeviceLabel(dev);
if (lbl == NULL) {
warnx("ChecksumMetadata: cannot get label for device %s", dev);
goto done;
}
fstat(fd, &sbuf);
off = lbl->al_offset;
len = lbl->al_size;
if (lseek(fd, off, SEEK_SET) == -1) {
warn("ChecksumMetadata: cannot seek to %qu for device %s", off, dev);
goto done;
}
if (gDebug) {
fprintf(stderr, "ChecksumMetadata: For device %s, metadata len (max) = %d\n", dev, len);
}
buf = malloc(len);
if (buf == NULL) {
warnx("ChecksumMetadata: cannot allocate %d bytes to read metadata for device %s", len, dev);
goto done;
}
read(fd, buf, len);
if (gDebug && gVerbose) {
fprintf(stderr, "ChecksumMetadata: calling crc32(0, %p, %d)\n", buf, len);
}
cksum = crc32(0, buf, len);
if (gDebug) {
fprintf(stderr, "ChecksumMetadata: For device %s, checksum = %u\n", dev, cksum);
}
done:
if (buf)
free(buf);
close(fd);
if (lbl)
free(lbl);
return cksum;
}
int
UpdateChecksum(const char *dev, uint32_t sum) {
struct applelabel *lbl;
lbl = getDeviceLabel(dev);
if (lbl == NULL) {
warnx("UpdateChecksum: cannot get label for device %s", dev);
return -1;
}
lbl->al_checksum = sum;
if (setDeviceLabel(dev, lbl) == -1) {
warnx("UpdateChecksum: cannot update label for device %s", dev);
return -1;
}
free(lbl);
return 0;
}
uint32_t
GetChecksum(const char *dev) {
struct applelabel *lbl;
uint32_t retval = 0;
lbl = getDeviceLabel(dev);
if (lbl == NULL) {
warnx("GetChecksum: cannot get label for device %s", dev);
return retval;
}
retval = lbl->al_checksum;
free(lbl);
return retval;
}
int
VerifyChecksum(const char *dev) {
int32_t written, data;
if ((data = ChecksumMetadata(dev)) < 1) {
warnx("VerifyChecksum: Metadata checksum is 0x%x", data);
}
if ((written = GetChecksum(dev)) < 1) {
warnx("VerifyChecksum: Metadata written checksum is 0x%x", written);
}
return data - written;
}
uint32_t
GetMetadataSize(const char *dev) {
struct applelabel *lbl = NULL;
uint32_t retval;
lbl = getDeviceLabel(dev);
if (lbl == NULL) {
warnx("GetMetadataSize: cannot get label for device %s", dev);
return 0;
}
retval = lbl->al_size;
free(lbl);
return retval;
}
int
IsAppleLabel(const char *dev) {
struct applelabel *lbl = NULL;
int retval = 0;
lbl = getDeviceLabel(dev);
if (lbl == NULL) {
warn("IsAppleLabel: cannot get label for device %s", dev);
return 0;
}
retval = lbl->al_magic == AL_MAGIC;
free(lbl);
return retval;
}
uint32_t
GetBlockSize(const char *dev) {
uint32_t retval = 0;
int fd;
fd = open(dev, O_RDONLY);
if (fd == -1) {
warn("BlockSize: cannot open %s", dev);
return 0;
}
if (ioctl(fd, DKIOCGETBLOCKSIZE, &retval) == -1) {
if (gDebug) {
retval = 4 * 1024; } else {
retval = 0;
warn("BlockSize: cannot get blocksize for device %s", dev);
}
}
close(fd);
return retval;
}
uint64_t
GetDiskSize(const char *dev) {
int bs;
uint64_t bc;
int fd;
uint64_t retval = 0;
fd = open(dev, O_RDONLY);
if (fd == -1) {
warn("GetDiskSize: cannot open %s", dev);
return 0;
}
bs = GetBlockSize(dev);
if (bs == 0) {
return 0;
}
if (ioctl(fd, DKIOCGETBLOCKCOUNT, &bc) == -1) {
if (gDebug) {
struct stat sbuf;
if (fstat(fd, &sbuf) == -1) {
warn("GetDiskSize: cannot fstat %s", dev);
} else {
retval = sbuf.st_size;
}
} else {
warn("GetDiskSize: cannot get block count for device %s", dev);
}
} else {
retval = bs * bc;
}
close(fd);
return retval;
}