* Copyright 2012, Jérôme Duval, korli@users.berlios.de.
* Copyright 2003, Tyler Dauwalder, tyler@dauwalder.net.
* Distributed under the terms of the MIT License.
*/
#include "Recognition.h"
#include "UdfString.h"
#include "MemoryChunk.h"
#include "Utils.h"
#include <string.h>
static status_t
walk_volume_recognition_sequence(int device, off_t offset, uint32 blockSize,
uint32 blockShift);
static status_t
walk_anchor_volume_descriptor_sequences(int device, off_t offset, off_t length,
uint32 blockSize, uint32 blockShift,
primary_volume_descriptor &primaryVolumeDescriptor,
logical_volume_descriptor &logicalVolumeDescriptor,
partition_descriptor partitionDescriptors[],
uint8 &partitionDescriptorCount);
static status_t
walk_volume_descriptor_sequence(extent_address descriptorSequence, int device,
uint32 blockSize, uint32 blockShift,
primary_volume_descriptor &primaryVolumeDescriptor,
logical_volume_descriptor &logicalVolumeDescriptor,
partition_descriptor partitionDescriptors[],
uint8 &partitionDescriptorCount);
static status_t
walk_integrity_sequence(int device, uint32 blockSize, uint32 blockShift,
extent_address descriptorSequence, uint32 sequenceNumber = 0);
status_t
udf_recognize(int device, off_t offset, off_t length, uint32 blockSize,
uint32 &blockShift, primary_volume_descriptor &primaryVolumeDescriptor,
logical_volume_descriptor &logicalVolumeDescriptor,
partition_descriptor partitionDescriptors[],
uint8 &partitionDescriptorCount)
{
TRACE(("udf_recognize: device: = %d, offset = %" B_PRIdOFF ", length = %"
B_PRIdOFF ", blockSize = %" B_PRIu32 "\n", device, offset, length,
blockSize));
status_t status = get_block_shift(blockSize, blockShift);
if (status != B_OK) {
TRACE_ERROR(("\nudf_recognize: Block size must be a positive power of "
"two! (blockSize = %" B_PRIu32 ")\n", blockSize));
return status;
}
TRACE(("blockShift: %" B_PRIu32 "\n", blockShift));
status = walk_volume_recognition_sequence(device, offset, blockSize,
blockShift);
if (status != B_OK) {
TRACE(("udf_recognize: Invalid sequence. status = %" B_PRId32
"\n", status));
return status;
}
status = walk_anchor_volume_descriptor_sequences(device, offset, length,
blockSize, blockShift, primaryVolumeDescriptor,
logicalVolumeDescriptor, partitionDescriptors,
partitionDescriptorCount);
if (status != B_OK) {
TRACE_ERROR(("udf_recognize: cannot find volume descriptor. status = %"
B_PRId32 "\n", status));
return status;
}
status = walk_integrity_sequence(device, blockSize, blockShift,
logicalVolumeDescriptor.integrity_sequence_extent());
if (status != B_OK) {
TRACE_ERROR(("udf_recognize: last integrity descriptor not closed. "
"status = %" B_PRId32 "\n", status));
return status;
}
return B_OK;
}
static
status_t
walk_volume_recognition_sequence(int device, off_t offset, uint32 blockSize,
uint32 blockShift)
{
TRACE(("walk_volume_recognition_sequence: device = %d, offset = %"
B_PRIdOFF ", blockSize = %" B_PRIu32 ", blockShift = %" B_PRIu32 "\n",
device, offset, blockSize, blockShift));
MemoryChunk chunk(blockSize);
if (chunk.InitCheck() != B_OK) {
TRACE_ERROR(("walk_volume_recognition_sequence: Failed to construct "
"MemoryChunk\n"));
return chunk.InitCheck();
}
bool foundExtended = false;
bool foundECMA167 = false;
bool foundECMA168 = false;
for (uint32 block = 16; true; block++) {
off_t address = (offset + block) << blockShift;
TRACE(("walk_volume_recognition_sequence: block = %" B_PRIu32 ", "
"address = %" B_PRIdOFF ", ", block, address));
ssize_t bytesRead = read_pos(device, address, chunk.Data(), blockSize);
if (bytesRead == (ssize_t)blockSize) {
volume_structure_descriptor_header* descriptor
= (volume_structure_descriptor_header *)(chunk.Data());
if (descriptor->id_matches(kVSDID_ISO)) {
TRACE(("found ISO9660 descriptor\n"));
} else if (descriptor->id_matches(kVSDID_BEA)) {
TRACE(("found BEA descriptor\n"));
foundExtended = true;
} else if (descriptor->id_matches(kVSDID_TEA)) {
TRACE(("found TEA descriptor\n"));
foundExtended = true;
} else if (descriptor->id_matches(kVSDID_ECMA167_2)) {
TRACE(("found ECMA-167 rev 2 descriptor\n"));
foundECMA167 = true;
} else if (descriptor->id_matches(kVSDID_ECMA167_3)) {
TRACE(("found ECMA-167 rev 3 descriptor\n"));
foundECMA167 = true;
} else if (descriptor->id_matches(kVSDID_BOOT)) {
TRACE(("found boot descriptor\n"));
} else if (descriptor->id_matches(kVSDID_ECMA168)) {
TRACE(("found ECMA-168 descriptor\n"));
foundECMA168 = true;
} else {
TRACE(("found invalid descriptor, id = `%.5s'\n", descriptor->id));
break;
}
} else {
TRACE(("read_pos(pos:%" B_PRIdOFF ", len:%" B_PRIu32 ") "
"failed with: 0x%lx\n", address, blockSize, bytesRead));
break;
}
}
return foundECMA167 || (foundExtended && !foundECMA168) ? B_OK : B_ERROR;
}
static status_t
walk_anchor_volume_descriptor_sequences(int device, off_t offset, off_t length,
uint32 blockSize, uint32 blockShift,
primary_volume_descriptor &primaryVolumeDescriptor,
logical_volume_descriptor &logicalVolumeDescriptor,
partition_descriptor partitionDescriptors[],
uint8 &partitionDescriptorCount)
{
DEBUG_INIT(NULL);
const uint8 avds_location_count = 4;
const off_t avds_locations[avds_location_count]
= { 256, length-1-256, length-1, 512, };
bool found_vds = false;
for (int32 i = 0; i < avds_location_count; i++) {
off_t block = avds_locations[i];
off_t address = (offset + block) << blockShift;
MemoryChunk chunk(blockSize);
anchor_volume_descriptor *anchor = NULL;
status_t anchorErr = chunk.InitCheck();
if (!anchorErr) {
ssize_t bytesRead = read_pos(device, address, chunk.Data(), blockSize);
anchorErr = bytesRead == (ssize_t)blockSize ? B_OK : B_IO_ERROR;
if (anchorErr) {
PRINT(("block %" B_PRIdOFF ": read_pos(pos:%" B_PRIdOFF ", "
"len:%" PRIu32 ") failed with error 0x%lx\n",
block, address, blockSize, bytesRead));
}
}
if (!anchorErr) {
anchor = reinterpret_cast<anchor_volume_descriptor*>(chunk.Data());
anchorErr = anchor->tag().init_check(block + offset);
if (anchorErr) {
PRINT(("block %" B_PRIdOFF ": invalid anchor\n", block));
} else {
PRINT(("block %" B_PRIdOFF ": valid anchor\n", block));
}
}
if (!anchorErr) {
PRINT(("block %" B_PRIdOFF ": anchor:\n", block));
PDUMP(anchor);
anchorErr = walk_volume_descriptor_sequence(anchor->main_vds(),
device, blockSize, blockShift, primaryVolumeDescriptor,
logicalVolumeDescriptor, partitionDescriptors,
partitionDescriptorCount);
if (anchorErr)
anchorErr = walk_volume_descriptor_sequence(anchor->reserve_vds(),
device, blockSize, blockShift, primaryVolumeDescriptor,
logicalVolumeDescriptor, partitionDescriptors,
partitionDescriptorCount);
}
if (!anchorErr) {
PRINT(("block %" B_PRIdOFF ": found valid vds\n",
avds_locations[i]));
found_vds = true;
break;
} else {
PRINT(("block %" B_PRIdOFF ": vds search failed\n",
avds_locations[i]));
}
}
status_t error = found_vds ? B_OK : B_ERROR;
RETURN(error);
}
static status_t
walk_tagid_partition_descriptor(descriptor_tag *tag, off_t block,
uint8& uniquePartitions, partition_descriptor* partitionDescriptors)
{
DEBUG_INIT(NULL);
status_t error = B_OK;
partition_descriptor *partition =
reinterpret_cast<partition_descriptor*>(tag);
PDUMP(partition);
if (partition->tag().init_check(block) == B_OK) {
bool foundDuplicate = false;
int num;
for (num = 0; num < uniquePartitions; num++) {
if (partitionDescriptors[num].partition_number()
== partition->partition_number()) {
foundDuplicate = true;
if (partitionDescriptors[num].vds_number()
< partition->vds_number()) {
partitionDescriptors[num] = *partition;
PRINT(("Replacing previous partition #%d "
"(vds_number: %" B_PRIu32 ") with new partition #%d "
"(vds_number: %" B_PRIu32 ")\n",
partitionDescriptors[num].partition_number(),
partitionDescriptors[num].vds_number(),
partition->partition_number(),
partition->vds_number()));
}
break;
}
}
if (!foundDuplicate) {
if (num < kMaxPartitionDescriptors) {
partitionDescriptors[num] = *partition;
uniquePartitions++;
PRINT(("Adding partition #%d (vds_number: %" B_PRIu32 ")\n",
partition->partition_number(),
partition->vds_number()));
} else {
bool foundReplacement = false;
for (int j = 0; j < uniquePartitions; j++) {
if (partitionDescriptors[j].vds_number()
< partition->vds_number()) {
foundReplacement = true;
partitionDescriptors[j] = *partition;
PRINT(("Replacing partition #%d "
"(vds_number: %" B_PRIu32 ") "
"with partition #%d "
"(vds_number: %" B_PRIu32 ")\n",
partitionDescriptors[j].partition_number(),
partitionDescriptors[j].vds_number(),
partition->partition_number(),
partition->vds_number()));
break;
}
}
if (!foundReplacement) {
PRINT(("Found more than kMaxPartitionDescriptors == %d "
"unique partition descriptors!\n",
kMaxPartitionDescriptors));
error = B_BAD_VALUE;
}
}
}
}
RETURN(error);
}
static
status_t
walk_volume_descriptor_sequence(extent_address descriptorSequence,
int device, uint32 blockSize, uint32 blockShift,
primary_volume_descriptor &primaryVolumeDescriptor,
logical_volume_descriptor &logicalVolumeDescriptor,
partition_descriptor partitionDescriptors[],
uint8 &partitionDescriptorCount)
{
DEBUG_INIT_ETC(NULL, ("descriptorSequence.loc:%" PRIu32 ", "
"descriptorSequence.len:%" PRIu32,
descriptorSequence.location(), descriptorSequence.length()));
uint32 count = descriptorSequence.length() >> blockShift;
bool foundLogicalVolumeDescriptor = false;
bool foundUnallocatedSpaceDescriptor = false;
bool foundUdfImplementationUseDescriptor = false;
uint8 uniquePartitions = 0;
status_t error = B_OK;
for (uint32 i = 0; i < count; i++) {
off_t block = descriptorSequence.location()+i;
off_t address = block << blockShift;
MemoryChunk chunk(blockSize);
descriptor_tag *tag = NULL;
PRINT(("descriptor #%" PRIu32 " (block %" B_PRIdOFF "):\n", i, block));
status_t loopError = chunk.InitCheck();
if (!loopError) {
ssize_t bytesRead = read_pos(device, address, chunk.Data(),
blockSize);
loopError = bytesRead == (ssize_t)blockSize ? B_OK : B_IO_ERROR;
if (loopError) {
PRINT(("block %" B_PRIdOFF ": read_pos(pos:%" B_PRIdOFF ", "
"len:%" B_PRIu32 ") failed with error 0x%lx\n",
block, address, blockSize, bytesRead));
}
}
if (!loopError) {
tag = reinterpret_cast<descriptor_tag *>(chunk.Data());
loopError = tag->init_check(block);
}
if (!loopError) {
switch (tag->id()) {
case TAGID_UNDEFINED:
break;
case TAGID_PRIMARY_VOLUME_DESCRIPTOR:
{
primary_volume_descriptor *primary =
reinterpret_cast<primary_volume_descriptor*>(tag);
PDUMP(primary);
primaryVolumeDescriptor = *primary;
break;
}
case TAGID_ANCHOR_VOLUME_DESCRIPTOR_POINTER:
break;
case TAGID_VOLUME_DESCRIPTOR_POINTER:
break;
case TAGID_IMPLEMENTATION_USE_VOLUME_DESCRIPTOR:
{
implementation_use_descriptor *impUse =
reinterpret_cast<implementation_use_descriptor*>(tag);
PDUMP(impUse);
if (impUse->tag().init_check(block) == B_OK
&& impUse->implementation_id().matches(
kLogicalVolumeInfoId201)) {
foundUdfImplementationUseDescriptor = true;
}
break;
}
case TAGID_PARTITION_DESCRIPTOR:
{
error = walk_tagid_partition_descriptor(
tag, block, uniquePartitions, partitionDescriptors);
break;
}
case TAGID_LOGICAL_VOLUME_DESCRIPTOR:
{
logical_volume_descriptor *logical =
reinterpret_cast<logical_volume_descriptor*>(tag);
PDUMP(logical);
if (foundLogicalVolumeDescriptor) {
if (logicalVolumeDescriptor.vds_number()
< logical->vds_number()) {
logicalVolumeDescriptor = *logical;
}
} else {
logicalVolumeDescriptor = *logical;
foundLogicalVolumeDescriptor = true;
}
break;
}
case TAGID_UNALLOCATED_SPACE_DESCRIPTOR:
{
unallocated_space_descriptor *unallocated =
reinterpret_cast<unallocated_space_descriptor*>(tag);
PDUMP(unallocated);
foundUnallocatedSpaceDescriptor = true;
(void)unallocated;
break;
}
case TAGID_TERMINATING_DESCRIPTOR:
{
terminating_descriptor *terminating =
reinterpret_cast<terminating_descriptor*>(tag);
PDUMP(terminating);
(void)terminating;
break;
}
case TAGID_LOGICAL_VOLUME_INTEGRITY_DESCRIPTOR:
break;
default:
break;
}
}
}
PRINT(("found %d unique partition%s\n", uniquePartitions,
(uniquePartitions == 1 ? "" : "s")));
if (!error && !foundUdfImplementationUseDescriptor) {
INFORM(("WARNING: no valid udf implementation use descriptor found\n"));
}
if (!error)
error = foundLogicalVolumeDescriptor
&& foundUnallocatedSpaceDescriptor
? B_OK : B_ERROR;
if (!error)
error = uniquePartitions >= 1 ? B_OK : B_ERROR;
if (!error)
partitionDescriptorCount = uniquePartitions;
RETURN(error);
}
\return
- \c B_OK: Success. the sequence was terminated by a valid, closed
integrity descriptor.
- \c B_ENTRY_NOT_FOUND: The sequence was empty.
- (other error code): The sequence was non-empty and did not end in a valid,
closed integrity descriptor.
*/
static status_t
walk_integrity_sequence(int device, uint32 blockSize, uint32 blockShift,
extent_address descriptorSequence, uint32 sequenceNumber)
{
DEBUG_INIT_ETC(NULL,
("descriptorSequence.loc:%" B_PRIu32 ", "
"descriptorSequence.len:%" B_PRIu32 ,
descriptorSequence.location(), descriptorSequence.length()));
uint32 count = descriptorSequence.length() >> blockShift;
bool lastDescriptorWasClosed = false;
uint16 highestMinimumUDFReadRevision = 0x0000;
status_t error = count > 0 ? B_OK : B_ENTRY_NOT_FOUND;
for (uint32 i = 0; error == B_OK && i < count; i++) {
off_t block = descriptorSequence.location()+i;
off_t address = block << blockShift;
MemoryChunk chunk(blockSize);
descriptor_tag *tag = NULL;
PRINT(("integrity descriptor #%" B_PRIu32 ":%" B_PRIu32
" (block %" B_PRIdOFF "):\n",
sequenceNumber, i, block));
status_t loopError = chunk.InitCheck();
if (!loopError) {
ssize_t bytesRead = read_pos(device, address, chunk.Data(), blockSize);
loopError = check_size_error(bytesRead, blockSize);
if (loopError) {
PRINT(("block %" B_PRIdOFF": read_pos(pos:%" B_PRIdOFF
", len:%" B_PRIu32 ") failed with error 0x%lx\n",
block, address, blockSize, bytesRead));
}
}
if (!loopError) {
tag = reinterpret_cast<descriptor_tag *>(chunk.Data());
loopError = tag->init_check(block);
}
if (!loopError) {
loopError = tag->id() == TAGID_LOGICAL_VOLUME_INTEGRITY_DESCRIPTOR
? B_OK : B_BAD_DATA;
if (!loopError) {
logical_volume_integrity_descriptor *descriptor =
reinterpret_cast<logical_volume_integrity_descriptor*>(chunk.Data());
PDUMP(descriptor);
lastDescriptorWasClosed = descriptor->integrity_type() == INTEGRITY_CLOSED;
if (lastDescriptorWasClosed) {
uint16 minimumRevision = descriptor->minimum_udf_read_revision();
if (minimumRevision > highestMinimumUDFReadRevision) {
highestMinimumUDFReadRevision = minimumRevision;
} else if (minimumRevision < highestMinimumUDFReadRevision) {
INFORM(("WARNING: found decreasing minimum udf read revision in integrity "
"sequence (last highest: 0x%04x, current: 0x%04x); using higher "
"revision.\n", highestMinimumUDFReadRevision, minimumRevision));
}
}
extent_address &next = descriptor->next_integrity_extent();
if (next.length() > 0) {
status_t nextError = walk_integrity_sequence(device, blockSize, blockShift,
next, sequenceNumber+1);
if (nextError && nextError != B_ENTRY_NOT_FOUND) {
error = nextError;
break;
} else {
break;
}
}
} else {
PDUMP(tag);
}
}
if (loopError) {
if (i == 0)
error = B_ENTRY_NOT_FOUND;
else
break;
}
}
if (!error)
error = lastDescriptorWasClosed ? B_OK : B_BAD_DATA;
if (error == B_OK
&& highestMinimumUDFReadRevision > UDF_MAX_READ_REVISION) {
error = B_ERROR;
FATAL(("found udf revision 0x%x more than max 0x%x\n",
highestMinimumUDFReadRevision, UDF_MAX_READ_REVISION));
}
RETURN(error);
}