nostalgia/deps/ox/src/ox/fs/filestore/filestoretemplate.hpp

778 lines
22 KiB
C++

/*
* Copyright 2015 - 2018 gtalent2@gmail.com
*
* 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 http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/ptrarith/nodebuffer.hpp>
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<typename size_t>
struct OX_PACKED FileStoreItem: public ptrarith::Item<size_t> {
ox::LittleEndian<size_t> id = 0;
ox::LittleEndian<uint8_t> fileType = 0;
ox::LittleEndian<size_t> links = 0;
ox::LittleEndian<size_t> left = 0;
ox::LittleEndian<size_t> right = 0;
FileStoreItem() = default;
explicit FileStoreItem(size_t size) {
this->setSize(size);
}
/**
* @return the size of the data + the size of the Item type
*/
size_t fullSize() const {
return sizeof(*this) + this->size();
}
ptrarith::Ptr<uint8_t, std::size_t> data() {
return ptrarith::Ptr<uint8_t, std::size_t>(this, this->fullSize(), sizeof(*this), this->size());
}
};
template<typename size_t>
class FileStoreTemplate {
public:
using InodeId_t = size_t;
private:
using Item = FileStoreItem<size_t>;
using ItemPtr = typename ptrarith::NodeBuffer<size_t, FileStoreItem<size_t>>::ItemPtr;
using Buffer = ptrarith::NodeBuffer<size_t, FileStoreItem<size_t>>;
static constexpr InodeId_t ReservedInodeEnd = 100;
static constexpr auto MaxInode = MaxValue<size_t> / 2;
struct OX_PACKED FileStoreData {
ox::LittleEndian<size_t> rootNode = 0;
ox::Random random;
};
size_t m_buffSize = 0;
mutable Buffer *m_buffer = nullptr;
public:
FileStoreTemplate() = default;
FileStoreTemplate(void *buff, size_t buffSize);
static Error format(void *buffer, size_t bufferSize);
Error setSize(InodeId_t buffSize);
Error incLinks(InodeId_t id);
Error decLinks(InodeId_t id);
Error write(InodeId_t id, void *data, FsSize_t dataLen, uint8_t fileType = 0);
Error remove(InodeId_t id);
Error read(InodeId_t id, void *data, FsSize_t dataSize, FsSize_t *size = nullptr) const;
Error read(InodeId_t id, FsSize_t readStart, FsSize_t readSize, void *data, FsSize_t *size = nullptr) const;
const ptrarith::Ptr<uint8_t, std::size_t> read(InodeId_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<typename T>
Error read(InodeId_t id, FsSize_t readStart,
FsSize_t readSize, T *data,
FsSize_t *size) const;
Result<StatInfo> stat(InodeId_t id);
Error resize();
Error resize(std::size_t size, void *newBuff = nullptr);
[[nodiscard]] InodeId_t spaceNeeded(FsSize_t size);
[[nodiscard]] InodeId_t size() const;
[[nodiscard]] InodeId_t available();
[[nodiscard]] char *buff();
Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t));
Result<InodeId_t> 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 ptr, 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, size_t size);
};
template<typename size_t>
FileStoreTemplate<size_t>::FileStoreTemplate(void *buff, size_t buffSize) {
m_buffSize = buffSize;
m_buffer = reinterpret_cast<ptrarith::NodeBuffer<size_t, FileStoreItem<size_t>>*>(buff);
if (!m_buffer->valid(buffSize)) {
m_buffSize = 0;
m_buffer = nullptr;
}
}
template<typename size_t>
Error FileStoreTemplate<size_t>::format(void *buffer, size_t bufferSize) {
auto nb = new (buffer) Buffer(bufferSize);
auto fsData = nb->malloc(sizeof(FileStoreData));
if (fsData.valid()) {
auto data = nb->template dataOf<FileStoreData>(fsData);
if (data.valid()) {
new (data) FileStoreData;
return OxError(0);
} else {
oxTrace("ox::fs::FileStoreTemplate::format::fail") << "Could not read data section of FileStoreData";
}
}
return OxError(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::setSize(InodeId_t size) {
if (m_buffSize >= size) {
return m_buffer->setSize(size);
}
return OxError(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::incLinks(InodeId_t id) {
auto item = find(id);
if (item.valid()) {
item->links++;
return OxError(0);
}
return OxError(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::decLinks(InodeId_t id) {
auto item = find(id);
if (item.valid()) {
item->links--;
if (item->links == 0) {
oxReturnError(remove(item));
}
return OxError(0);
}
return OxError(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::write(InodeId_t id, void *data, FsSize_t dataSize, uint8_t fileType) {
oxTrace("ox::fs::FileStoreTemplate::write") << "Attempting to write to inode" << id;
auto existing = find(id);
if (!canWrite(existing, dataSize)) {
oxReturnError(compact());
existing = find(id);
}
if (canWrite(existing, dataSize)) {
// delete the old node if it exists
if (existing.valid()) {
oxTrace("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);
// if first malloc failed, compact and try again
if (!dest.valid()) {
oxTrace("ox::fs::FileStoreTemplate::write") << "Allocation failed, compacting";
oxReturnError(compact());
dest = m_buffer->malloc(dataSize);
}
if (dest.valid()) {
oxTrace("ox::fs::FileStoreTemplate::write") << "Memory allocated";
dest->id = id;
dest->fileType = fileType;
auto destData = m_buffer->template dataOf<uint8_t>(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) {
oxTrace("ox::fs::FileStoreTemplate::write") << "Searching for root node at" << fsData->rootNode;
auto root = m_buffer->ptr(fsData->rootNode);
if (root.valid()) {
oxTrace("ox::fs::FileStoreTemplate::write") << "Placing" << dest->id << "on" << root->id << "at" << destData.offset();
return placeItem(dest);
} else {
oxTrace("ox::fs::FileStoreTemplate::write") << "Initializing root inode:" << dest->id << "( offset:" << dest.offset()
<< ", data size:" << destData.size() << ")";
fsData->rootNode = dest.offset();
oxTrace("ox::fs::FileStoreTemplate::write") << "Root inode:" << dest->id;
return OxError(0);
}
} else {
oxTrace("ox::fs::FileStoreTemplate::write::fail") << "Could not place item due to absence of FileStore header.";
}
}
}
oxReturnError(m_buffer->free(dest));
}
return OxError(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::remove(InodeId_t id) {
return remove(find(id));
}
template<typename size_t>
Error FileStoreTemplate<size_t>::read(InodeId_t id, void *out, FsSize_t outSize, FsSize_t *size) const {
oxTrace("ox::fs::FileStoreTemplate::read") << "Attempting to read from inode" << id;
auto src = find(id);
// error check
if (!src.valid()) {
oxTrace("ox::fs::FileStoreTemplate::read::fail") << "Could not find requested item:" << id;
return OxError(1);
}
auto srcData = m_buffer->template dataOf<uint8_t>(src);
oxTrace("ox::fs::FileStoreTemplate::read::found") << id << "found at"<< src.offset()
<< "with data section at" << srcData.offset();
oxTrace("ox::fs::FileStoreTemplate::read::outSize") << srcData.offset() << srcData.size() << outSize;
// error check
if (!(srcData.valid() && srcData.size() <= outSize)) {
oxTrace("ox::fs::FileStoreTemplate::read::fail")
<< "Could not read data section of item:" << id;
oxTrace("ox::fs::FileStoreTemplate::read::fail").del("")
<< "Item data section size: " << srcData.size()
<< ", Expected size: " << outSize;
return OxError(1);
}
ox_memcpy(out, srcData, srcData.size());
if (size) {
*size = src.size();
}
return OxError(0);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::read(InodeId_t id, FsSize_t readStart, FsSize_t readSize, void *data, FsSize_t *size) const {
auto src = find(id);
if (src.valid()) {
auto srcData = src->data();
if (srcData.valid()) {
auto sub = srcData.template subPtr<uint8_t>(readStart, readSize);
if (sub.valid()) {
ox_memcpy(data, sub, sub.size());
if (size) {
*size = sub.size();
}
return OxError(0);
} else {
oxTrace("ox::fs::FileStoreTemplate::read::fail") << "Could not read requested data sub-section of item:" << id;
}
} else {
oxTrace("ox::fs::FileStoreTemplate::read::fail") << "Could not read data section of item:" << id;
}
} else {
oxTrace("ox::fs::FileStoreTemplate::read::fail") << "Could not find requested item:" << id;
}
return OxError(1);
}
template<typename size_t>
template<typename T>
Error FileStoreTemplate<size_t>::read(InodeId_t id, FsSize_t readStart,
FsSize_t readSize, T *data, FsSize_t *size) const {
auto src = find(id);
if (src.valid()) {
auto srcData = src->data();
if (srcData.valid()) {
auto sub = srcData.template subPtr<uint8_t>(readStart, readSize);
if (sub.valid() && sub.size() % sizeof(T)) {
for (FsSize_t i = 0; i < sub.size() / sizeof(T); i++) {
// do byte-by-byte copy to ensure alignment is right when
// copying to final destination
T tmp;
for (size_t ii = 0; ii < sizeof(T); ii++) {
reinterpret_cast<uint8_t*>(&tmp)[ii] = *(sub.get() + ii);
}
*(data + i) = tmp;
}
if (size) {
*size = sub.size();
}
return OxError(0);
}
}
}
return OxError(1);
}
template<typename size_t>
const ptrarith::Ptr<uint8_t, std::size_t> FileStoreTemplate<size_t>::read(InodeId_t id) const {
auto item = find(id);
if (item.valid()) {
return item->data();
} else {
return nullptr;
}
}
template<typename size_t>
Error FileStoreTemplate<size_t>::resize() {
oxReturnError(compact());
const auto newSize = size() - available();
oxTrace("ox::fs::FileStoreTemplate::resize") << "resize to:" << newSize;
oxReturnError(m_buffer->setSize(newSize));
oxTrace("ox::fs::FileStoreTemplate::resize") << "resized to:" << m_buffer->size();
return OxError(0);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::resize(std::size_t size, void *newBuff) {
if (m_buffer->size() > size) {
return OxError(1);
}
m_buffSize = size;
if (newBuff) {
m_buffer = reinterpret_cast<Buffer*>(newBuff);
oxReturnError(m_buffer->setSize(size));
}
return OxError(0);
}
template<typename size_t>
Result<StatInfo> FileStoreTemplate<size_t>::stat(InodeId_t id) {
auto inode = find(id);
if (inode.valid()) {
return Result<StatInfo>({
id,
inode->links,
inode->size(),
inode->fileType,
});
}
return Result<StatInfo>({}, OxError(0));
}
template<typename size_t>
typename FileStoreTemplate<size_t>::InodeId_t FileStoreTemplate<size_t>::spaceNeeded(FsSize_t size) {
return m_buffer->spaceNeeded(size);
}
template<typename size_t>
typename FileStoreTemplate<size_t>::InodeId_t FileStoreTemplate<size_t>::size() const {
return m_buffer->size();
}
template<typename size_t>
typename FileStoreTemplate<size_t>::InodeId_t FileStoreTemplate<size_t>::available() {
return m_buffer->available();
}
template<typename size_t>
char *FileStoreTemplate<size_t>::buff() {
return reinterpret_cast<char*>(m_buffer);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) {
for (auto i = m_buffer->iterator(); i.valid(); i.next()) {
oxReturnError(cb(i->fileType, i.ptr().offset(), i.ptr().end()));
}
return OxError(0);
}
template<typename size_t>
Result<typename FileStoreTemplate<size_t>::InodeId_t> FileStoreTemplate<size_t>::generateInodeId() {
auto fsData = fileStoreData();
if (fsData) {
for (auto i = 0; i < 100; i++) {
auto inode = fsData->random.gen() % MaxValue<InodeId_t>;
if (inode > ReservedInodeEnd && !find(inode).valid()) {
return inode;
}
}
return {0, OxError(2)};
}
return {0, OxError(1)};
}
template<typename size_t>
Error FileStoreTemplate<size_t>::compact() {
auto isFirstItem = true;
return m_buffer->compact([this, &isFirstItem](uint64_t oldAddr, ItemPtr item) -> Error {
if (isFirstItem) {
isFirstItem = false;
return OxError(0);
}
if (!item.valid()) {
return OxError(1);
}
oxTrace("ox::FileStoreTemplate::compact::moveItem")
<< "Moving Item:" << item->id
<< "from" << oldAddr
<< "to" << 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, 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 OxError(0);
});
}
template<typename size_t>
typename FileStoreTemplate<size_t>::FileStoreData *FileStoreTemplate<size_t>::fileStoreData() const {
auto first = m_buffer->firstItem();
if (first.valid()) {
auto data = first->data();
if (data.valid()) {
return reinterpret_cast<FileStoreData*>(data.get());
}
}
return nullptr;
}
template<typename size_t>
Error FileStoreTemplate<size_t>::placeItem(ItemPtr item) {
auto fsData = fileStoreData();
if (fsData) {
auto root = m_buffer->ptr(fsData->rootNode);
if (root.valid()) {
if (root->id == item->id) {
fsData->rootNode = item;
item->left = root->left;
item->right = root->right;
oxTrace("ox::fs::FileStoreTemplate::placeItem") << "Overwrote Root Item:" << item->id;
return OxError(0);
} else {
return placeItem(root, item);
}
}
}
return OxError(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::placeItem(ItemPtr root, ItemPtr item, int depth) {
if (depth < 5000) {
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;
}
oxTrace("ox::fs::FileStoreTemplate::placeItem") << "Placed Item:" << item->id;
return OxError(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;
}
oxTrace("ox::fs::FileStoreTemplate::placeItem") << "Placed Item:" << item->id;
return OxError(0);
} else {
return placeItem(left, item, depth + 1);
}
} else {
oxTrace("ox::fs::FileStoreTemplate::placeItem::fail") << "Cannot insert an item on itself.";
return OxError(1);
}
} else {
oxTrace("ox::fs::FileStoreTemplate::placeItem::fail") << "Excessive recursion depth, stopping before stack overflow.";
return OxError(2);
}
}
template<typename size_t>
Error FileStoreTemplate<size_t>::unplaceItem(ItemPtr item) {
auto fsData = fileStoreData();
if (fsData) {
auto root = m_buffer->ptr(fsData->rootNode);
if (root.valid()) {
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 OxError(0);
} else {
return unplaceItem(root, item);
}
}
}
return OxError(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::unplaceItem(ItemPtr root, ItemPtr item, int depth) {
if (depth >= 5000) {
oxTrace("ox::fs::FileStoreTemplate::unplaceItem::fail") << "Excessive recursion depth, stopping before stack overflow.";
return OxError(1);
}
if (item->id > root->id) {
auto right = m_buffer->ptr(root->right);
if (right->id == item->id) {
root->right = 0;
oxTrace("ox::fs::FileStoreTemplate::unplaceItem") << "Unplaced Item:" << item->id;
} 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;
oxTrace("ox::fs::FileStoreTemplate::unplaceItem") << "Unplaced Item:" << item->id;
} else {
return unplaceItem(left, item, depth + 1);
}
} else {
return OxError(1);
}
if (item->right) {
oxReturnError(placeItem(m_buffer->ptr(item->right)));
}
if (item->left) {
oxReturnError(placeItem(m_buffer->ptr(item->left)));
}
return OxError(0);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::remove(ItemPtr item) {
if (item.valid()) {
oxReturnError(unplaceItem(item));
oxReturnError(m_buffer->free(item));
return OxError(0);
}
return OxError(1);
}
template<typename size_t>
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::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 size_t>
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::find(ItemPtr item, InodeId_t id, int depth) const {
if (depth > 5000) {
oxTrace("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;
}
if (id > item->id) {
oxTrace("ox::fs::FileStoreTemplate::find") << "Not a match, searching on" << item->right;
return find(m_buffer->ptr(item->right), id, depth + 1);
} else if (id < item->id) {
oxTrace("ox::fs::FileStoreTemplate::find") << "Not a match, searching on" << item->left;
return find(m_buffer->ptr(item->left), id, depth + 1);
} else if (id == item->id) {
oxTrace("ox::fs::FileStoreTemplate::find") << "Found" << id << "at" << item;
return item;
}
return nullptr;
}
template<typename size_t>
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::find(InodeId_t id) const {
oxTrace("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 size_t>
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::rootInode() {
auto fsData = fileStoreData();
if (fsData) {
return m_buffer->ptr(fsData->rootNode);
} else {
return nullptr;
}
}
template<typename size_t>
bool FileStoreTemplate<size_t>::canWrite(ItemPtr existing, size_t size) {
return existing.size() >= size || m_buffer->spaceNeeded(size) <= m_buffer->available();
}
template<typename size_t>
bool FileStoreTemplate<size_t>::valid() const {
return m_buffer;
}
extern template class FileStoreTemplate<uint16_t>;
extern template class FileStoreTemplate<uint32_t>;
using FileStore16 = FileStoreTemplate<uint16_t>;
using FileStore32 = FileStoreTemplate<uint32_t>;
}