#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <new>
#include <KernelExport.h>
#include <AutoDeleter.h>
#include <ddm_modules.h>
#include <disk_device_types.h>
#include <vmdk.h>
#ifdef _BOOT_MODE
# include <boot/partitions.h>
# include <util/kernel_cpp.h>
# undef TRACE_VMDK
#else
# include <DiskDeviceTypes.h>
#endif
#if TRACE_VMDK
# define TRACE(x...) dprintf("vmdk: " x)
#else
# define TRACE(x...) do { } while (false)
#endif
#define VMDK_PARTITION_MODULE_NAME "partitioning_systems/vmdk/v1"
static const off_t kMaxDescriptorSize = 64 * 1024;
struct VmdkCookie {
VmdkCookie(off_t contentOffset, off_t contentSize)
:
contentOffset(contentOffset),
contentSize(contentSize)
{
}
off_t contentOffset;
off_t contentSize;
};
enum {
TOKEN_END,
TOKEN_STRING,
TOKEN_ASSIGN
};
struct Token {
int type;
size_t length;
char string[1024];
void SetToEnd()
{
type = TOKEN_END;
string[0] = '\0';
length = 0;
}
void SetToAssign()
{
type = TOKEN_ASSIGN;
string[0] = '=';
string[1] = '\0';
length = 0;
}
void SetToString()
{
type = TOKEN_STRING;
string[0] = '\0';
length = 0;
}
void PushChar(char c)
{
if (length + 1 < sizeof(string)) {
string[length++] = c;
string[length] = '\0';
}
}
bool operator==(const char* other) const
{
return strcmp(string, other) == 0;
}
bool operator!=(const char* other) const
{
return !(*this == other);
}
};
static status_t
read_file(int fd, off_t offset, void* buffer, size_t size)
{
ssize_t bytesRead = pread(fd, buffer, size, offset);
if (bytesRead < 0)
return errno;
return (size_t)bytesRead == size ? B_OK : B_ERROR;
}
static int
next_token(char*& line, const char* lineEnd, Token& token)
{
while (line != lineEnd && isspace(*line))
line++;
if (line == lineEnd || *line == '#') {
token.SetToEnd();
return token.type;
}
switch (*line) {
case '=':
{
line++;
token.SetToAssign();
return token.type;
}
case '"':
{
token.SetToString();
line++;
while (line != lineEnd) {
if (*line == '"') {
line++;
break;
}
if (*line == '\\') {
line++;
if (line == lineEnd)
break;
}
token.PushChar(*(line++));
}
return token.type;
}
default:
{
token.SetToString();
while (line != lineEnd && *line != '#' && *line != '='
&& !isspace(*line)) {
token.PushChar(*(line++));
}
return token.type;
}
}
}
static status_t
parse_vmdk_header(int fd, off_t fileSize, VmdkCookie*& _cookie)
{
SparseExtentHeader header;
status_t error = read_file(fd, 0, &header, sizeof(header));
if (error != B_OK)
return error;
if (header.magicNumber != VMDK_SPARSE_MAGICNUMBER) {
TRACE("Error: Header magic mismatch!\n");
return B_BAD_DATA;
}
if (header.version != VMDK_SPARSE_VERSION) {
TRACE("Error: Header version mismatch!\n");
return B_BAD_DATA;
}
if (header.overHead > (uint64_t)fileSize / 512) {
TRACE("Error: Header overHead invalid!\n");
return B_BAD_DATA;
}
off_t headerSize = header.overHead * 512;
if (header.descriptorOffset < (sizeof(header) + 511) / 512
|| header.descriptorOffset >= header.overHead
|| header.descriptorSize == 0
|| header.overHead - header.descriptorOffset < header.descriptorSize) {
TRACE("Error: Invalid descriptor location!\n");
return B_BAD_DATA;
}
off_t descriptorOffset = header.descriptorOffset * 512;
off_t descriptorSize = header.descriptorSize * 512;
if (descriptorSize > kMaxDescriptorSize) {
TRACE("Error: Unsupported descriptor size!\n");
return B_UNSUPPORTED;
}
char* descriptor = (char*)malloc(descriptorSize + 1);
if (descriptor == NULL) {
TRACE("Error: Descriptor allocation failed!\n");
return B_NO_MEMORY;
}
MemoryDeleter descriptorDeleter(descriptor);
error = read_file(fd, descriptorOffset, descriptor, descriptorSize);
if (error != B_OK)
return error;
descriptor[descriptorSize] = '\0';
descriptorSize = strlen(descriptor);
uint64_t extendOffset = 0;
uint64_t extendSize = 0;
char* line = descriptor;
char* descriptorEnd = line + descriptorSize;
while (line < descriptorEnd) {
char* lineEnd = strchr(line, '\n');
if (lineEnd != NULL)
*lineEnd = '\0';
else
lineEnd = descriptorEnd;
Token token;
if (next_token(line, lineEnd, token) == TOKEN_END) {
line = lineEnd + 1;
continue;
}
Token token2;
switch (next_token(line, lineEnd, token2)) {
case TOKEN_END:
break;
case TOKEN_ASSIGN:
if (next_token(line, lineEnd, token2) != TOKEN_STRING) {
TRACE("Line not understood: %s = ?\n", token.string);
break;
}
if (token == "version") {
if (token2 != "1") {
TRACE("Unsupported descriptor version: %s\n",
token2.string);
return B_UNSUPPORTED;
}
} else if (token == "createType") {
if (token2 != "monolithicFlat") {
TRACE("Unsupported descriptor createType: %s\n",
token2.string);
return B_UNSUPPORTED;
}
}
break;
case TOKEN_STRING:
if (token != "RW")
break;
extendSize = strtoll(token2.string, NULL, 0);
if (extendSize == 0) {
TRACE("Bad extend size.\n");
return B_BAD_DATA;
}
if (next_token(line, lineEnd, token) != TOKEN_STRING
|| token != "FLAT"
|| next_token(line, lineEnd, token) != TOKEN_STRING
|| next_token(line, lineEnd, token2) != TOKEN_STRING) {
TRACE("Invalid/unsupported extend line\n");
break;
}
extendOffset = strtoll(token2.string, NULL, 0);
if (extendOffset == 0) {
TRACE("Bad extend offset.\n");
return B_BAD_DATA;
}
break;
}
line = lineEnd + 1;
}
if (extendOffset < (uint64_t)headerSize / 512
|| extendOffset >= (uint64_t)fileSize / 512
|| extendSize == 0
|| (uint64_t)fileSize / 512 - extendOffset < extendSize) {
TRACE("Error: Invalid extend location!\n");
return B_BAD_DATA;
}
TRACE("descriptor len: %lld\n", descriptorSize);
TRACE("header size: %lld\n", headerSize);
TRACE("file size: %lld\n", fileSize);
TRACE("extend offset: %lld\n", extendOffset * 512);
TRACE("extend size: %lld\n", extendSize * 512);
VmdkCookie* cookie = new(std::nothrow) VmdkCookie(extendOffset * 512,
extendSize * 512);
if (cookie == NULL)
return B_NO_MEMORY;
_cookie = cookie;
return B_OK;
}
static status_t
vmdk_std_ops(int32 op, ...)
{
TRACE("vmdk_std_ops(0x%lx)\n", op);
switch(op) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
}
return B_ERROR;
}
static float
vmdk_identify_partition(int fd, partition_data* partition, void** _cookie)
{
TRACE("vmdk_identify_partition(%d, %ld: %lld, %lld, %ld)\n", fd,
partition->id, partition->offset, partition->size,
partition->block_size);
VmdkCookie* cookie;
status_t error = parse_vmdk_header(fd, partition->size, cookie);
if (error != B_OK)
return -1;
*_cookie = cookie;
return 0.8f;
}
static status_t
vmdk_scan_partition(int fd, partition_data* partition, void* _cookie)
{
TRACE("vmdk_scan_partition(%d, %ld: %lld, %lld, %ld)\n", fd,
partition->id, partition->offset, partition->size,
partition->block_size);
VmdkCookie* cookie = (VmdkCookie*)_cookie;
ObjectDeleter<VmdkCookie> cookieDeleter(cookie);
partition->status = B_PARTITION_VALID;
partition->flags |= B_PARTITION_PARTITIONING_SYSTEM;
partition->content_size = partition->size;
partition->content_cookie = cookie;
partition_data* child = create_child_partition(partition->id, 0,
partition->offset + cookie->contentOffset, cookie->contentSize, -1);
if (child == NULL) {
partition->content_cookie = NULL;
return B_ERROR;
}
child->block_size = partition->block_size;
child->type = strdup(kPartitionTypeUnrecognized);
child->parameters = NULL;
child->cookie = NULL;
if (child->type == NULL) {
partition->content_cookie = NULL;
return B_NO_MEMORY;
}
cookieDeleter.Detach();
return B_OK;
}
static void
vmdk_free_identify_partition_cookie(partition_data*, void* cookie)
{
delete (VmdkCookie*)cookie;
}
static void
vmdk_free_partition_cookie(partition_data* partition)
{
}
static void
vmdk_free_partition_content_cookie(partition_data* partition)
{
delete (VmdkCookie*)partition->content_cookie;
}
#ifdef _BOOT_MODE
partition_module_info gVMwarePartitionModule =
#else
static partition_module_info vmdk_partition_module =
#endif
{
{
VMDK_PARTITION_MODULE_NAME,
0,
vmdk_std_ops
},
"vmdk",
VMDK_PARTITION_NAME,
0,
vmdk_identify_partition,
vmdk_scan_partition,
vmdk_free_identify_partition_cookie,
vmdk_free_partition_cookie,
vmdk_free_partition_content_cookie,
#ifdef _BOOT_MODE
NULL
#endif
};
#ifndef _BOOT_MODE
extern "C" partition_module_info* modules[];
_EXPORT partition_module_info* modules[] =
{
&vmdk_partition_module,
NULL
};
#endif