Placement new on arrays is a bit unreliable and could cause out-of-bounds data accesses.
199 lines
5.9 KiB
C++
199 lines
5.9 KiB
C++
#include "fs/ext2/Inode.h"
|
|
#include <luna/Buffer.h>
|
|
|
|
namespace Ext2
|
|
{
|
|
Inode::Inode(Badge<FileSystem>, FileSystem* fs) : m_fs(fs)
|
|
{
|
|
}
|
|
|
|
Result<usize> Inode::read(u8* buf, usize offset, usize length) const
|
|
{
|
|
if (length == 0) return 0;
|
|
|
|
if (offset > m_metadata.size) return 0;
|
|
if (offset + length > m_metadata.size) length = m_metadata.size - offset;
|
|
|
|
const usize block_size = m_fs->m_block_size;
|
|
|
|
usize to_read = length;
|
|
|
|
if (offset % block_size)
|
|
{
|
|
usize block_offset = (offset % block_size);
|
|
usize block = TRY(find_block(offset / block_size));
|
|
usize size_to_read = block_size - block_offset;
|
|
if (size_to_read > to_read) size_to_read = to_read;
|
|
|
|
usize host_offset = (block * block_size) + block_offset;
|
|
|
|
// FIXME: Cache this data.
|
|
TRY(m_fs->m_host_device->read(buf, host_offset, size_to_read));
|
|
|
|
to_read -= size_to_read;
|
|
buf += size_to_read;
|
|
offset += size_to_read;
|
|
}
|
|
|
|
while (to_read >= block_size)
|
|
{
|
|
usize block = TRY(find_block(offset / block_size));
|
|
|
|
usize host_offset = block * block_size;
|
|
|
|
// FIXME: Cache this data.
|
|
TRY(m_fs->m_host_device->read(buf, host_offset, block_size));
|
|
|
|
to_read -= block_size;
|
|
buf += block_size;
|
|
offset += block_size;
|
|
}
|
|
|
|
if (to_read > 0)
|
|
{
|
|
usize block = TRY(find_block(offset / block_size));
|
|
|
|
usize host_offset = block * block_size;
|
|
|
|
// FIXME: Cache this data.
|
|
TRY(m_fs->m_host_device->read(buf, host_offset, to_read));
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
Result<usize> Inode::find_block(usize index) const
|
|
{
|
|
if (index < 12) return m_raw_inode.direct_pointers[index];
|
|
|
|
usize block_index = (index - 12) * sizeof(u32);
|
|
if (block_index >= m_fs->m_block_size)
|
|
{
|
|
fail("ext2: Finding blocks beyond the singly indirect pointer block is not yet supported");
|
|
}
|
|
|
|
usize block_size = m_fs->m_block_size;
|
|
|
|
if (m_singly_indirect_block.is_empty())
|
|
{
|
|
TRY(m_singly_indirect_block.try_resize(block_size));
|
|
TRY(m_fs->m_host_device->read(m_singly_indirect_block.data(), m_raw_inode.singly_indirect_ptr * block_size,
|
|
block_size));
|
|
}
|
|
|
|
return *reinterpret_cast<u32*>(&m_singly_indirect_block.data()[block_index]);
|
|
}
|
|
|
|
Result<void> Inode::lazy_initialize_dir() const
|
|
{
|
|
check(m_type == VFS::InodeType::Directory);
|
|
|
|
const usize inode_size = m_metadata.size;
|
|
const usize block_size = m_fs->m_block_size;
|
|
|
|
u8* const buf = (u8*)TRY(calloc_impl(block_size, 1));
|
|
auto guard = make_scope_guard([buf] { free_impl(buf); });
|
|
|
|
m_entries.clear();
|
|
|
|
for (usize offset = 0; offset < inode_size; offset += block_size)
|
|
{
|
|
TRY(read(buf, offset, block_size));
|
|
|
|
usize dir_offset = 0;
|
|
while (dir_offset < block_size)
|
|
{
|
|
auto& entry = *(Ext2::RawDirectoryEntry*)&buf[dir_offset];
|
|
|
|
if (entry.inum != 0)
|
|
{
|
|
auto inode = TRY(m_fs->find_inode_by_number(entry.inum));
|
|
|
|
VFS::DirectoryEntry vfs_entry { inode, "" };
|
|
vfs_entry.name.adopt(entry.name,
|
|
m_fs->m_dirs_have_type_field ? entry.name_length_low : entry.name_length);
|
|
|
|
#ifdef EXT2_DEBUG
|
|
kdbgln("ext2: Read new directory entry: inum=%u, name=%s, namelen=%lu", entry.inum,
|
|
vfs_entry.name.chars(), vfs_entry.name.length());
|
|
#endif
|
|
|
|
TRY(m_entries.try_append(move(vfs_entry)));
|
|
}
|
|
|
|
dir_offset += entry.size;
|
|
}
|
|
}
|
|
|
|
m_dir_already_lazily_initialized = true;
|
|
|
|
return {};
|
|
}
|
|
|
|
Result<void> Inode::replace_entry(SharedPtr<VFS::Inode> inode, const char* name)
|
|
{
|
|
if (m_type != VFS::InodeType::Directory) return err(ENOTDIR);
|
|
if (!m_dir_already_lazily_initialized) TRY(lazy_initialize_dir());
|
|
|
|
for (auto& entry : m_entries)
|
|
{
|
|
if (!strcmp(name, entry.name.chars()))
|
|
{
|
|
entry.inode = inode;
|
|
return {};
|
|
}
|
|
}
|
|
|
|
return err(ENOENT);
|
|
}
|
|
|
|
Result<SharedPtr<VFS::Inode>> Inode::find(const char* name) const
|
|
{
|
|
if (m_type != VFS::InodeType::Directory) return err(ENOTDIR);
|
|
if (!m_dir_already_lazily_initialized) TRY(lazy_initialize_dir());
|
|
|
|
for (const auto& entry : m_entries)
|
|
{
|
|
if (!strcmp(name, entry.name.chars())) return entry.inode;
|
|
}
|
|
|
|
return err(ENOENT);
|
|
}
|
|
|
|
Option<VFS::DirectoryEntry> Inode::get(usize index) const
|
|
{
|
|
if (m_type != VFS::InodeType::Directory) return {};
|
|
if (!m_dir_already_lazily_initialized)
|
|
if (lazy_initialize_dir().has_error()) return {};
|
|
|
|
if (index >= m_entries.size()) return {};
|
|
|
|
return m_entries[index];
|
|
}
|
|
|
|
Result<StringView> Inode::readlink()
|
|
{
|
|
check(m_type == VFS::InodeType::Symlink);
|
|
|
|
if (!m_link.is_empty()) return m_link.view();
|
|
|
|
const usize length = m_metadata.size;
|
|
|
|
if (length < 60)
|
|
{
|
|
// The symlink location is stored inline within the inode's data blocks.
|
|
m_link = TRY(String::from_string_view(
|
|
StringView::from_fixed_size_cstring((char*)&m_raw_inode.direct_pointers[0], length)));
|
|
|
|
return m_link.view();
|
|
}
|
|
|
|
Buffer buf = TRY(Buffer::create_sized(length));
|
|
TRY(read(buf.data(), 0, length));
|
|
|
|
m_link = TRY(String::from_string_view(StringView::from_fixed_size_cstring((char*)buf.data(), length)));
|
|
|
|
return m_link.view();
|
|
}
|
|
}
|