1 /* 2 * Copyright 2003-2011, Haiku, Inc. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Tyler Akidau, haiku@akidau.net 7 */ 8 9 10 /*! \file Disc.cpp 11 12 Disc class implementation, used to enumerate the CD/DVD sessions. 13 14 The protocols followed in this module are based on information 15 taken from the "SCSI-3 Multimedia Commands" draft, revision 10A. 16 17 The SCSI command of interest is "READ TOC/PMA/ATIP", command 18 number \c 0x43. 19 20 The format of interest for said command is "Full TOC", format 21 number \c 0x2. 22 */ 23 24 25 #include "Disc.h" 26 27 #include <DiskDeviceDefs.h> 28 #include <DiskDeviceTypes.h> 29 30 #include "Debug.h" 31 32 33 DBG(static const char* kModuleDebugName = "session"); 34 35 36 /*! \brief An item that can be stored in a List object. 37 */ 38 struct list_item { 39 public: 40 list_item(uint32 index, list_item* next = NULL) 41 : 42 index(index), 43 next(next) 44 { 45 } 46 47 int32 index; 48 list_item* next; 49 }; 50 51 52 /*! \brief A simple, singly linked list. 53 */ 54 class List { 55 public: 56 List(); 57 ~List(); 58 59 list_item* Find(int32 index) const; 60 void Add(list_item* item); 61 void Clear(); 62 void SortAndRemoveDuplicates(); 63 64 list_item* First() const; 65 list_item* Last() const; 66 67 private: 68 list_item* fFirst; 69 list_item* fLast; 70 }; 71 72 73 /*! \brief Keeps track of track information. 74 */ 75 struct track : public list_item { 76 public: 77 track(uint32 index, off_t startLBA, uint8 control, uint8 adr, 78 track* next = NULL) 79 : 80 list_item(index, next), 81 start_lba(startLBA), 82 control(control), 83 adr(adr) 84 { 85 } 86 87 off_t start_lba; 88 uint8 control; 89 // Used to check for Yellow/Red Book mixed-mode CDs. 90 uint8 adr; 91 // only used to give what are probably useless warnings 92 }; 93 94 95 /*! \brief Keeps track of session information. 96 */ 97 struct session : public list_item { 98 public: 99 session(uint32 index, session* next = NULL); 100 101 bool first_track_hint_is_set(); 102 bool last_track_hint_is_set(); 103 bool end_lba_is_set(); // also implies control and adr are set 104 105 bool is_audio(); 106 107 int8 first_track_hint; 108 int8 last_track_hint; 109 int8 control; 110 int8 adr; 111 off_t end_lba; 112 113 List track_list; 114 }; 115 116 117 // #pragma mark - Helper functions 118 119 120 #ifdef DEBUG 121 /* 122 static void 123 dump_scsi_command(raw_device_command* cmd) 124 { 125 int i; 126 uint j; 127 scsi_table_of_contents_command* scsi_command 128 = (scsi_table_of_contents_command*)(&(cmd->command)); 129 130 for (i = 0; i < cmd->command_length; i++) 131 TRACE(("%.2x,", cmd->command[i])); 132 TRACE(("\n")); 133 134 TRACE(("raw_device_command:\n")); 135 TRACE((" command:\n")); 136 TRACE((" command = %d (0x%.2x)\n", scsi_command->command, 137 scsi_command->command)); 138 TRACE((" msf = %d\n", scsi_command->msf)); 139 TRACE((" format = %d (0x%.2x)\n", scsi_command->format, 140 scsi_command->format)); 141 TRACE((" number = %d\n", scsi_command->number)); 142 TRACE((" length = %d\n", 143 B_BENDIAN_TO_HOST_INT16(scsi_command->length))); 144 TRACE((" control = %d\n", scsi_command->control)); 145 TRACE((" command_length = %d\n", cmd->command_length)); 146 TRACE((" flags = %d\n", cmd->flags)); 147 TRACE((" scsi_status = 0x%x\n", cmd->scsi_status)); 148 TRACE((" cam_status = 0x%x\n", cmd->cam_status)); 149 TRACE((" data = %p\n", cmd->data)); 150 TRACE((" data_length = %ld\n", cmd->data_length)); 151 TRACE((" sense_data = %p\n", cmd->sense_data)); 152 TRACE((" sense_data_length = %ld\n", cmd->sense_data_length)); 153 TRACE((" timeout = %lld\n", cmd->timeout)); 154 TRACE(("data dump:\n")); 155 for (j = 0; j < 2048; j++) {//cmd->data_length; j++) { 156 uchar c = ((uchar*)cmd->data)[j]; 157 158 if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') 159 || ('0' <= c && c <= '9')) 160 TRACE(("\\%c,", c)); 161 else 162 TRACE(("%.2x,", c)); 163 } 164 TRACE(("\n")); 165 TRACE(("sense_data dump:\n")); 166 for (j = 0; j < cmd->sense_data_length; j++) { 167 uchar c = ((uchar*)cmd->sense_data)[j]; 168 if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') 169 || ('0' <= c && c <= '9')) 170 TRACE(("%c", c)); 171 else if (c == 0) 172 TRACE(("_")); 173 else 174 TRACE(("-")); 175 } 176 TRACE(("\n")); 177 } 178 */ 179 180 181 static void 182 dump_full_table_of_contents(uchar* data, uint16 dataLength) 183 { 184 cdrom_table_of_contents_header* header 185 = (cdrom_table_of_contents_header*)data; 186 cdrom_full_table_of_contents_entry* entries 187 = (cdrom_full_table_of_contents_entry*)(data + 4); 188 int headerLength = B_BENDIAN_TO_HOST_INT16(header->length); 189 190 if (dataLength < headerLength) { 191 TRACE(("dump_full_table_of_contents: warning, data buffer not large " 192 "enough (%d < %d)\n", dataLength, headerLength)); 193 headerLength = dataLength; 194 } 195 196 TRACE(("%s: table of contents dump:\n", kModuleDebugName)); 197 TRACE(("--------------------------------------------------\n")); 198 TRACE(("header:\n")); 199 TRACE((" length = %d\n", headerLength)); 200 TRACE((" first = %d\n", header->first)); 201 TRACE((" last = %d\n", header->last)); 202 203 int count = (headerLength - 2) / sizeof(cdrom_full_table_of_contents_entry); 204 TRACE(("\n")); 205 TRACE(("entry count = %d\n", count)); 206 207 for (int i = 0; i < count; i++) { 208 TRACE(("\n")); 209 TRACE(("entry #%d:\n", i)); 210 TRACE((" session = %d\n", entries[i].session)); 211 TRACE((" adr = %d\n", entries[i].adr)); 212 TRACE((" control = %d (%s track, copy %s)\n", entries[i].control, 213 (entries[i].control & kControlDataTrack ? "data" : "audio"), 214 (entries[i].control & kControlCopyPermitted 215 ? "permitted" : "prohibited"))); 216 TRACE((" tno = %d\n", entries[i].tno)); 217 TRACE((" point = %d (0x%.2x)\n", entries[i].point, 218 entries[i].point)); 219 TRACE((" minutes = %d\n", entries[i].minutes)); 220 TRACE((" frames = %d\n", entries[i].seconds)); 221 TRACE((" seconds = %d\n", entries[i].frames)); 222 TRACE((" zero = %d\n", entries[i].zero)); 223 TRACE((" pminutes = %d\n", entries[i].pminutes)); 224 TRACE((" pseconds = %d\n", entries[i].pseconds)); 225 TRACE((" pframes = %d\n", entries[i].pframes)); 226 TRACE((" lba = %" B_PRId64 "\n", 227 msf_to_lba(make_msf_address(entries[i].pminutes, 228 entries[i].pseconds, entries[i].pframes)))); 229 } 230 TRACE(("--------------------------------------------------\n")); 231 } 232 #endif // DEBUG 233 234 235 static status_t 236 read_table_of_contents(int deviceFD, uint32 first_session, uchar* buffer, 237 uint16 buffer_length, bool msf) 238 { 239 scsi_table_of_contents_command scsi_command; 240 raw_device_command raw_command; 241 const uint32 sense_data_length = 1024; 242 uchar sense_data[sense_data_length]; 243 status_t error = buffer ? B_OK : B_BAD_VALUE; 244 245 DEBUG_INIT_ETC(NULL, ("fd: %d, buffer: %p, buffer_length: %d", 246 deviceFD, buffer, buffer_length)); 247 248 if (error) 249 return error; 250 251 // This does not always work on the first try, so do it twice just in case. 252 for (int attempt = 0; attempt < 2; attempt++) { 253 // Init the scsi command and copy it into the "raw scsi command" 254 // ioctl struct 255 memset(raw_command.command, 0, 16); 256 scsi_command.command = 0x43; 257 scsi_command.msf = 1; 258 scsi_command.format = kFullTableOfContentsFormat; 259 scsi_command.number = first_session; 260 scsi_command.length = B_HOST_TO_BENDIAN_INT16(buffer_length); 261 scsi_command.control = 0; 262 scsi_command.reserved0 = scsi_command.reserved1 = scsi_command.reserved2 263 = scsi_command.reserved3 = scsi_command.reserved4 264 = scsi_command.reserved5 = scsi_command.reserved6 = 0; 265 memcpy(raw_command.command, &scsi_command, sizeof(scsi_command)); 266 267 // Init the rest of the raw command 268 raw_command.command_length = 10; 269 raw_command.flags = kScsiFlags; 270 raw_command.scsi_status = 0; 271 raw_command.cam_status = 0; 272 raw_command.data = buffer; 273 raw_command.data_length = buffer_length; 274 memset(raw_command.data, 0, raw_command.data_length); 275 raw_command.sense_data = sense_data; 276 raw_command.sense_data_length = sense_data_length; 277 memset(raw_command.sense_data, 0, raw_command.sense_data_length); 278 raw_command.timeout = kScsiTimeout; 279 280 if (ioctl(deviceFD, B_RAW_DEVICE_COMMAND, &raw_command, 281 sizeof(raw_command)) == 0) { 282 if (raw_command.scsi_status == 0 && raw_command.cam_status == 1) { 283 // SUCCESS!!! 284 DBG(dump_full_table_of_contents(buffer, buffer_length)); 285 return B_OK; 286 } else { 287 error = B_FILE_ERROR; 288 TRACE(("%s: scsi ioctl succeeded, but scsi command failed\n", 289 kModuleDebugName)); 290 } 291 } else { 292 error = errno; 293 TRACE(("%s: scsi command failed with error 0x%" B_PRIx32 "\n", 294 kModuleDebugName, error)); 295 } 296 } 297 298 return error; 299 } 300 301 302 // #pragma mark - List 303 // TODO: get rid of this, and use the standard DoublyLinkedList 304 305 306 /*! \brief Creates an empty list. 307 */ 308 List::List() 309 : 310 fFirst(NULL), 311 fLast(NULL) 312 { 313 } 314 315 316 List::~List() 317 { 318 Clear(); 319 } 320 321 322 /*! \brief Returns the ListItem with the given index, or NULL if not found. 323 */ 324 list_item* 325 List::Find(int32 index) const 326 { 327 // TRACE(("%s: List::Find(%ld)\n", kModuleDebugName, index)); 328 list_item* item = fFirst; 329 while (item && item->index != index) { 330 item = item->next; 331 } 332 return item; 333 } 334 335 336 /*! \brief Adds the given item to the end of the list. 337 338 \param item The item to add (may not be NULL) 339 */ 340 void 341 List::Add(list_item* item) 342 { 343 // TRACE(("%s: List::Add(%p)\n", kModuleDebugName, item)); 344 if (item) { 345 item->next = NULL; 346 if (fLast) { 347 fLast->next = item; 348 fLast = item; 349 } else { 350 fFirst = fLast = item; 351 } 352 } else { 353 TRACE(("%s: List::Add(): NULL item parameter\n", kModuleDebugName)); 354 } 355 } 356 357 358 /*! \brief Clears the list. 359 */ 360 void 361 List::Clear() 362 { 363 list_item* item = fFirst; 364 while (item) { 365 list_item* next = item->next; 366 delete item; 367 item = next; 368 } 369 fFirst = fLast = NULL; 370 } 371 372 373 /*! \brief Bubble sorts the list by index, removing any duplicates 374 (the first instance is kept). 375 376 \todo I believe duplicate removal is actually unnecessary, but 377 I need to verify that. 378 */ 379 void 380 List::SortAndRemoveDuplicates() 381 { 382 bool sorted = false; 383 while (!sorted) { 384 sorted = true; 385 386 list_item* prev = NULL; 387 list_item* item = fFirst; 388 list_item* next = NULL; 389 while (item && item->next) { 390 next = item->next; 391 // dprintf("List::Sort: %ld -> %ld\n", item->index, next->index); 392 if (item->index > next->index) { 393 sorted = false; 394 395 // Keep fLast up to date 396 if (next == fLast) 397 fLast = item; 398 399 // Swap 400 if (prev) { 401 // item is not fFirst 402 prev->next = next; 403 item->next = next->next; 404 next->next = item; 405 } else { 406 // item must be fFirst 407 fFirst = next; 408 item->next = next->next; 409 next->next = item; 410 } 411 } else if (item->index == next->index) { 412 // Duplicate indicies 413 TRACE(("%s: List::SortAndRemoveDuplicates: duplicate indicies " 414 "found (#%" B_PRId32 "); keeping first instance\n", 415 kModuleDebugName, item->index)); 416 item->next = next->next; 417 delete next; 418 next = item->next; 419 continue; 420 } 421 prev = item; 422 item = next; 423 } 424 } 425 } 426 427 428 /*! \brief Returns the first item in the list, or NULL if empty 429 */ 430 list_item* 431 List::First() const 432 { 433 return fFirst; 434 } 435 436 437 /*! \brief Returns the last item in the list, or NULL if empty 438 */ 439 list_item* 440 List::Last() const 441 { 442 return fLast; 443 } 444 445 446 // #pragma mark - session 447 448 449 /*! \brief Creates an unitialized session object 450 */ 451 session::session(uint32 index, session* next) 452 : 453 list_item(index, next), 454 first_track_hint(-1), 455 last_track_hint(-1), 456 control(-1), 457 adr(-1), 458 end_lba(0) 459 { 460 } 461 462 463 /*! \brief Returns true if the \a first_track_hint member has not been 464 set to a legal value yet. 465 */ 466 bool 467 session::first_track_hint_is_set() 468 { 469 return 1 <= first_track_hint && first_track_hint <= 99; 470 } 471 472 473 /*! \brief Returns true if the \a last_track_hint member has not been 474 set to a legal value yet. 475 */ 476 bool 477 session::last_track_hint_is_set() 478 { 479 return 1 <= last_track_hint && last_track_hint <= 99; 480 } 481 482 483 /*! \brief Returns true if the \a end_lba member has not been 484 set to a legal value yet. 485 486 The result of this function also signals that the \a control 487 and \a adr members have or have not been set, since they are 488 set at the same time as \a end_lba. 489 */ 490 bool 491 session::end_lba_is_set() 492 { 493 return end_lba > 0; 494 } 495 496 497 /*! \brief Returns true if the session is flagged as being audio. 498 499 If the \c control value for the session has not been set, returns 500 false. 501 */ 502 bool 503 session::is_audio() 504 { 505 return end_lba_is_set() && !(control & kControlDataTrack); 506 } 507 508 509 // #pragma mark - Disc 510 511 512 /*! \brief Creates a new Disc object by parsing the given table of contents 513 entries and checking the resultant data structure for errors and 514 warnings. 515 516 If successful, subsequent calls to InitCheck() will return \c B_OK, 517 elsewise they will return an error code. 518 */ 519 Disc::Disc(int fd) 520 : 521 fInitStatus(B_NO_INIT), 522 fSessionList(new List) 523 { 524 DEBUG_INIT_ETC("Disc", ("fd: %d", fd)); 525 526 uchar data[kBlockSize]; 527 /* 528 if (!error) 529 error = sessionInfo && index >= 0 ? B_OK : B_BAD_VALUE; 530 int32 session = index+1; 531 // Check for a valid session index 532 if (session < 1 || session > 99) 533 error = B_ENTRY_NOT_FOUND; 534 */ 535 536 status_t error = fSessionList ? B_OK : B_NO_MEMORY; 537 538 // Attempt to read the table of contents, first in lba mode, then in msf 539 // mode 540 if (!error) 541 error = read_table_of_contents(fd, 1, data, kBlockSize, false); 542 if (error) { 543 TRACE(("%s: lba read_toc failed, trying msf instead\n", 544 kModuleDebugName)); 545 error = read_table_of_contents(fd, 1, data, kBlockSize, true); 546 } 547 548 // Interpret the data returned, if successful 549 if (!error) { 550 cdrom_table_of_contents_header* header; 551 cdrom_full_table_of_contents_entry* entries; 552 int count; 553 554 header = (cdrom_table_of_contents_header*)data; 555 entries = (cdrom_full_table_of_contents_entry*)(data + 4); 556 header->length = B_BENDIAN_TO_HOST_INT16(header->length); 557 558 count = (header->length - 2) 559 / sizeof(cdrom_full_table_of_contents_entry); 560 561 count = _AdjustForYellowBook(entries, count); 562 error = _ParseTableOfContents(entries, count); 563 // Dump(); 564 if (!error) { 565 _SortAndRemoveDuplicates(); 566 error = _CheckForErrorsAndWarnings(); 567 } 568 } 569 570 PRINT(("Setting init status to 0x%" B_PRIx32 ", `%s'\n", error, 571 strerror(error))); 572 fInitStatus = error; 573 } 574 575 576 /*! \brief Destroys the Disc's internal list. 577 */ 578 Disc::~Disc() 579 { 580 delete fSessionList; 581 } 582 583 584 /*! \brief Returns \c B_OK if the object was successfully initialized, or 585 an error code if not. 586 */ 587 status_t 588 Disc::InitCheck() 589 { 590 return fInitStatus; 591 } 592 593 594 /*! \brief Stores the info for the given session (using 0 based indicies) in the 595 struct pointed to by \a sessionInfo. 596 597 Returns \c B_ENTRY_NOT_FOUND if no such session exists. 598 */ 599 Session* 600 Disc::GetSession(int32 index) 601 { 602 DEBUG_INIT_ETC("Disc", ("index: %" B_PRId32, index)); 603 int32 counter = -1; 604 for (session* session = (struct session*)fSessionList->First(); session; 605 session = (struct session*)session->next) { 606 if (session->is_audio()) { 607 counter++; 608 // only one session per audio session 609 if (counter == index) { 610 // Found an audio session. Take the start of the first 611 // track with the end of session. 612 track* track = (struct track*)session->track_list.First(); 613 if (track != NULL) { 614 PRINT(("found session #%" B_PRId32 " info (audio session)" 615 "\n", index)); 616 617 off_t startLBA = track->start_lba; 618 off_t endLBA = session->end_lba; 619 620 off_t offset = startLBA * kBlockSize; 621 off_t size = (endLBA - startLBA) * kBlockSize; 622 623 Session* result = new Session(offset, size, kBlockSize, 624 index, B_PARTITION_READ_ONLY, 625 kPartitionTypeAudioSession); 626 if (result == NULL) { 627 PRINT(("Error allocating new Session object; out of " 628 "memory!\n")); 629 } 630 return result; 631 } else { 632 PRINT(("Error: session #%" B_PRId32 " is an audio session " 633 "with no tracks!\n", index)); 634 return NULL; 635 } 636 } 637 } else { 638 for (track* track = (struct track*)session->track_list.First(); 639 track; track = (struct track*)track->next) { 640 counter++; 641 if (counter == index) { 642 PRINT(("found session #%" B_PRId32 " info (data session)\n", 643 index)); 644 645 off_t startLBA = track->start_lba; 646 if (startLBA < 0) { 647 WARN(("%s: warning: invalid negative start LBA of %" 648 B_PRId64 " for data track assuming 0\n", 649 kModuleDebugName, startLBA)); 650 startLBA = 0; 651 } 652 653 off_t endLBA = track->next 654 ? ((struct track*)track->next)->start_lba 655 : session->end_lba; 656 657 off_t offset = startLBA * kBlockSize; 658 off_t size = (endLBA - startLBA) * kBlockSize; 659 660 Session* result = new Session(offset, size, kBlockSize, 661 index, B_PARTITION_READ_ONLY, 662 kPartitionTypeDataSession); 663 if (result == NULL) { 664 PRINT(("Error allocating new Session object; out of " 665 "memory!\n")); 666 } 667 return result; 668 } 669 } 670 } 671 } 672 673 PRINT(("no session #%" B_PRId32 " found!\n", index)); 674 return NULL; 675 } 676 677 678 /*! \brief Dumps a printout of the disc using TRACE. 679 */ 680 void 681 Disc::Dump() 682 { 683 TRACE(("%s: Disc dump:\n", kModuleDebugName)); 684 session* session = (struct session*)fSessionList->First(); 685 while (session != NULL) { 686 TRACE(("session %" B_PRId32 ":\n", session->index)); 687 TRACE((" first track hint: %d\n", session->first_track_hint)); 688 TRACE((" last track hint: %d\n", session->last_track_hint)); 689 TRACE((" end_lba: %" B_PRId64 "\n", session->end_lba)); 690 TRACE((" control: %d (%s session, copy %s)\n", 691 session->control, (session->control & kControlDataTrack 692 ? "data" : "audio"), 693 (session->control & kControlCopyPermitted 694 ? "permitted" : "prohibited"))); 695 TRACE((" adr: %d\n", session->adr)); 696 track* track = (struct track*)session->track_list.First(); 697 while (track != NULL) { 698 TRACE((" track %" B_PRId32 ":\n", track->index)); 699 TRACE((" start_lba: %" B_PRId64 "\n", track->start_lba)); 700 track = (struct track*)track->next; 701 } 702 session = (struct session*)session->next; 703 } 704 } 705 706 707 /*! \brief Checks for Yellow Book data tracks in audio sessions and if found 708 inserts them as a new data session. 709 */ 710 uint32 711 Disc::_AdjustForYellowBook(cdrom_full_table_of_contents_entry entries[], 712 uint32 count) 713 { 714 uint8 foundCount = 0; 715 uint8 endLBAEntry = 0; 716 uint8 trackTwo = 0; 717 718 // Make sure TOC has only one session and that it is audio. 719 bool sessionIsAudio = true; 720 for (uint32 i = 0; i < count; i++) { 721 if (entries[i].point == 0xa2) { 722 if ((entries[i].control & kControlDataTrack) != 0) { 723 sessionIsAudio = false; 724 break; 725 } 726 foundCount++; 727 endLBAEntry = i; 728 } 729 } 730 if (!sessionIsAudio || foundCount != 1) 731 return count; 732 733 TRACE(("%s: Single audio session, checking for data track\n", 734 kModuleDebugName)); 735 736 // See if there are any data tracks. 737 for (uint32 i = 0; i < count; i++) { 738 if (entries[i].point > 0 && entries[i].point < 100 739 && (entries[i].control & kControlDataTrack) != 0) { 740 if (entries[i].point == 1) { 741 // Create a new endLBA point for session one. 742 entries[count] = entries[endLBAEntry]; 743 entries[count].control = entries[i].control; 744 745 // Get track two and use it's start as 746 // the end of our new session. 747 for (uint8 j = 0; j < count; j++) { 748 if (entries[j].point == 2) { 749 trackTwo = j; 750 break; 751 } 752 } 753 entries[count].pminutes = entries[trackTwo].pminutes; 754 entries[count].pseconds = entries[trackTwo].pseconds; 755 entries[count].pframes = entries[trackTwo].pframes; 756 757 // Change the other points to session two. 758 for (uint32 j = 0; j < count; j++) { 759 entries[j].session = 2; 760 } 761 entries[i].session = 1; 762 763 count++; 764 TRACE(("%s: first track is data, adjusted TOC\n", 765 kModuleDebugName)); 766 break; 767 } else { 768 // Change the track to session two. 769 entries[i].session = 2; 770 771 // Create a new endLBA point for session two. 772 entries[count] = entries[endLBAEntry]; 773 entries[count].session = 2; 774 entries[count].control = entries[i].control; 775 776 // Use the beginning of the data track as the 777 // end of the previous session. 778 entries[endLBAEntry].pminutes = entries[i].pminutes; 779 entries[endLBAEntry].pseconds = entries[i].pseconds; 780 entries[endLBAEntry].pframes = entries[i].pframes; 781 782 count++; 783 TRACE(("%s: last track is data, adjusted TOC\n", 784 kModuleDebugName)); 785 break; 786 } 787 } 788 } 789 return count; 790 } 791 792 793 /*! \brief Reads through the given table of contents data and creates an 794 unsorted, unverified (i.e. non-error-checked) list of sessions and tracks. 795 */ 796 status_t 797 Disc::_ParseTableOfContents(cdrom_full_table_of_contents_entry entries[], 798 uint32 count) 799 { 800 DEBUG_INIT_ETC("Disc", ("entries: %p, count: %" B_PRIu32, entries, count)); 801 802 for (uint32 i = 0; i < count; i++) { 803 // Find or create the appropriate session 804 uint8 sessionIndex = entries[i].session; 805 session* session = (struct session*)fSessionList->Find(sessionIndex); 806 if (session == NULL) { 807 session = new struct session(sessionIndex); 808 if (session == NULL) 809 return B_NO_MEMORY; 810 811 fSessionList->Add(session); 812 } 813 814 uint8 point = entries[i].point; 815 816 switch (point) { 817 // first track hint 818 case 0xA0: 819 if (!session->first_track_hint_is_set()) { 820 int8 firstTrackHint = entries[i].pminutes; 821 if (1 <= firstTrackHint && firstTrackHint <= 99) { 822 session->first_track_hint = firstTrackHint; 823 } else { 824 WARN(("%s: warning: illegal first track hint %d found " 825 "for session %d\n", kModuleDebugName, 826 firstTrackHint, sessionIndex)); 827 } 828 } else { 829 WARN(("%s: warning: duplicated first track hint values " 830 "found for session %d; using first value " 831 "encountered: %d", kModuleDebugName, sessionIndex, 832 session->first_track_hint)); 833 } 834 break; 835 836 // last track hint 837 case 0xA1: 838 if (!session->last_track_hint_is_set()) { 839 int8 lastTrackHint = entries[i].pminutes; 840 if (1 <= lastTrackHint && lastTrackHint <= 99) { 841 session->last_track_hint = lastTrackHint; 842 } else { 843 WARN(("%s: warning: illegal last track hint %d found " 844 "for session %d\n", kModuleDebugName, 845 lastTrackHint, sessionIndex)); 846 } 847 } else { 848 WARN(("%s: warning: duplicate last track hint values found " 849 "for session %d; using first value encountered: %d", 850 kModuleDebugName, sessionIndex, 851 session->last_track_hint)); 852 } 853 break; 854 855 // end of session address 856 case 0xA2: 857 if (!session->end_lba_is_set()) { 858 off_t endLBA = msf_to_lba(make_msf_address( 859 entries[i].pminutes, entries[i].pseconds, 860 entries[i].pframes)); 861 if (endLBA > 0) { 862 session->end_lba = endLBA; 863 // We also grab the session's control and adr values 864 // from this entry 865 session->control = entries[i].control; 866 session->adr = entries[i].adr; 867 } else { 868 WARN(("%s: warning: illegal end lba %" B_PRId64 " found" 869 " for session %d\n", kModuleDebugName, endLBA, 870 sessionIndex)); 871 } 872 } else { 873 WARN(("%s: warning: duplicate end lba values found for " 874 "session %d; using first value encountered: %" B_PRId64, 875 kModuleDebugName, sessionIndex, session->end_lba)); 876 } 877 break; 878 879 // Valid, but uninteresting, points 880 case 0xB0: 881 case 0xB1: 882 case 0xB2: 883 case 0xB3: 884 case 0xB4: 885 case 0xC0: 886 case 0xC1: 887 break; 888 889 default: 890 // Anything else had better be a valid track number, 891 // or it's an invalid point 892 if (1 <= point && point <= 99) { 893 // Create and add the track. We'll weed out any duplicates 894 // later. 895 uint8 trackIndex = point; 896 off_t startLBA = msf_to_lba(make_msf_address( 897 entries[i].pminutes, entries[i].pseconds, 898 entries[i].pframes)); 899 // The control and adr values grabbed here are only used 900 // later on to signal a warning if they don't match the 901 // corresponding values of the parent session. 902 track* track = new(std::nothrow) struct track(trackIndex, 903 startLBA, entries[i].control, entries[i].adr); 904 if (track == NULL) 905 return B_NO_MEMORY; 906 907 session->track_list.Add(track); 908 } else { 909 WARN(("%s: warning: illegal point 0x%2x found in table of " 910 "contents\n", kModuleDebugName, point)); 911 } 912 break; 913 } 914 } 915 return B_OK; 916 } 917 918 919 /*! \brief Bubble sorts the session list and each session's track lists, 920 removing all but the first of any duplicates (by index) found along 921 the way. 922 */ 923 void 924 Disc::_SortAndRemoveDuplicates() 925 { 926 fSessionList->SortAndRemoveDuplicates(); 927 session* session = (struct session*)fSessionList->First(); 928 while (session != NULL) { 929 session->track_list.SortAndRemoveDuplicates(); 930 session = (struct session*)session->next; 931 } 932 } 933 934 935 /* \brief Checks the sessions and tracks for any anomalies. 936 937 Errors will return an error code, warnings will return B_OK. 938 Both will print a notification using TRACE. 939 940 Anomalies that result in errors: 941 - Sessions with no end_lba set 942 - Sessions with no tracks 943 944 Anomalies that result in warnings: 945 - Inaccurate first_track_hint and/or last_track_hint values 946 - Sequences of sessions or tracks that do not start at 1, 947 do not end at or before 99, or are not strictly ascending. 948 (all tracks are checked as a single sequence, since track 949 numbering does not restart with each session). 950 - Tracks with different control and/or adr values than their 951 parent session 952 953 Anomalies that are currently *not* checked: 954 - First Track Hint or Last Track Hint control and adr values 955 that do not match the values for their session; Ingo's copy 956 of the BeOS R5 CD is like this, but I don't believe it's 957 a matter we need to worry about. This could certainly be 958 changed in the future if needed. 959 */ 960 status_t 961 Disc::_CheckForErrorsAndWarnings() { 962 int32 lastSessionIndex = 0; 963 int32 lastTrackIndex = 0; 964 965 for (session* session = (struct session*)fSessionList->First(); session; 966 session = (struct session*)session->next) { 967 // Check for errors 968 969 // missing end lba 970 if (!session->end_lba_is_set()) { 971 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: no end of " 972 "session address for session #%" B_PRId32 "\n", 973 kModuleDebugName, session->index)); 974 return B_ERROR; 975 } 976 977 // empty track list 978 track* track = (struct track*)session->track_list.First(); 979 if (track == NULL) { 980 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: session #%" 981 B_PRId32 "has no tracks\n", kModuleDebugName, session->index)); 982 return B_ERROR; 983 } 984 985 // Check for warnings 986 987 // incorrect first track hint 988 if (session->first_track_hint_is_set() 989 && session->first_track_hint != track->index) { 990 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session " 991 "#%" B_PRId32 ": first track hint (%d) doesn't match actual " 992 "first track (%" B_PRId32 ")\n", kModuleDebugName, 993 session->index, session->first_track_hint, track->index)); 994 } 995 996 // incorrect last track hint 997 struct track* last = (struct track*)session->track_list.Last(); 998 if (session->last_track_hint_is_set() && last 999 && session->last_track_hint != last->index) { 1000 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session " 1001 "#%" B_PRId32 ": last track hint (%d) doesn't match actual " 1002 "last track (%" B_PRId32 ")\n", kModuleDebugName, 1003 session->index, session->last_track_hint, last->index)); 1004 } 1005 1006 // invalid session sequence 1007 if (lastSessionIndex + 1 != session->index) { 1008 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index for " 1009 "session #%" B_PRId32 " is out of sequence (should have been #%" 1010 B_PRId32 ")\n", kModuleDebugName, session->index, 1011 lastSessionIndex)); 1012 } 1013 lastSessionIndex = session->index; 1014 1015 for (; track; track = (struct track*)track->next) { 1016 // invalid track sequence 1017 if (lastTrackIndex + 1 != track->index) { 1018 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index " 1019 "for track #%" B_PRId32 " is out of sequence (should have " 1020 "been #%" B_PRId32 ")\n", kModuleDebugName, track->index, 1021 lastTrackIndex)); 1022 } 1023 lastTrackIndex = track->index; 1024 1025 // mismatched control 1026 if (track->control != session->control) { 1027 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: control " 1028 "for track #%" B_PRId32 " (%d, %s track, copy %s) does not " 1029 "match control for parent session #%" B_PRId32 " (%d, %s " 1030 "session, copy %s)\n", kModuleDebugName, track->index, 1031 track->control, 1032 (track->control & kControlDataTrack ? "data" : "audio"), 1033 (track->control & kControlCopyPermitted 1034 ? "permitted" : "prohibited"), 1035 session->index, session->control, 1036 (session->control & kControlDataTrack ? "data" : "audio"), 1037 (session->control & kControlCopyPermitted 1038 ? "permitted" : "prohibited"))); 1039 } 1040 1041 // mismatched adr 1042 if (track->adr != session->adr) { 1043 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: adr " 1044 "for track #%" B_PRId32 " (adr = %d) does not match adr " 1045 "for parent session #%" B_PRId32 " (adr = %d)\n", 1046 kModuleDebugName, track->index, track->adr, session->index, 1047 session->adr)); 1048 } 1049 } 1050 } 1051 1052 return B_OK; 1053 } 1054 1055 1056 // #pragma mark - Session 1057 1058 1059 Session::Session(off_t offset, off_t size, uint32 blockSize, int32 index, 1060 uint32 flags, const char* type) 1061 : 1062 fOffset(offset), 1063 fSize(size), 1064 fBlockSize(blockSize), 1065 fIndex(index), 1066 fFlags(flags), 1067 fType(strdup(type)) 1068 { 1069 } 1070 1071 1072 Session::~Session() 1073 { 1074 free(fType); 1075 } 1076