/* * Copyright (c) 2021, sin-ack * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include namespace Kernel { // NOTE: According to the spec, logical blocks 0 to 15 are system use. constexpr u32 first_data_area_block = 16; constexpr u32 logical_sector_size = 2048; constexpr u32 max_cached_directory_entries = 128; ErrorOr> ISO9660FS::try_create(OpenFileDescription& description) { return TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) ISO9660FS(description))); } ISO9660FS::ISO9660FS(OpenFileDescription& description) : BlockBasedFileSystem(description) { set_block_size(logical_sector_size); m_logical_block_size = logical_sector_size; } ISO9660FS::~ISO9660FS() = default; bool ISO9660FS::is_initialized_while_locked() { VERIFY(m_lock.is_locked()); return !m_root_inode.is_null(); } ErrorOr ISO9660FS::initialize_while_locked() { VERIFY(m_lock.is_locked()); VERIFY(!is_initialized_while_locked()); TRY(BlockBasedFileSystem::initialize_while_locked()); TRY(parse_volume_set()); TRY(create_root_inode()); return {}; } Inode& ISO9660FS::root_inode() { VERIFY(!m_root_inode.is_null()); return *m_root_inode; } unsigned ISO9660FS::total_block_count() const { return LittleEndian { m_primary_volume->volume_space_size.little }; } unsigned ISO9660FS::total_inode_count() const { if (!m_cached_inode_count) { auto result = calculate_inode_count(); if (result.is_error()) { // FIXME: This should be able to return a ErrorOr. return 0; } } return m_cached_inode_count; } u8 ISO9660FS::internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const { if (has_flag(static_cast(entry.file_type), ISO::FileFlags::Directory)) { return DT_DIR; } return DT_REG; } ErrorOr ISO9660FS::prepare_to_clear_last_mount() { // FIXME: Do proper cleaning here. BlockBasedFileSystem::remove_disk_cache_before_last_unmount(); return {}; } ErrorOr ISO9660FS::parse_volume_set() { VERIFY(!m_primary_volume); auto block = TRY(KBuffer::try_create_with_size("ISO9660FS: Temporary volume descriptor storage"sv, m_logical_block_size, Memory::Region::Access::Read | Memory::Region::Access::Write)); auto block_buffer = UserOrKernelBuffer::for_kernel_buffer(block->data()); auto current_block_index = first_data_area_block; while (true) { auto result = raw_read(BlockIndex { current_block_index }, block_buffer); if (result.is_error()) { dbgln_if(ISO9660_DEBUG, "Failed to read volume descriptor from ISO file: {}", result.error()); return result; } auto const* header = reinterpret_cast(block->data()); if (StringView { header->identifier, 5 } != "CD001"sv) { dbgln_if(ISO9660_DEBUG, "Header magic at volume descriptor {} is not valid", current_block_index - first_data_area_block); return EIO; } switch (header->type) { case ISO::VolumeDescriptorType::PrimaryVolumeDescriptor: { auto const* primary_volume = reinterpret_cast(header); m_primary_volume = adopt_own_if_nonnull(new ISO::PrimaryVolumeDescriptor(*primary_volume)); break; } case ISO::VolumeDescriptorType::BootRecord: case ISO::VolumeDescriptorType::SupplementaryOrEnhancedVolumeDescriptor: case ISO::VolumeDescriptorType::VolumePartitionDescriptor: { break; } case ISO::VolumeDescriptorType::VolumeDescriptorSetTerminator: { goto all_headers_read; } default: dbgln_if(ISO9660_DEBUG, "Unexpected volume descriptor type {} in volume set", static_cast(header->type)); return EIO; } current_block_index++; } all_headers_read: if (!m_primary_volume) { dbgln_if(ISO9660_DEBUG, "Could not find primary volume"); return EIO; } m_logical_block_size = LittleEndian { m_primary_volume->logical_block_size.little }; return {}; } ErrorOr ISO9660FS::create_root_inode() { if (!m_primary_volume) { dbgln_if(ISO9660_DEBUG, "Primary volume doesn't exist, can't create root inode"); return EIO; } m_root_inode = TRY(ISO9660Inode::try_create_from_directory_record(*this, m_primary_volume->root_directory_record_header, {})); return {}; } ErrorOr ISO9660FS::calculate_inode_count() const { if (!m_primary_volume) { dbgln_if(ISO9660_DEBUG, "Primary volume doesn't exist, can't calculate inode count"); return EIO; } size_t inode_count = 1; TRY(visit_directory_record(m_primary_volume->root_directory_record_header, [&](ISO::DirectoryRecordHeader const* header) { if (header == nullptr) { return RecursionDecision::Continue; } inode_count += 1; if (has_flag(header->file_flags, ISO::FileFlags::Directory)) { if (header->file_identifier_length == 1) { auto file_identifier = reinterpret_cast(header + 1); if (file_identifier[0] == '\0' || file_identifier[0] == '\1') { return RecursionDecision::Continue; } } return RecursionDecision::Recurse; } return RecursionDecision::Continue; })); m_cached_inode_count = inode_count; return {}; } ErrorOr ISO9660FS::visit_directory_record(ISO::DirectoryRecordHeader const& record, Function(ISO::DirectoryRecordHeader const*)> const& visitor) const { if (!has_flag(record.file_flags, ISO::FileFlags::Directory)) { return {}; } ISO9660DirectoryIterator iterator { const_cast(*this), record }; while (!iterator.done()) { auto decision = TRY(visitor(*iterator)); switch (decision) { case RecursionDecision::Recurse: { auto has_moved = TRY(iterator.next()); if (!has_moved) { // If next() hasn't moved then we have read through all the // directories, and can exit. return {}; } continue; } case RecursionDecision::Continue: { while (!iterator.done()) { if (iterator.skip()) break; if (!iterator.go_up()) return {}; } continue; } case RecursionDecision::Break: return {}; } } return {}; } ErrorOr> ISO9660FS::directory_entry_for_record(Badge, ISO::DirectoryRecordHeader const* record) { u32 extent_location = LittleEndian { record->extent_location.little }; u32 data_length = LittleEndian { record->data_length.little }; auto key = calculate_directory_entry_cache_key(*record); auto it = m_directory_entry_cache.find(key); if (it != m_directory_entry_cache.end()) { dbgln_if(ISO9660_DEBUG, "Cache hit for dirent @ {}", extent_location); return it->value; } dbgln_if(ISO9660_DEBUG, "Cache miss for dirent @ {} :^(", extent_location); if (m_directory_entry_cache.size() == max_cached_directory_entries) { // FIXME: A smarter algorithm would probably be nicer. m_directory_entry_cache.remove(m_directory_entry_cache.begin()); } if (!(data_length % logical_block_size() == 0)) { dbgln_if(ISO9660_DEBUG, "Found a directory with non-logical block size aligned data length!"); return EIO; } auto blocks = TRY(KBuffer::try_create_with_size("ISO9660FS: Directory traversal buffer"sv, data_length, Memory::Region::Access::Read | Memory::Region::Access::Write)); auto blocks_buffer = UserOrKernelBuffer::for_kernel_buffer(blocks->data()); TRY(raw_read_blocks(BlockBasedFileSystem::BlockIndex { extent_location }, data_length / logical_block_size(), blocks_buffer)); auto entry = TRY(ISO9660FSDirectoryEntry::try_create(extent_location, data_length, move(blocks))); m_directory_entry_cache.set(key, entry); dbgln_if(ISO9660_DEBUG, "Cached dirent @ {}", extent_location); return entry; } u32 ISO9660FS::calculate_directory_entry_cache_key(ISO::DirectoryRecordHeader const& record) { return LittleEndian { record.extent_location.little }; } }