123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- /*
- * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <AK/Endian.h>
- #include <Kernel/FileSystem/ISO9660FS/DirectoryIterator.h>
- #include <Kernel/FileSystem/ISO9660FS/FileSystem.h>
- #include <Kernel/FileSystem/ISO9660FS/Inode.h>
- 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<NonnullRefPtr<FileSystem>> ISO9660FS::try_create(OpenFileDescription& description)
- {
- return TRY(adopt_nonnull_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<void> 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<void>.
- 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<ISO::FileFlags>(entry.file_type), ISO::FileFlags::Directory)) {
- return DT_DIR;
- }
- return DT_REG;
- }
- ErrorOr<void> ISO9660FS::prepare_to_clear_last_mount()
- {
- // FIXME: Do proper cleaning here.
- BlockBasedFileSystem::remove_disk_cache_before_last_unmount();
- return {};
- }
- ErrorOr<void> 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<ISO::VolumeDescriptorHeader const*>(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<ISO::PrimaryVolumeDescriptor const*>(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<u8>(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<void> 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<void> 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<u8 const*>(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<void> ISO9660FS::visit_directory_record(ISO::DirectoryRecordHeader const& record, Function<ErrorOr<RecursionDecision>(ISO::DirectoryRecordHeader const*)> const& visitor) const
- {
- if (!has_flag(record.file_flags, ISO::FileFlags::Directory)) {
- return {};
- }
- ISO9660DirectoryIterator iterator { const_cast<ISO9660FS&>(*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<NonnullLockRefPtr<ISO9660FSDirectoryEntry>> ISO9660FS::directory_entry_for_record(Badge<ISO9660DirectoryIterator>, 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 };
- }
- }
|