/* * Copyright 2015 - 2025 gary@drinkingtea.net * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #pragma once #include OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage) namespace ox { using InodeId_t = uint64_t; using FsSize_t = std::size_t; struct StatInfo { InodeId_t inode = 0; InodeId_t links = 0; FsSize_t size = 0; uint8_t fileType = 0; }; template struct OX_PACKED FileStoreItem: public ptrarith::Item { LittleEndian id = 0; LittleEndian fileType = 0; LittleEndian links = 0; LittleEndian left = 0; LittleEndian right = 0; FileStoreItem() = default; explicit FileStoreItem(size_t size) { this->setSize(size); } /** * @return the size of the data + the size of the Item type */ [[nodiscard]] size_t fullSize() const { return sizeof(*this) + this->size(); } ptrarith::Ptr data() { return ptrarith::Ptr(this, this->fullSize(), sizeof(*this), this->size()); } }; template class FileStoreTemplate { public: using InodeId_t = size_t; private: using Item = FileStoreItem; using ItemPtr = typename ptrarith::NodeBuffer>::ItemPtr; using Buffer = ptrarith::NodeBuffer>; static constexpr InodeId_t ReservedInodeEnd = 100; struct OX_PACKED FileStoreData { LittleEndian rootNode = 0; Random random; }; size_t m_buffSize = 0; mutable Buffer *m_buffer = nullptr; public: FileStoreTemplate() = default; FileStoreTemplate(void *buff, std::size_t buffSize); static Error format(void *buffer, std::size_t bufferSize); Error setSize(std::size_t buffSize); Error incLinks(uint64_t id); Error decLinks(uint64_t id); Error write(uint64_t id64, const void *data, FsSize_t dataSize, uint8_t fileType = 0); Error remove(uint64_t id); Error read(uint64_t id, void *out, FsSize_t outSize, FsSize_t *size = nullptr) const; Error read(uint64_t id, FsSize_t readStart, FsSize_t readSize, void *data, FsSize_t *size = nullptr) const; ptrarith::Ptr read(uint64_t id) const; /** * Reads the "file" at the given id. You are responsible for freeing * the data when done with it. * @param id id of the "file" * @param readStart where in the data to start reading * @param readSize how much data to read * @param data pointer to the pointer where the data is stored * @param size pointer to a value that will be assigned the size of data * @return 0 if read is a success */ template Error read(uint64_t id, FsSize_t readStart, FsSize_t readSize, T *data, FsSize_t *size) const; Result stat(uint64_t id) const; Error resize(); Error resize(std::size_t size, void *newBuff = nullptr); [[nodiscard]] InodeId_t spaceNeeded(FsSize_t size) const; [[nodiscard]] InodeId_t size() const; [[nodiscard]] InodeId_t available() const; [[nodiscard]] char *buff(); Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)); Result generateInodeId(); bool valid() const; Error compact(); private: FileStoreData *fileStoreData() const; /** * Places the given Item at the given ID. If it already exists, the * existing value will be overwritten. */ Error placeItem(ItemPtr item); /** * Places the given Item at the given ID. If it already exists, the * existing value will be overwritten. */ Error placeItem(ItemPtr root, ItemPtr item, int depth = 0); /** * Removes the given Item at the given ID. If it already exists, the * existing value will be overwritten. */ Error unplaceItem(ItemPtr item); /** * Removes the given Item at the given ID. If it already exists, the * existing value will be overwritten. */ Error unplaceItem(ItemPtr root, ItemPtr item, int depth = 0); Error remove(ItemPtr item); /** * Finds the parent an inode by its ID. */ ItemPtr findParent(ItemPtr item, size_t id, size_t oldAddr) const; /** * Finds an inode by its ID. */ ItemPtr find(ItemPtr item, InodeId_t id, int depth = 0) const; /** * Finds an inode by its ID. */ ItemPtr find(InodeId_t id) const; /** * Gets the root inode. */ ItemPtr rootInode(); bool canWrite(ItemPtr existing, std::size_t size); }; template FileStoreTemplate::FileStoreTemplate(void *buff, std::size_t buffSize) { m_buffSize = static_cast(buffSize); m_buffer = reinterpret_cast>*>(buff); if (!m_buffer->valid(m_buffSize)) { m_buffSize = 0; m_buffer = nullptr; } } template Error FileStoreTemplate::format(void *buffer, std::size_t bufferSize) { auto nb = new (buffer) Buffer(static_cast(bufferSize)); auto fsData = nb->malloc(sizeof(FileStoreData)).value; if (!fsData.valid()) { oxTrace("ox.fs.FileStoreTemplate.format.fail", "Could not read data section of FileStoreData"); return ox::Error(1, "Could not read data section of FileStoreData"); } auto data = nb->template dataOf(fsData); if (!data.valid()) { oxTrace("ox.fs.FileStoreTemplate.format.fail", "Could not read data section of FileStoreData"); return ox::Error(1, "Could not read data section of FileStoreData"); } new (data) FileStoreData; return {}; } template Error FileStoreTemplate::setSize(std::size_t size) { if (m_buffSize >= size) { return m_buffer->setSize(static_cast(size)); } return ox::Error(1); } template Error FileStoreTemplate::incLinks(uint64_t id) { OX_REQUIRE_M(item, find(static_cast(id)).validate()); ++item->links; return ox::Error(0); } template Error FileStoreTemplate::decLinks(uint64_t id) { OX_REQUIRE_M(item, find(static_cast(id)).validate()); --item->links; if (item->links == 0) { OX_RETURN_ERROR(remove(item)); } return ox::Error(0); } template Error FileStoreTemplate::write(uint64_t id64, const void *data, FsSize_t dataSize, uint8_t fileType) { const auto id = static_cast(id64); oxTracef("ox.fs.FileStoreTemplate.write", "Attempting to write to inode {}", id); auto existing = find(id); if (!canWrite(existing, dataSize)) { OX_RETURN_ERROR(compact()); existing = find(id); } if (canWrite(existing, dataSize)) { // delete the old node if it exists if (existing.valid()) { oxTracef("ox.fs.FileStoreTemplate.write", "Freeing old version of inode found at offset: {}", existing.offset()); auto err = m_buffer->free(existing); if (err) { oxTrace("ox.fs.FileStoreTemplate.write.fail", "Free of old version of inode failed"); return err; } existing = nullptr; } // write the given data auto dest = m_buffer->malloc(dataSize).value; // if first malloc failed, compact and try again if (!dest.valid()) { oxTrace("ox.fs.FileStoreTemplate.write", "Allocation failed, compacting"); OX_RETURN_ERROR(compact()); dest = m_buffer->malloc(dataSize).value; } if (dest.valid()) { oxTrace("ox.fs.FileStoreTemplate.write", "Memory allocated"); dest->id = id; dest->fileType = fileType; auto destData = m_buffer->template dataOf(dest); if (destData.valid()) { oxAssert(destData.size() == dataSize, "Allocation size does not match data."); // write data if any was provided if (data != nullptr) { ox::memcpy(destData, data, dest->size()); oxTrace("ox.fs.FileStoreTemplate.write", "Data written"); } auto fsData = fileStoreData(); if (fsData) { oxTracef("ox.fs.FileStoreTemplate.write", "Searching for root node at {}", fsData->rootNode.get()); auto root = m_buffer->ptr(fsData->rootNode); if (root.valid()) { oxTracef("ox.fs.FileStoreTemplate.write", "Placing {} on {} at {}", dest->id.get(), root->id.get(), destData.offset()); return placeItem(dest); } else { oxTracef("ox.fs.FileStoreTemplate.write", "Initializing root inode: {} (offset: {}, data size: {})", dest->id.get(), dest.offset(), destData.size()); fsData->rootNode = dest.offset(); oxTracef("ox.fs.FileStoreTemplate.write", "Root inode: {}", dest->id.get()); return ox::Error(0); } } else { oxTrace("ox.fs.FileStoreTemplate.write.fail", "Could not place item due to absence of FileStore header."); } } } OX_RETURN_ERROR(m_buffer->free(dest)); } return ox::Error(1); } template Error FileStoreTemplate::remove(uint64_t id) { return remove(find(static_cast(id))); } template Error FileStoreTemplate::read(uint64_t id, void *out, FsSize_t outSize, FsSize_t *size) const { oxTracef("ox.fs.FileStoreTemplate.read", "Attempting to read inode {}", id); auto src = find(static_cast(id)); // error check if (!src.valid()) { oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not find requested item: {}", id); return ox::Error(1, "Could not find requested item"); } auto srcData = m_buffer->template dataOf(src); oxTracef("ox.fs.FileStoreTemplate.read.found", "{} found at {} with data section at {}", id, src.offset(), srcData.offset()); oxTracef("ox.fs.FileStoreTemplate.read.outSize", "{} {} {}", srcData.offset(), srcData.size(), outSize); // error check if (!(srcData.valid() && srcData.size() <= outSize)) { oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not read data section of item: {}", id); oxTracef("ox.fs.FileStoreTemplate.read.fail", "Item data section size: {}, Expected size: {}", srcData.size(), outSize); return ox::Error(1, "Invalid inode"); } ox::memcpy(out, srcData, srcData.size()); if (size) { *size = src.size(); } return {}; } template Error FileStoreTemplate::read(uint64_t id, FsSize_t readStart, FsSize_t readSize, void *out, FsSize_t *size) const { oxTracef("ox.fs.FileStoreTemplate.read", "Attempting to read from inode {}", id); auto src = find(static_cast(id)); // error check if (!src.valid()) { oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not find requested item: {}", id); return ox::Error(1); } auto srcData = m_buffer->template dataOf(src); oxTracef("ox.fs.FileStoreTemplate.read.found", "{} found at {} with data section at {}", id, src.offset(), srcData.offset()); oxTracef("ox.fs.FileStoreTemplate.read.readSize", "{} {} {}", srcData.offset(), srcData.size(), readSize); // error check if (!(srcData.valid() && srcData.size() - readStart <= readSize)) { oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not read data section of item: {}", id); oxTracef("ox.fs.FileStoreTemplate.read.fail", "Item data section size: {}, Expected size: {}", srcData.size(), readSize); return ox::Error(1); } ox::memcpy(out, srcData.get() + readStart, readSize); if (size) { *size = src.size(); } return {}; } template template Error FileStoreTemplate::read(uint64_t id, FsSize_t readStart, FsSize_t readSize, T *out, FsSize_t *size) const { oxTracef("ox.fs.FileStoreTemplate.read", "Attempting to read from inode {}", id); auto src = find(static_cast(id)); // error check if (!src.valid()) { oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not find requested item: {}", id); return ox::Error(1); } auto srcData = m_buffer->template dataOf(src); oxTracef("ox.fs.FileStoreTemplate.read.found", "{} found at {} with data section at {}", id, src.offset(), srcData.offset()); oxTracef("ox.fs.FileStoreTemplate.read.readSize", "{} {} {}", srcData.offset(), srcData.size(), readSize); // error check if (!(srcData.valid() && srcData.size() - readStart <= readSize)) { oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not read data section of item: {}", id); oxTracef("ox.fs.FileStoreTemplate.read.fail", "Item data section size: {}, Expected size: {}", srcData.size(), readSize); return ox::Error(1); } ox::memcpy(out, srcData.get() + readStart, readSize); if (size) { *size = src.size(); } return {}; } template ptrarith::Ptr FileStoreTemplate::read(uint64_t id) const { auto item = find(static_cast(id)); if (item.valid()) { return item->data(); } else { return nullptr; } } template Error FileStoreTemplate::resize() { OX_RETURN_ERROR(compact()); const auto newSize = static_cast(size() - available()); oxTracef("ox.fs.FileStoreTemplate.resize", "resize to: {}", newSize); OX_RETURN_ERROR(m_buffer->setSize(newSize)); oxTracef("ox.fs.FileStoreTemplate.resize", "resized to: {}", m_buffer->size()); return ox::Error(0); } template Error FileStoreTemplate::resize(std::size_t size, void *newBuff) { if (m_buffer->size() > size) { return ox::Error(1); } m_buffSize = static_cast(size); if (newBuff) { m_buffer = reinterpret_cast(newBuff); OX_RETURN_ERROR(m_buffer->setSize(static_cast(size))); } return ox::Error(0); } template Result FileStoreTemplate::stat(uint64_t id) const { OX_REQUIRE(inode, find(static_cast(id)).validate()); return StatInfo { id, inode->links, inode->size(), inode->fileType, }; } template typename FileStoreTemplate::InodeId_t FileStoreTemplate::spaceNeeded(FsSize_t size) const { return m_buffer->spaceNeeded(size); } template typename FileStoreTemplate::InodeId_t FileStoreTemplate::size() const { return m_buffer->size(); } template typename FileStoreTemplate::InodeId_t FileStoreTemplate::available() const { return m_buffer->available(); } template char *FileStoreTemplate::buff() { return reinterpret_cast(m_buffer); } template Error FileStoreTemplate::walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) { for (auto i = m_buffer->iterator(); i.valid(); i.next()) { OX_RETURN_ERROR(cb(i->fileType, i.ptr().offset(), i.ptr().end())); } return ox::Error(0); } template Result::InodeId_t> FileStoreTemplate::generateInodeId() { auto fsData = fileStoreData(); if (!fsData) { return ox::Error(1); } for (auto i = 0; i < 100; i++) { auto inode = static_cast::InodeId_t>(fsData->random.gen() % MaxValue); if (inode > ReservedInodeEnd && !find(static_cast(inode)).valid()) { return inode; } } return ox::Error(2); } template Error FileStoreTemplate::compact() { auto isFirstItem = true; return m_buffer->compact([this, &isFirstItem](uint64_t oldAddr, ItemPtr item) -> Error { if (isFirstItem) { isFirstItem = false; return ox::Error(0); } if (!item.valid()) { return ox::Error(1); } oxTracef("ox.fs.FileStoreTemplate.compact.moveItem", "Moving Item: {} from {} to {}", item->id.get(), oldAddr, item.offset()); // update rootInode if this is it auto fsData = fileStoreData(); if (fsData && oldAddr == fsData->rootNode) { fsData->rootNode = item.offset(); } auto parent = findParent(rootInode(), item->id, static_cast(oldAddr)); oxAssert(parent.valid() || rootInode() == item.offset(), "Parent inode not found for item that should have parent."); if (parent.valid()) { if (parent->left == oldAddr) { parent->left = item; } else if (parent->right == oldAddr) { parent->right = item; } } return ox::Error(0); }); } template typename FileStoreTemplate::FileStoreData *FileStoreTemplate::fileStoreData() const { auto first = m_buffer->firstItem(); if (first.valid()) { auto data = first->data(); if (data.valid()) { return reinterpret_cast(data.get()); } } return nullptr; } template Error FileStoreTemplate::placeItem(ItemPtr item) { auto fsData = fileStoreData(); if (!fsData) { return ox::Error(1); } OX_REQUIRE_M(root, m_buffer->ptr(fsData->rootNode).validate()); if (root->id == item->id) { fsData->rootNode = item; item->left = root->left; item->right = root->right; oxTracef("ox.fs.FileStoreTemplate.placeItem", "Overwrote Root Item: {}", item->id.get()); return ox::Error(0); } else { return placeItem(root, item); } } template Error FileStoreTemplate::placeItem(ItemPtr root, ItemPtr item, int depth) { if (depth > 5000) { oxTrace("ox.fs.FileStoreTemplate.placeItem.fail", "Excessive recursion depth, stopping before stack overflow."); return ox::Error(2); } if (item->id > root->id) { auto right = m_buffer->ptr(root->right); if (!right.valid() || right->id == item->id) { root->right = item.offset(); if (right.valid()) { item->left = right->left; item->right = right->right; } oxTracef("ox.fs.FileStoreTemplate.placeItem", "Placed Item: {}", item->id.get()); return ox::Error(0); } else { return placeItem(right, item, depth + 1); } } else if (item->id < root->id) { auto left = m_buffer->ptr(root->left); if (!left.valid() || left->id == item->id) { root->left = item.offset(); if (left.valid()) { item->left = left->left; item->right = left->right; } oxTracef("ox.fs.FileStoreTemplate.placeItem", "Placed Item: {}", item->id.get()); return ox::Error(0); } else { return placeItem(left, item, depth + 1); } } else { oxTrace("ox.fs.FileStoreTemplate.placeItem.fail", "Cannot insert an item on itself."); return ox::Error(1, "Cannot insert an item on itself."); } } template Error FileStoreTemplate::unplaceItem(ItemPtr item) { auto fsData = fileStoreData(); if (!fsData) { return ox::Error(1); } OX_REQUIRE_M(root, m_buffer->ptr(fsData->rootNode).validate()); if (root->id == item->id) { item->left = root->left; item->right = root->right; auto left = m_buffer->ptr(item->left); auto right = m_buffer->ptr(item->right); if (right.valid()) { auto oldRoot = fsData->rootNode; fsData->rootNode = item->right; if (left.valid()) { auto err = placeItem(left); // undo if unable to place the left side of the tree if (err) { fsData->rootNode = oldRoot; return err; } } } else if (left.valid()) { fsData->rootNode = item->left; } else { fsData->rootNode = 0; } return ox::Error(0); } else { return unplaceItem(root, item); } } template Error FileStoreTemplate::unplaceItem(ItemPtr root, ItemPtr item, int depth) { if (depth >= 5000) { oxTrace("ox.fs.FileStoreTemplate.unplaceItem.fail", "Excessive recursion depth, stopping before stack overflow."); return ox::Error(1, "Excessive recursion depth, stopping before stack overflow."); } if (item->id > root->id) { auto right = m_buffer->ptr(root->right); if (right->id == item->id) { root->right = 0; oxTracef("ox.fs.FileStoreTemplate.unplaceItem", "Unplaced Item: {}", item->id.get()); } else { return unplaceItem(right, item, depth + 1); } } else if (item->id < root->id) { auto left = m_buffer->ptr(root->left); if (left->id == item->id) { root->left = 0; oxTracef("ox.fs.FileStoreTemplate.unplaceItem", "Unplaced Item: {}", item->id.get()); } else { return unplaceItem(left, item, depth + 1); } } else { return ox::Error(1); } if (item->right) { OX_RETURN_ERROR(placeItem(m_buffer->ptr(item->right))); } if (item->left) { OX_RETURN_ERROR(placeItem(m_buffer->ptr(item->left))); } return ox::Error(0); } template Error FileStoreTemplate::remove(ItemPtr item) { if (item.valid()) { OX_RETURN_ERROR(unplaceItem(item)); OX_RETURN_ERROR(m_buffer->free(item)); return ox::Error(0); } return ox::Error(1); } template typename FileStoreTemplate::ItemPtr FileStoreTemplate::findParent(ItemPtr item, size_t id, size_t oldAddr) const { // This is a little bit confusing. findParent uses the inode ID to find // where the target ID should be, but the actual address of that item is // currently invalid, so we check it against what is known to be the old // address of the item to confirm that we have the right item. if (item.valid()) { if (id > item->id) { if (item->right == oldAddr) { return item; } else { auto right = m_buffer->ptr(item->right); return findParent(right, id, oldAddr); } } else if (id < item->id) { if (item->left == oldAddr) { return item; } else { auto left = m_buffer->ptr(item->left); return findParent(left, id, oldAddr); } } } return nullptr; } template typename FileStoreTemplate::ItemPtr FileStoreTemplate::find(ItemPtr item, InodeId_t id, int depth) const { if (depth > 5000) { oxTracef("ox.fs.FileStoreTemplate.find.fail", "Excessive recursion depth, stopping before stack overflow. Search for: {}", id); return nullptr; } if (!item.valid()) { oxTrace("ox.fs.FileStoreTemplate.find.fail", "item invalid"); return nullptr; } // do search if (id > item->id) { oxTracef("ox.fs.FileStoreTemplate.find", "Not a match, searching on {}", item->right.get()); return find(m_buffer->ptr(item->right), id, depth + 1); } else if (id < item->id) { oxTracef("ox.fs.FileStoreTemplate.find", "Not a match, searching on {}", item->left.get()); return find(m_buffer->ptr(item->left), id, depth + 1); } else if (id == item->id) { oxTracef("ox.fs.FileStoreTemplate.find", "Found {} at {}", id, item.offset()); return item; } return nullptr; } template typename FileStoreTemplate::ItemPtr FileStoreTemplate::find(InodeId_t id) const { oxTracef("ox.fs.FileStoreTemplate.find", "Searching for inode: {}", id); auto fsData = fileStoreData(); if (fsData) { auto root = m_buffer->ptr(fsData->rootNode); if (root.valid()) { auto item = find(root, id); return item; } else { oxTrace("ox.fs.FileStoreTemplate.find.fail", "No root node"); } } else { oxTrace("ox.fs.FileStoreTemplate.find.fail", "No FileStore Data"); } return nullptr; } /** * Gets the root inode. */ template typename FileStoreTemplate::ItemPtr FileStoreTemplate::rootInode() { auto fsData = fileStoreData(); if (fsData) { return m_buffer->ptr(fsData->rootNode); } else { return nullptr; } } template bool FileStoreTemplate::canWrite(ItemPtr existing, std::size_t size) { const auto sz = static_cast(size); return existing.size() >= sz || m_buffer->spaceNeeded(sz) <= m_buffer->available(); } template bool FileStoreTemplate::valid() const { return m_buffer; } extern template class FileStoreTemplate; extern template class FileStoreTemplate; using FileStore16 = FileStoreTemplate; using FileStore32 = FileStoreTemplate; } OX_CLANG_NOWARN_END