* Copyright 2003-2011, Haiku, Inc.
* Distributed under the terms of the MIT license.
*
* Authors:
* Tyler Akidau, haiku@akidau.net
*/
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");
*/
struct list_item {
public:
list_item(uint32 index, list_item* next = NULL)
:
index(index),
next(next)
{
}
int32 index;
list_item* next;
};
*/
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;
};
*/
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;
uint8 adr;
};
*/
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();
bool is_audio();
int8 first_track_hint;
int8 last_track_hint;
int8 control;
int8 adr;
off_t end_lba;
List track_list;
};
#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
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;
for (int attempt = 0; attempt < 2; attempt++) {
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));
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,
sizeof(raw_command)) == 0) {
if (raw_command.scsi_status == 0 && raw_command.cam_status == 1) {
DBG(dump_full_table_of_contents(buffer, buffer_length));
return B_OK;
} else {
error = B_IO_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;
}
*/
List::List()
:
fFirst(NULL),
fLast(NULL)
{
}
List::~List()
{
Clear();
}
*/
list_item*
List::Find(int32 index) const
{
list_item* item = fFirst;
while (item && item->index != index) {
item = item->next;
}
return item;
}
\param item The item to add (may not be NULL)
*/
void
List::Add(list_item* 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));
}
}
*/
void
List::Clear()
{
list_item* item = fFirst;
while (item) {
list_item* next = item->next;
delete item;
item = next;
}
fFirst = fLast = NULL;
}
(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;
if (item->index > next->index) {
sorted = false;
if (next == fLast)
fLast = item;
if (prev) {
prev->next = next;
item->next = next->next;
next->next = item;
} else {
fFirst = next;
item->next = next->next;
next->next = item;
}
} else if (item->index == next->index) {
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;
}
}
}
*/
list_item*
List::First() const
{
return fFirst;
}
*/
list_item*
List::Last() const
{
return fLast;
}
*/
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)
{
}
set to a legal value yet.
*/
bool
session::first_track_hint_is_set()
{
return 1 <= first_track_hint && first_track_hint <= 99;
}
set to a legal value yet.
*/
bool
session::last_track_hint_is_set()
{
return 1 <= last_track_hint && last_track_hint <= 99;
}
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;
}
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);
}
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;
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);
}
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);
if (!error) {
_SortAndRemoveDuplicates();
error = _CheckForErrorsAndWarnings();
}
}
PRINT(("Setting init status to 0x%" B_PRIx32 ", `%s'\n", error,
strerror(error)));
fInitStatus = error;
}
*/
Disc::~Disc()
{
delete fSessionList;
}
an error code if not.
*/
status_t
Disc::InitCheck()
{
return fInitStatus;
}
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++;
if (counter == index) {
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;
}
*/
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;
}
}
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;
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));
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) {
entries[count] = entries[endLBAEntry];
entries[count].control = entries[i].control;
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;
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 {
entries[i].session = 2;
entries[count] = entries[endLBAEntry];
entries[count].session = 2;
entries[count].control = entries[i].control;
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;
}
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++) {
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) {
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;
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;
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;
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;
case 0xB0:
case 0xB1:
case 0xB2:
case 0xB3:
case 0xB4:
case 0xC0:
case 0xC1:
break;
default:
if (1 <= point && point <= 99) {
uint8 trackIndex = point;
off_t startLBA = msf_to_lba(make_msf_address(
entries[i].pminutes, entries[i].pseconds,
entries[i].pframes));
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;
}
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;
}
}
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) {
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;
}
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;
}
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));
}
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));
}
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) {
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;
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")));
}
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;
}
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);
}