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/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}