1 /* 2 * Copyright 2002, Tyler Dauwalder. 3 * This file may be used under the terms of the MIT License. 4 */ 5 /*! 6 \file iso9660.cpp 7 \brief disk_scanner filesystem module for iso9660 CD-ROM filesystems 8 9 <h5>iso9660</h5> 10 The standard to which this module is written is ECMA-119 second 11 edition, a freely available iso9660 equivalent. 12 13 <h5>Joliet</h5> 14 Joliet support comes courtesy of the following document: 15 16 http://www-plateau.cs.berkeley.edu/people/chaffee/jolspec.htm 17 18 As specified there, the existence of any of the following escape 19 sequences in a supplementary volume descriptor's "escape sequences" 20 field denotes a Joliet volume descriptor using unicode ucs-2 21 character encoding (2-byte characters, big-endian): 22 23 - UCS-2 Level 1: 0x252F40 == "%/@" 24 - UCS-2 Level 2: 0x252F43 == "%/C" 25 - UCS-2 Level 3: 0x252F45 == "%/E" 26 27 The following UCS-2 characters are considered illegal (we allow them, 28 printing out a warning if encountered): 29 30 - All values between 0x0000 and 0x001f inclusive == control chars 31 - 0x002A == '*' 32 - 0x002F == '/' 33 - 0x003A == ':' 34 - 0x003B == ';' 35 - 0x003F == '?' 36 - 0x005C == '\' 37 */ 38 39 #include <errno.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 #include <stdio.h> 44 45 #include <ByteOrder.h> 46 #include <fs_info.h> 47 #include <KernelExport.h> 48 49 #include "iso.h" 50 #include "iso9660.h" 51 52 //#define TRACE(x) ; 53 #define TRACE(x) dprintf x 54 55 // misc constants 56 static const char *kISO9660Signature = "CD001"; 57 static const uint32 kVolumeDescriptorLength = 2048; 58 #define ISO9660_VOLUME_IDENTIFIER_LENGTH 32 59 #define ISO9660_ESCAPE_SEQUENCE_LENGTH 32 60 61 //! Volume descriptor types 62 typedef enum { 63 ISO9660VD_BOOT, 64 ISO9660VD_PRIMARY, 65 ISO9660VD_SUPPLEMENTARY, 66 ISO9660VD_PARTITION, 67 ISO9660VD_TERMINATOR = 255 68 } iso9660_volume_descriptor_type; 69 70 /*! \brief The portion of the volume descriptor common to all 71 descriptor types. 72 */ 73 typedef struct iso9660_common_volume_descriptor { 74 uchar volume_descriptor_type; 75 char standard_identifier[5]; // should be 'CD001' 76 uchar volume_descriptor_version; 77 // Remaining bytes are unused 78 } __attribute__((packed)) iso9660_common_volume_descriptor; 79 80 /*! \brief Primary volume descriptor 81 */ 82 typedef struct iso9660_primary_volume_descriptor { 83 iso9660_common_volume_descriptor info; 84 uchar volume_flags; 85 char system_identifier[32]; 86 char volume_identifier[ISO9660_VOLUME_IDENTIFIER_LENGTH]; 87 uchar unused01[8]; 88 uint32 volume_space_size_little_endian; 89 uint32 volume_space_size_big_endian; 90 uchar unused02[ISO9660_ESCAPE_SEQUENCE_LENGTH]; 91 uint16 volume_set_size_little_endian; 92 uint16 volume_set_size_big_endian; 93 uint16 volume_sequence_number_little_endian; 94 uint16 volume_sequence_number_big_endian; 95 uint16 logical_block_size_little_endian; 96 uint16 logical_block_size_big_endian; 97 uint32 path_table_size_little_endian; 98 uint32 path_table_size_big_endian; 99 uint32 ignored02[4]; 100 uchar root_directory_record[34]; 101 char volume_set_identifier[28]; 102 // Remaining bytes are disinteresting to us 103 } __attribute__((packed)) iso9660_primary_volume_descriptor; 104 105 typedef struct iso9660_supplementary_volume_descriptor { 106 iso9660_common_volume_descriptor info; 107 uchar volume_flags; 108 char system_identifier[32]; 109 char volume_identifier[ISO9660_VOLUME_IDENTIFIER_LENGTH]; 110 uchar unused01[8]; 111 uint32 volume_space_size_little_endian; 112 uint32 volume_space_size_big_endian; 113 char escape_sequences[ISO9660_ESCAPE_SEQUENCE_LENGTH]; 114 uint16 volume_set_size_little_endian; 115 uint16 volume_set_size_big_endian; 116 uint16 volume_sequence_number_little_endian; 117 uint16 volume_sequence_number_big_endian; 118 uint16 logical_block_size_little_endian; 119 uint16 logical_block_size_big_endian; 120 uint32 path_table_size_little_endian; 121 uint32 path_table_size_big_endian; 122 uint32 ignored02[4]; 123 uchar root_directory_record[34]; 124 char volume_set_identifier[28]; 125 // Remaining bytes are disinteresting to us 126 } __attribute__((packed)) iso9660_supplementary_volume_descriptor; 127 128 typedef struct iso9660_directory_record { 129 uint8 length; 130 uint8 extended_attribute_record_length; 131 uint32 location_le; 132 uint32 location_be; 133 uint32 data_length; 134 uchar ignored[14]; 135 uint16 volume_space_le; 136 } __attribute__((packed)) iso9660_directory_record; 137 138 //---------------------------------------------------------------------------- 139 // iso9660_info 140 //---------------------------------------------------------------------------- 141 142 /*! \brief Creates a new iso9660_info struct with empty volume names. 143 144 \note Use the applicable set_XYZ_volume_name() functions rather than 145 messing with the volume name data members directly. 146 */ 147 iso9660_info::iso9660_info() 148 : iso9660_volume_name(NULL) 149 , joliet_volume_name(NULL) 150 { 151 } 152 153 /*! \brief Destroys the struct, freeing the volume name strings. 154 */ 155 iso9660_info::~iso9660_info() 156 { 157 if (iso9660_volume_name) { 158 free(iso9660_volume_name); 159 iso9660_volume_name = NULL; 160 } 161 if (joliet_volume_name) { 162 free(joliet_volume_name); 163 joliet_volume_name = NULL; 164 } 165 } 166 167 /*! \brief Returns true if a valid volume name exists. 168 */ 169 bool 170 iso9660_info::is_valid() 171 { 172 return iso9660_volume_name || joliet_volume_name; 173 } 174 175 /*! \brief Sets the iso9660 volume name. 176 177 \param name UTF-8 string containing the name. 178 \param length The length (in bytes) of the string. 179 */ 180 void 181 iso9660_info::set_iso9660_volume_name(const char *name, uint32 length) 182 { 183 set_string(&iso9660_volume_name, name, length); 184 } 185 186 /*! \brief Sets the Joliet volume name. 187 188 \param name UTF-8 string containing the name. 189 \param length The length (in bytes) of the string. 190 */ 191 void 192 iso9660_info::set_joliet_volume_name(const char *name, uint32 length) 193 { 194 set_string(&joliet_volume_name, name, length); 195 } 196 197 /*! \brief Returns the volume name of highest precedence. 198 199 Currently, the ordering is (decreasingly): 200 - Joliet 201 - iso9660 202 */ 203 const char* 204 iso9660_info::get_preferred_volume_name() 205 { 206 if (joliet_volume_name) 207 return joliet_volume_name; 208 else 209 return iso9660_volume_name; 210 } 211 212 /*! \brief Copies the given string into the old string, managing memory 213 deallocation and allocation as necessary. 214 */ 215 void 216 iso9660_info::set_string(char **string, const char *new_string, uint32 new_length) 217 { 218 TRACE(("iso9660_info::set_string(%p (`%s'), `%s', %ld)\n", string, *string, new_string, new_length)); 219 if (string) { 220 char *&old_string = *string; 221 if (old_string) 222 free(old_string); 223 if (new_string) { 224 old_string = (char*)malloc(new_length+1); 225 if (old_string) { 226 strncpy(old_string, new_string, new_length); 227 old_string[new_length] = 0; 228 } 229 } else { 230 old_string = NULL; 231 } 232 } 233 } 234 235 //---------------------------------------------------------------------------- 236 // C functions 237 //---------------------------------------------------------------------------- 238 239 /*! \brief Converts the given unicode character to utf8. 240 241 Courtesy Mr. Axel Dörfler. 242 243 \todo Once OpenTracker's locale kit is done, perhaps that functionality 244 should be used rather than outright stealing the code. 245 */ 246 static 247 void 248 unicode_to_utf8(uint32 c, char **out) 249 { 250 char *s = *out; 251 252 if (c < 0x80) 253 *(s++) = c; 254 else if (c < 0x800) { 255 *(s++) = 0xc0 | (c>>6); 256 *(s++) = 0x80 | (c & 0x3f); 257 } else if (c < 0x10000) { 258 *(s++) = 0xe0 | (c>>12); 259 *(s++) = 0x80 | ((c>>6) & 0x3f); 260 *(s++) = 0x80 | (c & 0x3f); 261 } else if (c <= 0x10ffff) { 262 *(s++) = 0xf0 | (c>>18); 263 *(s++) = 0x80 | ((c>>12) & 0x3f); 264 *(s++) = 0x80 | ((c>>6) & 0x3f); 265 *(s++) = 0x80 | (c & 0x3f); 266 } 267 *out = s; 268 } 269 270 static 271 const char* 272 volume_descriptor_type_to_string(iso9660_volume_descriptor_type type) 273 { 274 switch (type) { 275 case ISO9660VD_BOOT: return "boot"; 276 case ISO9660VD_PRIMARY: return "primary"; 277 case ISO9660VD_SUPPLEMENTARY: return "supplementary"; 278 case ISO9660VD_PARTITION: return "partiton"; 279 case ISO9660VD_TERMINATOR: return "terminator"; 280 default: return "invalid"; 281 } 282 } 283 284 static 285 void 286 dump_common_volume_descriptor(iso9660_common_volume_descriptor *common, const char *indent, 287 bool print_header) 288 { 289 if (print_header) 290 TRACE(("%siso9660_common_volume_descriptor:\n", indent)); 291 292 TRACE(("%s volume descriptor type == %d (%s)\n", indent, 293 common->volume_descriptor_type, 294 volume_descriptor_type_to_string((iso9660_volume_descriptor_type)common->volume_descriptor_type))); 295 TRACE(("%s standard identifier == %.5s (%s)\n", indent, common->standard_identifier, 296 strncmp(common->standard_identifier, kISO9660Signature, 5) == 0 ? "valid" : "INVALID")); 297 TRACE(("%s volume descriptor version == %d\n", indent, common->volume_descriptor_version)); 298 } 299 300 static 301 void 302 dump_directory_record(iso9660_directory_record *record, const char *indent); 303 304 static 305 void 306 dump_primary_volume_descriptor(iso9660_primary_volume_descriptor *primary, const char *indent, 307 bool print_header) 308 { 309 if (print_header) 310 TRACE(("%siso9660_primary_volume_descriptor:\n", indent)); 311 312 dump_common_volume_descriptor(&(primary->info), indent, false); 313 TRACE(("%s volume identifier == `%.32s'\n", indent, 314 primary->volume_identifier)); 315 TRACE(("%s volume space size == %ld\n", indent, 316 primary->volume_space_size_little_endian)); 317 TRACE(("%s volume set size == %d\n", indent, 318 primary->volume_set_size_little_endian)); 319 TRACE(("%s volume sequence number == %d\n", indent, 320 primary->volume_sequence_number_little_endian)); 321 TRACE(("%s logical block size == %d\n", indent, 322 primary->logical_block_size_little_endian)); 323 TRACE(("%s path table size == %ld\n", indent, 324 primary->path_table_size_little_endian)); 325 TRACE(("%s volume set identifier == %.28s\n", indent, 326 primary->volume_set_identifier)); 327 dump_directory_record((iso9660_directory_record*)primary->root_directory_record, indent); 328 } 329 330 static 331 void 332 dump_supplementary_volume_descriptor(iso9660_supplementary_volume_descriptor *supplementary, const char *indent, 333 bool print_header) 334 { 335 if (print_header) 336 TRACE(("%siso9660_supplementary_volume_descriptor:\n", indent)); 337 338 dump_primary_volume_descriptor((iso9660_primary_volume_descriptor*)supplementary, indent, false); 339 TRACE(("%s escape sequences ==", indent)); 340 for (int i = 0; i < ISO9660_ESCAPE_SEQUENCE_LENGTH; i++) { 341 TRACE((" %2x", supplementary->escape_sequences[i])); 342 if (i == ISO9660_ESCAPE_SEQUENCE_LENGTH/2-1) 343 TRACE(("\n ")); 344 } 345 TRACE(("\n")); 346 } 347 348 static 349 void 350 dump_directory_record(iso9660_directory_record *record, const char *indent) 351 { 352 TRACE(("%s root directory record:\n", indent)); 353 TRACE(("%s length == %d\n", indent, record->length)); 354 TRACE(("%s location == %ld\n", indent, record->location_le)); 355 TRACE(("%s data length == %ld\n", indent, record->data_length)); 356 TRACE(("%s volume sequence number == %d\n", indent, record->volume_space_le)); 357 } 358 359 static 360 status_t 361 check_common_volume_descriptor(iso9660_common_volume_descriptor *common) 362 { 363 status_t error = common ? B_OK : B_BAD_VALUE; 364 if (!error) { 365 error = strncmp(common->standard_identifier, kISO9660Signature, 366 5) == 0 ? B_OK : B_BAD_DATA; 367 } 368 return error; 369 } 370 371 372 // iso9660_fs_identify 373 /*! \brief Returns true if the given partition is a valid iso9660 partition. 374 375 See fs_identify_hook() for more information. 376 377 \todo Fill in partitionInfo->mounted_at with something useful. 378 */ 379 status_t 380 iso9660_fs_identify(int deviceFD, iso9660_info *info) 381 { 382 bool result = false; 383 uchar *buffer = NULL; 384 bool exit = false; 385 status_t error = B_OK; 386 nspace *vol; 387 388 TRACE(("identify(%d, %p)\n", deviceFD, info)); 389 off_t offset = 0x8000; 390 391 vol = (nspace *)calloc(sizeof(nspace), 1); 392 if (vol == NULL) { 393 return ENOMEM; 394 } 395 396 vol->fd = deviceFD; 397 398 // Read through the volume descriptors looking for a primary descriptor. 399 // If for some reason there are more than one primary descriptor, the 400 // volume name from the last encountered descriptor will be used. 401 while (!error && !exit) {// && count++ < 10) { 402 iso9660_common_volume_descriptor *common = NULL; 403 404 // Read the block containing the current descriptor 405 error = read_pos (vol->fdOfSession, offset, (void *)&buffer, ISO_PVD_SIZE); 406 offset += ISO_PVD_SIZE; 407 if (error < ISO_PVD_SIZE) { 408 break; 409 } 410 411 common = (iso9660_common_volume_descriptor*)buffer; 412 error = check_common_volume_descriptor(common); 413 // dump_common_volume_descriptor(common, "", true); 414 415 // Handle each type of descriptor appropriately 416 if (!error) { 417 TRACE(("found %s descriptor\n", volume_descriptor_type_to_string((iso9660_volume_descriptor_type)common->volume_descriptor_type))); 418 419 switch (common->volume_descriptor_type) { 420 case ISO9660VD_BOOT: 421 break; 422 423 case ISO9660VD_PRIMARY: 424 { 425 int i; 426 iso9660_primary_volume_descriptor *primary = (iso9660_primary_volume_descriptor*)buffer; 427 428 dump_primary_volume_descriptor(primary, " ", true); 429 430 // Cut off any trailing spaces from the volume id. Note 431 // that this allows for spaces INSIDE the volume id, even 432 // though that's not technically allowed by the standard; 433 // this was necessary to support certain RedHat 6.2 CD-ROMs 434 // from a certain Linux company who shall remain unnamed. ;-) 435 for (i = ISO9660_VOLUME_IDENTIFIER_LENGTH-1; i >= 0; i--) { 436 if (primary->volume_identifier[i] != 0x20) 437 break; 438 } 439 440 // Give a holler if the iso9660 name is already set 441 if (info->iso9660_volume_name) { 442 char str[ISO9660_VOLUME_IDENTIFIER_LENGTH+1]; 443 strncpy(str, primary->volume_identifier, i+1); 444 str[i+1] = 0; 445 TRACE(("duplicate iso9660 volume name found, using latter (`%s') " 446 "instead of former (`%s')\n", str, 447 info->iso9660_volume_name)); 448 } 449 450 info->set_iso9660_volume_name(primary->volume_identifier, i+1); 451 info->maxBlocks = primary->volume_set_size_little_endian; 452 break; 453 } 454 455 case ISO9660VD_SUPPLEMENTARY: 456 { 457 iso9660_supplementary_volume_descriptor *supplementary = (iso9660_supplementary_volume_descriptor*)buffer; 458 dump_supplementary_volume_descriptor((iso9660_supplementary_volume_descriptor*)supplementary, " ", true); 459 460 // Copy and null terminate the escape sequences 461 char escapes[ISO9660_ESCAPE_SEQUENCE_LENGTH+1]; 462 strncpy(escapes, supplementary->escape_sequences, ISO9660_ESCAPE_SEQUENCE_LENGTH); 463 escapes[ISO9660_ESCAPE_SEQUENCE_LENGTH] = 0; 464 465 // Check for a Joliet VD 466 if (strstr(escapes, "%/@") || strstr(escapes, "%/C") || strstr(escapes, "%/E")) { 467 char str[(ISO9660_VOLUME_IDENTIFIER_LENGTH*3/2)+1]; 468 // Since we're dealing with 16-bit Unicode, each UTF-8 sequence 469 // will be at most 3 bytes long. So we need 3/2 as many chars as 470 // we start out with. 471 char *str_iterator = str; 472 uint16 ch; 473 474 // Walk thru the unicode volume name, converting to utf8 as we go. 475 for (int i = 0; 476 (ch = B_BENDIAN_TO_HOST_INT16(((uint16*)supplementary->volume_identifier)[i])) 477 && i < ISO9660_VOLUME_IDENTIFIER_LENGTH; 478 i++) { 479 // Give a warning if the character is technically illegal 480 if ( ch <= 0x001F 481 || ch == 0x002A 482 || ch == 0x002F 483 || ch == 0x003A 484 || ch == 0x003B 485 || ch == 0x003F 486 || ch == 0x005C) 487 { 488 TRACE(("warning: illegal Joliet character found: 0%4x\n", ch)); 489 } 490 491 // Convert to utf-8 492 unicode_to_utf8(ch, &str_iterator); 493 } 494 *str_iterator = 0; 495 496 // Give a holler if the joliet name is already set 497 if (info->joliet_volume_name) { 498 TRACE(("duplicate joliet volume name found, using latter (`%s') " 499 "instead of former (`%s')\n", str, 500 info->joliet_volume_name)); 501 } 502 503 info->set_joliet_volume_name(str, strlen(str)); 504 } // end "if Joliet VD" 505 break; 506 } 507 508 case ISO9660VD_PARTITION: 509 break; 510 511 case ISO9660VD_TERMINATOR: 512 exit = true; 513 break; 514 515 default: 516 break; 517 } 518 } 519 if (buffer) { 520 free(buffer); 521 buffer = NULL; 522 } 523 } 524 525 free(vol); 526 527 return error; 528 /* 529 switch (error) { 530 case B_OK: 531 if (info.is_valid()) { 532 result = true; 533 if (partitionInfo->file_system_short_name) 534 strcpy(partitionInfo->file_system_short_name, "iso9660"); 535 if (partitionInfo->file_system_long_name) 536 strcpy(partitionInfo->file_system_long_name, "iso9660 CD-ROM File System"); 537 partitionInfo->file_system_flags = B_FS_IS_PERSISTENT; 538 if (priority) 539 *priority = 0; 540 // Copy the volume name of highest precedence 541 if (partitionInfo->volume_name) { 542 TRACE(("%s: iso9660 name: `%s'\n", kModuleDebugName, info.iso9660_volume_name)); 543 TRACE(("%s: joliet name: `%s'\n", kModuleDebugName, info.joliet_volume_name)); 544 const char *name = info.get_preferred_volume_name(); 545 int length = strlen(name); 546 if (length > B_FILE_NAME_LENGTH-1) 547 length = B_FILE_NAME_LENGTH-1; 548 strncpy(partitionInfo->volume_name, name, length); 549 partitionInfo->volume_name[length] = 0; 550 } 551 return 0.8; 552 } 553 break; 554 555 case B_BAD_DATA: 556 TRACE(("%s: identify: bad signature\n", kModuleDebugName)); 557 break; 558 559 default: 560 TRACE(("%s: identify error: 0x%lx\n", kModuleDebugName, 561 error)); 562 break; 563 } 564 565 return 0.0; 566 */ 567 } 568 569