/* * Copyright (c) 2021, sin-ack * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include 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 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 owner_identification; LittleAndBigEndian 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 record_length; u8 system_identifier[32]; u8 system_use[64]; u8 extended_attribute_record_version; u8 escape_sequence_length; u8 reserved[64]; LittleAndBigEndian 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 extent_location; LittleAndBigEndian data_length; NumericalDateAndTime recording_date_and_time; FileFlags file_flags; u8 file_unit_size; u8 interleave_gap_size; LittleAndBigEndian 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 volume_space_size; u8 unused3[32]; LittleAndBigEndian volume_set_size; LittleAndBigEndian volume_sequence_number; LittleAndBigEndian logical_block_size; LittleAndBigEndian 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 volume_partition_location; LittleAndBigEndian volume_partition_size; u8 system_use[1960]; }; static_assert(sizeof(VolumePartitionDescriptor) == 2048); } class ISO9660Inode; class ISO9660DirectoryIterator; class ISO9660FS final : public BlockBasedFileSystem { friend ISO9660Inode; friend ISO9660DirectoryIterator; public: struct DirectoryEntry : public RefCounted { 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 blocks; static KResultOr> try_create(u32 extent, u32 length, OwnPtr blocks) { return adopt_nonnull_ref_or_enomem(new (nothrow) DirectoryEntry(extent, length, move(blocks))); } private: DirectoryEntry(u32 extent, u32 length, OwnPtr blocks) : extent(extent) , length(length) , blocks(move(blocks)) { } }; static KResultOr> try_create(OpenFileDescription&); virtual ~ISO9660FS() override; virtual KResult initialize() override; virtual StringView class_name() const override { return "ISO9660FS"; } 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; KResultOr> directory_entry_for_record(Badge, ISO::DirectoryRecordHeader const* record); private: ISO9660FS(OpenFileDescription&); KResult parse_volume_set(); KResult create_root_inode(); KResult calculate_inode_count() const; u32 calculate_directory_entry_cache_key(ISO::DirectoryRecordHeader const&); KResult visit_directory_record(ISO::DirectoryRecordHeader const& record, Function(ISO::DirectoryRecordHeader const*)> const& visitor) const; OwnPtr m_primary_volume; RefPtr m_root_inode; mutable u32 m_cached_inode_count { 0 }; HashMap> m_directory_entry_cache; }; class ISO9660Inode final : public Inode { friend ISO9660FS; public: virtual ~ISO9660Inode() override; ISO9660FS& fs() { return static_cast(Inode::fs()); } ISO9660FS const& fs() const { return static_cast(Inode::fs()); } // ^Inode virtual KResultOr read_bytes(off_t, size_t, UserOrKernelBuffer& buffer, OpenFileDescription*) const override; virtual InodeMetadata metadata() const override; virtual KResult traverse_as_directory(Function) const override; virtual KResultOr> lookup(StringView name) override; virtual void flush_metadata() override; virtual KResultOr write_bytes(off_t, size_t, const UserOrKernelBuffer& buffer, OpenFileDescription*) override; virtual KResultOr> create_child(StringView name, mode_t, dev_t, UserID, GroupID) override; virtual KResult add_child(Inode&, const StringView& name, mode_t) override; virtual KResult remove_child(const StringView& name) override; virtual KResult chmod(mode_t) override; virtual KResult chown(UserID, GroupID) override; virtual KResult truncate(u64) override; virtual KResult set_atime(time_t) override; virtual KResult set_ctime(time_t) override; virtual KResult set_mtime(time_t) override; virtual void one_ref_left() 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); ISO9660Inode(ISO9660FS&, ISO::DirectoryRecordHeader const& record, StringView const& name); static KResultOr> try_create_from_directory_record(ISO9660FS&, ISO::DirectoryRecordHeader const& record, StringView const& name); static InodeIndex get_inode_index(ISO::DirectoryRecordHeader const& record, StringView const& 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;