1/*
2 * Copyright 2009-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <ctype.h>
8#include <errno.h>
9#include <string.h>
10
11#include <new>
12
13#include <KernelExport.h>
14
15#include <AutoDeleter.h>
16#include <ddm_modules.h>
17#include <disk_device_types.h>
18
19#include <vmdk.h>
20
21//#define TRACE_VMDK 1
22#ifdef _BOOT_MODE
23#	include <boot/partitions.h>
24#	include <util/kernel_cpp.h>
25#	undef TRACE_VMDK
26#else
27#	include <DiskDeviceTypes.h>
28#endif
29
30#if TRACE_VMDK
31#	define TRACE(x...) dprintf("vmdk: " x)
32#else
33#	define TRACE(x...) do { } while (false)
34#endif
35
36
37// module name
38#define VMDK_PARTITION_MODULE_NAME "partitioning_systems/vmdk/v1"
39
40
41// #pragma mark - VMDK header/descriptor parsing
42
43
44static const off_t kMaxDescriptorSize = 64 * 1024;
45
46
47struct VmdkCookie {
48	VmdkCookie(off_t contentOffset, off_t contentSize)
49		:
50		contentOffset(contentOffset),
51		contentSize(contentSize)
52	{
53	}
54
55	off_t	contentOffset;
56	off_t	contentSize;
57};
58
59
60enum {
61	TOKEN_END,
62	TOKEN_STRING,
63	TOKEN_ASSIGN
64};
65
66struct Token {
67	int			type;
68	size_t		length;
69	char		string[1024];
70
71	void SetToEnd()
72	{
73		type = TOKEN_END;
74		string[0] = '\0';
75		length = 0;
76	}
77
78	void SetToAssign()
79	{
80		type = TOKEN_ASSIGN;
81		string[0] = '=';
82		string[1] = '\0';
83		length = 0;
84	}
85
86	void SetToString()
87	{
88		type = TOKEN_STRING;
89		string[0] = '\0';
90		length = 0;
91	}
92
93	void PushChar(char c)
94	{
95		if (length + 1 < sizeof(string)) {
96			string[length++] = c;
97			string[length] = '\0';
98		}
99	}
100
101	bool operator==(const char* other) const
102	{
103		return strcmp(string, other) == 0;
104	}
105
106	bool operator!=(const char* other) const
107	{
108		return !(*this == other);
109	}
110};
111
112
113static status_t
114read_file(int fd, off_t offset, void* buffer, size_t size)
115{
116	ssize_t bytesRead = pread(fd, buffer, size, offset);
117	if (bytesRead < 0)
118		return errno;
119
120	return (size_t)bytesRead == size ? B_OK : B_ERROR;
121}
122
123
124static int
125next_token(char*& line, const char* lineEnd, Token& token)
126{
127	// skip whitespace
128	while (line != lineEnd && isspace(*line))
129		line++;
130
131	// comment/end of line
132	if (line == lineEnd || *line == '#') {
133		token.SetToEnd();
134		return token.type;
135	}
136
137	switch (*line) {
138		case '=':
139		{
140			line++;
141			token.SetToAssign();
142			return token.type;
143		}
144
145		case '"':
146		{
147			// quoted string
148			token.SetToString();
149			line++;
150			while (line != lineEnd) {
151				if (*line == '"') {
152					// end of string
153					line++;
154					break;
155				}
156
157				if (*line == '\\') {
158					// escaped char
159					line++;
160					if (line == lineEnd)
161						break;
162				}
163
164				token.PushChar(*(line++));
165			}
166
167			return token.type;
168		}
169
170		default:
171		{
172			// unquoted string
173			token.SetToString();
174			while (line != lineEnd && *line != '#' && *line != '='
175				&& !isspace(*line)) {
176				token.PushChar(*(line++));
177			}
178			return token.type;
179		}
180	}
181}
182
183
184static status_t
185parse_vmdk_header(int fd, off_t fileSize, VmdkCookie*& _cookie)
186{
187	// read the header
188	SparseExtentHeader header;
189	status_t error = read_file(fd, 0, &header, sizeof(header));
190	if (error != B_OK)
191		return error;
192
193	// check the header
194	if (header.magicNumber != VMDK_SPARSE_MAGICNUMBER) {
195		TRACE("Error: Header magic mismatch!\n");
196		return B_BAD_DATA;
197	}
198
199	if (header.version != VMDK_SPARSE_VERSION) {
200		TRACE("Error: Header version mismatch!\n");
201		return B_BAD_DATA;
202	}
203
204	if (header.overHead > (uint64_t)fileSize / 512) {
205		TRACE("Error: Header overHead invalid!\n");
206		return B_BAD_DATA;
207	}
208	off_t headerSize = header.overHead * 512;
209
210	if (header.descriptorOffset < (sizeof(header) + 511) / 512
211		|| header.descriptorOffset >= header.overHead
212		|| header.descriptorSize == 0
213		|| header.overHead - header.descriptorOffset < header.descriptorSize) {
214		TRACE("Error: Invalid descriptor location!\n");
215		return B_BAD_DATA;
216	}
217	off_t descriptorOffset = header.descriptorOffset * 512;
218	off_t descriptorSize = header.descriptorSize * 512;
219
220	if (descriptorSize > kMaxDescriptorSize) {
221		TRACE("Error: Unsupported descriptor size!\n");
222		return B_UNSUPPORTED;
223	}
224
225	// read descriptor
226	char* descriptor = (char*)malloc(descriptorSize + 1);
227	if (descriptor == NULL) {
228		TRACE("Error: Descriptor allocation failed!\n");
229		return B_NO_MEMORY;
230	}
231	MemoryDeleter descriptorDeleter(descriptor);
232
233	error = read_file(fd, descriptorOffset, descriptor, descriptorSize);
234	if (error != B_OK)
235		return error;
236
237	// determine the actual descriptor size
238	descriptor[descriptorSize] = '\0';
239	descriptorSize = strlen(descriptor);
240
241	// parse descriptor
242	uint64_t extendOffset = 0;
243	uint64_t extendSize = 0;
244
245	char* line = descriptor;
246	char* descriptorEnd = line + descriptorSize;
247	while (line < descriptorEnd) {
248		// determine the end of the line
249		char* lineEnd = strchr(line, '\n');
250		if (lineEnd != NULL)
251			*lineEnd = '\0';
252		else
253			lineEnd = descriptorEnd;
254
255		Token token;
256		if (next_token(line, lineEnd, token) == TOKEN_END) {
257			line = lineEnd + 1;
258			continue;
259		}
260
261		Token token2;
262		switch (next_token(line, lineEnd, token2)) {
263			case TOKEN_END:
264				break;
265
266			case TOKEN_ASSIGN:
267				if (next_token(line, lineEnd, token2) != TOKEN_STRING) {
268					TRACE("Line not understood: %s = ?\n", token.string);
269					break;
270				}
271
272				if (token == "version") {
273					if (token2 != "1") {
274						TRACE("Unsupported descriptor version: %s\n",
275							token2.string);
276						return B_UNSUPPORTED;
277					}
278				} else if (token == "createType") {
279					if (token2 != "monolithicFlat") {
280						TRACE("Unsupported descriptor createType: %s\n",
281							token2.string);
282						return B_UNSUPPORTED;
283					}
284				}
285
286				break;
287
288			case TOKEN_STRING:
289				if (token != "RW")
290					break;
291
292				extendSize = strtoll(token2.string, NULL, 0);
293				if (extendSize == 0) {
294					TRACE("Bad extend size.\n");
295					return B_BAD_DATA;
296				}
297
298				if (next_token(line, lineEnd, token) != TOKEN_STRING
299					|| token != "FLAT"
300					|| next_token(line, lineEnd, token) != TOKEN_STRING
301						// image name
302					|| next_token(line, lineEnd, token2) != TOKEN_STRING) {
303					TRACE("Invalid/unsupported extend line\n");
304					break;
305				}
306
307				extendOffset = strtoll(token2.string, NULL, 0);
308				if (extendOffset == 0) {
309					TRACE("Bad extend offset.\n");
310					return B_BAD_DATA;
311				}
312
313				break;
314		}
315
316		line = lineEnd + 1;
317	}
318
319	if (extendOffset < (uint64_t)headerSize / 512
320		|| extendOffset >= (uint64_t)fileSize / 512
321		|| extendSize == 0
322		|| (uint64_t)fileSize / 512 - extendOffset < extendSize) {
323		TRACE("Error: Invalid extend location!\n");
324		return B_BAD_DATA;
325	}
326
327	TRACE("descriptor len: %lld\n", descriptorSize);
328	TRACE("header size:    %lld\n", headerSize);
329	TRACE("file size:      %lld\n", fileSize);
330	TRACE("extend offset:  %lld\n", extendOffset * 512);
331	TRACE("extend size:    %lld\n", extendSize * 512);
332
333	VmdkCookie* cookie = new(std::nothrow) VmdkCookie(extendOffset * 512,
334		extendSize * 512);
335	if (cookie == NULL)
336		return B_NO_MEMORY;
337
338	_cookie = cookie;
339	return B_OK;
340}
341
342
343// #pragma mark - module hooks
344
345
346static status_t
347vmdk_std_ops(int32 op, ...)
348{
349	TRACE("vmdk_std_ops(0x%lx)\n", op);
350	switch(op) {
351		case B_MODULE_INIT:
352		case B_MODULE_UNINIT:
353			return B_OK;
354	}
355	return B_ERROR;
356}
357
358
359static float
360vmdk_identify_partition(int fd, partition_data* partition, void** _cookie)
361{
362	TRACE("vmdk_identify_partition(%d, %ld: %lld, %lld, %ld)\n", fd,
363		partition->id, partition->offset, partition->size,
364		partition->block_size);
365
366	VmdkCookie* cookie;
367	status_t error = parse_vmdk_header(fd, partition->size, cookie);
368	if (error != B_OK)
369		return -1;
370
371	*_cookie = cookie;
372	return 0.8f;
373}
374
375
376static status_t
377vmdk_scan_partition(int fd, partition_data* partition, void* _cookie)
378{
379	TRACE("vmdk_scan_partition(%d, %ld: %lld, %lld, %ld)\n", fd,
380		partition->id, partition->offset, partition->size,
381		partition->block_size);
382
383	VmdkCookie* cookie = (VmdkCookie*)_cookie;
384	ObjectDeleter<VmdkCookie> cookieDeleter(cookie);
385
386	// fill in the partition_data structure
387	partition->status = B_PARTITION_VALID;
388	partition->flags |= B_PARTITION_PARTITIONING_SYSTEM;
389	partition->content_size = partition->size;
390	// (no content_name and content_parameters)
391	// (content_type is set by the system)
392	partition->content_cookie = cookie;
393
394	// child
395	partition_data* child = create_child_partition(partition->id, 0,
396		partition->offset + cookie->contentOffset, cookie->contentSize, -1);
397	if (child == NULL) {
398		partition->content_cookie = NULL;
399		return B_ERROR;
400	}
401
402	child->block_size = partition->block_size;
403	// (no name)
404	child->type = strdup(kPartitionTypeUnrecognized);
405	child->parameters = NULL;
406	child->cookie = NULL;
407
408	// check for allocation problems
409	if (child->type == NULL) {
410		partition->content_cookie = NULL;
411		return B_NO_MEMORY;
412	}
413
414	cookieDeleter.Detach();
415	return B_OK;
416}
417
418
419static void
420vmdk_free_identify_partition_cookie(partition_data*/* partition*/, void* cookie)
421{
422	delete (VmdkCookie*)cookie;
423}
424
425
426static void
427vmdk_free_partition_cookie(partition_data* partition)
428{
429	// called for the child partition -- it doesn't have a cookie
430}
431
432
433static void
434vmdk_free_partition_content_cookie(partition_data* partition)
435{
436	delete (VmdkCookie*)partition->content_cookie;
437}
438
439
440#ifdef _BOOT_MODE
441partition_module_info gVMwarePartitionModule =
442#else
443static partition_module_info vmdk_partition_module =
444#endif
445{
446	{
447		VMDK_PARTITION_MODULE_NAME,
448		0,
449		vmdk_std_ops
450	},
451	"vmdk",								// short_name
452	VMDK_PARTITION_NAME,				// pretty_name
453
454	// flags
455	0,
456
457	// scanning
458	vmdk_identify_partition,				// identify_partition
459	vmdk_scan_partition,					// scan_partition
460	vmdk_free_identify_partition_cookie,	// free_identify_partition_cookie
461	vmdk_free_partition_cookie,				// free_partition_cookie
462	vmdk_free_partition_content_cookie,		// free_partition_content_cookie
463
464#ifdef _BOOT_MODE
465	NULL
466#endif	// _BOOT_MODE
467};
468
469
470#ifndef _BOOT_MODE
471extern "C" partition_module_info* modules[];
472_EXPORT partition_module_info* modules[] =
473{
474	&vmdk_partition_module,
475	NULL
476};
477#endif
478