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
33DBG(static const char* kModuleDebugName = "session");
34
35
36/*! \brief An item that can be stored in a List object.
37*/
38struct list_item {
39public:
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*/
54class List {
55public:
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
67private:
68	list_item*	fFirst;
69	list_item*	fLast;
70};
71
72
73/*! \brief Keeps track of track information.
74*/
75struct track : public list_item {
76public:
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*/
97struct session : public list_item {
98public:
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/*
122static void
123dump_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
181static void
182dump_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
235static status_t
236read_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*/
308List::List()
309	:
310	fFirst(NULL),
311	fLast(NULL)
312{
313}
314
315
316List::~List()
317{
318	Clear();
319}
320
321
322/*! \brief Returns the ListItem with the given index, or NULL if not found.
323*/
324list_item*
325List::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*/
340void
341List::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*/
360void
361List::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*/
379void
380List::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*/
430list_item*
431List::First() const
432{
433	return fFirst;
434}
435
436
437/*! \brief Returns the last item in the list, or NULL if empty
438*/
439list_item*
440List::Last() const
441{
442	return fLast;
443}
444
445
446//	#pragma mark - session
447
448
449/*! \brief Creates an unitialized session object
450*/
451session::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*/
466bool
467session::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*/
476bool
477session::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*/
490bool
491session::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*/
502bool
503session::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*/
519Disc::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*/
578Disc::~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*/
587status_t
588Disc::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*/
599Session*
600Disc::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*/
680void
681Disc::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*/
710uint32
711Disc::_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*/
796status_t
797Disc::_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*/
923void
924Disc::_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*/
960status_t
961Disc::_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
1059Session::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
1072Session::~Session()
1073{
1074	free(fType);
1075}
1076