mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 15:32:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			265 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			265 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * 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 };
 | |
| }
 | |
| 
 | |
| }
 | 
