xref: /haiku/src/add-ons/kernel/partitioning_systems/session/Disc.cpp (revision 508f54795f39c3e7552d87c95aae9dd8ec6f505b)
1 /*
2  * Copyright 2003-2009, 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 		//!< only used to give what are probably useless warnings
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      = %lld\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%lx\n", kModuleDebugName,
290 			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 (#%ld); keeping first instance\n", kModuleDebugName,
411 					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 		error = _ParseTableOfContents(entries, count);
558 //		Dump();
559 		if (!error) {
560 			_SortAndRemoveDuplicates();
561 			error = _CheckForErrorsAndWarnings();
562 		}
563 	}
564 
565 	PRINT(("Setting init status to 0x%lx, `%s'\n", error, strerror(error)));
566 	fInitStatus = error;
567 }
568 
569 
570 /*! \brief Destroys the Disc's internal list.
571 */
572 Disc::~Disc()
573 {
574 	delete fSessionList;
575 }
576 
577 
578 /*! \brief Returns \c B_OK if the object was successfully initialized, or
579 	an error code if not.
580 */
581 status_t
582 Disc::InitCheck()
583 {
584 	return fInitStatus;
585 }
586 
587 
588 /*! \brief Stores the info for the given session (using 0 based indicies) in the
589 	struct pointed to by \a sessionInfo.
590 
591 	Returns \c B_ENTRY_NOT_FOUND if no such session exists.
592 */
593 Session*
594 Disc::GetSession(int32 index)
595 {
596 	DEBUG_INIT_ETC("Disc", ("index: %ld", index));
597 	int32 counter = -1;
598 	for (session* session = (struct session*)fSessionList->First(); session;
599 			session = (struct session*)session->next) {
600 		if (session->is_audio()) {
601 			counter++;
602 				// only one session per audio session
603 			if (counter == index) {
604 				// Found an audio session. Take the start of the first
605 				// track with the end of session.
606 				track* track = (struct track*)session->track_list.First();
607 				if (track != NULL) {
608 					PRINT(("found session #%ld info (audio session)\n", index));
609 
610 					off_t startLBA = track->start_lba;
611 					off_t endLBA = session->end_lba;
612 
613 					off_t offset = startLBA * kBlockSize;
614 					off_t size = (endLBA - startLBA) * kBlockSize;
615 
616 					Session* result = new Session(offset, size, kBlockSize,
617 						index, B_PARTITION_READ_ONLY,
618 						kPartitionTypeAudioSession);
619 					if (result == NULL) {
620 						PRINT(("Error allocating new Session object; out of "
621 							"memory!\n"));
622 					}
623 					return result;
624 				} else {
625 					PRINT(("Error: session #%ld is an audio session with no "
626 						"tracks!\n", index));
627 					return NULL;
628 				}
629 			}
630 		} else {
631 			for (track* track = (struct track*)session->track_list.First();
632 					track; track = (struct track*)track->next) {
633 				counter++;
634 				if (counter == index) {
635 					PRINT(("found session #%ld info (data session)\n", index));
636 
637 					off_t startLBA = track->start_lba;
638 					if (startLBA < 0) {
639 						WARN(("%s: warning: invalid negative start LBA of %lld"
640 							" for data track assuming 0\n", kModuleDebugName,
641 							startLBA));
642 						startLBA = 0;
643 					}
644 
645 					off_t endLBA = track->next
646 						? ((struct track*)track->next)->start_lba
647 						: session->end_lba;
648 
649 					off_t offset = startLBA * kBlockSize;
650 					off_t size = (endLBA - startLBA) * kBlockSize;
651 
652 					Session* result = new Session(offset, size, kBlockSize,
653 						index, B_PARTITION_READ_ONLY,
654 						kPartitionTypeDataSession);
655 					if (result == NULL) {
656 						PRINT(("Error allocating new Session object; out of "
657 							"memory!\n"));
658 					}
659 					return result;
660 				}
661 			}
662 		}
663 	}
664 
665 	PRINT(("no session #%ld found!\n", index));
666 	return NULL;
667 }
668 
669 
670 /*! \brief Dumps a printout of the disc using TRACE.
671 */
672 void
673 Disc::Dump()
674 {
675 	TRACE(("%s: Disc dump:\n", kModuleDebugName));
676 	session* session = (struct session*)fSessionList->First();
677 	while (session != NULL) {
678 		TRACE(("session %ld:\n", session->index));
679 		TRACE(("  first track hint: %d\n", session->first_track_hint));
680 		TRACE(("  last track hint:  %d\n", session->last_track_hint));
681 		TRACE(("  end_lba:          %lld\n", session->end_lba));
682 		TRACE(("  control:          %d (%s session, copy %s)\n",
683 			session->control, (session->control & kControlDataTrack
684 				? "data" : "audio"),
685 			(session->control & kControlCopyPermitted
686 				? "permitted" : "prohibited")));
687 		TRACE(("  adr:              %d\n", session->adr));
688 		track* track = (struct track*)session->track_list.First();
689 		while (track != NULL) {
690 			TRACE(("  track %ld:\n", track->index));
691 			TRACE(("    start_lba: %lld\n", track->start_lba));
692 			track = (struct track*)track->next;
693 		}
694 		session = (struct session*)session->next;
695 	}
696 }
697 
698 
699 /*! \brief Reads through the given table of contents data and creates an
700 	unsorted, unverified (i.e. non-error-checked) list of sessions and tracks.
701 */
702 status_t
703 Disc::_ParseTableOfContents(cdrom_full_table_of_contents_entry entries[],
704 	uint32 count)
705 {
706 	DEBUG_INIT_ETC("Disc", ("entries: %p, count: %ld", entries, count));
707 
708 	for (uint32 i = 0; i < count; i++) {
709 		// Find or create the appropriate session
710 		uint8 sessionIndex = entries[i].session;
711 		session* session = (struct session*)fSessionList->Find(sessionIndex);
712 		if (session == NULL) {
713 			session = new struct session(sessionIndex);
714 			if (session == NULL)
715 				return B_NO_MEMORY;
716 
717 			fSessionList->Add(session);
718 		}
719 
720 		uint8 point = entries[i].point;
721 
722 		switch (point) {
723 			// first track hint
724 			case 0xA0:
725 				if (!session->first_track_hint_is_set()) {
726 					int8 firstTrackHint = entries[i].pminutes;
727 					if (1 <= firstTrackHint && firstTrackHint <= 99) {
728 						session->first_track_hint = firstTrackHint;
729 					} else {
730 						WARN(("%s: warning: illegal first track hint %d found "
731 							"for session %d\n", kModuleDebugName,
732 							firstTrackHint, sessionIndex));
733 					}
734 				} else {
735 					WARN(("%s: warning: duplicated first track hint values "
736 						"found for session %d; using first value "
737 						"encountered: %d", kModuleDebugName, sessionIndex,
738 						session->first_track_hint));
739 				}
740 				break;
741 
742 			// last track hint
743 			case 0xA1:
744 				if (!session->last_track_hint_is_set()) {
745 					int8 lastTrackHint = entries[i].pminutes;
746 					if (1 <= lastTrackHint && lastTrackHint <= 99) {
747 						session->last_track_hint = lastTrackHint;
748 					} else {
749 						WARN(("%s: warning: illegal last track hint %d found "
750 							"for session %d\n", kModuleDebugName,
751 							lastTrackHint, sessionIndex));
752 					}
753 				} else {
754 					WARN(("%s: warning: duplicate last track hint values found "
755 						"for session %d; using first value encountered: %d",
756 						kModuleDebugName, sessionIndex,
757 						session->last_track_hint));
758 				}
759 				break;
760 
761 			// end of session address
762 			case 0xA2:
763 				if (!session->end_lba_is_set()) {
764 					off_t endLBA = msf_to_lba(make_msf_address(
765 						entries[i].pminutes, entries[i].pseconds,
766 						entries[i].pframes));
767 					if (endLBA > 0) {
768 						session->end_lba = endLBA;
769 						// We also grab the session's control and adr values
770 						// from this entry
771 						session->control = entries[i].control;
772 						session->adr = entries[i].adr;
773 					} else {
774 						WARN(("%s: warning: illegal end lba %lld found for "
775 							"session %d\n", kModuleDebugName, endLBA,
776 							sessionIndex));
777 					}
778 				} else {
779 					WARN(("%s: warning: duplicate end lba values found for "
780 						"session %d; using first value encountered: %lld",
781 						kModuleDebugName, sessionIndex, session->end_lba));
782 				}
783 				break;
784 
785 			// Valid, but uninteresting, points
786 			case 0xB0:
787 			case 0xB1:
788 			case 0xB2:
789 			case 0xB3:
790 			case 0xB4:
791 			case 0xC0:
792 			case 0xC1:
793 				break;
794 
795 			default:
796 				// Anything else had better be a valid track number,
797 				// or it's an invalid point
798 				if (1 <= point && point <= 99) {
799 					// Create and add the track. We'll weed out any duplicates
800 					// later.
801 					uint8 trackIndex = point;
802 					off_t startLBA = msf_to_lba(make_msf_address(
803 						entries[i].pminutes, entries[i].pseconds,
804 						entries[i].pframes));
805 					// The control and adr values grabbed here are only used
806 					// later on to signal a warning if they don't match the
807 					// corresponding values of the parent session.
808 					track* track = new(std::nothrow) struct track(trackIndex,
809 						startLBA, entries[i].control, entries[i].adr);
810 					if (track == NULL)
811 						return B_NO_MEMORY;
812 
813 					session->track_list.Add(track);
814 				} else {
815 					WARN(("%s: warning: illegal point 0x%2x found in table of "
816 						"contents\n", kModuleDebugName, entries[i].point));
817 				}
818 				break;
819 		}
820 	}
821 	return B_OK;
822 }
823 
824 
825 /*! \brief Bubble sorts the session list and each session's track lists,
826 	removing all but the first of any duplicates (by index) found along
827 	the way.
828 */
829 void
830 Disc::_SortAndRemoveDuplicates()
831 {
832 	fSessionList->SortAndRemoveDuplicates();
833 	session* session = (struct session*)fSessionList->First();
834 	while (session != NULL) {
835 		session->track_list.SortAndRemoveDuplicates();
836 		session = (struct session*)session->next;
837 	}
838 }
839 
840 
841 /*	\brief Checks the sessions and tracks for any anomalies.
842 
843 	Errors will return an error code, warnings will return B_OK.
844 	Both will print a notification using TRACE.
845 
846 	Anomalies that result in errors:
847 	- Sessions with no end_lba set
848 	- Sessions with no tracks
849 
850 	Anomalies that result in warnings:
851 	- Inaccurate first_track_hint and/or last_track_hint values
852 	- Sequences of sessions or tracks that do not start at 1,
853 	  do not end at or before 99, or are not strictly ascending.
854 	  (all tracks are checked as a single sequence, since track
855 	  numbering does not restart with each session).
856 	- Tracks with different control and/or adr values than their
857 	  parent session
858 
859 	Anomalies that are currently *not* checked:
860 	- First Track Hint or Last Track Hint control and adr values
861 	  that do not match the values for their session; Ingo's copy
862 	  of the BeOS R5 CD is like this, but I don't believe it's
863 	  a matter we need to worry about. This could certainly be
864 	  changed in the future if needed.
865 */
866 status_t
867 Disc::_CheckForErrorsAndWarnings() {
868 	int32 lastSessionIndex = 0;
869 	int32 lastTrackIndex = 0;
870 
871 	for (session* session = (struct session*)fSessionList->First(); session;
872 			session = (struct session*)session->next) {
873 		// Check for errors
874 
875 		// missing end lba
876 		if (!session->end_lba_is_set()) {
877 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: no end of "
878 				"session address for session #%ld\n", kModuleDebugName,
879 				session->index));
880 			return B_ERROR;
881 		}
882 
883 		// empty track list
884 		track* track = (struct track*)session->track_list.First();
885 		if (track == NULL) {
886 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: session #%ld "
887 				"has no tracks\n", kModuleDebugName, session->index));
888 			return B_ERROR;
889 		}
890 
891 		// Check for warnings
892 
893 		// incorrect first track hint
894 		if (session->first_track_hint_is_set()
895 			&& session->first_track_hint != track->index) {
896 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session "
897 				"#%ld: first track hint (%d) doesn't match actual first track "
898 				"(%ld)\n", kModuleDebugName, session->index,
899 				session->first_track_hint, track->index));
900 		}
901 
902 		// incorrect last track hint
903 		struct track* last = (struct track*)session->track_list.Last();
904 		if (session->last_track_hint_is_set() && last
905 			&& session->last_track_hint != last->index) {
906 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session "
907 				"#%ld: last track hint (%d) doesn't match actual last track "
908 				"(%ld)\n", kModuleDebugName, session->index,
909 				session->last_track_hint, last->index));
910 		}
911 
912 		// invalid session sequence
913 		if (lastSessionIndex + 1 != session->index) {
914 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index for "
915 				"session #%ld is out of sequence (should have been #%ld)\n",
916 				kModuleDebugName, session->index, lastSessionIndex));
917 		}
918 		lastSessionIndex = session->index;
919 
920 		for (; track; track = (struct track*)track->next) {
921 			// invalid track sequence
922 			if (lastTrackIndex + 1 != track->index) {
923 				TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index "
924 					"for track #%ld is out of sequence (should have been "
925 					"#%ld)\n", kModuleDebugName, track->index, lastTrackIndex));
926 			}
927 			lastTrackIndex = track->index;
928 
929 			// mismatched control
930 			if (track->control != session->control) {
931 				TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: control "
932 					"for track #%ld (%d, %s track, copy %s) does not match "
933 					"control for parent session #%ld (%d, %s session, copy "
934 					"%s)\n", kModuleDebugName, track->index, track->control,
935 					(track->control & kControlDataTrack ? "data" : "audio"),
936 					(track->control & kControlCopyPermitted
937 						? "permitted" : "prohibited"),
938 					session->index, session->control,
939 					(session->control & kControlDataTrack ? "data" : "audio"),
940 					(session->control & kControlCopyPermitted
941 						? "permitted" : "prohibited")));
942 			}
943 
944 			// mismatched adr
945 			if (track->adr != session->adr) {
946 				TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: adr "
947 					"for track #%ld (adr = %d) does not match adr for parent "
948 					"session #%ld (adr = %d)\n", kModuleDebugName, track->index,
949 					track->adr, session->index, session->adr));
950 			}
951 		}
952 	}
953 
954 	return B_OK;
955 }
956 
957 
958 //	#pragma mark - Session
959 
960 
961 Session::Session(off_t offset, off_t size, uint32 blockSize, int32 index,
962 		uint32 flags, const char* type)
963 	:
964 	fOffset(offset),
965 	fSize(size),
966 	fBlockSize(blockSize),
967 	fIndex(index),
968 	fFlags(flags),
969 	fType(strdup(type))
970 {
971 }
972 
973 
974 Session::~Session()
975 {
976 	free(fType);
977 }
978