ISO9660FileSystem.cpp 23 KB

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