Serenity Operating System
at master 265 lines 8.8 kB view raw
1/* 2 * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Endian.h> 8#include <Kernel/FileSystem/ISO9660FS/DirectoryIterator.h> 9#include <Kernel/FileSystem/ISO9660FS/FileSystem.h> 10#include <Kernel/FileSystem/ISO9660FS/Inode.h> 11 12namespace Kernel { 13 14// NOTE: According to the spec, logical blocks 0 to 15 are system use. 15constexpr u32 first_data_area_block = 16; 16constexpr u32 logical_sector_size = 2048; 17constexpr u32 max_cached_directory_entries = 128; 18 19ErrorOr<NonnullLockRefPtr<FileSystem>> ISO9660FS::try_create(OpenFileDescription& description) 20{ 21 return TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) ISO9660FS(description))); 22} 23 24ISO9660FS::ISO9660FS(OpenFileDescription& description) 25 : BlockBasedFileSystem(description) 26{ 27 set_block_size(logical_sector_size); 28 m_logical_block_size = logical_sector_size; 29} 30 31ISO9660FS::~ISO9660FS() = default; 32 33bool ISO9660FS::is_initialized_while_locked() 34{ 35 VERIFY(m_lock.is_locked()); 36 return !m_root_inode.is_null(); 37} 38 39ErrorOr<void> ISO9660FS::initialize_while_locked() 40{ 41 VERIFY(m_lock.is_locked()); 42 VERIFY(!is_initialized_while_locked()); 43 44 TRY(BlockBasedFileSystem::initialize_while_locked()); 45 TRY(parse_volume_set()); 46 TRY(create_root_inode()); 47 return {}; 48} 49 50Inode& ISO9660FS::root_inode() 51{ 52 VERIFY(!m_root_inode.is_null()); 53 return *m_root_inode; 54} 55 56unsigned ISO9660FS::total_block_count() const 57{ 58 return LittleEndian { m_primary_volume->volume_space_size.little }; 59} 60 61unsigned ISO9660FS::total_inode_count() const 62{ 63 if (!m_cached_inode_count) { 64 auto result = calculate_inode_count(); 65 if (result.is_error()) { 66 // FIXME: This should be able to return a ErrorOr<void>. 67 return 0; 68 } 69 } 70 71 return m_cached_inode_count; 72} 73 74u8 ISO9660FS::internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const 75{ 76 if (has_flag(static_cast<ISO::FileFlags>(entry.file_type), ISO::FileFlags::Directory)) { 77 return DT_DIR; 78 } 79 80 return DT_REG; 81} 82 83ErrorOr<void> ISO9660FS::prepare_to_clear_last_mount() 84{ 85 // FIXME: Do proper cleaning here. 86 BlockBasedFileSystem::remove_disk_cache_before_last_unmount(); 87 return {}; 88} 89 90ErrorOr<void> ISO9660FS::parse_volume_set() 91{ 92 VERIFY(!m_primary_volume); 93 94 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)); 95 auto block_buffer = UserOrKernelBuffer::for_kernel_buffer(block->data()); 96 97 auto current_block_index = first_data_area_block; 98 while (true) { 99 auto result = raw_read(BlockIndex { current_block_index }, block_buffer); 100 if (result.is_error()) { 101 dbgln_if(ISO9660_DEBUG, "Failed to read volume descriptor from ISO file: {}", result.error()); 102 return result; 103 } 104 105 auto const* header = reinterpret_cast<ISO::VolumeDescriptorHeader const*>(block->data()); 106 if (StringView { header->identifier, 5 } != "CD001"sv) { 107 dbgln_if(ISO9660_DEBUG, "Header magic at volume descriptor {} is not valid", current_block_index - first_data_area_block); 108 return EIO; 109 } 110 111 switch (header->type) { 112 case ISO::VolumeDescriptorType::PrimaryVolumeDescriptor: { 113 auto const* primary_volume = reinterpret_cast<ISO::PrimaryVolumeDescriptor const*>(header); 114 m_primary_volume = adopt_own_if_nonnull(new ISO::PrimaryVolumeDescriptor(*primary_volume)); 115 break; 116 } 117 case ISO::VolumeDescriptorType::BootRecord: 118 case ISO::VolumeDescriptorType::SupplementaryOrEnhancedVolumeDescriptor: 119 case ISO::VolumeDescriptorType::VolumePartitionDescriptor: { 120 break; 121 } 122 case ISO::VolumeDescriptorType::VolumeDescriptorSetTerminator: { 123 goto all_headers_read; 124 } 125 default: 126 dbgln_if(ISO9660_DEBUG, "Unexpected volume descriptor type {} in volume set", static_cast<u8>(header->type)); 127 return EIO; 128 } 129 130 current_block_index++; 131 } 132 133all_headers_read: 134 if (!m_primary_volume) { 135 dbgln_if(ISO9660_DEBUG, "Could not find primary volume"); 136 return EIO; 137 } 138 139 m_logical_block_size = LittleEndian { m_primary_volume->logical_block_size.little }; 140 return {}; 141} 142 143ErrorOr<void> ISO9660FS::create_root_inode() 144{ 145 if (!m_primary_volume) { 146 dbgln_if(ISO9660_DEBUG, "Primary volume doesn't exist, can't create root inode"); 147 return EIO; 148 } 149 150 m_root_inode = TRY(ISO9660Inode::try_create_from_directory_record(*this, m_primary_volume->root_directory_record_header, {})); 151 return {}; 152} 153 154ErrorOr<void> ISO9660FS::calculate_inode_count() const 155{ 156 if (!m_primary_volume) { 157 dbgln_if(ISO9660_DEBUG, "Primary volume doesn't exist, can't calculate inode count"); 158 return EIO; 159 } 160 161 size_t inode_count = 1; 162 163 TRY(visit_directory_record(m_primary_volume->root_directory_record_header, [&](ISO::DirectoryRecordHeader const* header) { 164 if (header == nullptr) { 165 return RecursionDecision::Continue; 166 } 167 168 inode_count += 1; 169 170 if (has_flag(header->file_flags, ISO::FileFlags::Directory)) { 171 if (header->file_identifier_length == 1) { 172 auto file_identifier = reinterpret_cast<u8 const*>(header + 1); 173 if (file_identifier[0] == '\0' || file_identifier[0] == '\1') { 174 return RecursionDecision::Continue; 175 } 176 } 177 178 return RecursionDecision::Recurse; 179 } 180 181 return RecursionDecision::Continue; 182 })); 183 184 m_cached_inode_count = inode_count; 185 return {}; 186} 187 188ErrorOr<void> ISO9660FS::visit_directory_record(ISO::DirectoryRecordHeader const& record, Function<ErrorOr<RecursionDecision>(ISO::DirectoryRecordHeader const*)> const& visitor) const 189{ 190 if (!has_flag(record.file_flags, ISO::FileFlags::Directory)) { 191 return {}; 192 } 193 194 ISO9660DirectoryIterator iterator { const_cast<ISO9660FS&>(*this), record }; 195 196 while (!iterator.done()) { 197 auto decision = TRY(visitor(*iterator)); 198 switch (decision) { 199 case RecursionDecision::Recurse: { 200 auto has_moved = TRY(iterator.next()); 201 if (!has_moved) { 202 // If next() hasn't moved then we have read through all the 203 // directories, and can exit. 204 return {}; 205 } 206 207 continue; 208 } 209 case RecursionDecision::Continue: { 210 while (!iterator.done()) { 211 if (iterator.skip()) 212 break; 213 if (!iterator.go_up()) 214 return {}; 215 } 216 217 continue; 218 } 219 case RecursionDecision::Break: 220 return {}; 221 } 222 } 223 224 return {}; 225} 226 227ErrorOr<NonnullLockRefPtr<ISO9660FSDirectoryEntry>> ISO9660FS::directory_entry_for_record(Badge<ISO9660DirectoryIterator>, ISO::DirectoryRecordHeader const* record) 228{ 229 u32 extent_location = LittleEndian { record->extent_location.little }; 230 u32 data_length = LittleEndian { record->data_length.little }; 231 232 auto key = calculate_directory_entry_cache_key(*record); 233 auto it = m_directory_entry_cache.find(key); 234 if (it != m_directory_entry_cache.end()) { 235 dbgln_if(ISO9660_DEBUG, "Cache hit for dirent @ {}", extent_location); 236 return it->value; 237 } 238 dbgln_if(ISO9660_DEBUG, "Cache miss for dirent @ {} :^(", extent_location); 239 240 if (m_directory_entry_cache.size() == max_cached_directory_entries) { 241 // FIXME: A smarter algorithm would probably be nicer. 242 m_directory_entry_cache.remove(m_directory_entry_cache.begin()); 243 } 244 245 if (!(data_length % logical_block_size() == 0)) { 246 dbgln_if(ISO9660_DEBUG, "Found a directory with non-logical block size aligned data length!"); 247 return EIO; 248 } 249 250 auto blocks = TRY(KBuffer::try_create_with_size("ISO9660FS: Directory traversal buffer"sv, data_length, Memory::Region::Access::Read | Memory::Region::Access::Write)); 251 auto blocks_buffer = UserOrKernelBuffer::for_kernel_buffer(blocks->data()); 252 TRY(raw_read_blocks(BlockBasedFileSystem::BlockIndex { extent_location }, data_length / logical_block_size(), blocks_buffer)); 253 auto entry = TRY(ISO9660FSDirectoryEntry::try_create(extent_location, data_length, move(blocks))); 254 m_directory_entry_cache.set(key, entry); 255 256 dbgln_if(ISO9660_DEBUG, "Cached dirent @ {}", extent_location); 257 return entry; 258} 259 260u32 ISO9660FS::calculate_directory_entry_cache_key(ISO::DirectoryRecordHeader const& record) 261{ 262 return LittleEndian { record.extent_location.little }; 263} 264 265}