xref: /haiku/src/add-ons/kernel/partitioning_systems/vmdk/vmdk.cpp (revision 285f4cf441dbc269553bee753d151cbf0074f232)
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 
44 static const off_t kMaxDescriptorSize = 64 * 1024;
45 
46 
47 struct VmdkCookie {
VmdkCookieVmdkCookie48 	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 
60 enum {
61 	TOKEN_END,
62 	TOKEN_STRING,
63 	TOKEN_ASSIGN
64 };
65 
66 struct Token {
67 	int			type;
68 	size_t		length;
69 	char		string[1024];
70 
SetToEndToken71 	void SetToEnd()
72 	{
73 		type = TOKEN_END;
74 		string[0] = '\0';
75 		length = 0;
76 	}
77 
SetToAssignToken78 	void SetToAssign()
79 	{
80 		type = TOKEN_ASSIGN;
81 		string[0] = '=';
82 		string[1] = '\0';
83 		length = 0;
84 	}
85 
SetToStringToken86 	void SetToString()
87 	{
88 		type = TOKEN_STRING;
89 		string[0] = '\0';
90 		length = 0;
91 	}
92 
PushCharToken93 	void PushChar(char c)
94 	{
95 		if (length + 1 < sizeof(string)) {
96 			string[length++] = c;
97 			string[length] = '\0';
98 		}
99 	}
100 
operator ==Token101 	bool operator==(const char* other) const
102 	{
103 		return strcmp(string, other) == 0;
104 	}
105 
operator !=Token106 	bool operator!=(const char* other) const
107 	{
108 		return !(*this == other);
109 	}
110 };
111 
112 
113 static status_t
read_file(int fd,off_t offset,void * buffer,size_t size)114 read_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 
124 static int
next_token(char * & line,const char * lineEnd,Token & token)125 next_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 
184 static status_t
parse_vmdk_header(int fd,off_t fileSize,VmdkCookie * & _cookie)185 parse_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 
346 static status_t
vmdk_std_ops(int32 op,...)347 vmdk_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 
359 static float
vmdk_identify_partition(int fd,partition_data * partition,void ** _cookie)360 vmdk_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 
376 static status_t
vmdk_scan_partition(int fd,partition_data * partition,void * _cookie)377 vmdk_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 
419 static void
vmdk_free_identify_partition_cookie(partition_data *,void * cookie)420 vmdk_free_identify_partition_cookie(partition_data*/* partition*/, void* cookie)
421 {
422 	delete (VmdkCookie*)cookie;
423 }
424 
425 
426 static void
vmdk_free_partition_cookie(partition_data * partition)427 vmdk_free_partition_cookie(partition_data* partition)
428 {
429 	// called for the child partition -- it doesn't have a cookie
430 }
431 
432 
433 static void
vmdk_free_partition_content_cookie(partition_data * partition)434 vmdk_free_partition_content_cookie(partition_data* partition)
435 {
436 	delete (VmdkCookie*)partition->content_cookie;
437 }
438 
439 
440 #ifdef _BOOT_MODE
441 partition_module_info gVMwarePartitionModule =
442 #else
443 static 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
471 extern "C" partition_module_info* modules[];
472 _EXPORT partition_module_info* modules[] =
473 {
474 	&vmdk_partition_module,
475 	NULL
476 };
477 #endif
478