summaryrefslogtreecommitdiff
path: root/Kernel
diff options
context:
space:
mode:
authorLiav A <liavalb@gmail.com>2022-10-24 11:02:50 +0300
committerAndrew Kaster <andrewdkaster@gmail.com>2022-11-08 02:54:48 -0700
commit1c91881a1d027fb4dd75bedaac933898f3c97329 (patch)
treef2ba622959d1db81ec936a48d66cc583bba68fbe /Kernel
parentfca3b7f1f94bd7a4c4f13605d20502af82366d2e (diff)
downloadserenity-1c91881a1d027fb4dd75bedaac933898f3c97329.zip
Kernel: Split the ISO9660FileSystem.{cpp,h} files to smaller components
Diffstat (limited to 'Kernel')
-rw-r--r--Kernel/CMakeLists.txt4
-rw-r--r--Kernel/FileSystem/ISO9660FS/Definitions.h (renamed from Kernel/FileSystem/ISO9660FileSystem.h)122
-rw-r--r--Kernel/FileSystem/ISO9660FS/DirectoryEntry.h41
-rw-r--r--Kernel/FileSystem/ISO9660FS/DirectoryIterator.cpp134
-rw-r--r--Kernel/FileSystem/ISO9660FS/DirectoryIterator.h46
-rw-r--r--Kernel/FileSystem/ISO9660FS/FileSystem.cpp265
-rw-r--r--Kernel/FileSystem/ISO9660FS/FileSystem.h68
-rw-r--r--Kernel/FileSystem/ISO9660FS/Inode.cpp251
-rw-r--r--Kernel/FileSystem/ISO9660FS/Inode.h62
-rw-r--r--Kernel/FileSystem/ISO9660FileSystem.cpp655
-rw-r--r--Kernel/Syscalls/mount.cpp2
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>