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:
list_itemlist_item40 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:
tracktrack77 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
dump_full_table_of_contents(uchar * data,uint16 dataLength)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
read_table_of_contents(int deviceFD,uint32 first_session,uchar * buffer,uint16 buffer_length,bool msf)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 */
List()308 List::List()
309 :
310 fFirst(NULL),
311 fLast(NULL)
312 {
313 }
314
315
~List()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*
Find(int32 index) const325 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
Add(list_item * item)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
Clear()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
SortAndRemoveDuplicates()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*
First() const431 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*
Last() const440 List::Last() const
441 {
442 return fLast;
443 }
444
445
446 // #pragma mark - session
447
448
449 /*! \brief Creates an unitialized session object
450 */
session(uint32 index,session * next)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
first_track_hint_is_set()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
last_track_hint_is_set()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
end_lba_is_set()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
is_audio()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 */
Disc(int fd)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 */
~Disc()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
InitCheck()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*
GetSession(int32 index)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
Dump()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
_AdjustForYellowBook(cdrom_full_table_of_contents_entry entries[],uint32 count)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
_ParseTableOfContents(cdrom_full_table_of_contents_entry entries[],uint32 count)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
_SortAndRemoveDuplicates()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
_CheckForErrorsAndWarnings()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
Session(off_t offset,off_t size,uint32 blockSize,int32 index,uint32 flags,const char * type)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
~Session()1072 Session::~Session()
1073 {
1074 free(fType);
1075 }
1076