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 static void dump_directory_record(iso9660_directory_record *record, const char *indent); 140 141 142 //---------------------------------------------------------------------------- 143 // iso9660_info 144 //---------------------------------------------------------------------------- 145 146 /*! \brief Creates a new iso9660_info struct with empty volume names. 147 148 \note Use the applicable set_XYZ_volume_name() functions rather than 149 messing with the volume name data members directly. 150 */ 151 iso9660_info::iso9660_info() 152 : iso9660_volume_name(NULL) 153 , joliet_volume_name(NULL) 154 { 155 } 156 157 /*! \brief Destroys the struct, freeing the volume name strings. 158 */ 159 iso9660_info::~iso9660_info() 160 { 161 if (iso9660_volume_name) { 162 free(iso9660_volume_name); 163 iso9660_volume_name = NULL; 164 } 165 if (joliet_volume_name) { 166 free(joliet_volume_name); 167 joliet_volume_name = NULL; 168 } 169 } 170 171 /*! \brief Returns true if a valid volume name exists. 172 */ 173 bool 174 iso9660_info::is_valid() 175 { 176 return iso9660_volume_name || joliet_volume_name; 177 } 178 179 /*! \brief Sets the iso9660 volume name. 180 181 \param name UTF-8 string containing the name. 182 \param length The length (in bytes) of the string. 183 */ 184 void 185 iso9660_info::set_iso9660_volume_name(const char *name, uint32 length) 186 { 187 set_string(&iso9660_volume_name, name, length); 188 } 189 190 /*! \brief Sets the Joliet volume name. 191 192 \param name UTF-8 string containing the name. 193 \param length The length (in bytes) of the string. 194 */ 195 void 196 iso9660_info::set_joliet_volume_name(const char *name, uint32 length) 197 { 198 set_string(&joliet_volume_name, name, length); 199 } 200 201 /*! \brief Returns the volume name of highest precedence. 202 203 Currently, the ordering is (decreasingly): 204 - Joliet 205 - iso9660 206 */ 207 const char* 208 iso9660_info::get_preferred_volume_name() 209 { 210 if (joliet_volume_name) 211 return joliet_volume_name; 212 else 213 return iso9660_volume_name; 214 } 215 216 /*! \brief Copies the given string into the old string, managing memory 217 deallocation and allocation as necessary. 218 */ 219 void 220 iso9660_info::set_string(char **string, const char *new_string, uint32 new_length) 221 { 222 TRACE(("iso9660_info::set_string(%p (`%s'), `%s', %ld)\n", string, *string, new_string, new_length)); 223 if (string) { 224 char *&old_string = *string; 225 if (old_string) 226 free(old_string); 227 if (new_string) { 228 old_string = (char*)malloc(new_length+1); 229 if (old_string) { 230 strncpy(old_string, new_string, new_length); 231 old_string[new_length] = 0; 232 } 233 } else { 234 old_string = NULL; 235 } 236 } 237 } 238 239 240 // #pragma mark - C functions 241 242 243 /*! \brief Converts the given unicode character to utf8. 244 245 Courtesy Mr. Axel Dörfler. 246 247 \todo Once OpenTracker's locale kit is done, perhaps that functionality 248 should be used rather than outright stealing the code. 249 */ 250 static void 251 unicode_to_utf8(uint32 c, char **out) 252 { 253 char *s = *out; 254 255 if (c < 0x80) 256 *(s++) = c; 257 else if (c < 0x800) { 258 *(s++) = 0xc0 | (c>>6); 259 *(s++) = 0x80 | (c & 0x3f); 260 } else if (c < 0x10000) { 261 *(s++) = 0xe0 | (c>>12); 262 *(s++) = 0x80 | ((c>>6) & 0x3f); 263 *(s++) = 0x80 | (c & 0x3f); 264 } else if (c <= 0x10ffff) { 265 *(s++) = 0xf0 | (c>>18); 266 *(s++) = 0x80 | ((c>>12) & 0x3f); 267 *(s++) = 0x80 | ((c>>6) & 0x3f); 268 *(s++) = 0x80 | (c & 0x3f); 269 } 270 *out = s; 271 } 272 273 274 static const char* 275 volume_descriptor_type_to_string(iso9660_volume_descriptor_type type) 276 { 277 switch (type) { 278 case ISO9660VD_BOOT: return "boot"; 279 case ISO9660VD_PRIMARY: return "primary"; 280 case ISO9660VD_SUPPLEMENTARY: return "supplementary"; 281 case ISO9660VD_PARTITION: return "partiton"; 282 case ISO9660VD_TERMINATOR: return "terminator"; 283 default: return "invalid"; 284 } 285 } 286 287 288 static void 289 dump_common_volume_descriptor(iso9660_common_volume_descriptor *common, 290 const char *indent, bool print_header) 291 { 292 if (print_header) 293 TRACE(("%siso9660_common_volume_descriptor:\n", indent)); 294 295 TRACE(("%s volume descriptor type == %d (%s)\n", indent, 296 common->volume_descriptor_type, 297 volume_descriptor_type_to_string((iso9660_volume_descriptor_type)common->volume_descriptor_type))); 298 TRACE(("%s standard identifier == %.5s (%s)\n", indent, common->standard_identifier, 299 strncmp(common->standard_identifier, kISO9660Signature, 5) == 0 ? "valid" : "INVALID")); 300 TRACE(("%s volume descriptor version == %d\n", indent, common->volume_descriptor_version)); 301 } 302 303 304 static void 305 dump_primary_volume_descriptor(iso9660_primary_volume_descriptor *primary, 306 const char *indent, bool print_header) 307 { 308 if (print_header) 309 TRACE(("%siso9660_primary_volume_descriptor:\n", indent)); 310 311 dump_common_volume_descriptor(&(primary->info), indent, false); 312 TRACE(("%s volume identifier == `%.32s'\n", indent, 313 primary->volume_identifier)); 314 TRACE(("%s volume space size == %ld\n", indent, 315 primary->volume_space_size_little_endian)); 316 TRACE(("%s volume set size == %d\n", indent, 317 primary->volume_set_size_little_endian)); 318 TRACE(("%s volume sequence number == %d\n", indent, 319 primary->volume_sequence_number_little_endian)); 320 TRACE(("%s logical block size == %d\n", indent, 321 primary->logical_block_size_little_endian)); 322 TRACE(("%s path table size == %ld\n", indent, 323 primary->path_table_size_little_endian)); 324 TRACE(("%s volume set identifier == %.28s\n", indent, 325 primary->volume_set_identifier)); 326 dump_directory_record((iso9660_directory_record*)primary->root_directory_record, indent); 327 } 328 329 330 static void 331 dump_supplementary_volume_descriptor(iso9660_supplementary_volume_descriptor *supplementary, 332 const char *indent, bool print_header) 333 { 334 if (print_header) 335 TRACE(("%siso9660_supplementary_volume_descriptor:\n", indent)); 336 337 dump_primary_volume_descriptor((iso9660_primary_volume_descriptor*)supplementary, 338 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 349 static 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 360 static 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, 5) == 0 366 ? B_OK : B_BAD_DATA; 367 } 368 return error; 369 } 370 371 372 // #pragma mark - Public functions 373 374 375 // iso9660_fs_identify 376 /*! \brief Returns true if the given partition is a valid iso9660 partition. 377 378 See fs_identify_hook() for more information. 379 380 \todo Fill in partitionInfo->mounted_at with something useful. 381 */ 382 status_t 383 iso9660_fs_identify(int deviceFD, iso9660_info *info) 384 { 385 char buffer[ISO_PVD_SIZE]; 386 bool exit = false; 387 status_t error = B_OK; 388 nspace *vol; 389 390 TRACE(("identify(%d, %p)\n", deviceFD, info)); 391 off_t offset = 0x8000; 392 393 vol = (nspace *)calloc(sizeof(nspace), 1); 394 if (vol == NULL) 395 return ENOMEM; 396 397 vol->fd = deviceFD; 398 399 // Read through the volume descriptors looking for a primary descriptor. 400 // If for some reason there are more than one primary descriptor, the 401 // volume name from the last encountered descriptor will be used. 402 while (!error && !exit) {// && count++ < 10) { 403 iso9660_common_volume_descriptor *common = NULL; 404 405 // Read the block containing the current descriptor 406 error = read_pos (vol->fd, offset, (void *)&buffer, ISO_PVD_SIZE); 407 offset += ISO_PVD_SIZE; 408 if (error < ISO_PVD_SIZE) 409 break; 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 } 520 521 free(vol); 522 523 return error; 524 /* 525 switch (error) { 526 case B_OK: 527 if (info.is_valid()) { 528 result = true; 529 if (partitionInfo->file_system_short_name) 530 strcpy(partitionInfo->file_system_short_name, "iso9660"); 531 if (partitionInfo->file_system_long_name) 532 strcpy(partitionInfo->file_system_long_name, "iso9660 CD-ROM File System"); 533 partitionInfo->file_system_flags = B_FS_IS_PERSISTENT; 534 if (priority) 535 *priority = 0; 536 // Copy the volume name of highest precedence 537 if (partitionInfo->volume_name) { 538 TRACE(("%s: iso9660 name: `%s'\n", kModuleDebugName, info.iso9660_volume_name)); 539 TRACE(("%s: joliet name: `%s'\n", kModuleDebugName, info.joliet_volume_name)); 540 const char *name = info.get_preferred_volume_name(); 541 int length = strlen(name); 542 if (length > B_FILE_NAME_LENGTH-1) 543 length = B_FILE_NAME_LENGTH-1; 544 strncpy(partitionInfo->volume_name, name, length); 545 partitionInfo->volume_name[length] = 0; 546 } 547 return 0.8; 548 } 549 break; 550 551 case B_BAD_DATA: 552 TRACE(("%s: identify: bad signature\n", kModuleDebugName)); 553 break; 554 555 default: 556 TRACE(("%s: identify error: 0x%lx\n", kModuleDebugName, 557 error)); 558 break; 559 } 560 561 return 0.0; 562 */ 563 } 564 565