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