ISO9660FileSystem.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. /*
  2. * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "ISO9660FileSystem.h"
  7. #include <AK/CharacterTypes.h>
  8. #include <AK/Endian.h>
  9. #include <AK/HashFunctions.h>
  10. #include <AK/OwnPtr.h>
  11. #include <AK/StringHash.h>
  12. #include <AK/StringView.h>
  13. #include <Kernel/Debug.h>
  14. #include <Kernel/FileSystem/BlockBasedFileSystem.h>
  15. #include <Kernel/Forward.h>
  16. #include <Kernel/KBuffer.h>
  17. #include <Kernel/Library/LockRefPtr.h>
  18. #include <Kernel/Library/NonnullLockRefPtr.h>
  19. #include <Kernel/UnixTypes.h>
  20. #include <Kernel/UserOrKernelBuffer.h>
  21. namespace Kernel {
  22. // NOTE: According to the spec, logical blocks 0 to 15 are system use.
  23. constexpr u32 first_data_area_block = 16;
  24. constexpr u32 logical_sector_size = 2048;
  25. constexpr u32 max_cached_directory_entries = 128;
  26. struct DirectoryState {
  27. LockRefPtr<ISO9660FS::DirectoryEntry> entry;
  28. u32 offset { 0 };
  29. };
  30. class ISO9660DirectoryIterator {
  31. public:
  32. ISO9660DirectoryIterator(ISO9660FS& fs, ISO::DirectoryRecordHeader const& header)
  33. : m_fs(fs)
  34. , m_current_header(&header)
  35. {
  36. // FIXME: Panic or alternative method?
  37. (void)read_directory_contents();
  38. get_header();
  39. }
  40. ISO::DirectoryRecordHeader const* operator*() { return m_current_header; }
  41. // Recurses into subdirectories. May fail.
  42. ErrorOr<bool> next()
  43. {
  44. if (done())
  45. return false;
  46. dbgln_if(ISO9660_VERY_DEBUG, "next(): Called");
  47. if (has_flag(m_current_header->file_flags, ISO::FileFlags::Directory)) {
  48. dbgln_if(ISO9660_VERY_DEBUG, "next(): Recursing");
  49. {
  50. TRY(m_directory_stack.try_append(move(m_current_directory)));
  51. }
  52. dbgln_if(ISO9660_VERY_DEBUG, "next(): Pushed into directory stack");
  53. TRY(read_directory_contents());
  54. dbgln_if(ISO9660_VERY_DEBUG, "next(): Read directory contents");
  55. m_current_directory.offset = 0;
  56. get_header();
  57. if (m_current_header->length == 0) {
  58. // We have found an empty directory, let's continue with the
  59. // next one.
  60. if (!go_up())
  61. return false;
  62. } else {
  63. // We cannot skip here, as this is the first record in this
  64. // extent.
  65. return true;
  66. }
  67. }
  68. return skip();
  69. }
  70. // Skips to the directory in the list, returns whether there was a next one.
  71. // No allocation here, cannot fail.
  72. bool skip()
  73. {
  74. VERIFY(m_current_directory.entry);
  75. if (m_current_directory.offset >= m_current_directory.entry->length) {
  76. dbgln_if(ISO9660_VERY_DEBUG, "skip(): Was at last item already");
  77. return false;
  78. }
  79. m_current_directory.offset += m_current_header->length;
  80. get_header();
  81. if (m_current_header->length == 0) {
  82. // According to ECMA 119, if a logical block contains directory
  83. // records, then the leftover bytes in the logical block are
  84. // all zeros. So if our directory header has a length of 0,
  85. // we're probably looking at padding.
  86. //
  87. // Of course, this doesn't mean we're done; it only means that there
  88. // are no more directory entries in *this* logical block. If we
  89. // have at least one more logical block of data length to go, we
  90. // need to snap to the next logical block, because directory records
  91. // cannot span multiple logical blocks.
  92. u32 remaining_bytes = m_current_directory.entry->length - m_current_directory.offset;
  93. if (remaining_bytes > m_fs.logical_block_size()) {
  94. m_current_directory.offset += remaining_bytes % m_fs.logical_block_size();
  95. get_header();
  96. dbgln_if(ISO9660_VERY_DEBUG, "skip(): Snapped to next logical block (succeeded)");
  97. return true;
  98. }
  99. dbgln_if(ISO9660_VERY_DEBUG, "skip(): Was at the last logical block, at padding now (offset {}, data length {})", m_current_directory.entry->length, m_current_directory.offset);
  100. return false;
  101. }
  102. dbgln_if(ISO9660_VERY_DEBUG, "skip(): Skipped to next item");
  103. return true;
  104. }
  105. bool go_up()
  106. {
  107. if (m_directory_stack.is_empty()) {
  108. dbgln_if(ISO9660_VERY_DEBUG, "go_up(): Empty directory stack");
  109. return false;
  110. }
  111. m_current_directory = m_directory_stack.take_last();
  112. get_header();
  113. dbgln_if(ISO9660_VERY_DEBUG, "go_up(): Went up a directory");
  114. return true;
  115. }
  116. bool done() const
  117. {
  118. VERIFY(m_current_directory.entry);
  119. auto result = m_directory_stack.is_empty() && m_current_directory.offset >= m_current_directory.entry->length;
  120. dbgln_if(ISO9660_VERY_DEBUG, "done(): {}", result);
  121. return result;
  122. }
  123. private:
  124. ErrorOr<void> read_directory_contents()
  125. {
  126. m_current_directory.entry = TRY(m_fs.directory_entry_for_record({}, m_current_header));
  127. return {};
  128. }
  129. void get_header()
  130. {
  131. VERIFY(m_current_directory.entry);
  132. if (!m_current_directory.entry->blocks)
  133. return;
  134. m_current_header = reinterpret_cast<ISO::DirectoryRecordHeader const*>(m_current_directory.entry->blocks->data() + m_current_directory.offset);
  135. }
  136. ISO9660FS& m_fs;
  137. DirectoryState m_current_directory;
  138. ISO::DirectoryRecordHeader const* m_current_header { nullptr };
  139. Vector<DirectoryState> m_directory_stack;
  140. };
  141. ErrorOr<NonnullLockRefPtr<FileSystem>> ISO9660FS::try_create(OpenFileDescription& description)
  142. {
  143. return TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) ISO9660FS(description)));
  144. }
  145. ISO9660FS::ISO9660FS(OpenFileDescription& description)
  146. : BlockBasedFileSystem(description)
  147. {
  148. set_block_size(logical_sector_size);
  149. m_logical_block_size = logical_sector_size;
  150. }
  151. ISO9660FS::~ISO9660FS() = default;
  152. bool ISO9660FS::is_initialized_while_locked()
  153. {
  154. VERIFY(m_lock.is_locked());
  155. return !m_root_inode.is_null();
  156. }
  157. ErrorOr<void> ISO9660FS::initialize_while_locked()
  158. {
  159. VERIFY(m_lock.is_locked());
  160. VERIFY(!is_initialized_while_locked());
  161. TRY(BlockBasedFileSystem::initialize_while_locked());
  162. TRY(parse_volume_set());
  163. TRY(create_root_inode());
  164. return {};
  165. }
  166. Inode& ISO9660FS::root_inode()
  167. {
  168. VERIFY(!m_root_inode.is_null());
  169. return *m_root_inode;
  170. }
  171. unsigned ISO9660FS::total_block_count() const
  172. {
  173. return LittleEndian { m_primary_volume->volume_space_size.little };
  174. }
  175. unsigned ISO9660FS::total_inode_count() const
  176. {
  177. if (!m_cached_inode_count) {
  178. auto result = calculate_inode_count();
  179. if (result.is_error()) {
  180. // FIXME: This should be able to return a ErrorOr<void>.
  181. return 0;
  182. }
  183. }
  184. return m_cached_inode_count;
  185. }
  186. u8 ISO9660FS::internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const
  187. {
  188. if (has_flag(static_cast<ISO::FileFlags>(entry.file_type), ISO::FileFlags::Directory)) {
  189. return DT_DIR;
  190. }
  191. return DT_REG;
  192. }
  193. ErrorOr<void> ISO9660FS::prepare_to_clear_last_mount()
  194. {
  195. // FIXME: Do proper cleaning here.
  196. BlockBasedFileSystem::remove_disk_cache_before_last_unmount();
  197. return {};
  198. }
  199. ErrorOr<void> ISO9660FS::parse_volume_set()
  200. {
  201. VERIFY(!m_primary_volume);
  202. 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));
  203. auto block_buffer = UserOrKernelBuffer::for_kernel_buffer(block->data());
  204. auto current_block_index = first_data_area_block;
  205. while (true) {
  206. auto result = raw_read(BlockIndex { current_block_index }, block_buffer);
  207. if (result.is_error()) {
  208. dbgln_if(ISO9660_DEBUG, "Failed to read volume descriptor from ISO file: {}", result.error());
  209. return result;
  210. }
  211. auto const* header = reinterpret_cast<ISO::VolumeDescriptorHeader const*>(block->data());
  212. if (StringView { header->identifier, 5 } != "CD001"sv) {
  213. dbgln_if(ISO9660_DEBUG, "Header magic at volume descriptor {} is not valid", current_block_index - first_data_area_block);
  214. return EIO;
  215. }
  216. switch (header->type) {
  217. case ISO::VolumeDescriptorType::PrimaryVolumeDescriptor: {
  218. auto const* primary_volume = reinterpret_cast<ISO::PrimaryVolumeDescriptor const*>(header);
  219. m_primary_volume = adopt_own_if_nonnull(new ISO::PrimaryVolumeDescriptor(*primary_volume));
  220. break;
  221. }
  222. case ISO::VolumeDescriptorType::BootRecord:
  223. case ISO::VolumeDescriptorType::SupplementaryOrEnhancedVolumeDescriptor:
  224. case ISO::VolumeDescriptorType::VolumePartitionDescriptor: {
  225. break;
  226. }
  227. case ISO::VolumeDescriptorType::VolumeDescriptorSetTerminator: {
  228. goto all_headers_read;
  229. }
  230. default:
  231. dbgln_if(ISO9660_DEBUG, "Unexpected volume descriptor type {} in volume set", static_cast<u8>(header->type));
  232. return EIO;
  233. }
  234. current_block_index++;
  235. }
  236. all_headers_read:
  237. if (!m_primary_volume) {
  238. dbgln_if(ISO9660_DEBUG, "Could not find primary volume");
  239. return EIO;
  240. }
  241. m_logical_block_size = LittleEndian { m_primary_volume->logical_block_size.little };
  242. return {};
  243. }
  244. ErrorOr<void> ISO9660FS::create_root_inode()
  245. {
  246. if (!m_primary_volume) {
  247. dbgln_if(ISO9660_DEBUG, "Primary volume doesn't exist, can't create root inode");
  248. return EIO;
  249. }
  250. m_root_inode = TRY(ISO9660Inode::try_create_from_directory_record(*this, m_primary_volume->root_directory_record_header, {}));
  251. return {};
  252. }
  253. ErrorOr<void> ISO9660FS::calculate_inode_count() const
  254. {
  255. if (!m_primary_volume) {
  256. dbgln_if(ISO9660_DEBUG, "Primary volume doesn't exist, can't calculate inode count");
  257. return EIO;
  258. }
  259. size_t inode_count = 1;
  260. TRY(visit_directory_record(m_primary_volume->root_directory_record_header, [&](ISO::DirectoryRecordHeader const* header) {
  261. if (header == nullptr) {
  262. return RecursionDecision::Continue;
  263. }
  264. inode_count += 1;
  265. if (has_flag(header->file_flags, ISO::FileFlags::Directory)) {
  266. if (header->file_identifier_length == 1) {
  267. auto file_identifier = reinterpret_cast<u8 const*>(header + 1);
  268. if (file_identifier[0] == '\0' || file_identifier[0] == '\1') {
  269. return RecursionDecision::Continue;
  270. }
  271. }
  272. return RecursionDecision::Recurse;
  273. }
  274. return RecursionDecision::Continue;
  275. }));
  276. m_cached_inode_count = inode_count;
  277. return {};
  278. }
  279. ErrorOr<void> ISO9660FS::visit_directory_record(ISO::DirectoryRecordHeader const& record, Function<ErrorOr<RecursionDecision>(ISO::DirectoryRecordHeader const*)> const& visitor) const
  280. {
  281. if (!has_flag(record.file_flags, ISO::FileFlags::Directory)) {
  282. return {};
  283. }
  284. ISO9660DirectoryIterator iterator { const_cast<ISO9660FS&>(*this), record };
  285. while (!iterator.done()) {
  286. auto decision = TRY(visitor(*iterator));
  287. switch (decision) {
  288. case RecursionDecision::Recurse: {
  289. auto has_moved = TRY(iterator.next());
  290. if (!has_moved) {
  291. // If next() hasn't moved then we have read through all the
  292. // directories, and can exit.
  293. return {};
  294. }
  295. continue;
  296. }
  297. case RecursionDecision::Continue: {
  298. while (!iterator.done()) {
  299. if (iterator.skip())
  300. break;
  301. if (!iterator.go_up())
  302. return {};
  303. }
  304. continue;
  305. }
  306. case RecursionDecision::Break:
  307. return {};
  308. }
  309. }
  310. return {};
  311. }
  312. ErrorOr<NonnullLockRefPtr<ISO9660FS::DirectoryEntry>> ISO9660FS::directory_entry_for_record(Badge<ISO9660DirectoryIterator>, ISO::DirectoryRecordHeader const* record)
  313. {
  314. u32 extent_location = LittleEndian { record->extent_location.little };
  315. u32 data_length = LittleEndian { record->data_length.little };
  316. auto key = calculate_directory_entry_cache_key(*record);
  317. auto it = m_directory_entry_cache.find(key);
  318. if (it != m_directory_entry_cache.end()) {
  319. dbgln_if(ISO9660_DEBUG, "Cache hit for dirent @ {}", extent_location);
  320. return it->value;
  321. }
  322. dbgln_if(ISO9660_DEBUG, "Cache miss for dirent @ {} :^(", extent_location);
  323. if (m_directory_entry_cache.size() == max_cached_directory_entries) {
  324. // FIXME: A smarter algorithm would probably be nicer.
  325. m_directory_entry_cache.remove(m_directory_entry_cache.begin());
  326. }
  327. if (!(data_length % logical_block_size() == 0)) {
  328. dbgln_if(ISO9660_DEBUG, "Found a directory with non-logical block size aligned data length!");
  329. return EIO;
  330. }
  331. auto blocks = TRY(KBuffer::try_create_with_size("ISO9660FS: Directory traversal buffer"sv, data_length, Memory::Region::Access::Read | Memory::Region::Access::Write));
  332. auto blocks_buffer = UserOrKernelBuffer::for_kernel_buffer(blocks->data());
  333. TRY(raw_read_blocks(BlockBasedFileSystem::BlockIndex { extent_location }, data_length / logical_block_size(), blocks_buffer));
  334. auto entry = TRY(DirectoryEntry::try_create(extent_location, data_length, move(blocks)));
  335. m_directory_entry_cache.set(key, entry);
  336. dbgln_if(ISO9660_DEBUG, "Cached dirent @ {}", extent_location);
  337. return entry;
  338. }
  339. u32 ISO9660FS::calculate_directory_entry_cache_key(ISO::DirectoryRecordHeader const& record)
  340. {
  341. return LittleEndian { record.extent_location.little };
  342. }
  343. ErrorOr<size_t> ISO9660Inode::read_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer& buffer, OpenFileDescription*) const
  344. {
  345. VERIFY(m_inode_lock.is_locked());
  346. u32 data_length = LittleEndian { m_record.data_length.little };
  347. u32 extent_location = LittleEndian { m_record.extent_location.little };
  348. if (static_cast<u64>(offset) >= data_length)
  349. return 0;
  350. auto block = TRY(KBuffer::try_create_with_size("ISO9660FS: Inode read buffer"sv, fs().m_logical_block_size));
  351. auto block_buffer = UserOrKernelBuffer::for_kernel_buffer(block->data());
  352. size_t total_bytes = min(size, data_length - offset);
  353. size_t nread = 0;
  354. size_t blocks_already_read = offset / fs().m_logical_block_size;
  355. size_t initial_offset = offset % fs().m_logical_block_size;
  356. auto current_block_index = BlockBasedFileSystem::BlockIndex { extent_location + blocks_already_read };
  357. while (nread != total_bytes) {
  358. size_t bytes_to_read = min(total_bytes - nread, fs().logical_block_size() - initial_offset);
  359. auto buffer_offset = buffer.offset(nread);
  360. 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());
  361. TRY(const_cast<ISO9660FS&>(fs()).raw_read(current_block_index, block_buffer));
  362. TRY(buffer_offset.write(block->data() + initial_offset, bytes_to_read));
  363. nread += bytes_to_read;
  364. initial_offset = 0;
  365. current_block_index = BlockBasedFileSystem::BlockIndex { current_block_index.value() + 1 };
  366. }
  367. return nread;
  368. }
  369. InodeMetadata ISO9660Inode::metadata() const
  370. {
  371. return m_metadata;
  372. }
  373. ErrorOr<void> ISO9660Inode::traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> visitor) const
  374. {
  375. Array<u8, max_file_identifier_length> file_identifier_buffer;
  376. ErrorOr<void> result;
  377. return fs().visit_directory_record(m_record, [&](ISO::DirectoryRecordHeader const* record) {
  378. StringView filename = get_normalized_filename(*record, file_identifier_buffer);
  379. dbgln_if(ISO9660_VERY_DEBUG, "traverse_as_directory(): Found {}", filename);
  380. InodeIdentifier id { fsid(), get_inode_index(*record, filename) };
  381. auto entry = FileSystem::DirectoryEntryView(filename, id, static_cast<u8>(record->file_flags));
  382. result = visitor(entry);
  383. if (result.is_error())
  384. return RecursionDecision::Break;
  385. return RecursionDecision::Continue;
  386. });
  387. }
  388. ErrorOr<NonnullLockRefPtr<Inode>> ISO9660Inode::lookup(StringView name)
  389. {
  390. LockRefPtr<Inode> inode;
  391. Array<u8, max_file_identifier_length> file_identifier_buffer;
  392. TRY(fs().visit_directory_record(m_record, [&](ISO::DirectoryRecordHeader const* record) {
  393. StringView filename = get_normalized_filename(*record, file_identifier_buffer);
  394. if (filename == name) {
  395. auto maybe_inode = ISO9660Inode::try_create_from_directory_record(fs(), *record, filename);
  396. if (maybe_inode.is_error()) {
  397. // FIXME: The Inode API does not handle allocation failures very
  398. // well... we can't return a ErrorOr from here. It
  399. // would be nice if we could return a ErrorOr<void>(Or) from
  400. // any place where allocation may happen.
  401. dbgln("Could not allocate inode for lookup!");
  402. } else {
  403. inode = maybe_inode.release_value();
  404. }
  405. return RecursionDecision::Break;
  406. }
  407. return RecursionDecision::Continue;
  408. }));
  409. if (!inode)
  410. return ENOENT;
  411. return inode.release_nonnull();
  412. }
  413. ErrorOr<void> ISO9660Inode::flush_metadata()
  414. {
  415. return {};
  416. }
  417. ErrorOr<size_t> ISO9660Inode::write_bytes_locked(off_t, size_t, UserOrKernelBuffer const&, OpenFileDescription*)
  418. {
  419. return EROFS;
  420. }
  421. ErrorOr<NonnullLockRefPtr<Inode>> ISO9660Inode::create_child(StringView, mode_t, dev_t, UserID, GroupID)
  422. {
  423. return EROFS;
  424. }
  425. ErrorOr<void> ISO9660Inode::add_child(Inode&, StringView, mode_t)
  426. {
  427. return EROFS;
  428. }
  429. ErrorOr<void> ISO9660Inode::remove_child(StringView)
  430. {
  431. return EROFS;
  432. }
  433. ErrorOr<void> ISO9660Inode::chmod(mode_t)
  434. {
  435. return EROFS;
  436. }
  437. ErrorOr<void> ISO9660Inode::chown(UserID, GroupID)
  438. {
  439. return EROFS;
  440. }
  441. ErrorOr<void> ISO9660Inode::truncate(u64)
  442. {
  443. return EROFS;
  444. }
  445. ErrorOr<void> ISO9660Inode::update_timestamps(Optional<time_t>, Optional<time_t>, Optional<time_t>)
  446. {
  447. return EROFS;
  448. }
  449. ISO9660Inode::ISO9660Inode(ISO9660FS& fs, ISO::DirectoryRecordHeader const& record, StringView name)
  450. : Inode(fs, get_inode_index(record, name))
  451. , m_record(record)
  452. {
  453. dbgln_if(ISO9660_VERY_DEBUG, "Creating inode #{}", index());
  454. create_metadata();
  455. }
  456. ISO9660Inode::~ISO9660Inode() = default;
  457. ErrorOr<NonnullLockRefPtr<ISO9660Inode>> ISO9660Inode::try_create_from_directory_record(ISO9660FS& fs, ISO::DirectoryRecordHeader const& record, StringView name)
  458. {
  459. return adopt_nonnull_lock_ref_or_enomem(new (nothrow) ISO9660Inode(fs, record, name));
  460. }
  461. void ISO9660Inode::create_metadata()
  462. {
  463. u32 data_length = LittleEndian { m_record.data_length.little };
  464. bool is_directory = has_flag(m_record.file_flags, ISO::FileFlags::Directory);
  465. time_t recorded_at = parse_numerical_date_time(m_record.recording_date_and_time);
  466. m_metadata = {
  467. .inode = identifier(),
  468. .size = data_length,
  469. .mode = static_cast<mode_t>((is_directory ? S_IFDIR : S_IFREG) | (is_directory ? 0555 : 0444)),
  470. .uid = 0,
  471. .gid = 0,
  472. .link_count = 1,
  473. .atime = recorded_at,
  474. .ctime = recorded_at,
  475. .mtime = recorded_at,
  476. .dtime = 0,
  477. .block_count = 0,
  478. .block_size = 0,
  479. .major_device = 0,
  480. .minor_device = 0,
  481. };
  482. }
  483. time_t ISO9660Inode::parse_numerical_date_time(ISO::NumericalDateAndTime const& date)
  484. {
  485. i32 year_offset = date.years_since_1900 - 70;
  486. return (year_offset * 60 * 60 * 24 * 30 * 12)
  487. + (date.month * 60 * 60 * 24 * 30)
  488. + (date.day * 60 * 60 * 24)
  489. + (date.hour * 60 * 60)
  490. + (date.minute * 60)
  491. + date.second;
  492. }
  493. StringView ISO9660Inode::get_normalized_filename(ISO::DirectoryRecordHeader const& record, Bytes buffer)
  494. {
  495. auto const* file_identifier = reinterpret_cast<u8 const*>(&record + 1);
  496. auto filename = StringView { file_identifier, record.file_identifier_length };
  497. if (filename.length() == 1) {
  498. if (filename[0] == '\0') {
  499. filename = "."sv;
  500. }
  501. if (filename[0] == '\1') {
  502. filename = ".."sv;
  503. }
  504. }
  505. if (!has_flag(record.file_flags, ISO::FileFlags::Directory)) {
  506. // FIXME: We currently strip the file version from the filename,
  507. // but that may be used later down the line if the file actually
  508. // has multiple versions on the disk.
  509. Optional<size_t> semicolon = filename.find(';');
  510. if (semicolon.has_value()) {
  511. filename = filename.substring_view(0, semicolon.value());
  512. }
  513. if (filename[filename.length() - 1] == '.') {
  514. filename = filename.substring_view(0, filename.length() - 1);
  515. }
  516. }
  517. if (filename.length() > buffer.size()) {
  518. // FIXME: Rock Ridge allows filenames up to 255 characters, so we should
  519. // probably support that instead of truncating.
  520. filename = filename.substring_view(0, buffer.size());
  521. }
  522. for (size_t i = 0; i < filename.length(); i++) {
  523. buffer[i] = to_ascii_lowercase(filename[i]);
  524. }
  525. return { buffer.data(), filename.length() };
  526. }
  527. InodeIndex ISO9660Inode::get_inode_index(ISO::DirectoryRecordHeader const& record, StringView name)
  528. {
  529. if (name.is_null()) {
  530. // NOTE: This is the index of the root inode.
  531. return 1;
  532. }
  533. return { pair_int_hash(LittleEndian { record.extent_location.little }, string_hash(name.characters_without_null_termination(), name.length())) };
  534. }
  535. }