/* * 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 //------------------------------------------------------------------------------ // forward declarations //------------------------------------------------------------------------------ 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); //------------------------------------------------------------------------------ // externally visible functions //------------------------------------------------------------------------------ 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)); // Check the block size 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)); // Check for a valid volume recognition sequence 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; } // Now hunt down a volume descriptor sequence from one of // the anchor volume pointers (if there are any). 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; } // Now walk the integrity sequence and make sure the last integrity // descriptor is a closed descriptor 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; } //------------------------------------------------------------------------------ // local functions //------------------------------------------------------------------------------ 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)); // vrs starts at block 16. Each volume structure descriptor (vsd) // should be one block long. We're expecting to find 0 or more iso9660 // vsd's followed by some ECMA-167 vsd's. 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; } } // If we find an ECMA-167 descriptor, OR if we find a beginning // or terminating extended area descriptor with NO ECMA-168 // descriptors, we return B_OK to signal that we should go // looking for valid anchors. 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(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); // Found an avds, so try the main sequence first, then // the reserve sequence if the main one fails. 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 { // Both failed, so loop around and try another avds 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(tag); PDUMP(partition); if (partition->tag().init_check(block) == B_OK) { // Check for a previously discovered partition descriptor with // the same number as this partition. If found, keep the one with // the higher vds number. 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 we didn't find a duplicate, see if we have any open descriptor // spaces left. if (!foundDuplicate) { if (num < kMaxPartitionDescriptors) { // At least one more partition descriptor allowed partitionDescriptors[num] = *partition; uniquePartitions++; PRINT(("Adding partition #%d (vds_number: %" B_PRIu32 ")\n", partition->partition_number(), partition->vds_number())); } else { // We've found more than kMaxPartitionDescriptor uniquely- // numbered partitions. So, search through the partitions // we already have again, this time just looking for a // partition with a lower vds number. If we find one, // replace it with this one. If we don't, scream bloody // murder. 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(chunk.Data()); loopError = tag->init_check(block); } if (!loopError) { // Now decide what type of descriptor we have switch (tag->id()) { case TAGID_UNDEFINED: break; case TAGID_PRIMARY_VOLUME_DESCRIPTOR: { primary_volume_descriptor *primary = reinterpret_cast(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(tag); PDUMP(impUse); // Check for a matching implementation id string // (note that the revision version is not checked) 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(tag); PDUMP(logical); if (foundLogicalVolumeDescriptor) { // Keep the vd with the highest vds_number 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(tag); PDUMP(unallocated); foundUnallocatedSpaceDescriptor = true; (void)unallocated; // kill the warning break; } case TAGID_TERMINATING_DESCRIPTOR: { terminating_descriptor *terminating = reinterpret_cast(tag); PDUMP(terminating); (void)terminating; // kill the warning break; } case TAGID_LOGICAL_VOLUME_INTEGRITY_DESCRIPTOR: // Not found in this descriptor sequence 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); } /*! \brief Walks the integrity sequence in the extent given by \a descriptorSequence. \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(chunk.Data()); loopError = tag->init_check(block); } if (!loopError) { // Check the descriptor type and see if it's closed. loopError = tag->id() == TAGID_LOGICAL_VOLUME_INTEGRITY_DESCRIPTOR ? B_OK : B_BAD_DATA; if (!loopError) { logical_volume_integrity_descriptor *descriptor = reinterpret_cast(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)); } } // Check a continuation extent if necessary. Note that this effectively // ends our search through this extent 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) { // Continuation proved invalid error = nextError; break; } else { // Either the continuation was valid or empty; either way, // we're done searching. break; } } } else { PDUMP(tag); } } // If we hit an error on the first item, consider the extent empty, // otherwise just break out of the loop and assume part of the // extent is unrecorded 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); }