diff options
Diffstat (limited to 'Kernel/FileSystem/ISO9660FS')
-rw-r--r-- | Kernel/FileSystem/ISO9660FS/Definitions.h | 270 | ||||
-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 |
8 files changed, 1137 insertions, 0 deletions
diff --git a/Kernel/FileSystem/ISO9660FS/Definitions.h b/Kernel/FileSystem/ISO9660FS/Definitions.h new file mode 100644 index 0000000000..4b71e4b77a --- /dev/null +++ b/Kernel/FileSystem/ISO9660FS/Definitions.h @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/EnumBits.h> +#include <AK/Types.h> + +namespace Kernel { + +namespace ISO { + +// The implemented spec here is ECMA 119, available at: +// https://www.ecma-international.org/wp-content/uploads/ECMA-119_4th_edition_june_2019.pdf + +template<typename T> +struct [[gnu::packed]] LittleAndBigEndian { + T little; + T big; +}; + +// 8.4.26.1 Date and Time Format +struct [[gnu::packed]] AsciiDateAndTime { + // All of these fields are ASCII digits. :^) + u8 year[4]; + u8 month[2]; + u8 day[2]; + + u8 hour[2]; + u8 minute[2]; + u8 second[2]; + u8 hundredths_of_second[2]; + + // From OSDev wiki: + // Time zone offset from GMT in 15 minute intervals, starting at + // interval -48 (west) and running up to interval 52 (east). So value 0 + // indicates interval -48 which equals GMT-12 hours, and value 100 + // indicates interval 52 which equals GMT+13 hours. + u8 timezone_offset; +}; +static_assert(sizeof(AsciiDateAndTime) == 17); + +// 9.1.5 Recording Date and Time (BP 19 to 25) +struct [[gnu::packed]] NumericalDateAndTime { + u8 years_since_1900; + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; + // Same format as AsciiDateAndTime. + u8 timezone_offset; +}; +static_assert(sizeof(NumericalDateAndTime) == 7); + +// --- Path Table --- + +// 9.4 Format of a Path Table Record +struct [[gnu::packed]] PathTableRecord { + u8 directory_identifier_length; + u8 extended_attribute_record_length; + u32 extent_location; + u16 parent_directory_number; + + u8 directory_identifier[]; +}; +static_assert(sizeof(PathTableRecord) == 8); + +// --- Extended Attribute Record --- + +// 9.5.3 Permissions +enum class ExtendedPermissions : u16 { + SystemGroupReadable = 1 << 0, + SystemGroupExecutable = 1 << 2, + UserReadable = 1 << 4, + UserExecutable = 1 << 6, + GroupReadable = 1 << 8, + GroupExecutable = 1 << 10, + OtherReadable = 1 << 12, + OtherExecutable = 1 << 14, +}; +AK_ENUM_BITWISE_OPERATORS(ExtendedPermissions); + +// 9.5.8 Record Format +enum class RecordFormat : u8 { + NotSpecified = 0, + FixedLengthRecords = 1, + LittleEndianVariableRecords = 2, + BigEndianVariableRecords = 3, + // 4-127 are reserved for future standardization. + // 128-255 are reserved for system use. +}; + +// 9.5.9 Record Attributes +enum class RecordAttributes : u8 { + // This value means the record is stored like: \n123456\r. + LfCrDelimited = 0, + FortranVerticalSpacing = 1, + ContainsControlInformation = 2, + // 3-255 are reserved for future standardization. +}; + +// 9.5 Format of an Extended Attribute Record +struct [[gnu::packed]] ExtendedAttributeRecord { + LittleAndBigEndian<u16> owner_identification; + LittleAndBigEndian<u16> group_identification; + ExtendedPermissions permissions; + + AsciiDateAndTime file_creation_date_and_time; + AsciiDateAndTime file_modification_date_and_time; + AsciiDateAndTime file_expiration_date_and_time; + AsciiDateAndTime file_effective_date_and_time; + + RecordFormat record_format; + u8 record_attributes; + + LittleAndBigEndian<u16> record_length; + + u8 system_identifier[32]; + u8 system_use[64]; + + u8 extended_attribute_record_version; + u8 escape_sequence_length; + + u8 reserved[64]; + + LittleAndBigEndian<u16> application_use_length; + + // NOTE: Application use is immediately followed by escape sequences (no + // padding). + u8 application_use_and_escape_sequences[]; +}; +static_assert(sizeof(ExtendedAttributeRecord) == 250); + +// --- Files and Directories --- + +// 9.1.6 File Flags +enum class FileFlags : u8 { + Hidden = 1 << 0, // The "existence" flag + Directory = 1 << 1, + AssociatedFile = 1 << 2, + Record = 1 << 3, + Protection = 1 << 4, + // 5 and 6 are reserved. + MultiExtent = 1 << 7, +}; + +AK_ENUM_BITWISE_OPERATORS(FileFlags); + +struct [[gnu::packed]] DirectoryRecordHeader { + u8 length; + u8 extended_attribute_record_length; + LittleAndBigEndian<u32> extent_location; + LittleAndBigEndian<u32> data_length; + NumericalDateAndTime recording_date_and_time; + FileFlags file_flags; + u8 file_unit_size; + u8 interleave_gap_size; + LittleAndBigEndian<u16> volume_sequence_number; + u8 file_identifier_length; + + // NOTE: The file identifier itself is of variable length, so it and the + // fields following it are not included in this struct. Instead, they are: + // + // 34 to (33+file_identifier_length) - file identifier + // 1 byte of padding, if file_identifier_length is even + // + // The remaining bytes are system use (ISO9660 extensions). +}; +static_assert(sizeof(DirectoryRecordHeader) == 33); + +// --- Volume Descriptors --- + +enum class VolumeDescriptorType : u8 { + BootRecord = 0, + PrimaryVolumeDescriptor = 1, + SupplementaryOrEnhancedVolumeDescriptor = 2, + VolumePartitionDescriptor = 3, + // 4-254 are reserved. + VolumeDescriptorSetTerminator = 255, +}; + +// 8.1 Format of a Volume Descriptor +struct [[gnu::packed]] VolumeDescriptorHeader { + VolumeDescriptorType type; + // NOTE: Contains exactly "CD001". + u8 identifier[5]; + u8 version; +}; +static_assert(sizeof(VolumeDescriptorHeader) == 7); + +// 8.2 Boot Record +struct [[gnu::packed]] BootRecord { + VolumeDescriptorHeader header; + u8 boot_system_identifier[32]; + u8 boot_identifier[32]; + u8 boot_system_use[1977]; +}; +static_assert(sizeof(BootRecord) == 2048); + +// 8.3 Volume Descriptor Set Terminator +struct [[gnu::packed]] VolumeDescriptorSetTerminator { + VolumeDescriptorHeader header; + u8 zeros[2041]; +}; +static_assert(sizeof(VolumeDescriptorSetTerminator) == 2048); + +// 8.4 Primary Volume Descriptor +struct [[gnu::packed]] PrimaryVolumeDescriptor { + VolumeDescriptorHeader header; + u8 unused1; + u8 system_identifier[32]; + u8 volume_identifier[32]; + u64 unused2; + LittleAndBigEndian<u32> volume_space_size; + u8 unused3[32]; + LittleAndBigEndian<u16> volume_set_size; + LittleAndBigEndian<u16> volume_sequence_number; + LittleAndBigEndian<u16> logical_block_size; + LittleAndBigEndian<u32> path_table_size; + + u32 l_path_table_occurrence_location; + u32 l_path_table_optional_occurrence_location; + u32 m_path_table_occurrence_location; + u32 m_path_table_optional_occurrence_location; + + DirectoryRecordHeader root_directory_record_header; + u8 root_directory_identifier; // Exactly 0x00. + + u8 volume_set_identifier[128]; + u8 publisher_identifier[128]; + u8 data_preparer_identifier[128]; + u8 application_identifier[128]; + + u8 copyright_file_identifier[37]; + u8 abstract_file_identifier[37]; + u8 bibliographic_file_identifier[37]; + + AsciiDateAndTime volume_creation_date_and_time; + AsciiDateAndTime volume_modification_date_and_time; + AsciiDateAndTime volume_expiration_date_and_time; + AsciiDateAndTime volume_effective_date_and_time; + + u8 file_structure_version; // Always 0x01. + u8 unused4; + u8 application_use[512]; + u8 reserved[653]; +}; +static_assert(sizeof(PrimaryVolumeDescriptor) == 2048); + +// 8.6 Volume Partition Descriptor +struct [[gnu::packed]] VolumePartitionDescriptor { + VolumeDescriptorHeader header; + u8 unused; + + u8 system_identifier[32]; + u8 volume_partition_identifier[32]; + LittleAndBigEndian<u32> volume_partition_location; + LittleAndBigEndian<u32> volume_partition_size; + + u8 system_use[1960]; +}; +static_assert(sizeof(VolumePartitionDescriptor) == 2048); + +} + +} 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; |