Serenity Operating System
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}