1 /* 2 * Copyright 2009, 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 { 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 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 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 113 static status_t 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 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 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 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 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 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 420 vmdk_free_identify_partition_cookie(partition_data*/* partition*/, void* cookie) 421 { 422 delete (VmdkCookie*)cookie; 423 } 424 425 426 static void 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 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 #ifndef _BOOT_MODE 465 // querying (obsolete) 466 NULL, // get_supported_operations 467 NULL, // get_supported_child_operations 468 NULL, // supports_initializing_child 469 NULL, // is_sub_system_for 470 471 // validation hooks (obsolete) 472 NULL, // validate_resize 473 NULL, // validate_resize_child 474 NULL, // validate_move 475 NULL, // validate_move_child 476 NULL, // validate_set_name 477 NULL, // validate_set_content_name 478 NULL, // validate_set_type 479 NULL, // validate_set_parameters 480 NULL, // validate_set_content_parameters 481 NULL, // validate_initialize 482 NULL, // validate_create_child 483 NULL, // get_partitionable_spaces 484 NULL, // get_next_supported_type 485 NULL, // get_type_for_content_type 486 487 // shadow partition modification (obsolete) 488 NULL, // shadow_changed 489 490 // writing 491 NULL, // repair 492 NULL, // resize 493 NULL, // resize_child 494 NULL, // move 495 NULL, // move_child 496 NULL, // set_name 497 NULL, // set_content_name 498 NULL, // set_type 499 NULL, // set_parameters 500 NULL, // set_content_parameters 501 NULL, // initialize 502 NULL, // create_child 503 NULL, // delete_child 504 #else 505 NULL 506 #endif // _BOOT_MODE 507 }; 508 509 510 #ifndef _BOOT_MODE 511 extern "C" partition_module_info* modules[]; 512 _EXPORT partition_module_info* modules[] = 513 { 514 &vmdk_partition_module, 515 NULL 516 }; 517 #endif 518