#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_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;
}
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;
}
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)
{
}
bool
session::first_track_hint_is_set()
{
return 1 <= first_track_hint && first_track_hint <= 99;
}
bool
session::last_track_hint_is_set()
{
return 1 <= last_track_hint && last_track_hint <= 99;
}
bool
session::end_lba_is_set()
{
return end_lba > 0;
}
bool
session::is_audio()
{
return end_lba_is_set() && !(control & kControlDataTrack);
}
Disc::Disc(int fd)
:
fInitStatus(B_NO_INIT),
fSessionList(new List)
{
DEBUG_INIT_ETC("Disc", ("fd: %d", fd));
uchar data[kBlockSize];
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;
}
status_t
Disc::InitCheck()
{
return fInitStatus;
}
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;
}
}
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;
}
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;
}
void
Disc::_SortAndRemoveDuplicates()
{
fSessionList->SortAndRemoveDuplicates();
session* session = (struct session*)fSessionList->First();
while (session != NULL) {
session->track_list.SortAndRemoveDuplicates();
session = (struct session*)session->next;
}
}
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);
}