xref: /haiku/src/add-ons/kernel/partitioning_systems/session/Disc.cpp (revision 7a74a5df454197933bc6e80a542102362ee98703)
1 /*
2  * Copyright 2003-2011, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Tyler Akidau, haiku@akidau.net
7  */
8 
9 
10 /*!	\file Disc.cpp
11 
12 	Disc class implementation, used to enumerate the CD/DVD sessions.
13 
14 	The protocols followed in this module are based on information
15 	taken from the "SCSI-3 Multimedia Commands" draft, revision 10A.
16 
17 	The SCSI command of interest is "READ TOC/PMA/ATIP", command
18 	number \c 0x43.
19 
20 	The format of interest for said command is "Full TOC", format
21 	number \c 0x2.
22 */
23 
24 
25 #include "Disc.h"
26 
27 #include <DiskDeviceDefs.h>
28 #include <DiskDeviceTypes.h>
29 
30 #include "Debug.h"
31 
32 
33 DBG(static const char* kModuleDebugName = "session");
34 
35 
36 /*! \brief An item that can be stored in a List object.
37 */
38 struct list_item {
39 public:
40 	list_item(uint32 index, list_item* next = NULL)
41 		:
42 		index(index),
43 		next(next)
44 	{
45 	}
46 
47 	int32		index;
48 	list_item*	next;
49 };
50 
51 
52 /*! \brief A simple, singly linked list.
53 */
54 class List {
55 public:
56 	List();
57 	~List();
58 
59 	list_item* Find(int32 index) const;
60 	void Add(list_item* item);
61 	void Clear();
62 	void SortAndRemoveDuplicates();
63 
64 	list_item* First() const;
65 	list_item* Last() const;
66 
67 private:
68 	list_item*	fFirst;
69 	list_item*	fLast;
70 };
71 
72 
73 /*! \brief Keeps track of track information.
74 */
75 struct track : public list_item {
76 public:
77 	track(uint32 index, off_t startLBA, uint8 control, uint8 adr,
78 			track* next = NULL)
79 		:
80 		list_item(index, next),
81 		start_lba(startLBA),
82 		control(control),
83 		adr(adr)
84 	{
85 	}
86 
87 	off_t	start_lba;
88 	uint8	control;
89 		// Used to check for Yellow/Red Book mixed-mode CDs.
90 	uint8	adr;
91 		// only used to give what are probably useless warnings
92 };
93 
94 
95 /*! \brief Keeps track of session information.
96 */
97 struct session : public list_item {
98 public:
99 	session(uint32 index, session* next = NULL);
100 
101 	bool first_track_hint_is_set();
102 	bool last_track_hint_is_set();
103 	bool end_lba_is_set();	// also implies control and adr are set
104 
105 	bool is_audio();
106 
107 	int8	first_track_hint;
108 	int8	last_track_hint;
109 	int8	control;
110 	int8	adr;
111 	off_t	end_lba;
112 
113 	List	track_list;
114 };
115 
116 
117 //	#pragma mark - Helper functions
118 
119 
120 #ifdef DEBUG
121 /*
122 static void
123 dump_scsi_command(raw_device_command* cmd)
124 {
125 	int i;
126 	uint j;
127 	scsi_table_of_contents_command* scsi_command
128 		= (scsi_table_of_contents_command*)(&(cmd->command));
129 
130 	for (i = 0; i < cmd->command_length; i++)
131 		TRACE(("%.2x,", cmd->command[i]));
132 	TRACE(("\n"));
133 
134 	TRACE(("raw_device_command:\n"));
135 	TRACE(("  command:\n"));
136 	TRACE(("    command = %d (0x%.2x)\n", scsi_command->command,
137 		scsi_command->command));
138 	TRACE(("    msf     = %d\n", scsi_command->msf));
139 	TRACE(("    format  = %d (0x%.2x)\n", scsi_command->format,
140 		scsi_command->format));
141 	TRACE(("    number  = %d\n", scsi_command->number));
142 	TRACE(("    length  = %d\n",
143 		B_BENDIAN_TO_HOST_INT16(scsi_command->length)));
144 	TRACE(("    control = %d\n", scsi_command->control));
145 	TRACE(("  command_length    = %d\n", cmd->command_length));
146 	TRACE(("  flags             = %d\n", cmd->flags));
147 	TRACE(("  scsi_status       = 0x%x\n", cmd->scsi_status));
148 	TRACE(("  cam_status        = 0x%x\n", cmd->cam_status));
149 	TRACE(("  data              = %p\n", cmd->data));
150 	TRACE(("  data_length       = %ld\n", cmd->data_length));
151 	TRACE(("  sense_data        = %p\n", cmd->sense_data));
152 	TRACE(("  sense_data_length = %ld\n", cmd->sense_data_length));
153 	TRACE(("  timeout           = %lld\n", cmd->timeout));
154 	TRACE(("data dump:\n"));
155 	for (j = 0; j < 2048; j++) {//cmd->data_length; j++) {
156 		uchar c = ((uchar*)cmd->data)[j];
157 
158 		if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
159 			|| ('0' <= c && c <= '9'))
160 			TRACE(("\\%c,", c));
161 		else
162 			TRACE(("%.2x,", c));
163 	}
164 	TRACE(("\n"));
165 	TRACE(("sense_data dump:\n"));
166 	for (j = 0; j < cmd->sense_data_length; j++) {
167 		uchar c = ((uchar*)cmd->sense_data)[j];
168 		if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
169 			|| ('0' <= c && c <= '9'))
170 			TRACE(("%c", c));
171 		else if (c == 0)
172 			TRACE(("_"));
173 		else
174 			TRACE(("-"));
175 	}
176 	TRACE(("\n"));
177 }
178 */
179 
180 
181 static void
182 dump_full_table_of_contents(uchar* data, uint16 dataLength)
183 {
184 	cdrom_table_of_contents_header* header
185 		= (cdrom_table_of_contents_header*)data;
186 	cdrom_full_table_of_contents_entry* entries
187 		= (cdrom_full_table_of_contents_entry*)(data + 4);
188 	int headerLength = B_BENDIAN_TO_HOST_INT16(header->length);
189 
190 	if (dataLength < headerLength) {
191 		TRACE(("dump_full_table_of_contents: warning, data buffer not large "
192 			"enough (%d < %d)\n", dataLength, headerLength));
193 		headerLength = dataLength;
194 	}
195 
196 	TRACE(("%s: table of contents dump:\n", kModuleDebugName));
197 	TRACE(("--------------------------------------------------\n"));
198 	TRACE(("header:\n"));
199 	TRACE(("  length = %d\n", headerLength));
200 	TRACE(("  first  = %d\n", header->first));
201 	TRACE(("  last   = %d\n", header->last));
202 
203 	int count = (headerLength - 2) / sizeof(cdrom_full_table_of_contents_entry);
204 	TRACE(("\n"));
205 	TRACE(("entry count = %d\n", count));
206 
207 	for (int i = 0; i < count; i++) {
208 		TRACE(("\n"));
209 		TRACE(("entry #%d:\n", i));
210 		TRACE(("  session  = %d\n", entries[i].session));
211 		TRACE(("  adr      = %d\n", entries[i].adr));
212 		TRACE(("  control  = %d (%s track, copy %s)\n", entries[i].control,
213 			(entries[i].control & kControlDataTrack ? "data" : "audio"),
214 			(entries[i].control & kControlCopyPermitted
215 				? "permitted" : "prohibited")));
216 		TRACE(("  tno      = %d\n", entries[i].tno));
217 		TRACE(("  point    = %d (0x%.2x)\n", entries[i].point,
218 			entries[i].point));
219 		TRACE(("  minutes  = %d\n", entries[i].minutes));
220 		TRACE(("  frames   = %d\n", entries[i].seconds));
221 		TRACE(("  seconds  = %d\n", entries[i].frames));
222 		TRACE(("  zero     = %d\n", entries[i].zero));
223 		TRACE(("  pminutes = %d\n", entries[i].pminutes));
224 		TRACE(("  pseconds = %d\n", entries[i].pseconds));
225 		TRACE(("  pframes  = %d\n", entries[i].pframes));
226 		TRACE(("  lba      = %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 		count = _AdjustForYellowBook(entries, count);
558 		error = _ParseTableOfContents(entries, count);
559 //		Dump();
560 		if (!error) {
561 			_SortAndRemoveDuplicates();
562 			error = _CheckForErrorsAndWarnings();
563 		}
564 	}
565 
566 	PRINT(("Setting init status to 0x%lx, `%s'\n", error, strerror(error)));
567 	fInitStatus = error;
568 }
569 
570 
571 /*! \brief Destroys the Disc's internal list.
572 */
573 Disc::~Disc()
574 {
575 	delete fSessionList;
576 }
577 
578 
579 /*! \brief Returns \c B_OK if the object was successfully initialized, or
580 	an error code if not.
581 */
582 status_t
583 Disc::InitCheck()
584 {
585 	return fInitStatus;
586 }
587 
588 
589 /*! \brief Stores the info for the given session (using 0 based indicies) in the
590 	struct pointed to by \a sessionInfo.
591 
592 	Returns \c B_ENTRY_NOT_FOUND if no such session exists.
593 */
594 Session*
595 Disc::GetSession(int32 index)
596 {
597 	DEBUG_INIT_ETC("Disc", ("index: %ld", index));
598 	int32 counter = -1;
599 	for (session* session = (struct session*)fSessionList->First(); session;
600 			session = (struct session*)session->next) {
601 		if (session->is_audio()) {
602 			counter++;
603 				// only one session per audio session
604 			if (counter == index) {
605 				// Found an audio session. Take the start of the first
606 				// track with the end of session.
607 				track* track = (struct track*)session->track_list.First();
608 				if (track != NULL) {
609 					PRINT(("found session #%ld info (audio session)\n", index));
610 
611 					off_t startLBA = track->start_lba;
612 					off_t endLBA = session->end_lba;
613 
614 					off_t offset = startLBA * kBlockSize;
615 					off_t size = (endLBA - startLBA) * kBlockSize;
616 
617 					Session* result = new Session(offset, size, kBlockSize,
618 						index, B_PARTITION_READ_ONLY,
619 						kPartitionTypeAudioSession);
620 					if (result == NULL) {
621 						PRINT(("Error allocating new Session object; out of "
622 							"memory!\n"));
623 					}
624 					return result;
625 				} else {
626 					PRINT(("Error: session #%ld is an audio session with no "
627 						"tracks!\n", index));
628 					return NULL;
629 				}
630 			}
631 		} else {
632 			for (track* track = (struct track*)session->track_list.First();
633 					track; track = (struct track*)track->next) {
634 				counter++;
635 				if (counter == index) {
636 					PRINT(("found session #%ld info (data session)\n", index));
637 
638 					off_t startLBA = track->start_lba;
639 					if (startLBA < 0) {
640 						WARN(("%s: warning: invalid negative start LBA of %lld"
641 							" for data track assuming 0\n", kModuleDebugName,
642 							startLBA));
643 						startLBA = 0;
644 					}
645 
646 					off_t endLBA = track->next
647 						? ((struct track*)track->next)->start_lba
648 						: session->end_lba;
649 
650 					off_t offset = startLBA * kBlockSize;
651 					off_t size = (endLBA - startLBA) * kBlockSize;
652 
653 					Session* result = new Session(offset, size, kBlockSize,
654 						index, B_PARTITION_READ_ONLY,
655 						kPartitionTypeDataSession);
656 					if (result == NULL) {
657 						PRINT(("Error allocating new Session object; out of "
658 							"memory!\n"));
659 					}
660 					return result;
661 				}
662 			}
663 		}
664 	}
665 
666 	PRINT(("no session #%ld found!\n", index));
667 	return NULL;
668 }
669 
670 
671 /*! \brief Dumps a printout of the disc using TRACE.
672 */
673 void
674 Disc::Dump()
675 {
676 	TRACE(("%s: Disc dump:\n", kModuleDebugName));
677 	session* session = (struct session*)fSessionList->First();
678 	while (session != NULL) {
679 		TRACE(("session %ld:\n", session->index));
680 		TRACE(("  first track hint: %d\n", session->first_track_hint));
681 		TRACE(("  last track hint:  %d\n", session->last_track_hint));
682 		TRACE(("  end_lba:          %lld\n", session->end_lba));
683 		TRACE(("  control:          %d (%s session, copy %s)\n",
684 			session->control, (session->control & kControlDataTrack
685 				? "data" : "audio"),
686 			(session->control & kControlCopyPermitted
687 				? "permitted" : "prohibited")));
688 		TRACE(("  adr:              %d\n", session->adr));
689 		track* track = (struct track*)session->track_list.First();
690 		while (track != NULL) {
691 			TRACE(("  track %ld:\n", track->index));
692 			TRACE(("    start_lba: %lld\n", track->start_lba));
693 			track = (struct track*)track->next;
694 		}
695 		session = (struct session*)session->next;
696 	}
697 }
698 
699 
700 /*! \brief Checks for Yellow Book data tracks in audio sessions and if found
701 	inserts them as a new data session.
702 */
703 uint32
704 Disc::_AdjustForYellowBook(cdrom_full_table_of_contents_entry entries[],
705 	uint32 count)
706 {
707 	uint8 foundCount = 0;
708 	uint8 endLBAEntry = 0;
709 	uint8 trackTwo = 0;
710 
711 	// Make sure TOC has only one session and that it is audio.
712 	bool sessionIsAudio = true;
713 	for (uint32 i = 0; i < count; i++) {
714 		if (entries[i].point == 0xa2) {
715 			if ((entries[i].control & kControlDataTrack) != 0) {
716 				sessionIsAudio = false;
717 				break;
718 			}
719 			foundCount++;
720 			endLBAEntry = i;
721 		}
722 	}
723 	if (!sessionIsAudio || foundCount != 1)
724 		return count;
725 
726 	TRACE(("%s: Single audio session, checking for data track\n",
727 		kModuleDebugName));
728 
729 	// See if there are any data tracks.
730 	for (uint32 i = 0; i < count; i++) {
731 		if (entries[i].point > 0 && entries[i].point < 100
732 			&& (entries[i].control & kControlDataTrack) != 0) {
733 			if (entries[i].point == 1) {
734 				// Create a new endLBA point for session one.
735 				entries[count] = entries[endLBAEntry];
736 				entries[count].control = entries[i].control;
737 
738 				// Get track two and use it's start as
739 				// the end of our new session.
740 				for (uint8 j = 0; j < count; j++) {
741 					if (entries[j].point == 2) {
742 						trackTwo = j;
743 						break;
744 					}
745 				}
746 				entries[count].pminutes = entries[trackTwo].pminutes;
747 				entries[count].pseconds = entries[trackTwo].pseconds;
748 				entries[count].pframes = entries[trackTwo].pframes;
749 
750 				// Change the other points to session two.
751 				for (uint32 j = 0; j < count; j++) {
752 					entries[j].session = 2;
753 				}
754 				entries[i].session = 1;
755 
756 				count++;
757 				TRACE(("%s: first track is data, adjusted TOC\n",
758 					kModuleDebugName));
759 				break;
760 			} else {
761 				// Change the track to session two.
762 				entries[i].session = 2;
763 
764 				// Create a new endLBA point for session two.
765 				entries[count] = entries[endLBAEntry];
766 				entries[count].session = 2;
767 				entries[count].control = entries[i].control;
768 
769 				// Use the beginning of the data track as the
770 				// end of the previous session.
771 				entries[endLBAEntry].pminutes = entries[i].pminutes;
772 				entries[endLBAEntry].pseconds = entries[i].pseconds;
773 				entries[endLBAEntry].pframes = entries[i].pframes;
774 
775 				count++;
776 				TRACE(("%s: last track is data, adjusted TOC\n",
777 					kModuleDebugName));
778 				break;
779 			}
780 		}
781 	}
782 	return count;
783 }
784 
785 
786 /*! \brief Reads through the given table of contents data and creates an
787 	unsorted, unverified (i.e. non-error-checked) list of sessions and tracks.
788 */
789 status_t
790 Disc::_ParseTableOfContents(cdrom_full_table_of_contents_entry entries[],
791 	uint32 count)
792 {
793 	DEBUG_INIT_ETC("Disc", ("entries: %p, count: %ld", entries, count));
794 
795 	for (uint32 i = 0; i < count; i++) {
796 		// Find or create the appropriate session
797 		uint8 sessionIndex = entries[i].session;
798 		session* session = (struct session*)fSessionList->Find(sessionIndex);
799 		if (session == NULL) {
800 			session = new struct session(sessionIndex);
801 			if (session == NULL)
802 				return B_NO_MEMORY;
803 
804 			fSessionList->Add(session);
805 		}
806 
807 		uint8 point = entries[i].point;
808 
809 		switch (point) {
810 			// first track hint
811 			case 0xA0:
812 				if (!session->first_track_hint_is_set()) {
813 					int8 firstTrackHint = entries[i].pminutes;
814 					if (1 <= firstTrackHint && firstTrackHint <= 99) {
815 						session->first_track_hint = firstTrackHint;
816 					} else {
817 						WARN(("%s: warning: illegal first track hint %d found "
818 							"for session %d\n", kModuleDebugName,
819 							firstTrackHint, sessionIndex));
820 					}
821 				} else {
822 					WARN(("%s: warning: duplicated first track hint values "
823 						"found for session %d; using first value "
824 						"encountered: %d", kModuleDebugName, sessionIndex,
825 						session->first_track_hint));
826 				}
827 				break;
828 
829 			// last track hint
830 			case 0xA1:
831 				if (!session->last_track_hint_is_set()) {
832 					int8 lastTrackHint = entries[i].pminutes;
833 					if (1 <= lastTrackHint && lastTrackHint <= 99) {
834 						session->last_track_hint = lastTrackHint;
835 					} else {
836 						WARN(("%s: warning: illegal last track hint %d found "
837 							"for session %d\n", kModuleDebugName,
838 							lastTrackHint, sessionIndex));
839 					}
840 				} else {
841 					WARN(("%s: warning: duplicate last track hint values found "
842 						"for session %d; using first value encountered: %d",
843 						kModuleDebugName, sessionIndex,
844 						session->last_track_hint));
845 				}
846 				break;
847 
848 			// end of session address
849 			case 0xA2:
850 				if (!session->end_lba_is_set()) {
851 					off_t endLBA = msf_to_lba(make_msf_address(
852 						entries[i].pminutes, entries[i].pseconds,
853 						entries[i].pframes));
854 					if (endLBA > 0) {
855 						session->end_lba = endLBA;
856 						// We also grab the session's control and adr values
857 						// from this entry
858 						session->control = entries[i].control;
859 						session->adr = entries[i].adr;
860 					} else {
861 						WARN(("%s: warning: illegal end lba %lld found for "
862 							"session %d\n", kModuleDebugName, endLBA,
863 							sessionIndex));
864 					}
865 				} else {
866 					WARN(("%s: warning: duplicate end lba values found for "
867 						"session %d; using first value encountered: %lld",
868 						kModuleDebugName, sessionIndex, session->end_lba));
869 				}
870 				break;
871 
872 			// Valid, but uninteresting, points
873 			case 0xB0:
874 			case 0xB1:
875 			case 0xB2:
876 			case 0xB3:
877 			case 0xB4:
878 			case 0xC0:
879 			case 0xC1:
880 				break;
881 
882 			default:
883 				// Anything else had better be a valid track number,
884 				// or it's an invalid point
885 				if (1 <= point && point <= 99) {
886 					// Create and add the track. We'll weed out any duplicates
887 					// later.
888 					uint8 trackIndex = point;
889 					off_t startLBA = msf_to_lba(make_msf_address(
890 						entries[i].pminutes, entries[i].pseconds,
891 						entries[i].pframes));
892 					// The control and adr values grabbed here are only used
893 					// later on to signal a warning if they don't match the
894 					// corresponding values of the parent session.
895 					track* track = new(std::nothrow) struct track(trackIndex,
896 						startLBA, entries[i].control, entries[i].adr);
897 					if (track == NULL)
898 						return B_NO_MEMORY;
899 
900 					session->track_list.Add(track);
901 				} else {
902 					WARN(("%s: warning: illegal point 0x%2x found in table of "
903 						"contents\n", kModuleDebugName, point));
904 				}
905 				break;
906 		}
907 	}
908 	return B_OK;
909 }
910 
911 
912 /*! \brief Bubble sorts the session list and each session's track lists,
913 	removing all but the first of any duplicates (by index) found along
914 	the way.
915 */
916 void
917 Disc::_SortAndRemoveDuplicates()
918 {
919 	fSessionList->SortAndRemoveDuplicates();
920 	session* session = (struct session*)fSessionList->First();
921 	while (session != NULL) {
922 		session->track_list.SortAndRemoveDuplicates();
923 		session = (struct session*)session->next;
924 	}
925 }
926 
927 
928 /*	\brief Checks the sessions and tracks for any anomalies.
929 
930 	Errors will return an error code, warnings will return B_OK.
931 	Both will print a notification using TRACE.
932 
933 	Anomalies that result in errors:
934 	- Sessions with no end_lba set
935 	- Sessions with no tracks
936 
937 	Anomalies that result in warnings:
938 	- Inaccurate first_track_hint and/or last_track_hint values
939 	- Sequences of sessions or tracks that do not start at 1,
940 	  do not end at or before 99, or are not strictly ascending.
941 	  (all tracks are checked as a single sequence, since track
942 	  numbering does not restart with each session).
943 	- Tracks with different control and/or adr values than their
944 	  parent session
945 
946 	Anomalies that are currently *not* checked:
947 	- First Track Hint or Last Track Hint control and adr values
948 	  that do not match the values for their session; Ingo's copy
949 	  of the BeOS R5 CD is like this, but I don't believe it's
950 	  a matter we need to worry about. This could certainly be
951 	  changed in the future if needed.
952 */
953 status_t
954 Disc::_CheckForErrorsAndWarnings() {
955 	int32 lastSessionIndex = 0;
956 	int32 lastTrackIndex = 0;
957 
958 	for (session* session = (struct session*)fSessionList->First(); session;
959 			session = (struct session*)session->next) {
960 		// Check for errors
961 
962 		// missing end lba
963 		if (!session->end_lba_is_set()) {
964 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: no end of "
965 				"session address for session #%ld\n", kModuleDebugName,
966 				session->index));
967 			return B_ERROR;
968 		}
969 
970 		// empty track list
971 		track* track = (struct track*)session->track_list.First();
972 		if (track == NULL) {
973 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: session #%ld "
974 				"has no tracks\n", kModuleDebugName, session->index));
975 			return B_ERROR;
976 		}
977 
978 		// Check for warnings
979 
980 		// incorrect first track hint
981 		if (session->first_track_hint_is_set()
982 			&& session->first_track_hint != track->index) {
983 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session "
984 				"#%ld: first track hint (%d) doesn't match actual first track "
985 				"(%ld)\n", kModuleDebugName, session->index,
986 				session->first_track_hint, track->index));
987 		}
988 
989 		// incorrect last track hint
990 		struct track* last = (struct track*)session->track_list.Last();
991 		if (session->last_track_hint_is_set() && last
992 			&& session->last_track_hint != last->index) {
993 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session "
994 				"#%ld: last track hint (%d) doesn't match actual last track "
995 				"(%ld)\n", kModuleDebugName, session->index,
996 				session->last_track_hint, last->index));
997 		}
998 
999 		// invalid session sequence
1000 		if (lastSessionIndex + 1 != session->index) {
1001 			TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index for "
1002 				"session #%ld is out of sequence (should have been #%ld)\n",
1003 				kModuleDebugName, session->index, lastSessionIndex));
1004 		}
1005 		lastSessionIndex = session->index;
1006 
1007 		for (; track; track = (struct track*)track->next) {
1008 			// invalid track sequence
1009 			if (lastTrackIndex + 1 != track->index) {
1010 				TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index "
1011 					"for track #%ld is out of sequence (should have been "
1012 					"#%ld)\n", kModuleDebugName, track->index, lastTrackIndex));
1013 			}
1014 			lastTrackIndex = track->index;
1015 
1016 			// mismatched control
1017 			if (track->control != session->control) {
1018 				TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: control "
1019 					"for track #%ld (%d, %s track, copy %s) does not match "
1020 					"control for parent session #%ld (%d, %s session, copy "
1021 					"%s)\n", kModuleDebugName, track->index, track->control,
1022 					(track->control & kControlDataTrack ? "data" : "audio"),
1023 					(track->control & kControlCopyPermitted
1024 						? "permitted" : "prohibited"),
1025 					session->index, session->control,
1026 					(session->control & kControlDataTrack ? "data" : "audio"),
1027 					(session->control & kControlCopyPermitted
1028 						? "permitted" : "prohibited")));
1029 			}
1030 
1031 			// mismatched adr
1032 			if (track->adr != session->adr) {
1033 				TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: adr "
1034 					"for track #%ld (adr = %d) does not match adr for parent "
1035 					"session #%ld (adr = %d)\n", kModuleDebugName, track->index,
1036 					track->adr, session->index, session->adr));
1037 			}
1038 		}
1039 	}
1040 
1041 	return B_OK;
1042 }
1043 
1044 
1045 //	#pragma mark - Session
1046 
1047 
1048 Session::Session(off_t offset, off_t size, uint32 blockSize, int32 index,
1049 		uint32 flags, const char* type)
1050 	:
1051 	fOffset(offset),
1052 	fSize(size),
1053 	fBlockSize(blockSize),
1054 	fIndex(index),
1055 	fFlags(flags),
1056 	fType(strdup(type))
1057 {
1058 }
1059 
1060 
1061 Session::~Session()
1062 {
1063 	free(fType);
1064 }
1065