xref: /haiku/src/add-ons/kernel/partitioning_systems/session/Disc.cpp (revision 9b72c3a0890317cca7baf47d8c052f04cb2f2f8b)
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