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 // Init the scsi command and copy it into the "raw scsi command" 252 // ioctl struct 253 memset(raw_command.command, 0, 16); 254 scsi_command.command = 0x43; 255 scsi_command.msf = 1; 256 scsi_command.format = kFullTableOfContentsFormat; 257 scsi_command.number = first_session; 258 scsi_command.length = B_HOST_TO_BENDIAN_INT16(buffer_length); 259 scsi_command.control = 0; 260 scsi_command.reserved0 = scsi_command.reserved1 = scsi_command.reserved2 261 = scsi_command.reserved3 = scsi_command.reserved4 262 = scsi_command.reserved5 = scsi_command.reserved6 = 0; 263 memcpy(raw_command.command, &scsi_command, sizeof(scsi_command)); 264 265 // Init the rest of the raw command 266 raw_command.command_length = 10; 267 raw_command.flags = kScsiFlags; 268 raw_command.scsi_status = 0; 269 raw_command.cam_status = 0; 270 raw_command.data = buffer; 271 raw_command.data_length = buffer_length; 272 memset(raw_command.data, 0, raw_command.data_length); 273 raw_command.sense_data = sense_data; 274 raw_command.sense_data_length = sense_data_length; 275 memset(raw_command.sense_data, 0, raw_command.sense_data_length); 276 raw_command.timeout = kScsiTimeout; 277 278 if (ioctl(deviceFD, B_RAW_DEVICE_COMMAND, &raw_command) == 0) { 279 if (raw_command.scsi_status == 0 && raw_command.cam_status == 1) { 280 // SUCCESS!!! 281 DBG(dump_full_table_of_contents(buffer, buffer_length)); 282 } else { 283 error = B_FILE_ERROR; 284 TRACE(("%s: scsi ioctl succeeded, but scsi command failed\n", 285 kModuleDebugName)); 286 } 287 } else { 288 error = errno; 289 TRACE(("%s: scsi command failed with error 0x%" B_PRIx32 "\n", 290 kModuleDebugName, error)); 291 } 292 293 return error; 294 295 } 296 297 298 // #pragma mark - List 299 // TODO: get rid of this, and use the standard DoublyLinkedList 300 301 302 /*! \brief Creates an empty list. 303 */ 304 List::List() 305 : 306 fFirst(NULL), 307 fLast(NULL) 308 { 309 } 310 311 312 List::~List() 313 { 314 Clear(); 315 } 316 317 318 /*! \brief Returns the ListItem with the given index, or NULL if not found. 319 */ 320 list_item* 321 List::Find(int32 index) const 322 { 323 // TRACE(("%s: List::Find(%ld)\n", kModuleDebugName, index)); 324 list_item* item = fFirst; 325 while (item && item->index != index) { 326 item = item->next; 327 } 328 return item; 329 } 330 331 332 /*! \brief Adds the given item to the end of the list. 333 334 \param item The item to add (may not be NULL) 335 */ 336 void 337 List::Add(list_item* item) 338 { 339 // TRACE(("%s: List::Add(%p)\n", kModuleDebugName, item)); 340 if (item) { 341 item->next = NULL; 342 if (fLast) { 343 fLast->next = item; 344 fLast = item; 345 } else { 346 fFirst = fLast = item; 347 } 348 } else { 349 TRACE(("%s: List::Add(): NULL item parameter\n", kModuleDebugName)); 350 } 351 } 352 353 354 /*! \brief Clears the list. 355 */ 356 void 357 List::Clear() 358 { 359 list_item* item = fFirst; 360 while (item) { 361 list_item* next = item->next; 362 delete item; 363 item = next; 364 } 365 fFirst = fLast = NULL; 366 } 367 368 369 /*! \brief Bubble sorts the list by index, removing any duplicates 370 (the first instance is kept). 371 372 \todo I believe duplicate removal is actually unnecessary, but 373 I need to verify that. 374 */ 375 void 376 List::SortAndRemoveDuplicates() 377 { 378 bool sorted = false; 379 while (!sorted) { 380 sorted = true; 381 382 list_item* prev = NULL; 383 list_item* item = fFirst; 384 list_item* next = NULL; 385 while (item && item->next) { 386 next = item->next; 387 // dprintf("List::Sort: %ld -> %ld\n", item->index, next->index); 388 if (item->index > next->index) { 389 sorted = false; 390 391 // Keep fLast up to date 392 if (next == fLast) 393 fLast = item; 394 395 // Swap 396 if (prev) { 397 // item is not fFirst 398 prev->next = next; 399 item->next = next->next; 400 next->next = item; 401 } else { 402 // item must be fFirst 403 fFirst = next; 404 item->next = next->next; 405 next->next = item; 406 } 407 } else if (item->index == next->index) { 408 // Duplicate indicies 409 TRACE(("%s: List::SortAndRemoveDuplicates: duplicate indicies " 410 "found (#%" B_PRId32 "); keeping first instance\n", 411 kModuleDebugName, item->index)); 412 item->next = next->next; 413 delete next; 414 next = item->next; 415 continue; 416 } 417 prev = item; 418 item = next; 419 } 420 } 421 } 422 423 424 /*! \brief Returns the first item in the list, or NULL if empty 425 */ 426 list_item* 427 List::First() const 428 { 429 return fFirst; 430 } 431 432 433 /*! \brief Returns the last item in the list, or NULL if empty 434 */ 435 list_item* 436 List::Last() const 437 { 438 return fLast; 439 } 440 441 442 // #pragma mark - session 443 444 445 /*! \brief Creates an unitialized session object 446 */ 447 session::session(uint32 index, session* next) 448 : 449 list_item(index, next), 450 first_track_hint(-1), 451 last_track_hint(-1), 452 control(-1), 453 adr(-1), 454 end_lba(0) 455 { 456 } 457 458 459 /*! \brief Returns true if the \a first_track_hint member has not been 460 set to a legal value yet. 461 */ 462 bool 463 session::first_track_hint_is_set() 464 { 465 return 1 <= first_track_hint && first_track_hint <= 99; 466 } 467 468 469 /*! \brief Returns true if the \a last_track_hint member has not been 470 set to a legal value yet. 471 */ 472 bool 473 session::last_track_hint_is_set() 474 { 475 return 1 <= last_track_hint && last_track_hint <= 99; 476 } 477 478 479 /*! \brief Returns true if the \a end_lba member has not been 480 set to a legal value yet. 481 482 The result of this function also signals that the \a control 483 and \a adr members have or have not been set, since they are 484 set at the same time as \a end_lba. 485 */ 486 bool 487 session::end_lba_is_set() 488 { 489 return end_lba > 0; 490 } 491 492 493 /*! \brief Returns true if the session is flagged as being audio. 494 495 If the \c control value for the session has not been set, returns 496 false. 497 */ 498 bool 499 session::is_audio() 500 { 501 return end_lba_is_set() && !(control & kControlDataTrack); 502 } 503 504 505 // #pragma mark - Disc 506 507 508 /*! \brief Creates a new Disc object by parsing the given table of contents 509 entries and checking the resultant data structure for errors and 510 warnings. 511 512 If successful, subsequent calls to InitCheck() will return \c B_OK, 513 elsewise they will return an error code. 514 */ 515 Disc::Disc(int fd) 516 : 517 fInitStatus(B_NO_INIT), 518 fSessionList(new List) 519 { 520 DEBUG_INIT_ETC("Disc", ("fd: %d", fd)); 521 522 uchar data[kBlockSize]; 523 /* 524 if (!error) 525 error = sessionInfo && index >= 0 ? B_OK : B_BAD_VALUE; 526 int32 session = index+1; 527 // Check for a valid session index 528 if (session < 1 || session > 99) 529 error = B_ENTRY_NOT_FOUND; 530 */ 531 532 status_t error = fSessionList ? B_OK : B_NO_MEMORY; 533 534 // Attempt to read the table of contents, first in lba mode, then in msf 535 // mode 536 if (!error) 537 error = read_table_of_contents(fd, 1, data, kBlockSize, false); 538 if (error) { 539 TRACE(("%s: lba read_toc failed, trying msf instead\n", 540 kModuleDebugName)); 541 error = read_table_of_contents(fd, 1, data, kBlockSize, true); 542 } 543 544 // Interpret the data returned, if successful 545 if (!error) { 546 cdrom_table_of_contents_header* header; 547 cdrom_full_table_of_contents_entry* entries; 548 int count; 549 550 header = (cdrom_table_of_contents_header*)data; 551 entries = (cdrom_full_table_of_contents_entry*)(data + 4); 552 header->length = B_BENDIAN_TO_HOST_INT16(header->length); 553 554 count = (header->length - 2) 555 / sizeof(cdrom_full_table_of_contents_entry); 556 557 count = _AdjustForYellowBook(entries, count); 558 error = _ParseTableOfContents(entries, count); 559 // Dump(); 560 if (!error) { 561 _SortAndRemoveDuplicates(); 562 error = _CheckForErrorsAndWarnings(); 563 } 564 } 565 566 PRINT(("Setting init status to 0x%" B_PRIx32 ", `%s'\n", error, 567 strerror(error))); 568 fInitStatus = error; 569 } 570 571 572 /*! \brief Destroys the Disc's internal list. 573 */ 574 Disc::~Disc() 575 { 576 delete fSessionList; 577 } 578 579 580 /*! \brief Returns \c B_OK if the object was successfully initialized, or 581 an error code if not. 582 */ 583 status_t 584 Disc::InitCheck() 585 { 586 return fInitStatus; 587 } 588 589 590 /*! \brief Stores the info for the given session (using 0 based indicies) in the 591 struct pointed to by \a sessionInfo. 592 593 Returns \c B_ENTRY_NOT_FOUND if no such session exists. 594 */ 595 Session* 596 Disc::GetSession(int32 index) 597 { 598 DEBUG_INIT_ETC("Disc", ("index: %" B_PRId32, index)); 599 int32 counter = -1; 600 for (session* session = (struct session*)fSessionList->First(); session; 601 session = (struct session*)session->next) { 602 if (session->is_audio()) { 603 counter++; 604 // only one session per audio session 605 if (counter == index) { 606 // Found an audio session. Take the start of the first 607 // track with the end of session. 608 track* track = (struct track*)session->track_list.First(); 609 if (track != NULL) { 610 PRINT(("found session #%" B_PRId32 " info (audio session)" 611 "\n", index)); 612 613 off_t startLBA = track->start_lba; 614 off_t endLBA = session->end_lba; 615 616 off_t offset = startLBA * kBlockSize; 617 off_t size = (endLBA - startLBA) * kBlockSize; 618 619 Session* result = new Session(offset, size, kBlockSize, 620 index, B_PARTITION_READ_ONLY, 621 kPartitionTypeAudioSession); 622 if (result == NULL) { 623 PRINT(("Error allocating new Session object; out of " 624 "memory!\n")); 625 } 626 return result; 627 } else { 628 PRINT(("Error: session #%" B_PRId32 " is an audio session " 629 "with no tracks!\n", index)); 630 return NULL; 631 } 632 } 633 } else { 634 for (track* track = (struct track*)session->track_list.First(); 635 track; track = (struct track*)track->next) { 636 counter++; 637 if (counter == index) { 638 PRINT(("found session #%" B_PRId32 " info (data session)\n", 639 index)); 640 641 off_t startLBA = track->start_lba; 642 if (startLBA < 0) { 643 WARN(("%s: warning: invalid negative start LBA of %" 644 B_PRId64 " for data track assuming 0\n", 645 kModuleDebugName, startLBA)); 646 startLBA = 0; 647 } 648 649 off_t endLBA = track->next 650 ? ((struct track*)track->next)->start_lba 651 : session->end_lba; 652 653 off_t offset = startLBA * kBlockSize; 654 off_t size = (endLBA - startLBA) * kBlockSize; 655 656 Session* result = new Session(offset, size, kBlockSize, 657 index, B_PARTITION_READ_ONLY, 658 kPartitionTypeDataSession); 659 if (result == NULL) { 660 PRINT(("Error allocating new Session object; out of " 661 "memory!\n")); 662 } 663 return result; 664 } 665 } 666 } 667 } 668 669 PRINT(("no session #%" B_PRId32 " found!\n", index)); 670 return NULL; 671 } 672 673 674 /*! \brief Dumps a printout of the disc using TRACE. 675 */ 676 void 677 Disc::Dump() 678 { 679 TRACE(("%s: Disc dump:\n", kModuleDebugName)); 680 session* session = (struct session*)fSessionList->First(); 681 while (session != NULL) { 682 TRACE(("session %" B_PRId32 ":\n", session->index)); 683 TRACE((" first track hint: %d\n", session->first_track_hint)); 684 TRACE((" last track hint: %d\n", session->last_track_hint)); 685 TRACE((" end_lba: %" B_PRId64 "\n", session->end_lba)); 686 TRACE((" control: %d (%s session, copy %s)\n", 687 session->control, (session->control & kControlDataTrack 688 ? "data" : "audio"), 689 (session->control & kControlCopyPermitted 690 ? "permitted" : "prohibited"))); 691 TRACE((" adr: %d\n", session->adr)); 692 track* track = (struct track*)session->track_list.First(); 693 while (track != NULL) { 694 TRACE((" track %" B_PRId32 ":\n", track->index)); 695 TRACE((" start_lba: %" B_PRId64 "\n", track->start_lba)); 696 track = (struct track*)track->next; 697 } 698 session = (struct session*)session->next; 699 } 700 } 701 702 703 /*! \brief Checks for Yellow Book data tracks in audio sessions and if found 704 inserts them as a new data session. 705 */ 706 uint32 707 Disc::_AdjustForYellowBook(cdrom_full_table_of_contents_entry entries[], 708 uint32 count) 709 { 710 uint8 foundCount = 0; 711 uint8 endLBAEntry = 0; 712 uint8 trackTwo = 0; 713 714 // Make sure TOC has only one session and that it is audio. 715 bool sessionIsAudio = true; 716 for (uint32 i = 0; i < count; i++) { 717 if (entries[i].point == 0xa2) { 718 if ((entries[i].control & kControlDataTrack) != 0) { 719 sessionIsAudio = false; 720 break; 721 } 722 foundCount++; 723 endLBAEntry = i; 724 } 725 } 726 if (!sessionIsAudio || foundCount != 1) 727 return count; 728 729 TRACE(("%s: Single audio session, checking for data track\n", 730 kModuleDebugName)); 731 732 // See if there are any data tracks. 733 for (uint32 i = 0; i < count; i++) { 734 if (entries[i].point > 0 && entries[i].point < 100 735 && (entries[i].control & kControlDataTrack) != 0) { 736 if (entries[i].point == 1) { 737 // Create a new endLBA point for session one. 738 entries[count] = entries[endLBAEntry]; 739 entries[count].control = entries[i].control; 740 741 // Get track two and use it's start as 742 // the end of our new session. 743 for (uint8 j = 0; j < count; j++) { 744 if (entries[j].point == 2) { 745 trackTwo = j; 746 break; 747 } 748 } 749 entries[count].pminutes = entries[trackTwo].pminutes; 750 entries[count].pseconds = entries[trackTwo].pseconds; 751 entries[count].pframes = entries[trackTwo].pframes; 752 753 // Change the other points to session two. 754 for (uint32 j = 0; j < count; j++) { 755 entries[j].session = 2; 756 } 757 entries[i].session = 1; 758 759 count++; 760 TRACE(("%s: first track is data, adjusted TOC\n", 761 kModuleDebugName)); 762 break; 763 } else { 764 // Change the track to session two. 765 entries[i].session = 2; 766 767 // Create a new endLBA point for session two. 768 entries[count] = entries[endLBAEntry]; 769 entries[count].session = 2; 770 entries[count].control = entries[i].control; 771 772 // Use the beginning of the data track as the 773 // end of the previous session. 774 entries[endLBAEntry].pminutes = entries[i].pminutes; 775 entries[endLBAEntry].pseconds = entries[i].pseconds; 776 entries[endLBAEntry].pframes = entries[i].pframes; 777 778 count++; 779 TRACE(("%s: last track is data, adjusted TOC\n", 780 kModuleDebugName)); 781 break; 782 } 783 } 784 } 785 return count; 786 } 787 788 789 /*! \brief Reads through the given table of contents data and creates an 790 unsorted, unverified (i.e. non-error-checked) list of sessions and tracks. 791 */ 792 status_t 793 Disc::_ParseTableOfContents(cdrom_full_table_of_contents_entry entries[], 794 uint32 count) 795 { 796 DEBUG_INIT_ETC("Disc", ("entries: %p, count: %" B_PRIu32, entries, count)); 797 798 for (uint32 i = 0; i < count; i++) { 799 // Find or create the appropriate session 800 uint8 sessionIndex = entries[i].session; 801 session* session = (struct session*)fSessionList->Find(sessionIndex); 802 if (session == NULL) { 803 session = new struct session(sessionIndex); 804 if (session == NULL) 805 return B_NO_MEMORY; 806 807 fSessionList->Add(session); 808 } 809 810 uint8 point = entries[i].point; 811 812 switch (point) { 813 // first track hint 814 case 0xA0: 815 if (!session->first_track_hint_is_set()) { 816 int8 firstTrackHint = entries[i].pminutes; 817 if (1 <= firstTrackHint && firstTrackHint <= 99) { 818 session->first_track_hint = firstTrackHint; 819 } else { 820 WARN(("%s: warning: illegal first track hint %d found " 821 "for session %d\n", kModuleDebugName, 822 firstTrackHint, sessionIndex)); 823 } 824 } else { 825 WARN(("%s: warning: duplicated first track hint values " 826 "found for session %d; using first value " 827 "encountered: %d", kModuleDebugName, sessionIndex, 828 session->first_track_hint)); 829 } 830 break; 831 832 // last track hint 833 case 0xA1: 834 if (!session->last_track_hint_is_set()) { 835 int8 lastTrackHint = entries[i].pminutes; 836 if (1 <= lastTrackHint && lastTrackHint <= 99) { 837 session->last_track_hint = lastTrackHint; 838 } else { 839 WARN(("%s: warning: illegal last track hint %d found " 840 "for session %d\n", kModuleDebugName, 841 lastTrackHint, sessionIndex)); 842 } 843 } else { 844 WARN(("%s: warning: duplicate last track hint values found " 845 "for session %d; using first value encountered: %d", 846 kModuleDebugName, sessionIndex, 847 session->last_track_hint)); 848 } 849 break; 850 851 // end of session address 852 case 0xA2: 853 if (!session->end_lba_is_set()) { 854 off_t endLBA = msf_to_lba(make_msf_address( 855 entries[i].pminutes, entries[i].pseconds, 856 entries[i].pframes)); 857 if (endLBA > 0) { 858 session->end_lba = endLBA; 859 // We also grab the session's control and adr values 860 // from this entry 861 session->control = entries[i].control; 862 session->adr = entries[i].adr; 863 } else { 864 WARN(("%s: warning: illegal end lba %" B_PRId64 " found" 865 " for session %d\n", kModuleDebugName, endLBA, 866 sessionIndex)); 867 } 868 } else { 869 WARN(("%s: warning: duplicate end lba values found for " 870 "session %d; using first value encountered: %" B_PRId64, 871 kModuleDebugName, sessionIndex, session->end_lba)); 872 } 873 break; 874 875 // Valid, but uninteresting, points 876 case 0xB0: 877 case 0xB1: 878 case 0xB2: 879 case 0xB3: 880 case 0xB4: 881 case 0xC0: 882 case 0xC1: 883 break; 884 885 default: 886 // Anything else had better be a valid track number, 887 // or it's an invalid point 888 if (1 <= point && point <= 99) { 889 // Create and add the track. We'll weed out any duplicates 890 // later. 891 uint8 trackIndex = point; 892 off_t startLBA = msf_to_lba(make_msf_address( 893 entries[i].pminutes, entries[i].pseconds, 894 entries[i].pframes)); 895 // The control and adr values grabbed here are only used 896 // later on to signal a warning if they don't match the 897 // corresponding values of the parent session. 898 track* track = new(std::nothrow) struct track(trackIndex, 899 startLBA, entries[i].control, entries[i].adr); 900 if (track == NULL) 901 return B_NO_MEMORY; 902 903 session->track_list.Add(track); 904 } else { 905 WARN(("%s: warning: illegal point 0x%2x found in table of " 906 "contents\n", kModuleDebugName, point)); 907 } 908 break; 909 } 910 } 911 return B_OK; 912 } 913 914 915 /*! \brief Bubble sorts the session list and each session's track lists, 916 removing all but the first of any duplicates (by index) found along 917 the way. 918 */ 919 void 920 Disc::_SortAndRemoveDuplicates() 921 { 922 fSessionList->SortAndRemoveDuplicates(); 923 session* session = (struct session*)fSessionList->First(); 924 while (session != NULL) { 925 session->track_list.SortAndRemoveDuplicates(); 926 session = (struct session*)session->next; 927 } 928 } 929 930 931 /* \brief Checks the sessions and tracks for any anomalies. 932 933 Errors will return an error code, warnings will return B_OK. 934 Both will print a notification using TRACE. 935 936 Anomalies that result in errors: 937 - Sessions with no end_lba set 938 - Sessions with no tracks 939 940 Anomalies that result in warnings: 941 - Inaccurate first_track_hint and/or last_track_hint values 942 - Sequences of sessions or tracks that do not start at 1, 943 do not end at or before 99, or are not strictly ascending. 944 (all tracks are checked as a single sequence, since track 945 numbering does not restart with each session). 946 - Tracks with different control and/or adr values than their 947 parent session 948 949 Anomalies that are currently *not* checked: 950 - First Track Hint or Last Track Hint control and adr values 951 that do not match the values for their session; Ingo's copy 952 of the BeOS R5 CD is like this, but I don't believe it's 953 a matter we need to worry about. This could certainly be 954 changed in the future if needed. 955 */ 956 status_t 957 Disc::_CheckForErrorsAndWarnings() { 958 int32 lastSessionIndex = 0; 959 int32 lastTrackIndex = 0; 960 961 for (session* session = (struct session*)fSessionList->First(); session; 962 session = (struct session*)session->next) { 963 // Check for errors 964 965 // missing end lba 966 if (!session->end_lba_is_set()) { 967 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: no end of " 968 "session address for session #%" B_PRId32 "\n", 969 kModuleDebugName, session->index)); 970 return B_ERROR; 971 } 972 973 // empty track list 974 track* track = (struct track*)session->track_list.First(); 975 if (track == NULL) { 976 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: session #%" 977 B_PRId32 "has no tracks\n", kModuleDebugName, session->index)); 978 return B_ERROR; 979 } 980 981 // Check for warnings 982 983 // incorrect first track hint 984 if (session->first_track_hint_is_set() 985 && session->first_track_hint != track->index) { 986 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session " 987 "#%" B_PRId32 ": first track hint (%d) doesn't match actual " 988 "first track (%" B_PRId32 ")\n", kModuleDebugName, 989 session->index, session->first_track_hint, track->index)); 990 } 991 992 // incorrect last track hint 993 struct track* last = (struct track*)session->track_list.Last(); 994 if (session->last_track_hint_is_set() && last 995 && session->last_track_hint != last->index) { 996 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session " 997 "#%" B_PRId32 ": last track hint (%d) doesn't match actual " 998 "last track (%" B_PRId32 ")\n", kModuleDebugName, 999 session->index, session->last_track_hint, last->index)); 1000 } 1001 1002 // invalid session sequence 1003 if (lastSessionIndex + 1 != session->index) { 1004 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index for " 1005 "session #%" B_PRId32 " is out of sequence (should have been #%" 1006 B_PRId32 ")\n", kModuleDebugName, session->index, 1007 lastSessionIndex)); 1008 } 1009 lastSessionIndex = session->index; 1010 1011 for (; track; track = (struct track*)track->next) { 1012 // invalid track sequence 1013 if (lastTrackIndex + 1 != track->index) { 1014 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index " 1015 "for track #%" B_PRId32 " is out of sequence (should have " 1016 "been #%" B_PRId32 ")\n", kModuleDebugName, track->index, 1017 lastTrackIndex)); 1018 } 1019 lastTrackIndex = track->index; 1020 1021 // mismatched control 1022 if (track->control != session->control) { 1023 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: control " 1024 "for track #%" B_PRId32 " (%d, %s track, copy %s) does not " 1025 "match control for parent session #%" B_PRId32 " (%d, %s " 1026 "session, copy %s)\n", kModuleDebugName, track->index, 1027 track->control, 1028 (track->control & kControlDataTrack ? "data" : "audio"), 1029 (track->control & kControlCopyPermitted 1030 ? "permitted" : "prohibited"), 1031 session->index, session->control, 1032 (session->control & kControlDataTrack ? "data" : "audio"), 1033 (session->control & kControlCopyPermitted 1034 ? "permitted" : "prohibited"))); 1035 } 1036 1037 // mismatched adr 1038 if (track->adr != session->adr) { 1039 TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: adr " 1040 "for track #%" B_PRId32 " (adr = %d) does not match adr " 1041 "for parent session #%" B_PRId32 " (adr = %d)\n", 1042 kModuleDebugName, track->index, track->adr, session->index, 1043 session->adr)); 1044 } 1045 } 1046 } 1047 1048 return B_OK; 1049 } 1050 1051 1052 // #pragma mark - Session 1053 1054 1055 Session::Session(off_t offset, off_t size, uint32 blockSize, int32 index, 1056 uint32 flags, const char* type) 1057 : 1058 fOffset(offset), 1059 fSize(size), 1060 fBlockSize(blockSize), 1061 fIndex(index), 1062 fFlags(flags), 1063 fType(strdup(type)) 1064 { 1065 } 1066 1067 1068 Session::~Session() 1069 { 1070 free(fType); 1071 } 1072