/*
* Copyright 2003-2011, Haiku, Inc.
* Distributed under the terms of the MIT license.
*
* Authors:
* Tyler Akidau, haiku@akidau.net
*/
/*! \file Disc.cpp
Disc class implementation, used to enumerate the CD/DVD sessions.
The protocols followed in this module are based on information
taken from the "SCSI-3 Multimedia Commands" draft, revision 10A.
The SCSI command of interest is "READ TOC/PMA/ATIP", command
number \c 0x43.
The format of interest for said command is "Full TOC", format
number \c 0x2.
*/
#include "Disc.h"
#include <DiskDeviceDefs.h>
#include <DiskDeviceTypes.h>
#include "Debug.h"
DBG(static const char* kModuleDebugName = "session");
/*! \brief An item that can be stored in a List object.
*/
struct list_item {
public:
list_item(uint32 index, list_item* next = NULL)
:
index(index),
next(next)
{
}
int32 index;
list_item* next;
};
/*! \brief A simple, singly linked list.
*/
class List {
public:
List();
~List();
list_item* Find(int32 index) const;
void Add(list_item* item);
void Clear();
void SortAndRemoveDuplicates();
list_item* First() const;
list_item* Last() const;
private:
list_item* fFirst;
list_item* fLast;
};
/*! \brief Keeps track of track information.
*/
struct track : public list_item {
public:
track(uint32 index, off_t startLBA, uint8 control, uint8 adr,
track* next = NULL)
:
list_item(index, next),
start_lba(startLBA),
control(control),
adr(adr)
{
}
off_t start_lba;
uint8 control;
// Used to check for Yellow/Red Book mixed-mode CDs.
uint8 adr;
// only used to give what are probably useless warnings
};
/*! \brief Keeps track of session information.
*/
struct session : public list_item {
public:
session(uint32 index, session* next = NULL);
bool first_track_hint_is_set();
bool last_track_hint_is_set();
bool end_lba_is_set(); // also implies control and adr are set
bool is_audio();
int8 first_track_hint;
int8 last_track_hint;
int8 control;
int8 adr;
off_t end_lba;
List track_list;
};
// #pragma mark - Helper functions
#ifdef DEBUG
/*
static void
dump_scsi_command(raw_device_command* cmd)
{
int i;
uint j;
scsi_table_of_contents_command* scsi_command
= (scsi_table_of_contents_command*)(&(cmd->command));
for (i = 0; i < cmd->command_length; i++)
TRACE(("%.2x,", cmd->command[i]));
TRACE(("\n"));
TRACE(("raw_device_command:\n"));
TRACE((" command:\n"));
TRACE((" command = %d (0x%.2x)\n", scsi_command->command,
scsi_command->command));
TRACE((" msf = %d\n", scsi_command->msf));
TRACE((" format = %d (0x%.2x)\n", scsi_command->format,
scsi_command->format));
TRACE((" number = %d\n", scsi_command->number));
TRACE((" length = %d\n",
B_BENDIAN_TO_HOST_INT16(scsi_command->length)));
TRACE((" control = %d\n", scsi_command->control));
TRACE((" command_length = %d\n", cmd->command_length));
TRACE((" flags = %d\n", cmd->flags));
TRACE((" scsi_status = 0x%x\n", cmd->scsi_status));
TRACE((" cam_status = 0x%x\n", cmd->cam_status));
TRACE((" data = %p\n", cmd->data));
TRACE((" data_length = %ld\n", cmd->data_length));
TRACE((" sense_data = %p\n", cmd->sense_data));
TRACE((" sense_data_length = %ld\n", cmd->sense_data_length));
TRACE((" timeout = %lld\n", cmd->timeout));
TRACE(("data dump:\n"));
for (j = 0; j < 2048; j++) {//cmd->data_length; j++) {
uchar c = ((uchar*)cmd->data)[j];
if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
|| ('0' <= c && c <= '9'))
TRACE(("\\%c,", c));
else
TRACE(("%.2x,", c));
}
TRACE(("\n"));
TRACE(("sense_data dump:\n"));
for (j = 0; j < cmd->sense_data_length; j++) {
uchar c = ((uchar*)cmd->sense_data)[j];
if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
|| ('0' <= c && c <= '9'))
TRACE(("%c", c));
else if (c == 0)
TRACE(("_"));
else
TRACE(("-"));
}
TRACE(("\n"));
}
*/
static void
dump_full_table_of_contents(uchar* data, uint16 dataLength)
{
cdrom_table_of_contents_header* header
= (cdrom_table_of_contents_header*)data;
cdrom_full_table_of_contents_entry* entries
= (cdrom_full_table_of_contents_entry*)(data + 4);
int headerLength = B_BENDIAN_TO_HOST_INT16(header->length);
if (dataLength < headerLength) {
TRACE(("dump_full_table_of_contents: warning, data buffer not large "
"enough (%d < %d)\n", dataLength, headerLength));
headerLength = dataLength;
}
TRACE(("%s: table of contents dump:\n", kModuleDebugName));
TRACE(("--------------------------------------------------\n"));
TRACE(("header:\n"));
TRACE((" length = %d\n", headerLength));
TRACE((" first = %d\n", header->first));
TRACE((" last = %d\n", header->last));
int count = (headerLength - 2) / sizeof(cdrom_full_table_of_contents_entry);
TRACE(("\n"));
TRACE(("entry count = %d\n", count));
for (int i = 0; i < count; i++) {
TRACE(("\n"));
TRACE(("entry #%d:\n", i));
TRACE((" session = %d\n", entries[i].session));
TRACE((" adr = %d\n", entries[i].adr));
TRACE((" control = %d (%s track, copy %s)\n", entries[i].control,
(entries[i].control & kControlDataTrack ? "data" : "audio"),
(entries[i].control & kControlCopyPermitted
? "permitted" : "prohibited")));
TRACE((" tno = %d\n", entries[i].tno));
TRACE((" point = %d (0x%.2x)\n", entries[i].point,
entries[i].point));
TRACE((" minutes = %d\n", entries[i].minutes));
TRACE((" frames = %d\n", entries[i].seconds));
TRACE((" seconds = %d\n", entries[i].frames));
TRACE((" zero = %d\n", entries[i].zero));
TRACE((" pminutes = %d\n", entries[i].pminutes));
TRACE((" pseconds = %d\n", entries[i].pseconds));
TRACE((" pframes = %d\n", entries[i].pframes));
TRACE((" lba = %" B_PRId64 "\n",
msf_to_lba(make_msf_address(entries[i].pminutes,
entries[i].pseconds, entries[i].pframes))));
}
TRACE(("--------------------------------------------------\n"));
}
#endif // DEBUG
static status_t
read_table_of_contents(int deviceFD, uint32 first_session, uchar* buffer,
uint16 buffer_length, bool msf)
{
scsi_table_of_contents_command scsi_command;
raw_device_command raw_command;
const uint32 sense_data_length = 1024;
uchar sense_data[sense_data_length];
status_t error = buffer ? B_OK : B_BAD_VALUE;
DEBUG_INIT_ETC(NULL, ("fd: %d, buffer: %p, buffer_length: %d",
deviceFD, buffer, buffer_length));
if (error)
return error;
// This does not always work on the first try, so do it twice just in case.
for (int attempt = 0; attempt < 2; attempt++) {
// Init the scsi command and copy it into the "raw scsi command"
// ioctl struct
memset(raw_command.command, 0, 16);
scsi_command.command = 0x43;
scsi_command.msf = 1;
scsi_command.format = kFullTableOfContentsFormat;
scsi_command.number = first_session;
scsi_command.length = B_HOST_TO_BENDIAN_INT16(buffer_length);
scsi_command.control = 0;
scsi_command.reserved0 = scsi_command.reserved1 = scsi_command.reserved2
= scsi_command.reserved3 = scsi_command.reserved4
= scsi_command.reserved5 = scsi_command.reserved6 = 0;
memcpy(raw_command.command, &scsi_command, sizeof(scsi_command));
// Init the rest of the raw command
raw_command.command_length = 10;
raw_command.flags = kScsiFlags;
raw_command.scsi_status = 0;
raw_command.cam_status = 0;
raw_command.data = buffer;
raw_command.data_length = buffer_length;
memset(raw_command.data, 0, raw_command.data_length);
raw_command.sense_data = sense_data;
raw_command.sense_data_length = sense_data_length;
memset(raw_command.sense_data, 0, raw_command.sense_data_length);
raw_command.timeout = kScsiTimeout;
if (ioctl(deviceFD, B_RAW_DEVICE_COMMAND, &raw_command) == 0) {
if (raw_command.scsi_status == 0 && raw_command.cam_status == 1) {
// SUCCESS!!!
DBG(dump_full_table_of_contents(buffer, buffer_length));
return B_OK;
} else {
error = B_FILE_ERROR;
TRACE(("%s: scsi ioctl succeeded, but scsi command failed\n",
kModuleDebugName));
}
} else {
error = errno;
TRACE(("%s: scsi command failed with error 0x%" B_PRIx32 "\n",
kModuleDebugName, error));
}
}
return error;
}
// #pragma mark - List
// TODO: get rid of this, and use the standard DoublyLinkedList
/*! \brief Creates an empty list.
*/
List::List()
:
fFirst(NULL),
fLast(NULL)
{
}
List::~List()
{
Clear();
}
/*! \brief Returns the ListItem with the given index, or NULL if not found.
*/
list_item*
List::Find(int32 index) const
{
// TRACE(("%s: List::Find(%ld)\n", kModuleDebugName, index));
list_item* item = fFirst;
while (item && item->index != index) {
item = item->next;
}
return item;
}
/*! \brief Adds the given item to the end of the list.
\param item The item to add (may not be NULL)
*/
void
List::Add(list_item* item)
{
// TRACE(("%s: List::Add(%p)\n", kModuleDebugName, item));
if (item) {
item->next = NULL;
if (fLast) {
fLast->next = item;
fLast = item;
} else {
fFirst = fLast = item;
}
} else {
TRACE(("%s: List::Add(): NULL item parameter\n", kModuleDebugName));
}
}
/*! \brief Clears the list.
*/
void
List::Clear()
{
list_item* item = fFirst;
while (item) {
list_item* next = item->next;
delete item;
item = next;
}
fFirst = fLast = NULL;
}
/*! \brief Bubble sorts the list by index, removing any duplicates
(the first instance is kept).
\todo I believe duplicate removal is actually unnecessary, but
I need to verify that.
*/
void
List::SortAndRemoveDuplicates()
{
bool sorted = false;
while (!sorted) {
sorted = true;
list_item* prev = NULL;
list_item* item = fFirst;
list_item* next = NULL;
while (item && item->next) {
next = item->next;
// dprintf("List::Sort: %ld -> %ld\n", item->index, next->index);
if (item->index > next->index) {
sorted = false;
// Keep fLast up to date
if (next == fLast)
fLast = item;
// Swap
if (prev) {
// item is not fFirst
prev->next = next;
item->next = next->next;
next->next = item;
} else {
// item must be fFirst
fFirst = next;
item->next = next->next;
next->next = item;
}
} else if (item->index == next->index) {
// Duplicate indicies
TRACE(("%s: List::SortAndRemoveDuplicates: duplicate indicies "
"found (#%" B_PRId32 "); keeping first instance\n",
kModuleDebugName, item->index));
item->next = next->next;
delete next;
next = item->next;
continue;
}
prev = item;
item = next;
}
}
}
/*! \brief Returns the first item in the list, or NULL if empty
*/
list_item*
List::First() const
{
return fFirst;
}
/*! \brief Returns the last item in the list, or NULL if empty
*/
list_item*
List::Last() const
{
return fLast;
}
// #pragma mark - session
/*! \brief Creates an unitialized session object
*/
session::session(uint32 index, session* next)
:
list_item(index, next),
first_track_hint(-1),
last_track_hint(-1),
control(-1),
adr(-1),
end_lba(0)
{
}
/*! \brief Returns true if the \a first_track_hint member has not been
set to a legal value yet.
*/
bool
session::first_track_hint_is_set()
{
return 1 <= first_track_hint && first_track_hint <= 99;
}
/*! \brief Returns true if the \a last_track_hint member has not been
set to a legal value yet.
*/
bool
session::last_track_hint_is_set()
{
return 1 <= last_track_hint && last_track_hint <= 99;
}
/*! \brief Returns true if the \a end_lba member has not been
set to a legal value yet.
The result of this function also signals that the \a control
and \a adr members have or have not been set, since they are
set at the same time as \a end_lba.
*/
bool
session::end_lba_is_set()
{
return end_lba > 0;
}
/*! \brief Returns true if the session is flagged as being audio.
If the \c control value for the session has not been set, returns
false.
*/
bool
session::is_audio()
{
return end_lba_is_set() && !(control & kControlDataTrack);
}
// #pragma mark - Disc
/*! \brief Creates a new Disc object by parsing the given table of contents
entries and checking the resultant data structure for errors and
warnings.
If successful, subsequent calls to InitCheck() will return \c B_OK,
elsewise they will return an error code.
*/
Disc::Disc(int fd)
:
fInitStatus(B_NO_INIT),
fSessionList(new List)
{
DEBUG_INIT_ETC("Disc", ("fd: %d", fd));
uchar data[kBlockSize];
/*
if (!error)
error = sessionInfo && index >= 0 ? B_OK : B_BAD_VALUE;
int32 session = index+1;
// Check for a valid session index
if (session < 1 || session > 99)
error = B_ENTRY_NOT_FOUND;
*/
status_t error = fSessionList ? B_OK : B_NO_MEMORY;
// Attempt to read the table of contents, first in lba mode, then in msf
// mode
if (!error)
error = read_table_of_contents(fd, 1, data, kBlockSize, false);
if (error) {
TRACE(("%s: lba read_toc failed, trying msf instead\n",
kModuleDebugName));
error = read_table_of_contents(fd, 1, data, kBlockSize, true);
}
// Interpret the data returned, if successful
if (!error) {
cdrom_table_of_contents_header* header;
cdrom_full_table_of_contents_entry* entries;
int count;
header = (cdrom_table_of_contents_header*)data;
entries = (cdrom_full_table_of_contents_entry*)(data + 4);
header->length = B_BENDIAN_TO_HOST_INT16(header->length);
count = (header->length - 2)
/ sizeof(cdrom_full_table_of_contents_entry);
count = _AdjustForYellowBook(entries, count);
error = _ParseTableOfContents(entries, count);
// Dump();
if (!error) {
_SortAndRemoveDuplicates();
error = _CheckForErrorsAndWarnings();
}
}
PRINT(("Setting init status to 0x%" B_PRIx32 ", `%s'\n", error,
strerror(error)));
fInitStatus = error;
}
/*! \brief Destroys the Disc's internal list.
*/
Disc::~Disc()
{
delete fSessionList;
}
/*! \brief Returns \c B_OK if the object was successfully initialized, or
an error code if not.
*/
status_t
Disc::InitCheck()
{
return fInitStatus;
}
/*! \brief Stores the info for the given session (using 0 based indicies) in the
struct pointed to by \a sessionInfo.
Returns \c B_ENTRY_NOT_FOUND if no such session exists.
*/
Session*
Disc::GetSession(int32 index)
{
DEBUG_INIT_ETC("Disc", ("index: %" B_PRId32, index));
int32 counter = -1;
for (session* session = (struct session*)fSessionList->First(); session;
session = (struct session*)session->next) {
if (session->is_audio()) {
counter++;
// only one session per audio session
if (counter == index) {
// Found an audio session. Take the start of the first
// track with the end of session.
track* track = (struct track*)session->track_list.First();
if (track != NULL) {
PRINT(("found session #%" B_PRId32 " info (audio session)"
"\n", index));
off_t startLBA = track->start_lba;
off_t endLBA = session->end_lba;
off_t offset = startLBA * kBlockSize;
off_t size = (endLBA - startLBA) * kBlockSize;
Session* result = new Session(offset, size, kBlockSize,
index, B_PARTITION_READ_ONLY,
kPartitionTypeAudioSession);
if (result == NULL) {
PRINT(("Error allocating new Session object; out of "
"memory!\n"));
}
return result;
} else {
PRINT(("Error: session #%" B_PRId32 " is an audio session "
"with no tracks!\n", index));
return NULL;
}
}
} else {
for (track* track = (struct track*)session->track_list.First();
track; track = (struct track*)track->next) {
counter++;
if (counter == index) {
PRINT(("found session #%" B_PRId32 " info (data session)\n",
index));
off_t startLBA = track->start_lba;
if (startLBA < 0) {
WARN(("%s: warning: invalid negative start LBA of %"
B_PRId64 " for data track assuming 0\n",
kModuleDebugName, startLBA));
startLBA = 0;
}
off_t endLBA = track->next
? ((struct track*)track->next)->start_lba
: session->end_lba;
off_t offset = startLBA * kBlockSize;
off_t size = (endLBA - startLBA) * kBlockSize;
Session* result = new Session(offset, size, kBlockSize,
index, B_PARTITION_READ_ONLY,
kPartitionTypeDataSession);
if (result == NULL) {
PRINT(("Error allocating new Session object; out of "
"memory!\n"));
}
return result;
}
}
}
}
PRINT(("no session #%" B_PRId32 " found!\n", index));
return NULL;
}
/*! \brief Dumps a printout of the disc using TRACE.
*/
void
Disc::Dump()
{
TRACE(("%s: Disc dump:\n", kModuleDebugName));
session* session = (struct session*)fSessionList->First();
while (session != NULL) {
TRACE(("session %" B_PRId32 ":\n", session->index));
TRACE((" first track hint: %d\n", session->first_track_hint));
TRACE((" last track hint: %d\n", session->last_track_hint));
TRACE((" end_lba: %" B_PRId64 "\n", session->end_lba));
TRACE((" control: %d (%s session, copy %s)\n",
session->control, (session->control & kControlDataTrack
? "data" : "audio"),
(session->control & kControlCopyPermitted
? "permitted" : "prohibited")));
TRACE((" adr: %d\n", session->adr));
track* track = (struct track*)session->track_list.First();
while (track != NULL) {
TRACE((" track %" B_PRId32 ":\n", track->index));
TRACE((" start_lba: %" B_PRId64 "\n", track->start_lba));
track = (struct track*)track->next;
}
session = (struct session*)session->next;
}
}
/*! \brief Checks for Yellow Book data tracks in audio sessions and if found
inserts them as a new data session.
*/
uint32
Disc::_AdjustForYellowBook(cdrom_full_table_of_contents_entry entries[],
uint32 count)
{
uint8 foundCount = 0;
uint8 endLBAEntry = 0;
uint8 trackTwo = 0;
// Make sure TOC has only one session and that it is audio.
bool sessionIsAudio = true;
for (uint32 i = 0; i < count; i++) {
if (entries[i].point == 0xa2) {
if ((entries[i].control & kControlDataTrack) != 0) {
sessionIsAudio = false;
break;
}
foundCount++;
endLBAEntry = i;
}
}
if (!sessionIsAudio || foundCount != 1)
return count;
TRACE(("%s: Single audio session, checking for data track\n",
kModuleDebugName));
// See if there are any data tracks.
for (uint32 i = 0; i < count; i++) {
if (entries[i].point > 0 && entries[i].point < 100
&& (entries[i].control & kControlDataTrack) != 0) {
if (entries[i].point == 1) {
// Create a new endLBA point for session one.
entries[count] = entries[endLBAEntry];
entries[count].control = entries[i].control;
// Get track two and use it's start as
// the end of our new session.
for (uint8 j = 0; j < count; j++) {
if (entries[j].point == 2) {
trackTwo = j;
break;
}
}
entries[count].pminutes = entries[trackTwo].pminutes;
entries[count].pseconds = entries[trackTwo].pseconds;
entries[count].pframes = entries[trackTwo].pframes;
// Change the other points to session two.
for (uint32 j = 0; j < count; j++) {
entries[j].session = 2;
}
entries[i].session = 1;
count++;
TRACE(("%s: first track is data, adjusted TOC\n",
kModuleDebugName));
break;
} else {
// Change the track to session two.
entries[i].session = 2;
// Create a new endLBA point for session two.
entries[count] = entries[endLBAEntry];
entries[count].session = 2;
entries[count].control = entries[i].control;
// Use the beginning of the data track as the
// end of the previous session.
entries[endLBAEntry].pminutes = entries[i].pminutes;
entries[endLBAEntry].pseconds = entries[i].pseconds;
entries[endLBAEntry].pframes = entries[i].pframes;
count++;
TRACE(("%s: last track is data, adjusted TOC\n",
kModuleDebugName));
break;
}
}
}
return count;
}
/*! \brief Reads through the given table of contents data and creates an
unsorted, unverified (i.e. non-error-checked) list of sessions and tracks.
*/
status_t
Disc::_ParseTableOfContents(cdrom_full_table_of_contents_entry entries[],
uint32 count)
{
DEBUG_INIT_ETC("Disc", ("entries: %p, count: %" B_PRIu32, entries, count));
for (uint32 i = 0; i < count; i++) {
// Find or create the appropriate session
uint8 sessionIndex = entries[i].session;
session* session = (struct session*)fSessionList->Find(sessionIndex);
if (session == NULL) {
session = new struct session(sessionIndex);
if (session == NULL)
return B_NO_MEMORY;
fSessionList->Add(session);
}
uint8 point = entries[i].point;
switch (point) {
// first track hint
case 0xA0:
if (!session->first_track_hint_is_set()) {
int8 firstTrackHint = entries[i].pminutes;
if (1 <= firstTrackHint && firstTrackHint <= 99) {
session->first_track_hint = firstTrackHint;
} else {
WARN(("%s: warning: illegal first track hint %d found "
"for session %d\n", kModuleDebugName,
firstTrackHint, sessionIndex));
}
} else {
WARN(("%s: warning: duplicated first track hint values "
"found for session %d; using first value "
"encountered: %d", kModuleDebugName, sessionIndex,
session->first_track_hint));
}
break;
// last track hint
case 0xA1:
if (!session->last_track_hint_is_set()) {
int8 lastTrackHint = entries[i].pminutes;
if (1 <= lastTrackHint && lastTrackHint <= 99) {
session->last_track_hint = lastTrackHint;
} else {
WARN(("%s: warning: illegal last track hint %d found "
"for session %d\n", kModuleDebugName,
lastTrackHint, sessionIndex));
}
} else {
WARN(("%s: warning: duplicate last track hint values found "
"for session %d; using first value encountered: %d",
kModuleDebugName, sessionIndex,
session->last_track_hint));
}
break;
// end of session address
case 0xA2:
if (!session->end_lba_is_set()) {
off_t endLBA = msf_to_lba(make_msf_address(
entries[i].pminutes, entries[i].pseconds,
entries[i].pframes));
if (endLBA > 0) {
session->end_lba = endLBA;
// We also grab the session's control and adr values
// from this entry
session->control = entries[i].control;
session->adr = entries[i].adr;
} else {
WARN(("%s: warning: illegal end lba %" B_PRId64 " found"
" for session %d\n", kModuleDebugName, endLBA,
sessionIndex));
}
} else {
WARN(("%s: warning: duplicate end lba values found for "
"session %d; using first value encountered: %" B_PRId64,
kModuleDebugName, sessionIndex, session->end_lba));
}
break;
// Valid, but uninteresting, points
case 0xB0:
case 0xB1:
case 0xB2:
case 0xB3:
case 0xB4:
case 0xC0:
case 0xC1:
break;
default:
// Anything else had better be a valid track number,
// or it's an invalid point
if (1 <= point && point <= 99) {
// Create and add the track. We'll weed out any duplicates
// later.
uint8 trackIndex = point;
off_t startLBA = msf_to_lba(make_msf_address(
entries[i].pminutes, entries[i].pseconds,
entries[i].pframes));
// The control and adr values grabbed here are only used
// later on to signal a warning if they don't match the
// corresponding values of the parent session.
track* track = new(std::nothrow) struct track(trackIndex,
startLBA, entries[i].control, entries[i].adr);
if (track == NULL)
return B_NO_MEMORY;
session->track_list.Add(track);
} else {
WARN(("%s: warning: illegal point 0x%2x found in table of "
"contents\n", kModuleDebugName, point));
}
break;
}
}
return B_OK;
}
/*! \brief Bubble sorts the session list and each session's track lists,
removing all but the first of any duplicates (by index) found along
the way.
*/
void
Disc::_SortAndRemoveDuplicates()
{
fSessionList->SortAndRemoveDuplicates();
session* session = (struct session*)fSessionList->First();
while (session != NULL) {
session->track_list.SortAndRemoveDuplicates();
session = (struct session*)session->next;
}
}
/* \brief Checks the sessions and tracks for any anomalies.
Errors will return an error code, warnings will return B_OK.
Both will print a notification using TRACE.
Anomalies that result in errors:
- Sessions with no end_lba set
- Sessions with no tracks
Anomalies that result in warnings:
- Inaccurate first_track_hint and/or last_track_hint values
- Sequences of sessions or tracks that do not start at 1,
do not end at or before 99, or are not strictly ascending.
(all tracks are checked as a single sequence, since track
numbering does not restart with each session).
- Tracks with different control and/or adr values than their
parent session
Anomalies that are currently *not* checked:
- First Track Hint or Last Track Hint control and adr values
that do not match the values for their session; Ingo's copy
of the BeOS R5 CD is like this, but I don't believe it's
a matter we need to worry about. This could certainly be
changed in the future if needed.
*/
status_t
Disc::_CheckForErrorsAndWarnings() {
int32 lastSessionIndex = 0;
int32 lastTrackIndex = 0;
for (session* session = (struct session*)fSessionList->First(); session;
session = (struct session*)session->next) {
// Check for errors
// missing end lba
if (!session->end_lba_is_set()) {
TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: no end of "
"session address for session #%" B_PRId32 "\n",
kModuleDebugName, session->index));
return B_ERROR;
}
// empty track list
track* track = (struct track*)session->track_list.First();
if (track == NULL) {
TRACE(("%s: Disc::_CheckForErrorsAndWarnings: error: session #%"
B_PRId32 "has no tracks\n", kModuleDebugName, session->index));
return B_ERROR;
}
// Check for warnings
// incorrect first track hint
if (session->first_track_hint_is_set()
&& session->first_track_hint != track->index) {
TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session "
"#%" B_PRId32 ": first track hint (%d) doesn't match actual "
"first track (%" B_PRId32 ")\n", kModuleDebugName,
session->index, session->first_track_hint, track->index));
}
// incorrect last track hint
struct track* last = (struct track*)session->track_list.Last();
if (session->last_track_hint_is_set() && last
&& session->last_track_hint != last->index) {
TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: session "
"#%" B_PRId32 ": last track hint (%d) doesn't match actual "
"last track (%" B_PRId32 ")\n", kModuleDebugName,
session->index, session->last_track_hint, last->index));
}
// invalid session sequence
if (lastSessionIndex + 1 != session->index) {
TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index for "
"session #%" B_PRId32 " is out of sequence (should have been #%"
B_PRId32 ")\n", kModuleDebugName, session->index,
lastSessionIndex));
}
lastSessionIndex = session->index;
for (; track; track = (struct track*)track->next) {
// invalid track sequence
if (lastTrackIndex + 1 != track->index) {
TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: index "
"for track #%" B_PRId32 " is out of sequence (should have "
"been #%" B_PRId32 ")\n", kModuleDebugName, track->index,
lastTrackIndex));
}
lastTrackIndex = track->index;
// mismatched control
if (track->control != session->control) {
TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: control "
"for track #%" B_PRId32 " (%d, %s track, copy %s) does not "
"match control for parent session #%" B_PRId32 " (%d, %s "
"session, copy %s)\n", kModuleDebugName, track->index,
track->control,
(track->control & kControlDataTrack ? "data" : "audio"),
(track->control & kControlCopyPermitted
? "permitted" : "prohibited"),
session->index, session->control,
(session->control & kControlDataTrack ? "data" : "audio"),
(session->control & kControlCopyPermitted
? "permitted" : "prohibited")));
}
// mismatched adr
if (track->adr != session->adr) {
TRACE(("%s: Disc::_CheckForErrorsAndWarnings: warning: adr "
"for track #%" B_PRId32 " (adr = %d) does not match adr "
"for parent session #%" B_PRId32 " (adr = %d)\n",
kModuleDebugName, track->index, track->adr, session->index,
session->adr));
}
}
}
return B_OK;
}
// #pragma mark - Session
Session::Session(off_t offset, off_t size, uint32 blockSize, int32 index,
uint32 flags, const char* type)
:
fOffset(offset),
fSize(size),
fBlockSize(blockSize),
fIndex(index),
fFlags(flags),
fType(strdup(type))
{
}
Session::~Session()
{
free(fType);
}
↑ V512 A call of the 'memcpy' function will lead to underflow of the buffer 'raw_command.command'.