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