diff options
author | Liav A <liavalb@gmail.com> | 2022-10-24 11:02:50 +0300 |
---|---|---|
committer | Andrew Kaster <andrewdkaster@gmail.com> | 2022-11-08 02:54:48 -0700 |
commit | 1c91881a1d027fb4dd75bedaac933898f3c97329 (patch) | |
tree | f2ba622959d1db81ec936a48d66cc583bba68fbe /Kernel | |
parent | fca3b7f1f94bd7a4c4f13605d20502af82366d2e (diff) | |
download | serenity-1c91881a1d027fb4dd75bedaac933898f3c97329.zip |
Kernel: Split the ISO9660FileSystem.{cpp,h} files to smaller components
Diffstat (limited to 'Kernel')
-rw-r--r-- | Kernel/CMakeLists.txt | 4 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/Definitions.h (renamed from Kernel/FileSystem/ISO9660FileSystem.h) | 122 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/DirectoryEntry.h | 41 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/DirectoryIterator.cpp | 134 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/DirectoryIterator.h | 46 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/FileSystem.cpp | 265 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/FileSystem.h | 68 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/Inode.cpp | 251 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/Inode.h | 62 | ||||
-rw-r--r-- | Kernel/FileSystem/ISO9660FileSystem.cpp | 655 | ||||
-rw-r--r-- | Kernel/Syscalls/mount.cpp | 2 |
11 files changed, 871 insertions, 779 deletions
diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 7f89394cf1..d57afe29a9 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -122,7 +122,9 @@ set(KERNEL_SOURCES FileSystem/InodeFile.cpp FileSystem/InodeMetadata.cpp FileSystem/InodeWatcher.cpp - FileSystem/ISO9660FileSystem.cpp + FileSystem/ISO9660FS/DirectoryIterator.cpp + FileSystem/ISO9660FS/FileSystem.cpp + FileSystem/ISO9660FS/Inode.cpp FileSystem/Mount.cpp FileSystem/OpenFileDescription.cpp FileSystem/Plan9FS/FileSystem.cpp diff --git a/Kernel/FileSystem/ISO9660FileSystem.h b/Kernel/FileSystem/ISO9660FS/Definitions.h index dd44683766..4b71e4b77a 100644 --- a/Kernel/FileSystem/ISO9660FileSystem.h +++ b/Kernel/FileSystem/ISO9660FS/Definitions.h @@ -7,15 +7,7 @@ #pragma once #include <AK/EnumBits.h> -#include <AK/Error.h> -#include <AK/HashMap.h> -#include <AK/RecursionDecision.h> -#include <AK/StringView.h> #include <AK/Types.h> -#include <Kernel/FileSystem/BlockBasedFileSystem.h> -#include <Kernel/FileSystem/Inode.h> -#include <Kernel/KBuffer.h> -#include <Kernel/Library/NonnullLockRefPtr.h> namespace Kernel { @@ -275,118 +267,4 @@ static_assert(sizeof(VolumePartitionDescriptor) == 2048); } -class ISO9660Inode; -class ISO9660DirectoryIterator; - -class ISO9660FS final : public BlockBasedFileSystem { - friend ISO9660Inode; - friend ISO9660DirectoryIterator; - -public: - struct DirectoryEntry final : public AtomicRefCounted<DirectoryEntry> { - u32 extent { 0 }; - u32 length { 0 }; - - // NOTE: This can never be empty if we read the directory successfully. - // We need it as an OwnPtr to default-construct this struct. - OwnPtr<KBuffer> blocks; - - static ErrorOr<NonnullLockRefPtr<DirectoryEntry>> try_create(u32 extent, u32 length, OwnPtr<KBuffer> blocks) - { - return adopt_nonnull_lock_ref_or_enomem(new (nothrow) DirectoryEntry(extent, length, move(blocks))); - } - - private: - DirectoryEntry(u32 extent, u32 length, OwnPtr<KBuffer> blocks) - : extent(extent) - , length(length) - , blocks(move(blocks)) - { - } - }; - - static ErrorOr<NonnullLockRefPtr<FileSystem>> try_create(OpenFileDescription&); - - virtual ~ISO9660FS() override; - virtual StringView class_name() const override { return "ISO9660FS"sv; } - virtual Inode& root_inode() override; - - virtual unsigned total_block_count() const override; - virtual unsigned total_inode_count() const override; - - virtual u8 internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const override; - - ErrorOr<NonnullLockRefPtr<DirectoryEntry>> directory_entry_for_record(Badge<ISO9660DirectoryIterator>, ISO::DirectoryRecordHeader const* record); - -private: - ISO9660FS(OpenFileDescription&); - - virtual ErrorOr<void> prepare_to_clear_last_mount() override; - - virtual bool is_initialized_while_locked() override; - virtual ErrorOr<void> initialize_while_locked() override; - - ErrorOr<void> parse_volume_set(); - ErrorOr<void> create_root_inode(); - ErrorOr<void> calculate_inode_count() const; - - u32 calculate_directory_entry_cache_key(ISO::DirectoryRecordHeader const&); - - ErrorOr<void> visit_directory_record(ISO::DirectoryRecordHeader const& record, Function<ErrorOr<RecursionDecision>(ISO::DirectoryRecordHeader const*)> const& visitor) const; - - OwnPtr<ISO::PrimaryVolumeDescriptor> m_primary_volume; - LockRefPtr<ISO9660Inode> m_root_inode; - - mutable u32 m_cached_inode_count { 0 }; - HashMap<u32, NonnullLockRefPtr<DirectoryEntry>> m_directory_entry_cache; -}; - -class ISO9660Inode final : public Inode { - friend ISO9660FS; - -public: - virtual ~ISO9660Inode() override; - - ISO9660FS& fs() { return static_cast<ISO9660FS&>(Inode::fs()); } - ISO9660FS const& fs() const { return static_cast<ISO9660FS const&>(Inode::fs()); } - - // ^Inode - virtual InodeMetadata metadata() const override; - virtual ErrorOr<void> traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)>) const override; - virtual ErrorOr<NonnullLockRefPtr<Inode>> lookup(StringView name) override; - virtual ErrorOr<void> flush_metadata() override; - virtual ErrorOr<NonnullLockRefPtr<Inode>> create_child(StringView name, mode_t, dev_t, UserID, GroupID) override; - virtual ErrorOr<void> add_child(Inode&, StringView name, mode_t) override; - virtual ErrorOr<void> remove_child(StringView name) override; - virtual ErrorOr<void> chmod(mode_t) override; - virtual ErrorOr<void> chown(UserID, GroupID) override; - virtual ErrorOr<void> truncate(u64) override; - virtual ErrorOr<void> update_timestamps(Optional<time_t> atime, Optional<time_t> ctime, Optional<time_t> mtime) override; - -private: - // HACK: The base ISO 9660 standard says the maximum filename length is 37 - // bytes large; however, we can read filenames longer than that right now - // without any problems, so let's allow it anyway. - static constexpr size_t max_file_identifier_length = 256 - sizeof(ISO::DirectoryRecordHeader); - - // ^Inode - virtual ErrorOr<size_t> read_bytes_locked(off_t, size_t, UserOrKernelBuffer& buffer, OpenFileDescription*) const override; - virtual ErrorOr<size_t> write_bytes_locked(off_t, size_t, UserOrKernelBuffer const& buffer, OpenFileDescription*) override; - - ISO9660Inode(ISO9660FS&, ISO::DirectoryRecordHeader const& record, StringView name); - static ErrorOr<NonnullLockRefPtr<ISO9660Inode>> try_create_from_directory_record(ISO9660FS&, ISO::DirectoryRecordHeader const& record, StringView name); - - static InodeIndex get_inode_index(ISO::DirectoryRecordHeader const& record, StringView name); - static StringView get_normalized_filename(ISO::DirectoryRecordHeader const& record, Bytes buffer); - - void create_metadata(); - time_t parse_numerical_date_time(ISO::NumericalDateAndTime const&); - - InodeMetadata m_metadata; - ISO::DirectoryRecordHeader m_record; -}; - } - -using Kernel::ISO::has_any_flag; -using Kernel::ISO::has_flag; diff --git a/Kernel/FileSystem/ISO9660FS/DirectoryEntry.h b/Kernel/FileSystem/ISO9660FS/DirectoryEntry.h new file mode 100644 index 0000000000..e46d610eb7 --- /dev/null +++ b/Kernel/FileSystem/ISO9660FS/DirectoryEntry.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Types.h> +#include <Kernel/KBuffer.h> + +namespace Kernel { + +struct ISO9660FSDirectoryEntry final : public AtomicRefCounted<ISO9660FSDirectoryEntry> { + u32 extent { 0 }; + u32 length { 0 }; + + // NOTE: This can never be empty if we read the directory successfully. + // We need it as an OwnPtr to default-construct this struct. + OwnPtr<KBuffer> blocks; + + static ErrorOr<NonnullLockRefPtr<ISO9660FSDirectoryEntry>> try_create(u32 extent, u32 length, OwnPtr<KBuffer> blocks) + { + return adopt_nonnull_lock_ref_or_enomem(new (nothrow) ISO9660FSDirectoryEntry(extent, length, move(blocks))); + } + +private: + ISO9660FSDirectoryEntry(u32 extent, u32 length, OwnPtr<KBuffer> blocks) + : extent(extent) + , length(length) + , blocks(move(blocks)) + { + } +}; + +struct ISO9660FSDirectoryState { + LockRefPtr<ISO9660FSDirectoryEntry> entry; + u32 offset { 0 }; +}; + +} diff --git a/Kernel/FileSystem/ISO9660FS/DirectoryIterator.cpp b/Kernel/FileSystem/ISO9660FS/DirectoryIterator.cpp new file mode 100644 index 0000000000..a901122814 --- /dev/null +++ b/Kernel/FileSystem/ISO9660FS/DirectoryIterator.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Types.h> +#include <Kernel/FileSystem/ISO9660FS/Definitions.h> +#include <Kernel/FileSystem/ISO9660FS/DirectoryIterator.h> +#include <Kernel/KBuffer.h> + +namespace Kernel { + +ISO9660DirectoryIterator::ISO9660DirectoryIterator(ISO9660FS& fs, ISO::DirectoryRecordHeader const& header) + : m_fs(fs) + , m_current_header(&header) +{ + // FIXME: Panic or alternative method? + (void)read_directory_contents(); + get_header(); +} + +ErrorOr<bool> ISO9660DirectoryIterator::next() +{ + if (done()) + return false; + dbgln_if(ISO9660_VERY_DEBUG, "next(): Called"); + + if (has_flag(m_current_header->file_flags, ISO::FileFlags::Directory)) { + dbgln_if(ISO9660_VERY_DEBUG, "next(): Recursing"); + { + TRY(m_directory_stack.try_append(move(m_current_directory))); + } + + dbgln_if(ISO9660_VERY_DEBUG, "next(): Pushed into directory stack"); + + TRY(read_directory_contents()); + + dbgln_if(ISO9660_VERY_DEBUG, "next(): Read directory contents"); + + m_current_directory.offset = 0; + get_header(); + if (m_current_header->length == 0) { + // We have found an empty directory, let's continue with the + // next one. + if (!go_up()) + return false; + } else { + // We cannot skip here, as this is the first record in this + // extent. + return true; + } + } + + return skip(); +} + +bool ISO9660DirectoryIterator::skip() +{ + VERIFY(m_current_directory.entry); + + if (m_current_directory.offset >= m_current_directory.entry->length) { + dbgln_if(ISO9660_VERY_DEBUG, "skip(): Was at last item already"); + return false; + } + + m_current_directory.offset += m_current_header->length; + get_header(); + if (m_current_header->length == 0) { + // According to ECMA 119, if a logical block contains directory + // records, then the leftover bytes in the logical block are + // all zeros. So if our directory header has a length of 0, + // we're probably looking at padding. + // + // Of course, this doesn't mean we're done; it only means that there + // are no more directory entries in *this* logical block. If we + // have at least one more logical block of data length to go, we + // need to snap to the next logical block, because directory records + // cannot span multiple logical blocks. + u32 remaining_bytes = m_current_directory.entry->length - m_current_directory.offset; + if (remaining_bytes > m_fs.logical_block_size()) { + m_current_directory.offset += remaining_bytes % m_fs.logical_block_size(); + get_header(); + + dbgln_if(ISO9660_VERY_DEBUG, "skip(): Snapped to next logical block (succeeded)"); + return true; + } + + dbgln_if(ISO9660_VERY_DEBUG, "skip(): Was at the last logical block, at padding now (offset {}, data length {})", m_current_directory.entry->length, m_current_directory.offset); + return false; + } + + dbgln_if(ISO9660_VERY_DEBUG, "skip(): Skipped to next item"); + return true; +} + +bool ISO9660DirectoryIterator::go_up() +{ + if (m_directory_stack.is_empty()) { + dbgln_if(ISO9660_VERY_DEBUG, "go_up(): Empty directory stack"); + return false; + } + + m_current_directory = m_directory_stack.take_last(); + get_header(); + + dbgln_if(ISO9660_VERY_DEBUG, "go_up(): Went up a directory"); + return true; +} + +bool ISO9660DirectoryIterator::done() const +{ + VERIFY(m_current_directory.entry); + auto result = m_directory_stack.is_empty() && m_current_directory.offset >= m_current_directory.entry->length; + dbgln_if(ISO9660_VERY_DEBUG, "done(): {}", result); + return result; +} + +ErrorOr<void> ISO9660DirectoryIterator::read_directory_contents() +{ + m_current_directory.entry = TRY(m_fs.directory_entry_for_record({}, m_current_header)); + return {}; +} + +void ISO9660DirectoryIterator::get_header() +{ + VERIFY(m_current_directory.entry); + if (!m_current_directory.entry->blocks) + return; + + m_current_header = reinterpret_cast<ISO::DirectoryRecordHeader const*>(m_current_directory.entry->blocks->data() + m_current_directory.offset); +} + +} diff --git a/Kernel/FileSystem/ISO9660FS/DirectoryIterator.h b/Kernel/FileSystem/ISO9660FS/DirectoryIterator.h new file mode 100644 index 0000000000..ae771e59cd --- /dev/null +++ b/Kernel/FileSystem/ISO9660FS/DirectoryIterator.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Types.h> +#include <Kernel/FileSystem/ISO9660FS/Definitions.h> +#include <Kernel/FileSystem/ISO9660FS/DirectoryEntry.h> +#include <Kernel/FileSystem/ISO9660FS/FileSystem.h> +#include <Kernel/KBuffer.h> + +namespace Kernel { + +class ISO9660DirectoryIterator { +public: + ISO9660DirectoryIterator(ISO9660FS& fs, ISO::DirectoryRecordHeader const& header); + + ISO::DirectoryRecordHeader const* operator*() { return m_current_header; } + + // Recurses into subdirectories. May fail. + ErrorOr<bool> next(); + + // Skips to the directory in the list, returns whether there was a next one. + // No allocation here, cannot fail. + bool skip(); + + bool go_up(); + bool done() const; + +private: + ErrorOr<void> read_directory_contents(); + + void get_header(); + + ISO9660FS& m_fs; + + ISO9660FSDirectoryState m_current_directory; + ISO::DirectoryRecordHeader const* m_current_header { nullptr }; + + Vector<ISO9660FSDirectoryState> m_directory_stack; +}; + +} diff --git a/Kernel/FileSystem/ISO9660FS/FileSystem.cpp b/Kernel/FileSystem/ISO9660FS/FileSystem.cpp new file mode 100644 index 0000000000..450531b597 --- /dev/null +++ b/Kernel/FileSystem/ISO9660FS/FileSystem.cpp @@ -0,0 +1,265 @@ +/* + * 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<NonnullLockRefPtr<FileSystem>> 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<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 }; +} + +} diff --git a/Kernel/FileSystem/ISO9660FS/FileSystem.h b/Kernel/FileSystem/ISO9660FS/FileSystem.h new file mode 100644 index 0000000000..4e6fe859a4 --- /dev/null +++ b/Kernel/FileSystem/ISO9660FS/FileSystem.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/EnumBits.h> +#include <AK/Error.h> +#include <AK/HashMap.h> +#include <AK/RecursionDecision.h> +#include <AK/StringView.h> +#include <AK/Types.h> +#include <Kernel/FileSystem/BlockBasedFileSystem.h> +#include <Kernel/FileSystem/ISO9660FS/Definitions.h> +#include <Kernel/FileSystem/ISO9660FS/DirectoryEntry.h> +#include <Kernel/FileSystem/Inode.h> +#include <Kernel/KBuffer.h> +#include <Kernel/Library/NonnullLockRefPtr.h> + +namespace Kernel { + +class ISO9660Inode; +class ISO9660DirectoryIterator; + +class ISO9660FS final : public BlockBasedFileSystem { + friend ISO9660Inode; + friend ISO9660DirectoryIterator; + +public: + static ErrorOr<NonnullLockRefPtr<FileSystem>> try_create(OpenFileDescription&); + + virtual ~ISO9660FS() override; + virtual StringView class_name() const override { return "ISO9660FS"sv; } + virtual Inode& root_inode() override; + + virtual unsigned total_block_count() const override; + virtual unsigned total_inode_count() const override; + + virtual u8 internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const override; + + ErrorOr<NonnullLockRefPtr<ISO9660FSDirectoryEntry>> directory_entry_for_record(Badge<ISO9660DirectoryIterator>, ISO::DirectoryRecordHeader const* record); + +private: + ISO9660FS(OpenFileDescription&); + + virtual ErrorOr<void> prepare_to_clear_last_mount() override; + + virtual bool is_initialized_while_locked() override; + virtual ErrorOr<void> initialize_while_locked() override; + + ErrorOr<void> parse_volume_set(); + ErrorOr<void> create_root_inode(); + ErrorOr<void> calculate_inode_count() const; + + u32 calculate_directory_entry_cache_key(ISO::DirectoryRecordHeader const&); + + ErrorOr<void> visit_directory_record(ISO::DirectoryRecordHeader const& record, Function<ErrorOr<RecursionDecision>(ISO::DirectoryRecordHeader const*)> const& visitor) const; + + OwnPtr<ISO::PrimaryVolumeDescriptor> m_primary_volume; + LockRefPtr<ISO9660Inode> m_root_inode; + + mutable u32 m_cached_inode_count { 0 }; + HashMap<u32, NonnullLockRefPtr<ISO9660FSDirectoryEntry>> m_directory_entry_cache; +}; + +} diff --git a/Kernel/FileSystem/ISO9660FS/Inode.cpp b/Kernel/FileSystem/ISO9660FS/Inode.cpp new file mode 100644 index 0000000000..733d9322b5 --- /dev/null +++ b/Kernel/FileSystem/ISO9660FS/Inode.cpp @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/CharacterTypes.h> +#include <AK/Endian.h> +#include <Kernel/FileSystem/ISO9660FS/Inode.h> + +namespace Kernel { + +ErrorOr<size_t> ISO9660Inode::read_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer& buffer, OpenFileDescription*) const +{ + VERIFY(m_inode_lock.is_locked()); + + u32 data_length = LittleEndian { m_record.data_length.little }; + u32 extent_location = LittleEndian { m_record.extent_location.little }; + + if (static_cast<u64>(offset) >= data_length) + return 0; + + auto block = TRY(KBuffer::try_create_with_size("ISO9660FS: Inode read buffer"sv, fs().m_logical_block_size)); + auto block_buffer = UserOrKernelBuffer::for_kernel_buffer(block->data()); + + size_t total_bytes = min(size, data_length - offset); + size_t nread = 0; + size_t blocks_already_read = offset / fs().m_logical_block_size; + size_t initial_offset = offset % fs().m_logical_block_size; + + auto current_block_index = BlockBasedFileSystem::BlockIndex { extent_location + blocks_already_read }; + while (nread != total_bytes) { + size_t bytes_to_read = min(total_bytes - nread, fs().logical_block_size() - initial_offset); + auto buffer_offset = buffer.offset(nread); + dbgln_if(ISO9660_VERY_DEBUG, "ISO9660Inode::read_bytes: Reading {} bytes into buffer offset {}/{}, logical block index: {}", bytes_to_read, nread, total_bytes, current_block_index.value()); + + TRY(const_cast<ISO9660FS&>(fs()).raw_read(current_block_index, block_buffer)); + TRY(buffer_offset.write(block->data() + initial_offset, bytes_to_read)); + + nread += bytes_to_read; + initial_offset = 0; + current_block_index = BlockBasedFileSystem::BlockIndex { current_block_index.value() + 1 }; + } + + return nread; +} + +InodeMetadata ISO9660Inode::metadata() const +{ + return m_metadata; +} + +ErrorOr<void> ISO9660Inode::traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> visitor) const +{ + Array<u8, max_file_identifier_length> file_identifier_buffer; + ErrorOr<void> result; + + return fs().visit_directory_record(m_record, [&](ISO::DirectoryRecordHeader const* record) { + StringView filename = get_normalized_filename(*record, file_identifier_buffer); + dbgln_if(ISO9660_VERY_DEBUG, "traverse_as_directory(): Found {}", filename); + + InodeIdentifier id { fsid(), get_inode_index(*record, filename) }; + auto entry = FileSystem::DirectoryEntryView(filename, id, static_cast<u8>(record->file_flags)); + + result = visitor(entry); + if (result.is_error()) + return RecursionDecision::Break; + + return RecursionDecision::Continue; + }); +} + +ErrorOr<NonnullLockRefPtr<Inode>> ISO9660Inode::lookup(StringView name) +{ + LockRefPtr<Inode> inode; + Array<u8, max_file_identifier_length> file_identifier_buffer; + + TRY(fs().visit_directory_record(m_record, [&](ISO::DirectoryRecordHeader const* record) { + StringView filename = get_normalized_filename(*record, file_identifier_buffer); + + if (filename == name) { + auto maybe_inode = ISO9660Inode::try_create_from_directory_record(fs(), *record, filename); + if (maybe_inode.is_error()) { + // FIXME: The Inode API does not handle allocation failures very + // well... we can't return a ErrorOr from here. It + // would be nice if we could return a ErrorOr<void>(Or) from + // any place where allocation may happen. + dbgln("Could not allocate inode for lookup!"); + } else { + inode = maybe_inode.release_value(); + } + return RecursionDecision::Break; + } + + return RecursionDecision::Continue; + })); + + if (!inode) + return ENOENT; + return inode.release_nonnull(); +} + +ErrorOr<void> ISO9660Inode::flush_metadata() +{ + return {}; +} + +ErrorOr<size_t> ISO9660Inode::write_bytes_locked(off_t, size_t, UserOrKernelBuffer const&, OpenFileDescription*) +{ + return EROFS; +} + +ErrorOr<NonnullLockRefPtr<Inode>> ISO9660Inode::create_child(StringView, mode_t, dev_t, UserID, GroupID) +{ + return EROFS; +} + +ErrorOr<void> ISO9660Inode::add_child(Inode&, StringView, mode_t) +{ + return EROFS; +} + +ErrorOr<void> ISO9660Inode::remove_child(StringView) +{ + return EROFS; +} + +ErrorOr<void> ISO9660Inode::chmod(mode_t) +{ + return EROFS; +} + +ErrorOr<void> ISO9660Inode::chown(UserID, GroupID) +{ + return EROFS; +} + +ErrorOr<void> ISO9660Inode::truncate(u64) +{ + return EROFS; +} + +ErrorOr<void> ISO9660Inode::update_timestamps(Optional<time_t>, Optional<time_t>, Optional<time_t>) +{ + return EROFS; +} + +ISO9660Inode::ISO9660Inode(ISO9660FS& fs, ISO::DirectoryRecordHeader const& record, StringView name) + : Inode(fs, get_inode_index(record, name)) + , m_record(record) +{ + dbgln_if(ISO9660_VERY_DEBUG, "Creating inode #{}", index()); + create_metadata(); +} + +ISO9660Inode::~ISO9660Inode() = default; + +ErrorOr<NonnullLockRefPtr<ISO9660Inode>> ISO9660Inode::try_create_from_directory_record(ISO9660FS& fs, ISO::DirectoryRecordHeader const& record, StringView name) +{ + return adopt_nonnull_lock_ref_or_enomem(new (nothrow) ISO9660Inode(fs, record, name)); +} + +void ISO9660Inode::create_metadata() +{ + u32 data_length = LittleEndian { m_record.data_length.little }; + bool is_directory = has_flag(m_record.file_flags, ISO::FileFlags::Directory); + time_t recorded_at = parse_numerical_date_time(m_record.recording_date_and_time); + + m_metadata = { + .inode = identifier(), + .size = data_length, + .mode = static_cast<mode_t>((is_directory ? S_IFDIR : S_IFREG) | (is_directory ? 0555 : 0444)), + .uid = 0, + .gid = 0, + .link_count = 1, + .atime = recorded_at, + .ctime = recorded_at, + .mtime = recorded_at, + .dtime = 0, + .block_count = 0, + .block_size = 0, + .major_device = 0, + .minor_device = 0, + }; +} + +time_t ISO9660Inode::parse_numerical_date_time(ISO::NumericalDateAndTime const& date) +{ + i32 year_offset = date.years_since_1900 - 70; + + return (year_offset * 60 * 60 * 24 * 30 * 12) + + (date.month * 60 * 60 * 24 * 30) + + (date.day * 60 * 60 * 24) + + (date.hour * 60 * 60) + + (date.minute * 60) + + date.second; +} + +StringView ISO9660Inode::get_normalized_filename(ISO::DirectoryRecordHeader const& record, Bytes buffer) +{ + auto const* file_identifier = reinterpret_cast<u8 const*>(&record + 1); + auto filename = StringView { file_identifier, record.file_identifier_length }; + + if (filename.length() == 1) { + if (filename[0] == '\0') { + filename = "."sv; + } + + if (filename[0] == '\1') { + filename = ".."sv; + } + } + + if (!has_flag(record.file_flags, ISO::FileFlags::Directory)) { + // FIXME: We currently strip the file version from the filename, + // but that may be used later down the line if the file actually + // has multiple versions on the disk. + Optional<size_t> semicolon = filename.find(';'); + if (semicolon.has_value()) { + filename = filename.substring_view(0, semicolon.value()); + } + + if (filename[filename.length() - 1] == '.') { + filename = filename.substring_view(0, filename.length() - 1); + } + } + + if (filename.length() > buffer.size()) { + // FIXME: Rock Ridge allows filenames up to 255 characters, so we should + // probably support that instead of truncating. + filename = filename.substring_view(0, buffer.size()); + } + + for (size_t i = 0; i < filename.length(); i++) { + buffer[i] = to_ascii_lowercase(filename[i]); + } + + return { buffer.data(), filename.length() }; +} + +InodeIndex ISO9660Inode::get_inode_index(ISO::DirectoryRecordHeader const& record, StringView name) +{ + if (name.is_null()) { + // NOTE: This is the index of the root inode. + return 1; + } + + return { pair_int_hash(LittleEndian { record.extent_location.little }, string_hash(name.characters_without_null_termination(), name.length())) }; +} + +} diff --git a/Kernel/FileSystem/ISO9660FS/Inode.h b/Kernel/FileSystem/ISO9660FS/Inode.h new file mode 100644 index 0000000000..18d3898ec2 --- /dev/null +++ b/Kernel/FileSystem/ISO9660FS/Inode.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <Kernel/FileSystem/ISO9660FS/FileSystem.h> +#include <Kernel/FileSystem/Inode.h> + +namespace Kernel { + +class ISO9660Inode final : public Inode { + friend ISO9660FS; + +public: + virtual ~ISO9660Inode() override; + + ISO9660FS& fs() { return static_cast<ISO9660FS&>(Inode::fs()); } + ISO9660FS const& fs() const { return static_cast<ISO9660FS const&>(Inode::fs()); } + + // ^Inode + virtual InodeMetadata metadata() const override; + virtual ErrorOr<void> traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)>) const override; + virtual ErrorOr<NonnullLockRefPtr<Inode>> lookup(StringView name) override; + virtual ErrorOr<void> flush_metadata() override; + virtual ErrorOr<NonnullLockRefPtr<Inode>> create_child(StringView name, mode_t, dev_t, UserID, GroupID) override; + virtual ErrorOr<void> add_child(Inode&, StringView name, mode_t) override; + virtual ErrorOr<void> remove_child(StringView name) override; + virtual ErrorOr<void> chmod(mode_t) override; + virtual ErrorOr<void> chown(UserID, GroupID) override; + virtual ErrorOr<void> truncate(u64) override; + virtual ErrorOr<void> update_timestamps(Optional<time_t> atime, Optional<time_t> ctime, Optional<time_t> mtime) override; + +private: + // HACK: The base ISO 9660 standard says the maximum filename length is 37 + // bytes large; however, we can read filenames longer than that right now + // without any problems, so let's allow it anyway. + static constexpr size_t max_file_identifier_length = 256 - sizeof(ISO::DirectoryRecordHeader); + + // ^Inode + virtual ErrorOr<size_t> read_bytes_locked(off_t, size_t, UserOrKernelBuffer& buffer, OpenFileDescription*) const override; + virtual ErrorOr<size_t> write_bytes_locked(off_t, size_t, UserOrKernelBuffer const& buffer, OpenFileDescription*) override; + + ISO9660Inode(ISO9660FS&, ISO::DirectoryRecordHeader const& record, StringView name); + static ErrorOr<NonnullLockRefPtr<ISO9660Inode>> try_create_from_directory_record(ISO9660FS&, ISO::DirectoryRecordHeader const& record, StringView name); + + static InodeIndex get_inode_index(ISO::DirectoryRecordHeader const& record, StringView name); + static StringView get_normalized_filename(ISO::DirectoryRecordHeader const& record, Bytes buffer); + + void create_metadata(); + time_t parse_numerical_date_time(ISO::NumericalDateAndTime const&); + + InodeMetadata m_metadata; + ISO::DirectoryRecordHeader m_record; +}; + +} + +using Kernel::ISO::has_any_flag; +using Kernel::ISO::has_flag; diff --git a/Kernel/FileSystem/ISO9660FileSystem.cpp b/Kernel/FileSystem/ISO9660FileSystem.cpp deleted file mode 100644 index e9fd2bc448..0000000000 --- a/Kernel/FileSystem/ISO9660FileSystem.cpp +++ /dev/null @@ -1,655 +0,0 @@ -/* - * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "ISO9660FileSystem.h" -#include <AK/CharacterTypes.h> -#include <AK/Endian.h> -#include <AK/HashFunctions.h> -#include <AK/OwnPtr.h> -#include <AK/StringHash.h> -#include <AK/StringView.h> -#include <Kernel/Debug.h> -#include <Kernel/FileSystem/BlockBasedFileSystem.h> -#include <Kernel/Forward.h> -#include <Kernel/KBuffer.h> -#include <Kernel/Library/LockRefPtr.h> -#include <Kernel/Library/NonnullLockRefPtr.h> -#include <Kernel/UnixTypes.h> -#include <Kernel/UserOrKernelBuffer.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; - -struct DirectoryState { - LockRefPtr<ISO9660FS::DirectoryEntry> entry; - u32 offset { 0 }; -}; - -class ISO9660DirectoryIterator { -public: - ISO9660DirectoryIterator(ISO9660FS& fs, ISO::DirectoryRecordHeader const& header) - : m_fs(fs) - , m_current_header(&header) - { - // FIXME: Panic or alternative method? - (void)read_directory_contents(); - get_header(); - } - - ISO::DirectoryRecordHeader const* operator*() { return m_current_header; } - - // Recurses into subdirectories. May fail. - ErrorOr<bool> next() - { - if (done()) - return false; - dbgln_if(ISO9660_VERY_DEBUG, "next(): Called"); - - if (has_flag(m_current_header->file_flags, ISO::FileFlags::Directory)) { - dbgln_if(ISO9660_VERY_DEBUG, "next(): Recursing"); - { - TRY(m_directory_stack.try_append(move(m_current_directory))); - } - - dbgln_if(ISO9660_VERY_DEBUG, "next(): Pushed into directory stack"); - - TRY(read_directory_contents()); - - dbgln_if(ISO9660_VERY_DEBUG, "next(): Read directory contents"); - - m_current_directory.offset = 0; - get_header(); - if (m_current_header->length == 0) { - // We have found an empty directory, let's continue with the - // next one. - if (!go_up()) - return false; - } else { - // We cannot skip here, as this is the first record in this - // extent. - return true; - } - } - - return skip(); - } - - // Skips to the directory in the list, returns whether there was a next one. - // No allocation here, cannot fail. - bool skip() - { - VERIFY(m_current_directory.entry); - - if (m_current_directory.offset >= m_current_directory.entry->length) { - dbgln_if(ISO9660_VERY_DEBUG, "skip(): Was at last item already"); - return false; - } - - m_current_directory.offset += m_current_header->length; - get_header(); - if (m_current_header->length == 0) { - // According to ECMA 119, if a logical block contains directory - // records, then the leftover bytes in the logical block are - // all zeros. So if our directory header has a length of 0, - // we're probably looking at padding. - // - // Of course, this doesn't mean we're done; it only means that there - // are no more directory entries in *this* logical block. If we - // have at least one more logical block of data length to go, we - // need to snap to the next logical block, because directory records - // cannot span multiple logical blocks. - u32 remaining_bytes = m_current_directory.entry->length - m_current_directory.offset; - if (remaining_bytes > m_fs.logical_block_size()) { - m_current_directory.offset += remaining_bytes % m_fs.logical_block_size(); - get_header(); - - dbgln_if(ISO9660_VERY_DEBUG, "skip(): Snapped to next logical block (succeeded)"); - return true; - } - - dbgln_if(ISO9660_VERY_DEBUG, "skip(): Was at the last logical block, at padding now (offset {}, data length {})", m_current_directory.entry->length, m_current_directory.offset); - return false; - } - - dbgln_if(ISO9660_VERY_DEBUG, "skip(): Skipped to next item"); - return true; - } - - bool go_up() - { - if (m_directory_stack.is_empty()) { - dbgln_if(ISO9660_VERY_DEBUG, "go_up(): Empty directory stack"); - return false; - } - - m_current_directory = m_directory_stack.take_last(); - get_header(); - - dbgln_if(ISO9660_VERY_DEBUG, "go_up(): Went up a directory"); - return true; - } - - bool done() const - { - VERIFY(m_current_directory.entry); - auto result = m_directory_stack.is_empty() && m_current_directory.offset >= m_current_directory.entry->length; - dbgln_if(ISO9660_VERY_DEBUG, "done(): {}", result); - return result; - } - -private: - ErrorOr<void> read_directory_contents() - { - m_current_directory.entry = TRY(m_fs.directory_entry_for_record({}, m_current_header)); - return {}; - } - - void get_header() - { - VERIFY(m_current_directory.entry); - if (!m_current_directory.entry->blocks) - return; - - m_current_header = reinterpret_cast<ISO::DirectoryRecordHeader const*>(m_current_directory.entry->blocks->data() + m_current_directory.offset); - } - - ISO9660FS& m_fs; - - DirectoryState m_current_directory; - ISO::DirectoryRecordHeader const* m_current_header { nullptr }; - - Vector<DirectoryState> m_directory_stack; -}; - -ErrorOr<NonnullLockRefPtr<FileSystem>> 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<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<ISO9660FS::DirectoryEntry>> 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(DirectoryEntry::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 }; -} - -ErrorOr<size_t> ISO9660Inode::read_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer& buffer, OpenFileDescription*) const -{ - VERIFY(m_inode_lock.is_locked()); - - u32 data_length = LittleEndian { m_record.data_length.little }; - u32 extent_location = LittleEndian { m_record.extent_location.little }; - - if (static_cast<u64>(offset) >= data_length) - return 0; - - auto block = TRY(KBuffer::try_create_with_size("ISO9660FS: Inode read buffer"sv, fs().m_logical_block_size)); - auto block_buffer = UserOrKernelBuffer::for_kernel_buffer(block->data()); - - size_t total_bytes = min(size, data_length - offset); - size_t nread = 0; - size_t blocks_already_read = offset / fs().m_logical_block_size; - size_t initial_offset = offset % fs().m_logical_block_size; - - auto current_block_index = BlockBasedFileSystem::BlockIndex { extent_location + blocks_already_read }; - while (nread != total_bytes) { - size_t bytes_to_read = min(total_bytes - nread, fs().logical_block_size() - initial_offset); - auto buffer_offset = buffer.offset(nread); - dbgln_if(ISO9660_VERY_DEBUG, "ISO9660Inode::read_bytes: Reading {} bytes into buffer offset {}/{}, logical block index: {}", bytes_to_read, nread, total_bytes, current_block_index.value()); - - TRY(const_cast<ISO9660FS&>(fs()).raw_read(current_block_index, block_buffer)); - TRY(buffer_offset.write(block->data() + initial_offset, bytes_to_read)); - - nread += bytes_to_read; - initial_offset = 0; - current_block_index = BlockBasedFileSystem::BlockIndex { current_block_index.value() + 1 }; - } - - return nread; -} - -InodeMetadata ISO9660Inode::metadata() const -{ - return m_metadata; -} - -ErrorOr<void> ISO9660Inode::traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> visitor) const -{ - Array<u8, max_file_identifier_length> file_identifier_buffer; - ErrorOr<void> result; - - return fs().visit_directory_record(m_record, [&](ISO::DirectoryRecordHeader const* record) { - StringView filename = get_normalized_filename(*record, file_identifier_buffer); - dbgln_if(ISO9660_VERY_DEBUG, "traverse_as_directory(): Found {}", filename); - - InodeIdentifier id { fsid(), get_inode_index(*record, filename) }; - auto entry = FileSystem::DirectoryEntryView(filename, id, static_cast<u8>(record->file_flags)); - - result = visitor(entry); - if (result.is_error()) - return RecursionDecision::Break; - - return RecursionDecision::Continue; - }); -} - -ErrorOr<NonnullLockRefPtr<Inode>> ISO9660Inode::lookup(StringView name) -{ - LockRefPtr<Inode> inode; - Array<u8, max_file_identifier_length> file_identifier_buffer; - - TRY(fs().visit_directory_record(m_record, [&](ISO::DirectoryRecordHeader const* record) { - StringView filename = get_normalized_filename(*record, file_identifier_buffer); - - if (filename == name) { - auto maybe_inode = ISO9660Inode::try_create_from_directory_record(fs(), *record, filename); - if (maybe_inode.is_error()) { - // FIXME: The Inode API does not handle allocation failures very - // well... we can't return a ErrorOr from here. It - // would be nice if we could return a ErrorOr<void>(Or) from - // any place where allocation may happen. - dbgln("Could not allocate inode for lookup!"); - } else { - inode = maybe_inode.release_value(); - } - return RecursionDecision::Break; - } - - return RecursionDecision::Continue; - })); - - if (!inode) - return ENOENT; - return inode.release_nonnull(); -} - -ErrorOr<void> ISO9660Inode::flush_metadata() -{ - return {}; -} - -ErrorOr<size_t> ISO9660Inode::write_bytes_locked(off_t, size_t, UserOrKernelBuffer const&, OpenFileDescription*) -{ - return EROFS; -} - -ErrorOr<NonnullLockRefPtr<Inode>> ISO9660Inode::create_child(StringView, mode_t, dev_t, UserID, GroupID) -{ - return EROFS; -} - -ErrorOr<void> ISO9660Inode::add_child(Inode&, StringView, mode_t) -{ - return EROFS; -} - -ErrorOr<void> ISO9660Inode::remove_child(StringView) -{ - return EROFS; -} - -ErrorOr<void> ISO9660Inode::chmod(mode_t) -{ - return EROFS; -} - -ErrorOr<void> ISO9660Inode::chown(UserID, GroupID) -{ - return EROFS; -} - -ErrorOr<void> ISO9660Inode::truncate(u64) -{ - return EROFS; -} - -ErrorOr<void> ISO9660Inode::update_timestamps(Optional<time_t>, Optional<time_t>, Optional<time_t>) -{ - return EROFS; -} - -ISO9660Inode::ISO9660Inode(ISO9660FS& fs, ISO::DirectoryRecordHeader const& record, StringView name) - : Inode(fs, get_inode_index(record, name)) - , m_record(record) -{ - dbgln_if(ISO9660_VERY_DEBUG, "Creating inode #{}", index()); - create_metadata(); -} - -ISO9660Inode::~ISO9660Inode() = default; - -ErrorOr<NonnullLockRefPtr<ISO9660Inode>> ISO9660Inode::try_create_from_directory_record(ISO9660FS& fs, ISO::DirectoryRecordHeader const& record, StringView name) -{ - return adopt_nonnull_lock_ref_or_enomem(new (nothrow) ISO9660Inode(fs, record, name)); -} - -void ISO9660Inode::create_metadata() -{ - u32 data_length = LittleEndian { m_record.data_length.little }; - bool is_directory = has_flag(m_record.file_flags, ISO::FileFlags::Directory); - time_t recorded_at = parse_numerical_date_time(m_record.recording_date_and_time); - - m_metadata = { - .inode = identifier(), - .size = data_length, - .mode = static_cast<mode_t>((is_directory ? S_IFDIR : S_IFREG) | (is_directory ? 0555 : 0444)), - .uid = 0, - .gid = 0, - .link_count = 1, - .atime = recorded_at, - .ctime = recorded_at, - .mtime = recorded_at, - .dtime = 0, - .block_count = 0, - .block_size = 0, - .major_device = 0, - .minor_device = 0, - }; -} - -time_t ISO9660Inode::parse_numerical_date_time(ISO::NumericalDateAndTime const& date) -{ - i32 year_offset = date.years_since_1900 - 70; - - return (year_offset * 60 * 60 * 24 * 30 * 12) - + (date.month * 60 * 60 * 24 * 30) - + (date.day * 60 * 60 * 24) - + (date.hour * 60 * 60) - + (date.minute * 60) - + date.second; -} - -StringView ISO9660Inode::get_normalized_filename(ISO::DirectoryRecordHeader const& record, Bytes buffer) -{ - auto const* file_identifier = reinterpret_cast<u8 const*>(&record + 1); - auto filename = StringView { file_identifier, record.file_identifier_length }; - - if (filename.length() == 1) { - if (filename[0] == '\0') { - filename = "."sv; - } - - if (filename[0] == '\1') { - filename = ".."sv; - } - } - - if (!has_flag(record.file_flags, ISO::FileFlags::Directory)) { - // FIXME: We currently strip the file version from the filename, - // but that may be used later down the line if the file actually - // has multiple versions on the disk. - Optional<size_t> semicolon = filename.find(';'); - if (semicolon.has_value()) { - filename = filename.substring_view(0, semicolon.value()); - } - - if (filename[filename.length() - 1] == '.') { - filename = filename.substring_view(0, filename.length() - 1); - } - } - - if (filename.length() > buffer.size()) { - // FIXME: Rock Ridge allows filenames up to 255 characters, so we should - // probably support that instead of truncating. - filename = filename.substring_view(0, buffer.size()); - } - - for (size_t i = 0; i < filename.length(); i++) { - buffer[i] = to_ascii_lowercase(filename[i]); - } - - return { buffer.data(), filename.length() }; -} - -InodeIndex ISO9660Inode::get_inode_index(ISO::DirectoryRecordHeader const& record, StringView name) -{ - if (name.is_null()) { - // NOTE: This is the index of the root inode. - return 1; - } - - return { pair_int_hash(LittleEndian { record.extent_location.little }, string_hash(name.characters_without_null_termination(), name.length())) }; -} - -} diff --git a/Kernel/Syscalls/mount.cpp b/Kernel/Syscalls/mount.cpp index 24b1ecb509..4d1d9e503e 100644 --- a/Kernel/Syscalls/mount.cpp +++ b/Kernel/Syscalls/mount.cpp @@ -8,7 +8,7 @@ #include <Kernel/FileSystem/DevPtsFS/FileSystem.h> #include <Kernel/FileSystem/Ext2FileSystem.h> #include <Kernel/FileSystem/FATFS/FileSystem.h> -#include <Kernel/FileSystem/ISO9660FileSystem.h> +#include <Kernel/FileSystem/ISO9660FS/FileSystem.h> #include <Kernel/FileSystem/Plan9FS/FileSystem.h> #include <Kernel/FileSystem/ProcFS/FileSystem.h> #include <Kernel/FileSystem/SysFS/FileSystem.h> |