Serenity Operating System
at master 256 lines 8.4 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/CharacterTypes.h> 8#include <AK/Endian.h> 9#include <Kernel/FileSystem/ISO9660FS/Inode.h> 10 11namespace Kernel { 12 13ErrorOr<size_t> ISO9660Inode::read_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer& buffer, OpenFileDescription*) const 14{ 15 VERIFY(m_inode_lock.is_locked()); 16 17 u32 data_length = LittleEndian { m_record.data_length.little }; 18 u32 extent_location = LittleEndian { m_record.extent_location.little }; 19 20 if (static_cast<u64>(offset) >= data_length) 21 return 0; 22 23 auto block = TRY(KBuffer::try_create_with_size("ISO9660FS: Inode read buffer"sv, fs().m_logical_block_size)); 24 auto block_buffer = UserOrKernelBuffer::for_kernel_buffer(block->data()); 25 26 size_t total_bytes = min(size, data_length - offset); 27 size_t nread = 0; 28 size_t blocks_already_read = offset / fs().m_logical_block_size; 29 size_t initial_offset = offset % fs().m_logical_block_size; 30 31 auto current_block_index = BlockBasedFileSystem::BlockIndex { extent_location + blocks_already_read }; 32 while (nread != total_bytes) { 33 size_t bytes_to_read = min(total_bytes - nread, fs().logical_block_size() - initial_offset); 34 auto buffer_offset = buffer.offset(nread); 35 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()); 36 37 TRY(const_cast<ISO9660FS&>(fs()).raw_read(current_block_index, block_buffer)); 38 TRY(buffer_offset.write(block->data() + initial_offset, bytes_to_read)); 39 40 nread += bytes_to_read; 41 initial_offset = 0; 42 current_block_index = BlockBasedFileSystem::BlockIndex { current_block_index.value() + 1 }; 43 } 44 45 return nread; 46} 47 48InodeMetadata ISO9660Inode::metadata() const 49{ 50 return m_metadata; 51} 52 53ErrorOr<void> ISO9660Inode::traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> visitor) const 54{ 55 Array<u8, max_file_identifier_length> file_identifier_buffer; 56 ErrorOr<void> result; 57 58 return fs().visit_directory_record(m_record, [&](ISO::DirectoryRecordHeader const* record) { 59 StringView filename = get_normalized_filename(*record, file_identifier_buffer); 60 dbgln_if(ISO9660_VERY_DEBUG, "traverse_as_directory(): Found {}", filename); 61 62 InodeIdentifier id { fsid(), get_inode_index(*record, filename) }; 63 auto entry = FileSystem::DirectoryEntryView(filename, id, static_cast<u8>(record->file_flags)); 64 65 result = visitor(entry); 66 if (result.is_error()) 67 return RecursionDecision::Break; 68 69 return RecursionDecision::Continue; 70 }); 71} 72 73ErrorOr<void> ISO9660Inode::replace_child(StringView, Inode&) 74{ 75 return EROFS; 76} 77 78ErrorOr<NonnullRefPtr<Inode>> ISO9660Inode::lookup(StringView name) 79{ 80 RefPtr<Inode> inode; 81 Array<u8, max_file_identifier_length> file_identifier_buffer; 82 83 TRY(fs().visit_directory_record(m_record, [&](ISO::DirectoryRecordHeader const* record) { 84 StringView filename = get_normalized_filename(*record, file_identifier_buffer); 85 86 if (filename == name) { 87 auto maybe_inode = ISO9660Inode::try_create_from_directory_record(fs(), *record, filename); 88 if (maybe_inode.is_error()) { 89 // FIXME: The Inode API does not handle allocation failures very 90 // well... we can't return a ErrorOr from here. It 91 // would be nice if we could return a ErrorOr<void>(Or) from 92 // any place where allocation may happen. 93 dbgln("Could not allocate inode for lookup!"); 94 } else { 95 inode = maybe_inode.release_value(); 96 } 97 return RecursionDecision::Break; 98 } 99 100 return RecursionDecision::Continue; 101 })); 102 103 if (!inode) 104 return ENOENT; 105 return inode.release_nonnull(); 106} 107 108ErrorOr<void> ISO9660Inode::flush_metadata() 109{ 110 return {}; 111} 112 113ErrorOr<size_t> ISO9660Inode::write_bytes_locked(off_t, size_t, UserOrKernelBuffer const&, OpenFileDescription*) 114{ 115 return EROFS; 116} 117 118ErrorOr<NonnullRefPtr<Inode>> ISO9660Inode::create_child(StringView, mode_t, dev_t, UserID, GroupID) 119{ 120 return EROFS; 121} 122 123ErrorOr<void> ISO9660Inode::add_child(Inode&, StringView, mode_t) 124{ 125 return EROFS; 126} 127 128ErrorOr<void> ISO9660Inode::remove_child(StringView) 129{ 130 return EROFS; 131} 132 133ErrorOr<void> ISO9660Inode::chmod(mode_t) 134{ 135 return EROFS; 136} 137 138ErrorOr<void> ISO9660Inode::chown(UserID, GroupID) 139{ 140 return EROFS; 141} 142 143ErrorOr<void> ISO9660Inode::truncate(u64) 144{ 145 return EROFS; 146} 147 148ErrorOr<void> ISO9660Inode::update_timestamps(Optional<Time>, Optional<Time>, Optional<Time>) 149{ 150 return EROFS; 151} 152 153ISO9660Inode::ISO9660Inode(ISO9660FS& fs, ISO::DirectoryRecordHeader const& record, StringView name) 154 : Inode(fs, get_inode_index(record, name)) 155 , m_record(record) 156{ 157 dbgln_if(ISO9660_VERY_DEBUG, "Creating inode #{}", index()); 158 create_metadata(); 159} 160 161ISO9660Inode::~ISO9660Inode() = default; 162 163ErrorOr<NonnullRefPtr<ISO9660Inode>> ISO9660Inode::try_create_from_directory_record(ISO9660FS& fs, ISO::DirectoryRecordHeader const& record, StringView name) 164{ 165 return adopt_nonnull_ref_or_enomem(new (nothrow) ISO9660Inode(fs, record, name)); 166} 167 168void ISO9660Inode::create_metadata() 169{ 170 u32 data_length = LittleEndian { m_record.data_length.little }; 171 bool is_directory = has_flag(m_record.file_flags, ISO::FileFlags::Directory); 172 auto recorded_at = Time::from_timespec({ parse_numerical_date_time(m_record.recording_date_and_time), 0 }); 173 174 m_metadata = { 175 .inode = identifier(), 176 .size = data_length, 177 .mode = static_cast<mode_t>((is_directory ? S_IFDIR : S_IFREG) | (is_directory ? 0555 : 0444)), 178 .uid = 0, 179 .gid = 0, 180 .link_count = 1, 181 .atime = recorded_at, 182 .ctime = recorded_at, 183 .mtime = recorded_at, 184 .dtime = {}, 185 .block_count = 0, 186 .block_size = 0, 187 .major_device = 0, 188 .minor_device = 0, 189 }; 190} 191 192time_t ISO9660Inode::parse_numerical_date_time(ISO::NumericalDateAndTime const& date) 193{ 194 i32 year_offset = date.years_since_1900 - 70; 195 196 return (year_offset * 60 * 60 * 24 * 30 * 12) 197 + (date.month * 60 * 60 * 24 * 30) 198 + (date.day * 60 * 60 * 24) 199 + (date.hour * 60 * 60) 200 + (date.minute * 60) 201 + date.second; 202} 203 204StringView ISO9660Inode::get_normalized_filename(ISO::DirectoryRecordHeader const& record, Bytes buffer) 205{ 206 auto const* file_identifier = reinterpret_cast<u8 const*>(&record + 1); 207 auto filename = StringView { file_identifier, record.file_identifier_length }; 208 209 if (filename.length() == 1) { 210 if (filename[0] == '\0') { 211 filename = "."sv; 212 } 213 214 if (filename[0] == '\1') { 215 filename = ".."sv; 216 } 217 } 218 219 if (!has_flag(record.file_flags, ISO::FileFlags::Directory)) { 220 // FIXME: We currently strip the file version from the filename, 221 // but that may be used later down the line if the file actually 222 // has multiple versions on the disk. 223 Optional<size_t> semicolon = filename.find(';'); 224 if (semicolon.has_value()) { 225 filename = filename.substring_view(0, semicolon.value()); 226 } 227 228 if (filename[filename.length() - 1] == '.') { 229 filename = filename.substring_view(0, filename.length() - 1); 230 } 231 } 232 233 if (filename.length() > buffer.size()) { 234 // FIXME: Rock Ridge allows filenames up to 255 characters, so we should 235 // probably support that instead of truncating. 236 filename = filename.substring_view(0, buffer.size()); 237 } 238 239 for (size_t i = 0; i < filename.length(); i++) { 240 buffer[i] = to_ascii_lowercase(filename[i]); 241 } 242 243 return { buffer.data(), filename.length() }; 244} 245 246InodeIndex ISO9660Inode::get_inode_index(ISO::DirectoryRecordHeader const& record, StringView name) 247{ 248 if (name.is_null()) { 249 // NOTE: This is the index of the root inode. 250 return 1; 251 } 252 253 return { pair_int_hash(LittleEndian { record.extent_location.little }, string_hash(name.characters_without_null_termination(), name.length())) }; 254} 255 256}