Work on POSIX-like read() and lseek() support.

This commit is contained in:
Andreas Kling 2018-10-14 21:19:27 +02:00
parent 9f9b4a2382
commit c94044a04a
Notes: sideshowbarker 2024-07-19 18:48:17 +09:00
11 changed files with 201 additions and 36 deletions

View file

@ -256,6 +256,82 @@ Vector<unsigned> Ext2FileSystem::blockListForInode(const ext2_inode& e2inode) co
return list;
}
ssize_t Ext2FileSystem::readInodeBytes(InodeIdentifier inode, FileOffset offset, size_t count, byte* buffer) const
{
ASSERT(offset >= 0);
ASSERT(inode.fileSystemID() == id());
auto e2inode = lookupExt2Inode(inode.index());
if (!e2inode) {
printf("[ext2fs] readInodeBytes: metadata lookup for inode %u failed\n", inode.index());
return -EIO;
}
#if 0
// FIXME: We can't fail here while the directory traversal depends on this function. :]
if (isDirectory(e2inode->i_mode))
return -EISDIR;
#endif
if (e2inode->i_size == 0)
return 0;
// Symbolic links shorter than 60 characters are store inline inside the i_block array.
// This avoids wasting an entire block on short links. (Most links are short.)
static const unsigned maxInlineSymlinkLength = 60;
if (isSymbolicLink(e2inode->i_mode) && e2inode->i_size < maxInlineSymlinkLength) {
ssize_t nread = min(e2inode->i_size - offset, static_cast<FileOffset>(count));
memcpy(buffer, e2inode->i_block + offset, nread);
return nread;
}
// FIXME: It's grossly inefficient to fetch the blocklist on every call to readInodeBytes().
// It needs to be cached!
auto list = blockListForInode(*e2inode);
if (list.isEmpty()) {
printf("[ext2fs] readInodeBytes: empty block list for inode %u\n", inode.index());
return -EIO;
}
dword firstBlockLogicalIndex = offset / blockSize();
dword lastBlockLogicalIndex = (offset + count) / blockSize();
if (lastBlockLogicalIndex >= list.size())
lastBlockLogicalIndex = list.size() - 1;
dword offsetIntoFirstBlock = offset % blockSize();
ssize_t nread = 0;
size_t remainingCount = min((FileOffset)count, e2inode->i_size - offset);
byte* out = buffer;
#ifdef EXT2_DEBUG
printf("ok let's do it, read(%llu, %u) -> blocks %u thru %u, oifb: %u\n", offset, count, firstBlockLogicalIndex, lastBlockLogicalIndex, offsetIntoFirstBlock);
#endif
for (dword bi = firstBlockLogicalIndex; bi <= lastBlockLogicalIndex; ++bi) {
auto block = readBlock(list[bi]);
if (!block) {
printf("[ext2fs] readInodeBytes: readBlock(%u) failed (lbi: %u)\n", list[bi], bi);
return -EIO;
}
dword offsetIntoBlock;
if (bi == firstBlockLogicalIndex)
offsetIntoBlock = offsetIntoFirstBlock;
else
offsetIntoBlock = 0;
dword numBytesToCopy = min(blockSize() - offsetIntoBlock, remainingCount);
memcpy(out, block.pointer() + offsetIntoBlock, numBytesToCopy);
remainingCount -= numBytesToCopy;
nread += numBytesToCopy;
out += numBytesToCopy;
}
return nread;
}
ByteBuffer Ext2FileSystem::readInode(InodeIdentifier inode) const
{
ASSERT(inode.fileSystemID() == id());
@ -266,41 +342,25 @@ ByteBuffer Ext2FileSystem::readInode(InodeIdentifier inode) const
return nullptr;
}
if (e2inode->i_size == 0)
return ByteBuffer::createEmpty();
auto contents = ByteBuffer::createUninitialized(e2inode->i_size);
// Symbolic links shorter than 60 characters are store inline inside the i_block array.
// This avoids wasting an entire block on short links. (Most links are short.)
static const unsigned maxInlineSymlinkLength = 60;
if (isSymbolicLink(e2inode->i_mode) && e2inode->i_size < maxInlineSymlinkLength) {
auto inlineSymlink = ByteBuffer::createUninitialized(e2inode->i_size);
memcpy(inlineSymlink.pointer(), e2inode->i_block, e2inode->i_size);
return inlineSymlink;
ssize_t nread;
byte buffer[512];
byte* out = contents.pointer();
FileOffset offset = 0;
for (;;) {
nread = readInodeBytes(inode, offset, sizeof(buffer), buffer);
if (nread <= 0)
break;
memcpy(out, buffer, nread);
out += nread;
offset += nread;
}
auto list = blockListForInode(*e2inode);
if (list.isEmpty()) {
printf("[ext2fs] readInode: empty block list for inode %u\n", inode.index());
if (nread < 0) {
printf("[ext2fs] readInode: ERROR: %d\n", nread);
return nullptr;
}
auto contents = ByteBuffer::createUninitialized(list.size() * blockSize());
auto* out = contents.pointer();
for (unsigned i = 0; i < list.size(); ++i) {
auto block = readBlock(list[i]);
if (!block) {
printf("[ext2fs] readInode: readBlock(%u) failed\n", list[i]);
return nullptr;
}
memcpy(out, block.pointer(), block.size());
out += blockSize();
}
#ifdef EXT2_DEBUG
printf("trim from %u to %u\n", contents.size(), e2inode->i_size);
#endif
contents.trim(e2inode->i_size);
return contents;
}

View file

@ -40,6 +40,7 @@ private:
virtual InodeMetadata inodeMetadata(InodeIdentifier) const override;
virtual bool setModificationTime(InodeIdentifier, dword timestamp) override;
virtual InodeIdentifier createInode(InodeIdentifier parentInode, const String& name, word mode) override;
virtual ssize_t readInodeBytes(InodeIdentifier, FileOffset offset, size_t count, byte* buffer) const override;
bool isDirectoryInode(unsigned) const;
unsigned allocateInode(unsigned preferredGroup, unsigned expectedSize);

View file

@ -11,7 +11,61 @@ FileHandle::~FileHandle()
{
}
ByteBuffer FileHandle::read()
bool additionWouldOverflow(FileOffset a, FileOffset b)
{
ASSERT(a > 0);
uint64_t ua = a;
return (ua + b) > maxFileOffset;
}
FileOffset FileHandle::lseek(FileOffset offset, SeekType seekType)
{
if (!m_vnode)
return -EBADF;
// FIXME: The file type should be cached on the vnode.
// It's silly that we have to do a full metadata lookup here.
auto metadata = m_vnode->inode.metadata();
if (metadata.isSocket() || metadata.isFIFO())
return -ESPIPE;
FileOffset newOffset;
switch (seekType) {
case SeekType::Absolute:
newOffset = offset;
break;
case SeekType::RelativeToCurrent:
newOffset = m_currentOffset + offset;
if (additionWouldOverflow(m_currentOffset, offset))
return -EOVERFLOW;
if (newOffset < 0)
return -EINVAL;
break;
case SeekType::RelativeToEnd:
// FIXME: Implement!
notImplemented();
break;
default:
return -EINVAL;
}
m_currentOffset = newOffset;
return m_currentOffset;
}
ssize_t FileHandle::read(byte* buffer, size_t count)
{
if (m_vnode->isCharacterDevice()) {
// FIXME: What should happen to m_currentOffset?
return m_vnode->characterDevice()->read(buffer, count);
}
ssize_t nread = m_vnode->fileSystem()->readInodeBytes(m_vnode->inode, m_currentOffset, count, buffer);
m_currentOffset += nread;
return nread;
}
ByteBuffer FileHandle::readEntireFile()
{
if (m_vnode->isCharacterDevice()) {
auto buffer = ByteBuffer::createUninitialized(1024);

View file

@ -3,16 +3,27 @@
#include "VirtualFileSystem.h"
#include <AK/ByteBuffer.h>
enum class SeekType {
Absolute, // SEEK_SET
RelativeToCurrent, // SEEK_CUR
RelativeToEnd, // SEEK_END
};
class FileHandle {
public:
explicit FileHandle(RetainPtr<VirtualFileSystem::Node>&&);
~FileHandle();
ByteBuffer read();
FileOffset lseek(FileOffset, SeekType);
ssize_t read(byte* buffer, size_t count);
ByteBuffer readEntireFile();
private:
friend class VirtualFileSystem;
RetainPtr<VirtualFileSystem::Node> m_vnode;
FileOffset m_currentOffset { 0 };
};

View file

@ -3,6 +3,7 @@
#include "BlockDevice.h"
#include "InodeIdentifier.h"
#include "InodeMetadata.h"
#include "Limits.h"
#include <AK/ByteBuffer.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
@ -27,6 +28,8 @@ public:
virtual bool writeInode(InodeIdentifier, const ByteBuffer&) = 0;
virtual InodeMetadata inodeMetadata(InodeIdentifier) const = 0;
virtual ssize_t readInodeBytes(InodeIdentifier, FileOffset offset, size_t count, byte* buffer) const = 0;
struct DirectoryEntry {
String name;
InodeIdentifier inode;

View file

@ -1,6 +1,12 @@
#pragma once
#include <limits>
typedef dword size_t;
typedef signed_dword ssize_t;
static const size_t GoodBufferSize = 4096;
typedef int64_t FileOffset;
inline static const FileOffset maxFileOffset = std::numeric_limits<FileOffset>::max();

View file

@ -26,7 +26,7 @@ VFS_OBJS = \
OBJS = $(AK_OBJS) $(VFS_OBJS)
CXXFLAGS = -std=c++17 -Os -W -Wall -Wextra -Wconversion -I. -I.. -ggdb3 -Wno-class-memaccess
CXXFLAGS = -std=c++17 -O0 -W -Wall -Wextra -Wconversion -I. -I.. -ggdb3 -Wno-class-memaccess
#test.o: BlockDevice.h FileBackedBlockDevice.h FileSystem.h Ext2FileSystem.h VirtualFileSystem.h FileHandle.h

View file

@ -121,3 +121,9 @@ bool SyntheticFileSystem::writeInode(InodeIdentifier, const ByteBuffer&)
printf("FIXME: Implement SyntheticFileSystem::writeInode().\n");
return false;
}
ssize_t SyntheticFileSystem::readInodeBytes(InodeIdentifier, FileOffset offset, size_t count, byte* buffer) const
{
printf("FIXME: Implement SyntheticFileSystem::readInodeBytes().\n");
return 0;
}

View file

@ -17,6 +17,7 @@ public:
virtual InodeMetadata inodeMetadata(InodeIdentifier) const override;
virtual bool setModificationTime(InodeIdentifier, dword timestamp) override;
virtual InodeIdentifier createInode(InodeIdentifier parentInode, const String& name, word mode) override;
virtual ssize_t readInodeBytes(InodeIdentifier, FileOffset offset, size_t count, byte* buffer) const override;
private:
SyntheticFileSystem();

View file

@ -6,6 +6,7 @@
#include <AK/String.h>
#include <AK/Vector.h>
#include "InodeIdentifier.h"
#include "Limits.h"
class CharacterDevice;
class FileHandle;

View file

@ -44,7 +44,7 @@ int main(int c, char** v)
printf("failed to open %s inside fs image\n", v[2]);
return 1;
}
auto contents = handle->read();
auto contents = handle->readEntireFile();
FILE* fout = fopen(v[3], "w");
if (!fout) {
@ -71,7 +71,7 @@ int main(int c, char** v)
printf("handle = %p\n", handle.ptr());
ASSERT(handle);
auto contents = handle->read();
auto contents = handle->readEntireFile();
ASSERT(contents);
printf("contents: '%s'\n", contents->pointer());
@ -141,11 +141,33 @@ int main(int c, char** v)
printf("failed to open %s\n", pathbuf);
continue;
}
auto contents = handle->read();
auto contents = handle->readEntireFile();
fwrite(contents.pointer(), sizeof(char), contents.size(), stdout);
continue;
}
if (cmd == "kat" && parts.size() > 1) {
char pathbuf[1024];
sprintf(pathbuf, "%s/%s", currentDirectory.characters(), parts[1].characters());
auto handle = vfs.open(pathbuf);
if (!handle) {
printf("failed to open %s\n", pathbuf);
continue;
}
ssize_t nread;
byte buffer[512];
for (;;) {
nread = handle->read(buffer, sizeof(buffer));
printf("read() returned %d\n", nread);
if (nread <= 0)
break;
fwrite(buffer, 1, nread, stdout);
}
if (nread < 0)
printf("ERROR: %d\n", nread);
continue;
}
if (cmd == "ma") {
SimpleMalloc::dump();
continue;