diff --git a/VirtualFileSystem/Ext2FileSystem.cpp b/VirtualFileSystem/Ext2FileSystem.cpp index 0c5e7c08aed..041590b005d 100644 --- a/VirtualFileSystem/Ext2FileSystem.cpp +++ b/VirtualFileSystem/Ext2FileSystem.cpp @@ -256,6 +256,82 @@ Vector 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(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; } diff --git a/VirtualFileSystem/Ext2FileSystem.h b/VirtualFileSystem/Ext2FileSystem.h index 97c7ae23ee3..92aa3ddbe4f 100644 --- a/VirtualFileSystem/Ext2FileSystem.h +++ b/VirtualFileSystem/Ext2FileSystem.h @@ -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); diff --git a/VirtualFileSystem/FileHandle.cpp b/VirtualFileSystem/FileHandle.cpp index 25f101f0392..3718bffbc06 100644 --- a/VirtualFileSystem/FileHandle.cpp +++ b/VirtualFileSystem/FileHandle.cpp @@ -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); diff --git a/VirtualFileSystem/FileHandle.h b/VirtualFileSystem/FileHandle.h index 66aa88508c6..3a202acbfe3 100644 --- a/VirtualFileSystem/FileHandle.h +++ b/VirtualFileSystem/FileHandle.h @@ -3,16 +3,27 @@ #include "VirtualFileSystem.h" #include +enum class SeekType { + Absolute, // SEEK_SET + RelativeToCurrent, // SEEK_CUR + RelativeToEnd, // SEEK_END +}; + class FileHandle { public: explicit FileHandle(RetainPtr&&); ~FileHandle(); - ByteBuffer read(); + FileOffset lseek(FileOffset, SeekType); + ssize_t read(byte* buffer, size_t count); + + ByteBuffer readEntireFile(); private: friend class VirtualFileSystem; RetainPtr m_vnode; + + FileOffset m_currentOffset { 0 }; }; diff --git a/VirtualFileSystem/FileSystem.h b/VirtualFileSystem/FileSystem.h index 9c9548d7882..99288b1eebc 100644 --- a/VirtualFileSystem/FileSystem.h +++ b/VirtualFileSystem/FileSystem.h @@ -3,6 +3,7 @@ #include "BlockDevice.h" #include "InodeIdentifier.h" #include "InodeMetadata.h" +#include "Limits.h" #include #include #include @@ -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; diff --git a/VirtualFileSystem/Limits.h b/VirtualFileSystem/Limits.h index ebe763e2fed..a9bbf862b56 100644 --- a/VirtualFileSystem/Limits.h +++ b/VirtualFileSystem/Limits.h @@ -1,6 +1,12 @@ #pragma once +#include + 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::max(); + diff --git a/VirtualFileSystem/Makefile b/VirtualFileSystem/Makefile index 7b3df94bb46..28994083b29 100644 --- a/VirtualFileSystem/Makefile +++ b/VirtualFileSystem/Makefile @@ -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 diff --git a/VirtualFileSystem/SyntheticFileSystem.cpp b/VirtualFileSystem/SyntheticFileSystem.cpp index 2d12eae04b1..723f58d96d3 100644 --- a/VirtualFileSystem/SyntheticFileSystem.cpp +++ b/VirtualFileSystem/SyntheticFileSystem.cpp @@ -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; +} diff --git a/VirtualFileSystem/SyntheticFileSystem.h b/VirtualFileSystem/SyntheticFileSystem.h index 76f7075de06..dda6c263309 100644 --- a/VirtualFileSystem/SyntheticFileSystem.h +++ b/VirtualFileSystem/SyntheticFileSystem.h @@ -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(); diff --git a/VirtualFileSystem/VirtualFileSystem.h b/VirtualFileSystem/VirtualFileSystem.h index 9993b91f526..2f968b7e38f 100644 --- a/VirtualFileSystem/VirtualFileSystem.h +++ b/VirtualFileSystem/VirtualFileSystem.h @@ -6,6 +6,7 @@ #include #include #include "InodeIdentifier.h" +#include "Limits.h" class CharacterDevice; class FileHandle; diff --git a/VirtualFileSystem/test.cpp b/VirtualFileSystem/test.cpp index c7183611eef..9bb4fa3338c 100644 --- a/VirtualFileSystem/test.cpp +++ b/VirtualFileSystem/test.cpp @@ -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;