This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
|
||||
# enable warnings
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunsafe-buffer-usage")
|
||||
endif()
|
||||
|
||||
add_library(
|
||||
OxFS
|
||||
src/filestore/filestoretemplate.cpp
|
||||
src/filesystem/filelocation.cpp
|
||||
src/filesystem/pathiterator.cpp
|
||||
src/filesystem/directory.cpp
|
||||
src/filesystem/filesystem.cpp
|
||||
src/filesystem/passthroughfs.cpp
|
||||
)
|
||||
|
||||
if(NOT MSVC)
|
||||
target_compile_options(OxFS PRIVATE -Wsign-conversion)
|
||||
endif()
|
||||
|
||||
if(NOT OX_BARE_METAL)
|
||||
set_property(
|
||||
TARGET
|
||||
OxFS
|
||||
PROPERTY
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
OxFS PUBLIC
|
||||
OxMetalClaw
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
OxFS PUBLIC
|
||||
include
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY
|
||||
include/ox
|
||||
DESTINATION
|
||||
include
|
||||
)
|
||||
|
||||
if(NOT OX_BARE_METAL)
|
||||
add_executable(
|
||||
oxfs-tool
|
||||
src/tool.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
oxfs-tool
|
||||
OxFS
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
oxfs-tool
|
||||
DESTINATION
|
||||
bin
|
||||
)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
OxFS
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
|
||||
if(OX_RUN_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
@@ -0,0 +1,777 @@
|
||||
/*
|
||||
* 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/fs/ptrarith/nodebuffer.hpp>
|
||||
|
||||
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<typename size_t>
|
||||
struct OX_PACKED FileStoreItem: public ptrarith::Item<size_t> {
|
||||
LittleEndian<size_t> id = 0;
|
||||
LittleEndian<uint8_t> fileType = 0;
|
||||
LittleEndian<size_t> links = 0;
|
||||
LittleEndian<size_t> left = 0;
|
||||
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
|
||||
*/
|
||||
[[nodiscard]]
|
||||
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;
|
||||
|
||||
struct OX_PACKED FileStoreData {
|
||||
LittleEndian<size_t> 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<uint8_t, std::size_t> 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<typename T>
|
||||
Error read(uint64_t id, FsSize_t readStart,
|
||||
FsSize_t readSize, T *data,
|
||||
FsSize_t *size) const;
|
||||
|
||||
Result<StatInfo> 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<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 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<typename size_t>
|
||||
FileStoreTemplate<size_t>::FileStoreTemplate(void *buff, std::size_t buffSize) {
|
||||
m_buffSize = static_cast<size_t>(buffSize);
|
||||
m_buffer = reinterpret_cast<ptrarith::NodeBuffer<size_t, FileStoreItem<size_t>>*>(buff);
|
||||
if (!m_buffer->valid(m_buffSize)) {
|
||||
m_buffSize = 0;
|
||||
m_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::format(void *buffer, std::size_t bufferSize) {
|
||||
auto nb = new (buffer) Buffer(static_cast<size_t>(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<FileStoreData>(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<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::setSize(std::size_t size) {
|
||||
if (m_buffSize >= size) {
|
||||
return m_buffer->setSize(static_cast<size_t>(size));
|
||||
}
|
||||
return ox::Error(1);
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::incLinks(uint64_t id) {
|
||||
OX_REQUIRE_M(item, find(static_cast<size_t>(id)).validate());
|
||||
++item->links;
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::decLinks(uint64_t id) {
|
||||
OX_REQUIRE_M(item, find(static_cast<size_t>(id)).validate());
|
||||
--item->links;
|
||||
if (item->links == 0) {
|
||||
OX_RETURN_ERROR(remove(item));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::write(uint64_t id64, const void *data, FsSize_t dataSize, uint8_t fileType) {
|
||||
const auto id = static_cast<size_t>(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<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) {
|
||||
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 {};
|
||||
}
|
||||
} 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<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::remove(uint64_t id) {
|
||||
return remove(find(static_cast<size_t>(id)));
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::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<size_t>(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<uint8_t>(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<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::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<size_t>(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<uint8_t>(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<typename size_t>
|
||||
template<typename T>
|
||||
Error FileStoreTemplate<size_t>::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<size_t>(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<uint8_t>(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<typename size_t>
|
||||
ptrarith::Ptr<uint8_t, std::size_t> FileStoreTemplate<size_t>::read(uint64_t id) const {
|
||||
auto item = find(static_cast<size_t>(id));
|
||||
if (item.valid()) {
|
||||
return item->data();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::resize() {
|
||||
OX_RETURN_ERROR(compact());
|
||||
const auto newSize = static_cast<std::size_t>(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 {};
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::resize(std::size_t size, void *newBuff) {
|
||||
if (m_buffer->size() > size) {
|
||||
return ox::Error{1, "new buffer is too small for existing data"};
|
||||
}
|
||||
m_buffSize = static_cast<size_t>(size);
|
||||
if (newBuff) {
|
||||
m_buffer = static_cast<Buffer*>(newBuff);
|
||||
OX_RETURN_ERROR(m_buffer->setSize(static_cast<size_t>(size)));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Result<StatInfo> FileStoreTemplate<size_t>::stat(uint64_t id) const {
|
||||
OX_REQUIRE(inode, find(static_cast<size_t>(id)).validate());
|
||||
return StatInfo {
|
||||
id,
|
||||
inode->links,
|
||||
inode->size(),
|
||||
inode->fileType,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
typename FileStoreTemplate<size_t>::InodeId_t FileStoreTemplate<size_t>::spaceNeeded(FsSize_t size) const {
|
||||
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() const {
|
||||
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()) {
|
||||
OX_RETURN_ERROR(cb(i->fileType, i.ptr().offset(), i.ptr().end()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Result<typename FileStoreTemplate<size_t>::InodeId_t> FileStoreTemplate<size_t>::generateInodeId() {
|
||||
auto fsData = fileStoreData();
|
||||
if (!fsData) {
|
||||
return ox::Error(1);
|
||||
}
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
auto inode = static_cast<typename FileStoreTemplate<size_t>::InodeId_t>(fsData->random.gen() % MaxValue<InodeId_t>);
|
||||
if (inode > ReservedInodeEnd && !find(static_cast<size_t>(inode)).valid()) {
|
||||
return inode;
|
||||
}
|
||||
}
|
||||
return ox::Error(2);
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
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<size_t>(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 {};
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {};
|
||||
} else {
|
||||
return placeItem(root, item);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::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 {};
|
||||
} 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 {};
|
||||
} 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<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::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 {};
|
||||
} else {
|
||||
return unplaceItem(root, item);
|
||||
}
|
||||
}
|
||||
|
||||
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 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 {};
|
||||
}
|
||||
|
||||
template<typename size_t>
|
||||
Error FileStoreTemplate<size_t>::remove(ItemPtr item) {
|
||||
if (item.valid()) {
|
||||
OX_RETURN_ERROR(unplaceItem(item));
|
||||
OX_RETURN_ERROR(m_buffer->free(item));
|
||||
return {};
|
||||
}
|
||||
return ox::Error(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) {
|
||||
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 size_t>
|
||||
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::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 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, std::size_t size) {
|
||||
const auto sz = static_cast<size_t>(size);
|
||||
return existing.size() >= sz || m_buffer->spaceNeeded(sz) <= 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>;
|
||||
|
||||
}
|
||||
|
||||
OX_CLANG_NOWARN_END
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* 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/fs/filesystem/pathiterator.hpp>
|
||||
#include <ox/fs/filestore/filestoretemplate.hpp>
|
||||
#include <ox/fs/ptrarith/nodebuffer.hpp>
|
||||
#include <ox/std/byteswap.hpp>
|
||||
|
||||
#include "types.hpp"
|
||||
|
||||
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
|
||||
|
||||
namespace ox {
|
||||
|
||||
template<typename InodeId_t>
|
||||
struct OX_PACKED DirectoryEntry {
|
||||
|
||||
public:
|
||||
struct OX_PACKED DirectoryEntryData {
|
||||
// DirectoryEntry fields
|
||||
LittleEndian<InodeId_t> inode = 0;
|
||||
char name[MaxFileNameLength];
|
||||
|
||||
static constexpr std::size_t spaceNeeded(std::size_t chars) {
|
||||
return offsetof(DirectoryEntryData, name) + chars;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// NodeBuffer fields
|
||||
LittleEndian<InodeId_t> prev = 0;
|
||||
LittleEndian<InodeId_t> next = 0;
|
||||
|
||||
|
||||
private:
|
||||
LittleEndian<InodeId_t> m_bufferSize = sizeof(DirectoryEntry);
|
||||
|
||||
public:
|
||||
constexpr DirectoryEntry() noexcept = default;
|
||||
|
||||
Error init(InodeId_t inode, ox::StringViewCR name, size_t bufferSize) noexcept {
|
||||
oxTracef("ox.fs.DirectoryEntry.init", "inode: {}, name: {}, bufferSize: {}", inode, name, bufferSize);
|
||||
m_bufferSize = static_cast<InodeId_t>(bufferSize);
|
||||
auto d = data();
|
||||
if (d.valid()) {
|
||||
d->inode = inode;
|
||||
auto const maxStrSz = bufferSize - 1 - sizeof(*this);
|
||||
ox::strncpy(d->name, name.data(), ox::min(maxStrSz, name.size()));
|
||||
return {};
|
||||
}
|
||||
return ox::Error(1);
|
||||
}
|
||||
|
||||
ptrarith::Ptr<DirectoryEntryData, InodeId_t> data() noexcept {
|
||||
oxTracef("ox.fs.DirectoryEntry.data", "{} {} {}", this->fullSize(), sizeof(*this), this->size());
|
||||
return ptrarith::Ptr<DirectoryEntryData, InodeId_t>(this, this->fullSize(), sizeof(*this), this->size(), this->size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of the data + the size of the Item type
|
||||
*/
|
||||
[[nodiscard]]
|
||||
InodeId_t fullSize() const {
|
||||
return m_bufferSize;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
InodeId_t size() const {
|
||||
return fullSize() - static_cast<InodeId_t>(sizeof(*this));
|
||||
}
|
||||
|
||||
void setSize(std::size_t) {
|
||||
// ignore set value
|
||||
}
|
||||
|
||||
static constexpr std::size_t spaceNeeded(std::size_t chars) {
|
||||
return sizeof(DirectoryEntry) + offsetof(DirectoryEntryData, name) + chars;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
class Directory {
|
||||
|
||||
private:
|
||||
using Buffer = ptrarith::NodeBuffer<InodeId_t, DirectoryEntry<InodeId_t>>;
|
||||
|
||||
InodeId_t m_inodeId = 0;
|
||||
std::size_t m_size = 0;
|
||||
FileStore m_fs;
|
||||
|
||||
public:
|
||||
Directory() noexcept = default;
|
||||
|
||||
Directory(FileStore fs, uint64_t inode) noexcept;
|
||||
|
||||
/**
|
||||
* Initializes Directory.
|
||||
*/
|
||||
Error init() noexcept;
|
||||
|
||||
Error mkdir(PathIterator path, bool parents);
|
||||
|
||||
/**
|
||||
* @param parents indicates the operation should create non-existent directories in the path, like mkdir -p
|
||||
*/
|
||||
Error write(PathIterator path, uint64_t inode64) noexcept;
|
||||
|
||||
Error remove(PathIterator path) noexcept;
|
||||
|
||||
template<typename F>
|
||||
Error ls(F cb) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Result<typename FileStore::InodeId_t> findEntry(const StringView &name) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Result<typename FileStore::InodeId_t> find(PathIterator path) const noexcept;
|
||||
|
||||
};
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
Directory<FileStore, InodeId_t>::Directory(FileStore fs, uint64_t inodeId) noexcept {
|
||||
m_fs = fs;
|
||||
m_inodeId = static_cast<InodeId_t>(inodeId);
|
||||
auto buff = m_fs.read(static_cast<InodeId_t>(inodeId)).template to<Buffer>();
|
||||
if (buff.valid()) {
|
||||
m_size = buff.size();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
Error Directory<FileStore, InodeId_t>::init() noexcept {
|
||||
constexpr auto Size = sizeof(Buffer);
|
||||
oxTracef("ox.fs.Directory.init", "Initializing Directory with Inode ID: {}", m_inodeId);
|
||||
OX_RETURN_ERROR(m_fs.write(m_inodeId, nullptr, Size, static_cast<uint8_t>(FileType::Directory)));
|
||||
auto buff = m_fs.read(m_inodeId).template to<Buffer>();
|
||||
if (!buff.valid()) {
|
||||
m_size = 0;
|
||||
return ox::Error(1);
|
||||
}
|
||||
new (buff) Buffer(Size);
|
||||
m_size = Size;
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
Error Directory<FileStore, InodeId_t>::mkdir(PathIterator path, bool parents) {
|
||||
if (path.valid()) {
|
||||
oxTrace("ox.fs.Directory.mkdir", path.fullPath());
|
||||
// determine if already exists
|
||||
ox::StringView name;
|
||||
OX_RETURN_ERROR(path.get(name));
|
||||
auto childInode = find(PathIterator(name));
|
||||
if (!childInode.ok()) {
|
||||
// if this is not the last item in the path and parents is disabled,
|
||||
// return an error
|
||||
if (!parents && path.hasNext()) {
|
||||
return ox::Error(1);
|
||||
}
|
||||
childInode = m_fs.generateInodeId();
|
||||
oxTracef("ox.fs.Directory.mkdir", "Generated Inode ID: {}", childInode.value);
|
||||
oxLogError(childInode.error);
|
||||
OX_RETURN_ERROR(childInode.error);
|
||||
// initialize the directory
|
||||
Directory<FileStore, InodeId_t> child(m_fs, childInode.value);
|
||||
OX_RETURN_ERROR(child.init());
|
||||
auto err = write(PathIterator(name), childInode.value);
|
||||
if (err) {
|
||||
oxLogError(err);
|
||||
// could not index the directory, delete it
|
||||
oxLogError(m_fs.remove(childInode.value));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
Directory<FileStore, InodeId_t> child(m_fs, childInode.value);
|
||||
if (path.hasNext()) {
|
||||
OX_RETURN_ERROR(child.mkdir(path.next(), parents));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
Error Directory<FileStore, InodeId_t>::write(PathIterator path, uint64_t inode64) noexcept {
|
||||
const auto inode = static_cast<InodeId_t>(inode64);
|
||||
ox::StringView name;
|
||||
if (path.next().hasNext()) { // not yet at target directory, recurse to next one
|
||||
oxTracef("ox.fs.Directory.write", "Attempting to write to next sub-Directory: {} of {}",
|
||||
name, path.fullPath());
|
||||
OX_RETURN_ERROR(path.get(name));
|
||||
OX_REQUIRE(nextChild, findEntry(name));
|
||||
oxTracef("ox.fs.Directory.write", "{}: {}", name, nextChild);
|
||||
if (nextChild) {
|
||||
return Directory(m_fs, nextChild).write(path.next(), inode);
|
||||
} else {
|
||||
oxTracef("ox.fs.Directory.write", "{} not found and not allowed to create it.", name);
|
||||
return ox::Error(1, "File not found and not allowed to create it.");
|
||||
}
|
||||
} else {
|
||||
oxTrace("ox.fs.Directory.write", path.fullPath());
|
||||
OX_RETURN_ERROR(path.next(name));
|
||||
// insert the new entry on this directory
|
||||
// get the name
|
||||
// find existing version of directory
|
||||
oxTracef("ox.fs.Directory.write", "Searching for directory inode {}", m_inodeId);
|
||||
OX_REQUIRE(oldStat, m_fs.stat(m_inodeId));
|
||||
oxTracef("ox.fs.Directory.write", "Found existing directory of size {}", oldStat.size);
|
||||
auto old = m_fs.read(m_inodeId).template to<Buffer>();
|
||||
if (!old.valid()) {
|
||||
oxTrace("ox.fs.Directory.write.fail", "Could not read existing version of Directory");
|
||||
return ox::Error(1, "Could not read existing version of Directory");
|
||||
}
|
||||
const auto pathSize = name.size() + 1;
|
||||
const auto entryDataSize = DirectoryEntry<InodeId_t>::DirectoryEntryData::spaceNeeded(pathSize);
|
||||
const auto newSize = oldStat.size + Buffer::spaceNeeded(entryDataSize);
|
||||
auto cpy = ox_malloca(newSize, Buffer, *old, oldStat.size);
|
||||
if (cpy == nullptr) {
|
||||
oxTrace("ox.fs.Directory.write.fail", "Could not allocate memory for copy of Directory");
|
||||
return ox::Error(1, "Could not allocate memory for copy of Directory");
|
||||
}
|
||||
OX_RETURN_ERROR(cpy->setSize(newSize));
|
||||
auto val = cpy->malloc(entryDataSize).value;
|
||||
if (!val.valid()) {
|
||||
oxTrace("ox.fs.Directory.write.fail", "Could not allocate memory for new directory entry");
|
||||
return ox::Error(1, "Could not allocate memory for new directory entry");
|
||||
}
|
||||
oxTracef("ox.fs.Directory.write", "Attempting to write Directory entry: {}", name);
|
||||
OX_RETURN_ERROR(val->init(inode, name, val.size()));
|
||||
return m_fs.write(m_inodeId, cpy.get(), cpy->size(), static_cast<uint8_t>(FileType::Directory));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
Error Directory<FileStore, InodeId_t>::remove(PathIterator path) noexcept {
|
||||
ox::StringView name;
|
||||
OX_RETURN_ERROR(path.get(name));
|
||||
oxTrace("ox.fs.Directory.remove", name);
|
||||
auto buff = m_fs.read(m_inodeId).template to<Buffer>();
|
||||
if (buff.valid()) {
|
||||
oxTrace("ox.fs.Directory.remove", "Found directory buffer.");
|
||||
for (auto i = buff->iterator(); i.valid(); i.next()) {
|
||||
auto data = i->data();
|
||||
if (data.valid()) {
|
||||
if (name == data->name) {
|
||||
OX_RETURN_ERROR(buff->free(i));
|
||||
}
|
||||
} else {
|
||||
oxTrace("ox.fs.Directory.remove", "INVALID DIRECTORY ENTRY");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
oxTrace("ox.fs.Directory.remove.fail", "Could not find directory buffer");
|
||||
return ox::Error(1, "Could not find directory buffer");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
template<typename F>
|
||||
Error Directory<FileStore, InodeId_t>::ls(F cb) noexcept {
|
||||
oxTrace("ox.fs.Directory.ls");
|
||||
auto buff = m_fs.read(m_inodeId).template to<Buffer>();
|
||||
if (!buff.valid()) {
|
||||
oxTrace("ox.fs.Directory.ls.fail", "Could not directory buffer");
|
||||
return ox::Error(1, "Could not directory buffer");
|
||||
}
|
||||
oxTrace("ox.fs.Directory.ls", "Found directory buffer.");
|
||||
for (auto i = buff->iterator(); i.valid(); i.next()) {
|
||||
auto data = i->data();
|
||||
if (data.valid()) {
|
||||
OX_RETURN_ERROR(cb(data->name, data->inode));
|
||||
} else {
|
||||
oxTrace("ox.fs.Directory.ls", "INVALID DIRECTORY ENTRY");
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
Result<typename FileStore::InodeId_t> Directory<FileStore, InodeId_t>::findEntry(StringViewCR name) const noexcept {
|
||||
oxTrace("ox.fs.Directory.findEntry", name);
|
||||
auto buff = m_fs.read(m_inodeId).template to<Buffer>();
|
||||
if (!buff.valid()) {
|
||||
oxTrace("ox.fs.Directory.findEntry.fail", "Could not findEntry directory buffer");
|
||||
return ox::Error(2, "Could not findEntry directory buffer");
|
||||
}
|
||||
oxTracef("ox.fs.Directory.findEntry", "Found directory buffer, size: {}", buff.size());
|
||||
for (auto i = buff->iterator(); i.valid(); i.next()) {
|
||||
auto data = i->data();
|
||||
if (data.valid()) {
|
||||
oxTracef("ox.fs.Directory.findEntry", "Comparing \"{}\" to \"{}\"", name, data->name);
|
||||
if (name == data->name) {
|
||||
oxTracef("ox.fs.Directory.findEntry", "\"{}\" match found.", name);
|
||||
return static_cast<InodeId_t>(data->inode);
|
||||
}
|
||||
} else {
|
||||
oxTrace("ox.fs.Directory.findEntry", "INVALID DIRECTORY ENTRY");
|
||||
}
|
||||
}
|
||||
oxTrace("ox.fs.Directory.findEntry.fail", "Entry not present");
|
||||
return ox::Error(1, "Entry not present");
|
||||
}
|
||||
|
||||
template<typename FileStore, typename InodeId_t>
|
||||
Result<typename FileStore::InodeId_t> Directory<FileStore, InodeId_t>::find(PathIterator path) const noexcept {
|
||||
// determine if already exists
|
||||
ox::StringView name;
|
||||
OX_RETURN_ERROR(path.get(name));
|
||||
OX_REQUIRE(v, findEntry(name));
|
||||
// recurse if not at end of path
|
||||
if (auto p = path.next(); p.valid()) {
|
||||
Directory dir(m_fs, v);
|
||||
return dir.find(p);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
extern template class Directory<FileStore16, uint16_t>;
|
||||
extern template class Directory<FileStore32, uint32_t>;
|
||||
|
||||
extern template struct DirectoryEntry<uint16_t>;
|
||||
extern template struct DirectoryEntry<uint32_t>;
|
||||
|
||||
using Directory16 = Directory<FileStore16, uint16_t>;
|
||||
using Directory32 = Directory<FileStore32, uint32_t>;
|
||||
|
||||
}
|
||||
|
||||
OX_CLANG_NOWARN_END
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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/std/std.hpp>
|
||||
#include <ox/model/def.hpp>
|
||||
#include <ox/model/typenamecatcher.hpp>
|
||||
#include <ox/model/types.hpp>
|
||||
|
||||
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
|
||||
|
||||
namespace ox {
|
||||
|
||||
enum class FileAddressType: int8_t {
|
||||
None = -1,
|
||||
Path,
|
||||
ConstPath,
|
||||
Inode,
|
||||
};
|
||||
|
||||
class FileAddress {
|
||||
|
||||
template<typename T>
|
||||
friend constexpr Error model(T *h, CommonPtrWith<FileAddress> auto *fa) noexcept;
|
||||
|
||||
public:
|
||||
static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
|
||||
union Data {
|
||||
static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress.Data";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
char *path = nullptr;
|
||||
const char *constPath;
|
||||
uint64_t inode;
|
||||
};
|
||||
|
||||
protected:
|
||||
FileAddressType m_type = FileAddressType::None;
|
||||
Data m_data{};
|
||||
|
||||
public:
|
||||
constexpr FileAddress() noexcept = default;
|
||||
|
||||
FileAddress(const FileAddress &other) noexcept;
|
||||
|
||||
FileAddress(FileAddress &&other) noexcept;
|
||||
|
||||
FileAddress(std::nullptr_t) noexcept;
|
||||
|
||||
FileAddress(uint64_t inode) noexcept;
|
||||
|
||||
explicit FileAddress(StringViewCR path) noexcept;
|
||||
|
||||
constexpr FileAddress(ox::StringLiteral path) noexcept;
|
||||
|
||||
constexpr ~FileAddress() noexcept;
|
||||
|
||||
FileAddress &operator=(const FileAddress &other) noexcept;
|
||||
|
||||
FileAddress &operator=(FileAddress &&other) noexcept;
|
||||
|
||||
bool operator==(const FileAddress &other) const noexcept;
|
||||
|
||||
bool operator==(StringViewCR path) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr FileAddressType type() const noexcept {
|
||||
switch (m_type) {
|
||||
case FileAddressType::Path:
|
||||
case FileAddressType::ConstPath:
|
||||
return FileAddressType::Path;
|
||||
default:
|
||||
return m_type;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Result<uint64_t> getInode() const noexcept {
|
||||
switch (m_type) {
|
||||
case FileAddressType::Inode:
|
||||
return m_data.inode;
|
||||
default:
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Result<ox::CStringView> getPath() const noexcept {
|
||||
switch (m_type) {
|
||||
case FileAddressType::Path:
|
||||
return ox::CStringView(m_data.path);
|
||||
case FileAddressType::ConstPath:
|
||||
return ox::CStringView(m_data.constPath);
|
||||
default:
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const noexcept {
|
||||
return m_type != FileAddressType::None;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Cleanup memory allocations.
|
||||
*/
|
||||
constexpr void cleanup() noexcept;
|
||||
|
||||
/**
|
||||
* Clears fields, but does not delete allocations.
|
||||
*/
|
||||
constexpr void clear() noexcept;
|
||||
|
||||
};
|
||||
|
||||
constexpr FileAddress::FileAddress(ox::StringLiteral path) noexcept {
|
||||
m_data.constPath = path.c_str();
|
||||
m_type = FileAddressType::ConstPath;
|
||||
}
|
||||
|
||||
constexpr FileAddress::~FileAddress() noexcept {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
constexpr void FileAddress::cleanup() noexcept {
|
||||
if (m_type == FileAddressType::Path) {
|
||||
safeDeleteArray(m_data.path);
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void FileAddress::clear() noexcept {
|
||||
m_data.path = nullptr;
|
||||
m_type = FileAddressType::None;
|
||||
}
|
||||
|
||||
template<>
|
||||
constexpr const char *getModelTypeName<FileAddress::Data>() noexcept {
|
||||
return FileAddress::Data::TypeName;
|
||||
}
|
||||
|
||||
template<>
|
||||
constexpr const char *getModelTypeName<FileAddress>() noexcept {
|
||||
return FileAddress::TypeName;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr Error model(T *h, CommonPtrWith<FileAddress::Data> auto *obj) noexcept {
|
||||
OX_RETURN_ERROR(h->template setTypeInfo<FileAddress::Data>());
|
||||
OX_RETURN_ERROR(h->fieldCString("path", &obj->path));
|
||||
OX_RETURN_ERROR(h->fieldCString("constPath", &obj->path));
|
||||
OX_RETURN_ERROR(h->field("inode", &obj->inode));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr Error model(T *h, CommonPtrWith<FileAddress> auto *fa) noexcept {
|
||||
OX_RETURN_ERROR(h->template setTypeInfo<FileAddress>());
|
||||
if constexpr(T::opType() == OpType::Reflect) {
|
||||
int8_t type = -1;
|
||||
OX_RETURN_ERROR(h->field("type", &type));
|
||||
OX_RETURN_ERROR(h->field("data", UnionView(&fa->m_data, type)));
|
||||
} else if constexpr(T::opType() == OpType::Read) {
|
||||
auto type = static_cast<int8_t>(fa->m_type);
|
||||
OX_RETURN_ERROR(h->field("type", &type));
|
||||
fa->m_type = static_cast<FileAddressType>(type);
|
||||
OX_RETURN_ERROR(h->field("data", UnionView(&fa->m_data, static_cast<int>(fa->m_type))));
|
||||
} else if constexpr(T::opType() == OpType::Write) {
|
||||
auto const type = static_cast<int8_t>(fa->m_type);
|
||||
OX_RETURN_ERROR(h->field("type", &type));
|
||||
OX_RETURN_ERROR(h->field("data", UnionView(&fa->m_data, static_cast<int>(fa->m_type))));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OX_CLANG_NOWARN_END
|
||||
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* 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/std/buffer.hpp>
|
||||
#include <ox/std/span.hpp>
|
||||
|
||||
#include <ox/fs/filestore/filestoretemplate.hpp>
|
||||
#include <ox/fs/filesystem/filelocation.hpp>
|
||||
#include <ox/fs/filesystem/types.hpp>
|
||||
|
||||
#include "directory.hpp"
|
||||
|
||||
namespace ox {
|
||||
|
||||
namespace detail {
|
||||
inline void fsBuffFree(char *buff) noexcept {
|
||||
safeDelete(buff);
|
||||
}
|
||||
}
|
||||
|
||||
class FileSystem {
|
||||
|
||||
public:
|
||||
virtual ~FileSystem() noexcept = default;
|
||||
|
||||
virtual Error mkdir(StringViewCR path, bool recursive) noexcept = 0;
|
||||
|
||||
/**
|
||||
* Moves an entry from one directory to another.
|
||||
* @param src the path to the file
|
||||
* @param dest the path of the destination directory
|
||||
*/
|
||||
virtual Error move(StringViewCR src, StringViewCR dest) noexcept = 0;
|
||||
|
||||
Error read(const FileAddress &addr, void *buffer, std::size_t size) noexcept;
|
||||
|
||||
Result<Buffer> read(FileAddress const &addr, size_t size) noexcept;
|
||||
|
||||
Result<Buffer> read(StringViewCR path, size_t size) noexcept;
|
||||
|
||||
Result<Buffer> read(const FileAddress &addr) noexcept;
|
||||
|
||||
Result<Buffer> read(StringViewCR path) noexcept;
|
||||
|
||||
Error read(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
|
||||
return readFilePath(path, buffer, buffSize);
|
||||
}
|
||||
|
||||
Error read(uint64_t inode, void *buffer, std::size_t buffSize) noexcept {
|
||||
return readFileInode(inode, buffer, buffSize);
|
||||
}
|
||||
|
||||
Error read(
|
||||
FileAddress const &addr,
|
||||
size_t readStart,
|
||||
size_t readSize,
|
||||
void *buffer,
|
||||
size_t *size) noexcept;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param path
|
||||
* @param readStart
|
||||
* @param readSize
|
||||
* @param buff
|
||||
* @return error or number of bytes read
|
||||
*/
|
||||
Result<size_t> read(
|
||||
StringViewCR path, size_t readStart, size_t readSize, ox::Span<char> buff) noexcept;
|
||||
|
||||
virtual Result<Vector<String>> ls(StringViewCR dir) const noexcept = 0;
|
||||
|
||||
Error remove(StringViewCR path, bool recursive = false) noexcept {
|
||||
return removePath(path, recursive);
|
||||
}
|
||||
|
||||
virtual Error resize(uint64_t size, void *buffer) noexcept = 0;
|
||||
|
||||
Error write(StringViewCR path, const void *buffer, uint64_t size) noexcept {
|
||||
return writeFilePath(path, buffer, size, FileType::NormalFile);
|
||||
}
|
||||
|
||||
Error write(StringViewCR path, ox::SpanView<char> const&buff) noexcept {
|
||||
return write(path, buff.data(), buff.size(), FileType::NormalFile);
|
||||
}
|
||||
|
||||
Error write(uint64_t inode, const void *buffer, uint64_t size) noexcept {
|
||||
return write(inode, buffer, size, FileType::NormalFile);
|
||||
}
|
||||
|
||||
Error write(uint64_t inode, ox::SpanView<char> const&buff) noexcept {
|
||||
return write(inode, buff.data(), buff.size(), FileType::NormalFile);
|
||||
}
|
||||
|
||||
Error write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType = FileType::NormalFile) noexcept;
|
||||
|
||||
Error write(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept {
|
||||
return writeFilePath(path, buffer, size, fileType);
|
||||
}
|
||||
|
||||
Error write(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept {
|
||||
return writeFileInode(inode, buffer, size, fileType);
|
||||
}
|
||||
|
||||
Result<FileStat> stat(uint64_t inode) const noexcept {
|
||||
return statInode(inode);
|
||||
}
|
||||
|
||||
Result<FileStat> stat(StringViewCR path) const noexcept {
|
||||
return statPath(path);
|
||||
}
|
||||
|
||||
Result<FileStat> stat(const FileAddress &addr) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool exists(uint64_t inode) const noexcept {
|
||||
return statInode(inode).ok();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool exists(ox::StringView path) const noexcept {
|
||||
return statPath(path).ok();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool exists(FileAddress const&addr) const noexcept {
|
||||
return stat(addr).ok();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
virtual uint64_t spaceNeeded(uint64_t size) const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual Result<uint64_t> available() const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual Result<uint64_t> size() const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual char *buff() noexcept = 0;
|
||||
|
||||
virtual Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual bool valid() const noexcept = 0;
|
||||
|
||||
protected:
|
||||
virtual Result<FileStat> statInode(uint64_t inode) const noexcept = 0;
|
||||
|
||||
virtual Result<FileStat> statPath(StringViewCR path) const noexcept = 0;
|
||||
|
||||
virtual Error readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept = 0;
|
||||
|
||||
virtual Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept = 0;
|
||||
|
||||
virtual Error readFilePathRange(
|
||||
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept = 0;
|
||||
|
||||
virtual Error readFileInodeRange(uint64_t inode, size_t readStart, size_t readSize, void *buffer, size_t *size) noexcept = 0;
|
||||
|
||||
virtual Error removePath(StringViewCR path, bool recursive) noexcept = 0;
|
||||
|
||||
virtual Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept = 0;
|
||||
|
||||
virtual Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept = 0;
|
||||
|
||||
};
|
||||
|
||||
class MemFS: public FileSystem {
|
||||
public:
|
||||
Result<const char*> directAccess(const FileAddress &addr) const noexcept;
|
||||
|
||||
Result<const char*> directAccess(StringViewCR path) const noexcept {
|
||||
return directAccessPath(path);
|
||||
}
|
||||
|
||||
Result<const char*> directAccess(uint64_t inode) const noexcept {
|
||||
return directAccessInode(inode);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual Result<const char*> directAccessPath(StringViewCR path) const noexcept = 0;
|
||||
|
||||
virtual Result<const char*> directAccessInode(uint64_t inode) const noexcept = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* FileSystemTemplate used to create file system that wraps around a FileStore,
|
||||
* taking an inode size and a directory type as parameters.
|
||||
*
|
||||
* Note: Directory parameter must have a default constructor.
|
||||
*/
|
||||
template<typename FileStore, typename Directory>
|
||||
class FileSystemTemplate: public MemFS {
|
||||
private:
|
||||
static constexpr auto InodeFsData = 2;
|
||||
|
||||
struct OX_PACKED FileSystemData {
|
||||
LittleEndian<typename FileStore::InodeId_t> rootDirInode;
|
||||
};
|
||||
|
||||
FileStore m_fs;
|
||||
void(*m_freeBuffer)(char*) = nullptr;
|
||||
|
||||
public:
|
||||
FileSystemTemplate() noexcept = default;
|
||||
|
||||
FileSystemTemplate(void *buffer, uint64_t bufferSize, void(*freeBuffer)(char*) = detail::fsBuffFree) noexcept;
|
||||
|
||||
explicit FileSystemTemplate(ox::Buffer &buffer) noexcept;
|
||||
|
||||
explicit FileSystemTemplate(FileStore fs) noexcept;
|
||||
|
||||
~FileSystemTemplate() noexcept override;
|
||||
|
||||
static Error format(void *buff, uint64_t buffSize) noexcept;
|
||||
|
||||
Error mkdir(StringViewCR path, bool recursive) noexcept override;
|
||||
|
||||
Error move(StringViewCR src, StringViewCR dest) noexcept override;
|
||||
|
||||
Error readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept override;
|
||||
|
||||
Result<const char*> directAccessPath(StringViewCR) const noexcept override;
|
||||
|
||||
Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept override;
|
||||
|
||||
Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;
|
||||
|
||||
Error readFilePathRange(
|
||||
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;
|
||||
|
||||
Error removePath(StringViewCR path, bool recursive) noexcept override;
|
||||
|
||||
Result<const char*> directAccessInode(uint64_t) const noexcept override;
|
||||
|
||||
Result<Vector<String>> ls(StringViewCR dir) const noexcept override;
|
||||
|
||||
template<typename F>
|
||||
Error ls(StringViewCR path, F cb) const;
|
||||
|
||||
/**
|
||||
* Resizes FileSystem to minimum possible size.
|
||||
*/
|
||||
Error resize() noexcept;
|
||||
|
||||
Error resize(uint64_t size, void *buffer) noexcept override;
|
||||
|
||||
Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept override;
|
||||
|
||||
Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept override;
|
||||
|
||||
Result<FileStat> statInode(uint64_t inode) const noexcept override;
|
||||
|
||||
Result<FileStat> statPath(StringViewCR path) const noexcept override;
|
||||
|
||||
[[nodiscard]]
|
||||
uint64_t spaceNeeded(uint64_t size) const noexcept override;
|
||||
|
||||
Result<uint64_t> available() const noexcept override;
|
||||
|
||||
Result<uint64_t> size() const noexcept override;
|
||||
|
||||
char *buff() noexcept override;
|
||||
|
||||
Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept override;
|
||||
|
||||
[[nodiscard]]
|
||||
bool valid() const noexcept override;
|
||||
|
||||
private:
|
||||
Result<FileSystemData> fileSystemData() const noexcept;
|
||||
|
||||
/**
|
||||
* Finds the inode ID at the given path.
|
||||
*/
|
||||
Result<uint64_t> find(StringViewCR path) const noexcept;
|
||||
|
||||
Result<Directory> rootDir() const noexcept;
|
||||
|
||||
};
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(FileStore fs) noexcept {
|
||||
m_fs = fs;
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(ox::Buffer &buffer) noexcept:
|
||||
m_fs(buffer.data(), static_cast<std::size_t>(buffer.size())) {
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(void *buffer, uint64_t bufferSize, void(*freeBuffer)(char*)) noexcept:
|
||||
m_fs(buffer, static_cast<std::size_t>(bufferSize)),
|
||||
m_freeBuffer(freeBuffer) {
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
FileSystemTemplate<FileStore, Directory>::~FileSystemTemplate() noexcept {
|
||||
if (m_freeBuffer && m_fs.buff()) {
|
||||
m_freeBuffer(m_fs.buff());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::format(void *buff, uint64_t buffSize) noexcept {
|
||||
OX_RETURN_ERROR(FileStore::format(buff, static_cast<size_t>(buffSize)));
|
||||
FileStore fs(buff, static_cast<size_t>(buffSize));
|
||||
|
||||
constexpr auto rootDirInode = MaxValue<typename FileStore::InodeId_t> / 2;
|
||||
Directory rootDir(fs, rootDirInode);
|
||||
OX_RETURN_ERROR(rootDir.init());
|
||||
|
||||
FileSystemData fd;
|
||||
fd.rootDirInode = rootDirInode;
|
||||
oxTracef("ox.fs.FileSystemTemplate.format", "rootDirInode: {}", fd.rootDirInode.get());
|
||||
OX_RETURN_ERROR(fs.write(InodeFsData, &fd, sizeof(fd)));
|
||||
|
||||
if (!fs.read(fd.rootDirInode).valid()) {
|
||||
oxTrace("ox.fs.FileSystemTemplate.format.error", "FileSystemTemplate::format did not correctly create root directory");
|
||||
return ox::Error(1);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::mkdir(StringViewCR path, bool recursive) noexcept {
|
||||
oxTracef("ox.fs.FileSystemTemplate.mkdir", "path: {}, recursive: {}", path, recursive);
|
||||
OX_REQUIRE_M(rootDir, this->rootDir());
|
||||
return rootDir.mkdir(path, recursive);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::move(StringViewCR src, StringViewCR dest) noexcept {
|
||||
OX_REQUIRE(fd, fileSystemData());
|
||||
Directory rootDir(m_fs, fd.rootDirInode);
|
||||
OX_REQUIRE_M(inode, rootDir.find(src));
|
||||
OX_RETURN_ERROR(rootDir.write(dest, inode));
|
||||
OX_RETURN_ERROR(rootDir.remove(src));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
|
||||
oxTrace("ox.fs.FileSystemTemplate.readFilePath", path);
|
||||
OX_REQUIRE(fd, fileSystemData());
|
||||
Directory rootDir(m_fs, fd.rootDirInode);
|
||||
OX_REQUIRE(s, stat(path));
|
||||
if (s.size > buffSize) {
|
||||
return ox::Error(1, "Buffer to small to load file");
|
||||
}
|
||||
return readFileInodeRange(s.inode, 0, static_cast<size_t>(s.size), buffer, &buffSize);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<const char*> FileSystemTemplate<FileStore, Directory>::directAccessPath(StringViewCR path) const noexcept {
|
||||
OX_REQUIRE(fd, fileSystemData());
|
||||
Directory rootDir(m_fs, fd.rootDirInode);
|
||||
OX_REQUIRE(inode, rootDir.find(path));
|
||||
return directAccessInode(inode);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::readFileInode(uint64_t inode, void *buffer, std::size_t buffSize) noexcept {
|
||||
oxTracef("ox.fs.FileSystemTemplate.readFileInode", "{}", inode);
|
||||
OX_REQUIRE(s, stat(inode));
|
||||
if (s.size > buffSize) {
|
||||
return ox::Error(1, "Buffer to small to load file");
|
||||
}
|
||||
return readFileInodeRange(inode, 0, static_cast<size_t>(s.size), buffer, &buffSize);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept {
|
||||
return m_fs.read(inode, readStart, readSize, reinterpret_cast<uint8_t*>(buffer), size);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::readFilePathRange(
|
||||
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
|
||||
OX_REQUIRE(s, stat(path));
|
||||
return readFileInodeRange(s.inode, readStart, readSize, buffer, buffSize);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::removePath(StringViewCR path, bool recursive) noexcept {
|
||||
OX_REQUIRE(fd, fileSystemData());
|
||||
Directory rootDir(m_fs, fd.rootDirInode);
|
||||
OX_REQUIRE(inode, rootDir.find(path));
|
||||
OX_REQUIRE(st, statInode(inode));
|
||||
if (st.fileType == FileType::NormalFile || recursive) {
|
||||
if (auto err = rootDir.remove(path)) {
|
||||
// removal failed, try putting the index back
|
||||
oxLogError(rootDir.write(path, inode));
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
oxTrace("FileSystemTemplate.remove.fail", "Tried to remove directory without recursive setting.");
|
||||
return ox::Error(1);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<const char*> FileSystemTemplate<FileStore, Directory>::directAccessInode(uint64_t inode) const noexcept {
|
||||
auto data = m_fs.read(inode);
|
||||
if (!data.valid()) {
|
||||
return ox::Error(1, "Data not valid");
|
||||
}
|
||||
return reinterpret_cast<char*>(data.get());
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<Vector<String>> FileSystemTemplate<FileStore, Directory>::ls(StringViewCR path) const noexcept {
|
||||
Vector<String> out;
|
||||
OX_RETURN_ERROR(ls(path, [&out](StringViewCR name, typename FileStore::InodeId_t) {
|
||||
out.emplace_back(name);
|
||||
return ox::Error{};
|
||||
}));
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
template<typename F>
|
||||
Error FileSystemTemplate<FileStore, Directory>::ls(StringViewCR path, F cb) const {
|
||||
oxTracef("ox.fs.FileSystemTemplate.ls", "path: {}", path);
|
||||
OX_REQUIRE(s, stat(path));
|
||||
Directory dir(m_fs, s.inode);
|
||||
return dir.ls(cb);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::resize() noexcept {
|
||||
return m_fs.resize();
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::resize(uint64_t size, void *buffer) noexcept {
|
||||
OX_RETURN_ERROR(m_fs.resize(static_cast<size_t>(size), buffer));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::writeFilePath(
|
||||
StringViewCR path,
|
||||
const void *buffer,
|
||||
uint64_t size,
|
||||
FileType fileType) noexcept {
|
||||
oxTrace("ox.fs.FileSystemTemplate.writeFilePath", path);
|
||||
auto [inode, err] = find(path);
|
||||
if (err) {
|
||||
OX_RETURN_ERROR(m_fs.generateInodeId().moveTo(inode));
|
||||
OX_REQUIRE_M(rootDir, this->rootDir());
|
||||
OX_RETURN_ERROR(rootDir.write(path, inode));
|
||||
}
|
||||
OX_RETURN_ERROR(writeFileInode(inode, buffer, size, fileType));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept {
|
||||
oxTrace("ox.fs.FileSystemTemplate.writeFileInode", ox::intToStr(inode));
|
||||
return m_fs.write(inode, buffer, static_cast<size_t>(size), static_cast<uint8_t>(fileType));
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<FileStat> FileSystemTemplate<FileStore, Directory>::statInode(uint64_t inode) const noexcept {
|
||||
OX_REQUIRE(s, m_fs.stat(inode));
|
||||
FileStat out;
|
||||
out.inode = s.inode;
|
||||
out.links = s.links;
|
||||
out.size = s.size;
|
||||
out.fileType = static_cast<FileType>(s.fileType);
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<FileStat> FileSystemTemplate<FileStore, Directory>::statPath(StringViewCR path) const noexcept {
|
||||
OX_REQUIRE(inode, find(path));
|
||||
return stat(inode);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
uint64_t FileSystemTemplate<FileStore, Directory>::spaceNeeded(uint64_t size) const noexcept {
|
||||
return m_fs.spaceNeeded(static_cast<size_t>(size));
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::available() const noexcept {
|
||||
return m_fs.available();
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::size() const noexcept {
|
||||
return m_fs.size();
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
char *FileSystemTemplate<FileStore, Directory>::buff() noexcept {
|
||||
return m_fs.buff();
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Error FileSystemTemplate<FileStore, Directory>::walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept {
|
||||
return m_fs.walk(cb);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
bool FileSystemTemplate<FileStore, Directory>::valid() const noexcept {
|
||||
return m_fs.valid();
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<typename FileSystemTemplate<FileStore, Directory>::FileSystemData> FileSystemTemplate<FileStore, Directory>::fileSystemData() const noexcept {
|
||||
FileSystemData fd;
|
||||
OX_RETURN_ERROR(m_fs.read(InodeFsData, &fd, sizeof(fd)));
|
||||
return fd;
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::find(StringViewCR path) const noexcept {
|
||||
OX_REQUIRE(fd, fileSystemData());
|
||||
// return root as a special case
|
||||
if (path == "/") {
|
||||
return static_cast<uint64_t>(fd.rootDirInode);
|
||||
}
|
||||
Directory rootDir(m_fs, fd.rootDirInode);
|
||||
OX_REQUIRE(out, rootDir.find(path));
|
||||
return static_cast<uint64_t>(out);
|
||||
}
|
||||
|
||||
template<typename FileStore, typename Directory>
|
||||
Result<Directory> FileSystemTemplate<FileStore, Directory>::rootDir() const noexcept {
|
||||
OX_REQUIRE(fd, fileSystemData());
|
||||
return Directory(m_fs, fd.rootDirInode);
|
||||
}
|
||||
|
||||
extern template class FileSystemTemplate<FileStore16, Directory16>;
|
||||
extern template class FileSystemTemplate<FileStore32, Directory32>;
|
||||
|
||||
using FileSystem16 = FileSystemTemplate<FileStore16, Directory16>;
|
||||
using FileSystem32 = FileSystemTemplate<FileStore32, Directory32>;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
#if __has_include(<filesystem>) && defined(OX_USE_STDLIB)
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <ox/std/bit.hpp>
|
||||
#include "filesystem.hpp"
|
||||
|
||||
#define OX_HAS_PASSTHROUGHFS
|
||||
|
||||
namespace ox {
|
||||
|
||||
constexpr auto HasPassThroughFS = true;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class PassThroughFS: public FileSystem {
|
||||
private:
|
||||
std::filesystem::path m_path;
|
||||
|
||||
public:
|
||||
explicit PassThroughFS(StringViewCR dirPath);
|
||||
|
||||
~PassThroughFS() noexcept override;
|
||||
|
||||
[[nodiscard]]
|
||||
String basePath() const noexcept;
|
||||
|
||||
Error mkdir(StringViewCR path, bool recursive) noexcept override;
|
||||
|
||||
Error move(StringViewCR src, StringViewCR dest) noexcept override;
|
||||
|
||||
Result<Vector<String>> ls(StringViewCR dir) const noexcept override;
|
||||
|
||||
template<typename F>
|
||||
Error ls(StringViewCR dir, F cb) const noexcept;
|
||||
|
||||
Error resize(uint64_t size, void *buffer) noexcept override;
|
||||
|
||||
Result<FileStat> statInode(uint64_t inode) const noexcept override;
|
||||
|
||||
Result<FileStat> statPath(StringViewCR path) const noexcept override;
|
||||
|
||||
[[nodiscard]]
|
||||
uint64_t spaceNeeded(uint64_t size) const noexcept override;
|
||||
|
||||
Result<uint64_t> available() const noexcept override;
|
||||
|
||||
Result<uint64_t> size() const noexcept override;
|
||||
|
||||
[[nodiscard]]
|
||||
char *buff() noexcept override;
|
||||
|
||||
Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept override;
|
||||
|
||||
[[nodiscard]]
|
||||
bool valid() const noexcept override;
|
||||
|
||||
protected:
|
||||
Error readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept override;
|
||||
|
||||
Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept override;
|
||||
|
||||
Error readFilePathRange(
|
||||
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;
|
||||
|
||||
Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;
|
||||
|
||||
Error removePath(StringViewCR path, bool recursive) noexcept override;
|
||||
|
||||
Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept override;
|
||||
|
||||
Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Strips the leading slashes from a string.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
static std::string_view stripSlash(StringView path) noexcept;
|
||||
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
Error PassThroughFS::ls(StringViewCR dir, F cb) const noexcept {
|
||||
std::error_code ec;
|
||||
const auto di = std::filesystem::directory_iterator(m_path / stripSlash(dir), ec);
|
||||
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: ls failed"));
|
||||
for (auto &p : di) {
|
||||
OX_RETURN_ERROR(cb(p.path().filename().c_str(), 0));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
namespace ox {
|
||||
|
||||
constexpr auto HasPassThroughFS = false;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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/std/std.hpp>
|
||||
|
||||
namespace ox {
|
||||
|
||||
constexpr std::size_t MaxFileNameLength = 255;
|
||||
|
||||
class PathIterator {
|
||||
private:
|
||||
const char *m_path = nullptr;
|
||||
std::size_t m_iterator = 0;
|
||||
std::size_t m_maxSize = 0;
|
||||
|
||||
public:
|
||||
PathIterator(const char *path, std::size_t maxSize, std::size_t iterator = 0);
|
||||
|
||||
PathIterator(const char *path);
|
||||
|
||||
PathIterator(StringViewCR path);
|
||||
|
||||
Error dirPath(char *pathOut, std::size_t pathOutSize);
|
||||
|
||||
Error next(StringView &fileName);
|
||||
|
||||
Error get(StringView &fileName);
|
||||
|
||||
Result<std::size_t> nextSize() const;
|
||||
|
||||
[[nodiscard]]
|
||||
bool hasNext() const;
|
||||
|
||||
[[nodiscard]]
|
||||
bool valid() const;
|
||||
|
||||
[[nodiscard]]
|
||||
PathIterator next() const;
|
||||
|
||||
[[nodiscard]]
|
||||
const char *fullPath() const;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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/std/types.hpp>
|
||||
|
||||
namespace ox {
|
||||
|
||||
enum class FileType: uint8_t {
|
||||
None = 0,
|
||||
NormalFile = 1,
|
||||
Directory = 2
|
||||
};
|
||||
|
||||
constexpr const char *toString(FileType t) noexcept {
|
||||
switch (t) {
|
||||
case FileType::NormalFile:
|
||||
return "Normal File";
|
||||
case FileType::Directory:
|
||||
return "Directory";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
struct FileStat {
|
||||
uint64_t inode = 0;
|
||||
uint64_t links = 0;
|
||||
uint64_t size = 0;
|
||||
FileType fileType = FileType::None;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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 "filestore/filestoretemplate.hpp"
|
||||
#include "filesystem/filelocation.hpp"
|
||||
#include "filesystem/filesystem.hpp"
|
||||
#include "filesystem/passthroughfs.hpp"
|
||||
#include "filesystem/directory.hpp"
|
||||
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
* 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/std/stddef.hpp>
|
||||
#include <ox/std/trace.hpp>
|
||||
|
||||
#include "ptr.hpp"
|
||||
|
||||
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
|
||||
|
||||
namespace ox::ptrarith {
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
class OX_PACKED NodeBuffer {
|
||||
|
||||
public:
|
||||
struct OX_PACKED Header {
|
||||
LittleEndian<size_t> size = sizeof(Header); // capacity
|
||||
LittleEndian<size_t> bytesUsed = sizeof(Header);
|
||||
LittleEndian<size_t> firstItem = 0;
|
||||
};
|
||||
|
||||
using ItemPtr = Ptr<Item, size_t, sizeof(Header)>;
|
||||
|
||||
class Iterator {
|
||||
private:
|
||||
NodeBuffer *m_buffer = nullptr;
|
||||
ItemPtr m_current;
|
||||
size_t m_it = 0;
|
||||
|
||||
public:
|
||||
Iterator(NodeBuffer *buffer, ItemPtr current) noexcept {
|
||||
m_buffer = buffer;
|
||||
m_current = current;
|
||||
oxTrace("ox.ptrarith.Iterator.start") << current.offset();
|
||||
}
|
||||
|
||||
operator const Item*() const noexcept {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
ItemPtr ptr() noexcept {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Item *get() noexcept {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
operator ItemPtr() noexcept {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
operator Item*() noexcept {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
const Item *operator->() const noexcept {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
Item *operator->() noexcept {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool valid() const noexcept {
|
||||
return m_current.valid();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool hasNext() noexcept {
|
||||
if (m_current.valid()) {
|
||||
oxTrace("ox.ptrarith.NodeBuffer.Iterator.hasNext.current") << m_current.offset();
|
||||
auto next = m_buffer->next(m_current);
|
||||
return next.valid() && m_buffer->firstItem() != next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void next() noexcept {
|
||||
oxTrace("ox.ptrarith.NodeBuffer.Iterator.next") << m_it++;
|
||||
if (hasNext()) {
|
||||
m_current = m_buffer->next(m_current);
|
||||
} else {
|
||||
m_current = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Header m_header;
|
||||
|
||||
public:
|
||||
NodeBuffer(const NodeBuffer &other, std::size_t size) noexcept;
|
||||
|
||||
explicit NodeBuffer(std::size_t size) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const Iterator iterator() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Iterator iterator() noexcept;
|
||||
|
||||
ItemPtr firstItem() noexcept;
|
||||
|
||||
ItemPtr lastItem() noexcept;
|
||||
|
||||
/**
|
||||
* @return the data section of the given item
|
||||
*/
|
||||
template<typename T>
|
||||
Ptr<T, size_t, sizeof(Item)> dataOf(ItemPtr) noexcept;
|
||||
|
||||
ItemPtr prev(Item *item) noexcept;
|
||||
|
||||
ItemPtr next(Item *item) noexcept;
|
||||
|
||||
/**
|
||||
* Like pointer but omits checks that assume the memory at the offset has
|
||||
* already been initialed as an Item.
|
||||
*/
|
||||
ItemPtr uninitializedPtr(size_t offset) noexcept;
|
||||
|
||||
ItemPtr ptr(size_t offset) noexcept;
|
||||
|
||||
Result<ItemPtr> malloc(std::size_t size) noexcept;
|
||||
|
||||
Error free(ItemPtr item) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool valid(size_t maxSize) const noexcept;
|
||||
|
||||
/**
|
||||
* Set size, capacity.
|
||||
*/
|
||||
Error setSize(std::size_t size) noexcept;
|
||||
|
||||
/**
|
||||
* Get size, capacity.
|
||||
* @return capacity
|
||||
*/
|
||||
[[nodiscard]]
|
||||
constexpr size_t size() const noexcept;
|
||||
|
||||
/**
|
||||
* @return the bytes still available in this NodeBuffer
|
||||
*/
|
||||
[[nodiscard]]
|
||||
size_t available() const noexcept;
|
||||
|
||||
/**
|
||||
* @return the actual number a bytes need to store the given number of
|
||||
* bytes
|
||||
*/
|
||||
[[nodiscard]]
|
||||
static size_t spaceNeeded(std::size_t size) noexcept;
|
||||
|
||||
template<typename F>
|
||||
Error compact(F cb = [](uint64_t, ItemPtr) {}) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]]
|
||||
uint8_t *data() noexcept;
|
||||
|
||||
};
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
NodeBuffer<size_t, Item>::NodeBuffer(std::size_t size) noexcept {
|
||||
m_header.size = static_cast<size_t>(size);
|
||||
ox::memset(this + 1, 0, size - sizeof(*this));
|
||||
oxTracef("ox.ptrarith.NodeBuffer.constructor", "{}", m_header.firstItem.get());
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
NodeBuffer<size_t, Item>::NodeBuffer(const NodeBuffer &other, std::size_t size) noexcept {
|
||||
oxTracef("ox.ptrarith.NodeBuffer.copy", "other.m_header.firstItem: {}", other.m_header.firstItem.get());
|
||||
ox::memset(this + 1, 0, size - sizeof(*this));
|
||||
ox::memcpy(this, &other, size);
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
const typename NodeBuffer<size_t, Item>::Iterator NodeBuffer<size_t, Item>::iterator() const noexcept {
|
||||
return Iterator(this, firstItem());
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
typename NodeBuffer<size_t, Item>::Iterator NodeBuffer<size_t, Item>::iterator() noexcept {
|
||||
oxTracef("ox.ptrarith.NodeBuffer.iterator.size", "{}", m_header.size.get());
|
||||
return Iterator(this, firstItem());
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::firstItem() noexcept {
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::firstItem") << m_header.firstItem;
|
||||
return ptr(m_header.firstItem);
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::lastItem() noexcept {
|
||||
auto first = ptr(m_header.firstItem);
|
||||
if (first.valid()) {
|
||||
return prev(first);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
template<typename T>
|
||||
Ptr<T, size_t, sizeof(Item)> NodeBuffer<size_t, Item>::dataOf(ItemPtr ip) noexcept {
|
||||
auto out = ip.template subPtr<T>(sizeof(Item));
|
||||
oxAssert(out.size() == ip.size() - sizeof(Item), "Sub Ptr has invalid size.");
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::prev(Item *item) noexcept {
|
||||
return ptr(item->prev);
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::next(Item *item) noexcept {
|
||||
return ptr(item->next);
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::uninitializedPtr(size_t itemOffset) noexcept {
|
||||
// make sure this can be read as an Item, and then use Item::size for the size
|
||||
std::size_t itemSpace = m_header.size - itemOffset;
|
||||
if (itemOffset >= sizeof(Header) &&
|
||||
itemOffset + itemSpace <= size() &&
|
||||
itemSpace >= sizeof(Item)) {
|
||||
return ItemPtr(this, m_header.size, itemOffset, itemSpace);
|
||||
} else {
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemOffset:" << itemOffset;
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemOffset >= sizeof(Header):" << (itemOffset >= sizeof(Header));
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemSpace >= sizeof(Item):" << (itemSpace >= sizeof(Item));
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemSpace >= item->fullSize():" << (itemSpace >= item->fullSize());
|
||||
return ItemPtr(this, m_header.size, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::ptr(size_t itemOffset) noexcept {
|
||||
// make sure this can be read as an Item, and then use Item::size for the size
|
||||
std::size_t itemSpace = m_header.size - itemOffset;
|
||||
auto item = reinterpret_cast<Item*>(reinterpret_cast<uint8_t*>(this) + itemOffset);
|
||||
if (itemOffset >= sizeof(Header) &&
|
||||
itemOffset + itemSpace <= size() &&
|
||||
itemSpace >= sizeof(Item) &&
|
||||
itemSpace >= item->fullSize()) {
|
||||
return ItemPtr(this, m_header.size, itemOffset, item->fullSize());
|
||||
} else {
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemOffset:" << itemOffset;
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemOffset >= sizeof(Header):" << (itemOffset >= sizeof(Header));
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemSpace >= sizeof(Item):" << (itemSpace >= sizeof(Item));
|
||||
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemSpace >= item->fullSize():" << (itemSpace >= item->fullSize());
|
||||
return ItemPtr(this, m_header.size, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
Result<typename NodeBuffer<size_t, Item>::ItemPtr> NodeBuffer<size_t, Item>::malloc(std::size_t size) noexcept {
|
||||
const auto sz = static_cast<std::size_t>(size);
|
||||
oxTracef("ox.ptrarith.NodeBuffer.malloc", "Size: {}", sz);
|
||||
size_t fullSize = static_cast<size_t>(sz + sizeof(Item));
|
||||
if (m_header.size - m_header.bytesUsed >= fullSize) {
|
||||
auto last = lastItem();
|
||||
size_t addr;
|
||||
if (last.valid()) {
|
||||
addr = last.offset() + last.size();
|
||||
} else {
|
||||
// there is no first item, so this must be the first item
|
||||
if (!m_header.firstItem) {
|
||||
oxTrace("ox.ptrarith.NodeBuffer.malloc", "No first item, initializing.");
|
||||
m_header.firstItem = static_cast<size_t>(sizeof(m_header));
|
||||
addr = m_header.firstItem;
|
||||
} else {
|
||||
oxTrace("ox.ptrarith.NodeBuffer.malloc.fail", "NodeBuffer is in invalid state.");
|
||||
return ox::Error(1, "NodeBuffer is in invalid state.");
|
||||
}
|
||||
}
|
||||
oxTracef("ox.ptrarith.NodeBuffer.malloc", "buffer size: {}; addr: {}; fullSize: {}", m_header.size.get(), addr, fullSize);
|
||||
auto out = ItemPtr(this, m_header.size, addr, fullSize);
|
||||
if (!out.valid()) {
|
||||
oxTrace("ox.ptrarith.NodeBuffer.malloc.fail", "Unknown");
|
||||
return ox::Error(1, "NodeBuffer::malloc: unknown failure");
|
||||
}
|
||||
ox::memset(out, 0, fullSize);
|
||||
new (out) Item;
|
||||
out->setSize(sz);
|
||||
|
||||
auto first = firstItem();
|
||||
auto oldLast = last;
|
||||
out->next = first.offset();
|
||||
if (first.valid()) {
|
||||
first->prev = out.offset();
|
||||
} else {
|
||||
oxTrace("ox.ptrarith.NodeBuffer.malloc.fail", "NodeBuffer malloc failed due to invalid first element pointer.");
|
||||
return ox::Error(1, "NodeBuffer malloc failed due to invalid first element pointer.");
|
||||
}
|
||||
|
||||
if (oldLast.valid()) {
|
||||
out->prev = oldLast.offset();
|
||||
oldLast->next = out.offset();
|
||||
} else { // check to see if this is the first allocation
|
||||
if (out.offset() != first.offset()) {
|
||||
// if this is not the first allocation, there should be an oldLast
|
||||
oxTrace("ox.ptrarith.NodeBuffer.malloc.fail", "NodeBuffer malloc failed due to invalid last element pointer.");
|
||||
return ox::Error(1, "NodeBuffer malloc failed due to invalid last element pointer.");
|
||||
}
|
||||
out->prev = out.offset();
|
||||
}
|
||||
m_header.bytesUsed += out.size();
|
||||
oxTracef("ox.ptrarith.NodeBuffer.malloc", "Offset: {}", out.offset());
|
||||
return out;
|
||||
}
|
||||
oxTracef("ox.ptrarith.NodeBuffer.malloc.fail", "Insufficient space: {} needed, {} available", fullSize, available());
|
||||
return ox::Error(1);
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
Error NodeBuffer<size_t, Item>::free(ItemPtr item) noexcept {
|
||||
oxTracef("ox.ptrarith.NodeBuffer.free", "offset: {}", item.offset());
|
||||
auto prev = this->prev(item);
|
||||
auto next = this->next(item);
|
||||
if (prev.valid() && next.valid()) {
|
||||
if (next != item) {
|
||||
prev->next = next;
|
||||
next->prev = prev;
|
||||
if (item.offset() == m_header.firstItem) {
|
||||
m_header.firstItem = next;
|
||||
}
|
||||
} else {
|
||||
// only one item, null out first
|
||||
oxTrace("ox.ptrarith.NodeBuffer.free", "Nulling out firstItem.");
|
||||
m_header.firstItem = 0;
|
||||
}
|
||||
} else {
|
||||
if (!prev.valid()) {
|
||||
oxTracef("ox.ptrarith.NodeBuffer.free.fail", "NodeBuffer free failed due to invalid prev element pointer: {}", prev.offset());
|
||||
return ox::Error(1);
|
||||
}
|
||||
if (!next.valid()) {
|
||||
oxTracef("ox.ptrarith.NodeBuffer.free.fail", "NodeBuffer free failed due to invalid next element pointer: {}", next.offset());
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
m_header.bytesUsed -= item.size();
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
Error NodeBuffer<size_t, Item>::setSize(std::size_t size) noexcept {
|
||||
oxTracef("ox.ptrarith.NodeBuffer.setSize", "{} to {}", m_header.size.get(), size);
|
||||
auto last = lastItem();
|
||||
auto end = last.valid() ? last.end() : sizeof(m_header);
|
||||
oxTracef("ox.ptrarith.NodeBuffer.setSize", "end: {}", end);
|
||||
if (end > size) {
|
||||
// resizing to less than buffer size
|
||||
return ox::Error(1);
|
||||
} else {
|
||||
m_header.size = static_cast<size_t>(size);
|
||||
auto data = reinterpret_cast<uint8_t*>(this) + end;
|
||||
ox::memset(data, 0, size - end);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
constexpr size_t NodeBuffer<size_t, Item>::size() const noexcept {
|
||||
return m_header.size;
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
bool NodeBuffer<size_t, Item>::valid(size_t maxSize) const noexcept {
|
||||
return m_header.size <= maxSize;
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
size_t NodeBuffer<size_t, Item>::available() const noexcept {
|
||||
return m_header.size - m_header.bytesUsed;
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
size_t NodeBuffer<size_t, Item>::spaceNeeded(std::size_t size) noexcept {
|
||||
return static_cast<size_t>(sizeof(Item) + size);
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
template<typename F>
|
||||
Error NodeBuffer<size_t, Item>::compact(F cb) noexcept {
|
||||
auto src = firstItem();
|
||||
auto dest = ptr(sizeof(*this));
|
||||
while (dest.offset() <= src.offset()) {
|
||||
if (!src.valid()) {
|
||||
return ox::Error(1);
|
||||
}
|
||||
if (!dest.valid()) {
|
||||
return ox::Error(2);
|
||||
}
|
||||
// move node
|
||||
ox::memcpy(dest, src, src->fullSize());
|
||||
OX_RETURN_ERROR(cb(src, dest));
|
||||
// update surrounding nodes
|
||||
auto prev = ptr(dest->prev);
|
||||
if (prev.valid()) {
|
||||
prev->next = dest;
|
||||
}
|
||||
auto next = ptr(dest->next);
|
||||
if (next.valid()) {
|
||||
next->prev = dest;
|
||||
}
|
||||
// update iterators
|
||||
src = ptr(dest->next);
|
||||
dest = uninitializedPtr(dest.offset() + dest->fullSize());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename size_t, typename Item>
|
||||
uint8_t *NodeBuffer<size_t, Item>::data() noexcept {
|
||||
return reinterpret_cast<uint8_t*>(ptr(sizeof(*this)).get());
|
||||
}
|
||||
|
||||
|
||||
template<typename size_t>
|
||||
struct OX_PACKED Item {
|
||||
public:
|
||||
LittleEndian<size_t> prev = 0;
|
||||
LittleEndian<size_t> next = 0;
|
||||
|
||||
private:
|
||||
LittleEndian<size_t> m_size = sizeof(Item);
|
||||
|
||||
public:
|
||||
size_t size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void setSize(std::size_t size) {
|
||||
m_size = static_cast<size_t>(size);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
OX_CLANG_NOWARN_END
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* 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/std/std.hpp>
|
||||
|
||||
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
|
||||
|
||||
namespace ox::ptrarith {
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset = 1>
|
||||
class [[nodiscard]] Ptr {
|
||||
|
||||
private:
|
||||
uint8_t *m_dataStart = nullptr;
|
||||
size_t m_dataSize = 0;
|
||||
size_t m_itemOffset = 0;
|
||||
size_t m_itemSize = 0;
|
||||
// this should be removed later on, but the excessive validation is
|
||||
// desirable during during heavy development
|
||||
mutable uint8_t m_validated = false;
|
||||
|
||||
public:
|
||||
constexpr Ptr() noexcept = default;
|
||||
|
||||
constexpr Ptr(std::nullptr_t) noexcept;
|
||||
|
||||
constexpr Ptr(
|
||||
void *pDataStart,
|
||||
std::size_t pDataSize,
|
||||
std::size_t pItemStart,
|
||||
std::size_t pItemSize = sizeof(T),
|
||||
std::size_t pItemTypeSize = sizeof(T),
|
||||
bool pPrevalidated = false) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid() const noexcept;
|
||||
|
||||
constexpr size_t size() const noexcept;
|
||||
|
||||
constexpr size_t offset() const noexcept;
|
||||
|
||||
constexpr size_t end() noexcept;
|
||||
|
||||
constexpr const T *get() const noexcept;
|
||||
|
||||
constexpr T *get() noexcept;
|
||||
|
||||
constexpr const T *operator->() const noexcept;
|
||||
|
||||
constexpr T *operator->() noexcept;
|
||||
|
||||
constexpr operator const T*() const noexcept;
|
||||
|
||||
constexpr operator T*() noexcept;
|
||||
|
||||
constexpr const T &operator*() const noexcept;
|
||||
|
||||
constexpr T &operator*() noexcept;
|
||||
|
||||
constexpr operator size_t() const noexcept;
|
||||
|
||||
constexpr bool operator==(const Ptr<T, size_t, minOffset> &other) const noexcept;
|
||||
|
||||
constexpr bool operator!=(const Ptr<T, size_t, minOffset> &other) const noexcept;
|
||||
|
||||
template<typename SubT>
|
||||
constexpr const Ptr<SubT, size_t, sizeof(T)> subPtr(size_t offset, size_t size) const noexcept;
|
||||
|
||||
template<typename SubT>
|
||||
constexpr const Ptr<SubT, size_t, sizeof(T)> subPtr(size_t offset) const noexcept;
|
||||
|
||||
template<typename SubT>
|
||||
constexpr Ptr<SubT, size_t, sizeof(T)> subPtr(size_t offset, size_t size) noexcept;
|
||||
|
||||
template<typename SubT>
|
||||
constexpr Ptr<SubT, size_t, sizeof(T)> subPtr(size_t offset) noexcept;
|
||||
|
||||
template<typename SubT>
|
||||
constexpr const Ptr<SubT, size_t, minOffset> to() const noexcept;
|
||||
|
||||
constexpr Result<Ptr<T, size_t, minOffset>> validate() const noexcept;
|
||||
|
||||
};
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr Ptr<T, size_t, minOffset>::Ptr(std::nullptr_t) noexcept {
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr Ptr<T, size_t, minOffset>::Ptr(
|
||||
void *pDataStart,
|
||||
std::size_t pDataSize,
|
||||
std::size_t pItemStart,
|
||||
std::size_t pItemSize,
|
||||
std::size_t pItemTypeSize,
|
||||
bool pPrevalidated) noexcept {
|
||||
const auto dataSize = static_cast<size_t>(pDataSize);
|
||||
const auto itemStart = static_cast<size_t>(pItemStart);
|
||||
const auto itemSize = static_cast<size_t>(pItemSize);
|
||||
const auto itemTypeSize = static_cast<size_t>(pItemTypeSize);
|
||||
// do some sanity checks before assuming this is valid
|
||||
if (itemSize >= itemTypeSize &&
|
||||
pDataStart &&
|
||||
itemStart >= minOffset &&
|
||||
itemStart + itemSize <= dataSize) {
|
||||
m_dataStart = reinterpret_cast<uint8_t*>(pDataStart);
|
||||
m_dataSize = dataSize;
|
||||
m_itemOffset = itemStart;
|
||||
m_itemSize = itemSize;
|
||||
m_validated = pPrevalidated;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr bool Ptr<T, size_t, minOffset>::valid() const noexcept {
|
||||
m_validated = m_dataStart != nullptr;
|
||||
return m_validated;
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr size_t Ptr<T, size_t, minOffset>::size() const noexcept {
|
||||
return m_itemSize;
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr size_t Ptr<T, size_t, minOffset>::offset() const noexcept {
|
||||
return m_itemOffset;
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr size_t Ptr<T, size_t, minOffset>::end() noexcept {
|
||||
return m_itemOffset + m_itemSize;
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr const T *Ptr<T, size_t, minOffset>::get() const noexcept {
|
||||
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::get())");
|
||||
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::get())");
|
||||
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr T *Ptr<T, size_t, minOffset>::get() noexcept {
|
||||
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::get())");
|
||||
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::get())");
|
||||
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr const T *Ptr<T, size_t, minOffset>::operator->() const noexcept {
|
||||
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::operator->())");
|
||||
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::operator->())");
|
||||
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr T *Ptr<T, size_t, minOffset>::operator->() noexcept {
|
||||
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::operator->())");
|
||||
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::operator->())");
|
||||
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr Ptr<T, size_t, minOffset>::operator const T*() const noexcept {
|
||||
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::operator const T*())");
|
||||
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::operator const T*())");
|
||||
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr Ptr<T, size_t, minOffset>::operator T*() noexcept {
|
||||
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::operator T*())");
|
||||
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::operator T*())");
|
||||
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr const T &Ptr<T, size_t, minOffset>::operator*() const noexcept {
|
||||
oxAssert(m_validated, "Unvalidated pointer dereference. (ox::fs::Ptr::operator*())");
|
||||
oxAssert(valid(), "Invalid pointer dereference. (ox::fs::Ptr::operator*())");
|
||||
return *reinterpret_cast<T*>(m_dataStart + m_itemOffset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr T &Ptr<T, size_t, minOffset>::operator*() noexcept {
|
||||
oxAssert(m_validated, "Unvalidated pointer dereference. (ox::fs::Ptr::operator*())");
|
||||
oxAssert(valid(), "Invalid pointer dereference. (ox::fs::Ptr::operator*())");
|
||||
return *reinterpret_cast<T*>(m_dataStart + m_itemOffset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr Ptr<T, size_t, minOffset>::operator size_t() const noexcept {
|
||||
if (m_dataStart && m_itemOffset) {
|
||||
return m_itemOffset;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr bool Ptr<T, size_t, minOffset>::operator==(const Ptr<T, size_t, minOffset> &other) const noexcept {
|
||||
return m_dataStart == other.m_dataStart &&
|
||||
m_itemOffset == other.m_itemOffset &&
|
||||
m_itemSize == other.m_itemSize;
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr bool Ptr<T, size_t, minOffset>::operator!=(const Ptr<T, size_t, minOffset> &other) const noexcept {
|
||||
return m_dataStart != other.m_dataStart ||
|
||||
m_itemOffset != other.m_itemOffset ||
|
||||
m_itemSize != other.m_itemSize;
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
template<typename SubT>
|
||||
constexpr const Ptr<SubT, size_t, sizeof(T)> Ptr<T, size_t, minOffset>::subPtr(size_t offset, size_t size) const noexcept {
|
||||
return Ptr<SubT, size_t, sizeof(T)>(get(), this->size(), offset, size);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
template<typename SubT>
|
||||
constexpr const Ptr<SubT, size_t, sizeof(T)> Ptr<T, size_t, minOffset>::subPtr(size_t offset) const noexcept {
|
||||
oxTracef("ox.ptrarith.Ptr.subPtr", "{} {} {} {} {}", m_itemOffset, this->size(), offset, m_itemSize, (m_itemSize - offset));
|
||||
return subPtr<SubT>(offset, m_itemSize - offset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
template<typename SubT>
|
||||
constexpr Ptr<SubT, size_t, sizeof(T)> Ptr<T, size_t, minOffset>::subPtr(size_t offset, size_t size) noexcept {
|
||||
return Ptr<SubT, size_t, sizeof(T)>(get(), this->size(), offset, size);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
template<typename SubT>
|
||||
constexpr Ptr<SubT, size_t, sizeof(T)> Ptr<T, size_t, minOffset>::subPtr(size_t offset) noexcept {
|
||||
oxTracef("ox.ptrarith.Ptr.subPtr", "{} {} {} {} {}", m_itemOffset, this->size(), offset, m_itemSize, (m_itemSize - offset));
|
||||
return subPtr<SubT>(offset, m_itemSize - offset);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
template<typename SubT>
|
||||
constexpr const Ptr<SubT, size_t, minOffset> Ptr<T, size_t, minOffset>::to() const noexcept {
|
||||
return Ptr<SubT, size_t, minOffset>(m_dataStart, m_dataSize, m_itemOffset, m_itemSize);
|
||||
}
|
||||
|
||||
template<typename T, typename size_t, size_t minOffset>
|
||||
constexpr Result<Ptr<T, size_t, minOffset>> Ptr<T, size_t, minOffset>::validate() const noexcept {
|
||||
if (valid()) {
|
||||
return *this;
|
||||
}
|
||||
return ox::Error(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OX_CLANG_NOWARN_END
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <ox/fs/filestore/filestoretemplate.hpp>
|
||||
|
||||
namespace ox {
|
||||
|
||||
template class FileStoreTemplate<uint16_t>;
|
||||
template class FileStoreTemplate<uint32_t>;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <ox/fs/filesystem/directory.hpp>
|
||||
|
||||
namespace ox {
|
||||
|
||||
template class Directory<FileStore16, uint16_t>;
|
||||
template class Directory<FileStore32, uint32_t>;
|
||||
|
||||
template struct DirectoryEntry<uint16_t>;
|
||||
template struct DirectoryEntry<uint32_t>;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <ox/model/modelops.hpp>
|
||||
|
||||
#include <ox/fs/filesystem/filelocation.hpp>
|
||||
|
||||
namespace ox {
|
||||
|
||||
FileAddress::FileAddress(const FileAddress &other) noexcept {
|
||||
operator=(other);
|
||||
}
|
||||
|
||||
FileAddress::FileAddress(FileAddress &&other) noexcept {
|
||||
operator=(std::move(other));
|
||||
}
|
||||
|
||||
FileAddress::FileAddress(std::nullptr_t) noexcept {
|
||||
}
|
||||
|
||||
FileAddress::FileAddress(uint64_t inode) noexcept {
|
||||
m_data.inode = inode;
|
||||
m_type = FileAddressType::Inode;
|
||||
}
|
||||
|
||||
FileAddress::FileAddress(ox::StringViewCR path) noexcept {
|
||||
auto pathSize = path.bytes();
|
||||
m_data.path = new char[pathSize + 1];
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
memcpy(m_data.path, path.data(), pathSize);
|
||||
m_data.path[pathSize] = 0;
|
||||
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||
m_type = FileAddressType::Path;
|
||||
}
|
||||
|
||||
FileAddress &FileAddress::operator=(const FileAddress &other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
cleanup();
|
||||
m_type = other.m_type;
|
||||
switch (m_type) {
|
||||
case FileAddressType::Path:
|
||||
{
|
||||
if (other.m_data.path) {
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
auto strSize = ox::strlen(other.m_data.path) + 1;
|
||||
m_data.path = new char[strSize];
|
||||
ox::memcpy(m_data.path, other.m_data.path, strSize);
|
||||
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||
} else {
|
||||
m_data.constPath = "";
|
||||
m_type = FileAddressType::ConstPath;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Inode:
|
||||
m_data = other.m_data;
|
||||
break;
|
||||
case FileAddressType::None:
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
FileAddress &FileAddress::operator=(FileAddress &&other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
cleanup();
|
||||
m_type = other.m_type;
|
||||
switch (m_type) {
|
||||
case FileAddressType::Path:
|
||||
{
|
||||
m_data.path = other.m_data.path;
|
||||
break;
|
||||
}
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Inode:
|
||||
m_data = other.m_data;
|
||||
break;
|
||||
case FileAddressType::None:
|
||||
break;
|
||||
}
|
||||
other.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool FileAddress::operator==(FileAddress const&other) const noexcept {
|
||||
if (m_type != other.m_type) {
|
||||
auto const aIsPath =
|
||||
m_type == FileAddressType::Path || m_type == FileAddressType::ConstPath;
|
||||
auto const bIsPath =
|
||||
other.m_type == FileAddressType::Path || other.m_type == FileAddressType::ConstPath;
|
||||
if (!(aIsPath && bIsPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
switch (m_type) {
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Path: {
|
||||
auto const a = getPath();
|
||||
auto const b = other.getPath();
|
||||
return (other.m_type == FileAddressType::ConstPath || other.m_type == FileAddressType::Path)
|
||||
&& (a.value == b.value);
|
||||
}
|
||||
case FileAddressType::Inode:
|
||||
return m_data.inode == other.m_data.inode;
|
||||
case FileAddressType::None:
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileAddress::operator==(StringViewCR path) const noexcept {
|
||||
auto [p, err] = getPath();
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
return path == p;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <ox/std/error.hpp>
|
||||
#include <ox/std/utility.hpp>
|
||||
|
||||
#include <ox/fs/filesystem/filesystem.hpp>
|
||||
|
||||
namespace ox {
|
||||
|
||||
Result<const char*> MemFS::directAccess(const FileAddress &addr) const noexcept {
|
||||
switch (addr.type()) {
|
||||
case FileAddressType::Inode:
|
||||
return directAccess(addr.getInode().value);
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Path:
|
||||
return directAccess(StringView(addr.getPath().value));
|
||||
default:
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
Error FileSystem::read(const FileAddress &addr, void *buffer, std::size_t size) noexcept {
|
||||
switch (addr.type()) {
|
||||
case FileAddressType::Inode:
|
||||
return readFileInode(addr.getInode().value, buffer, size);
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Path:
|
||||
return readFilePath(StringView(addr.getPath().value), buffer, size);
|
||||
default:
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
Result<Buffer> FileSystem::read(FileAddress const &addr, size_t const size) noexcept {
|
||||
Result<Buffer> out;
|
||||
out.value.resize(size);
|
||||
switch (addr.type()) {
|
||||
case FileAddressType::Inode:
|
||||
OX_RETURN_ERROR(readFileInode(addr.getInode().value, out.value.data(), size));
|
||||
break;
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Path:
|
||||
OX_RETURN_ERROR(readFilePath(StringView{addr.getPath().value}, out.value.data(), size));
|
||||
break;
|
||||
default:
|
||||
return ox::Error{1};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Result<Buffer> FileSystem::read(StringViewCR path, size_t const size) noexcept {
|
||||
Result<Buffer> out;
|
||||
out.value.resize(size);
|
||||
OX_RETURN_ERROR(readFilePath(path, out.value.data(), size));
|
||||
return out;
|
||||
}
|
||||
|
||||
Result<Buffer> FileSystem::read(const FileAddress &addr) noexcept {
|
||||
OX_REQUIRE(s, stat(addr));
|
||||
Buffer buff(static_cast<std::size_t>(s.size));
|
||||
OX_RETURN_ERROR(read(addr, buff.data(), buff.size()));
|
||||
return buff;
|
||||
}
|
||||
|
||||
Result<Buffer> FileSystem::read(StringViewCR path) noexcept {
|
||||
OX_REQUIRE(s, statPath(path));
|
||||
Buffer buff(static_cast<std::size_t>(s.size));
|
||||
OX_RETURN_ERROR(readFilePath(path, buff.data(), buff.size()));
|
||||
return buff;
|
||||
}
|
||||
|
||||
Error FileSystem::read(
|
||||
FileAddress const &addr,
|
||||
std::size_t const readStart,
|
||||
std::size_t const readSize,
|
||||
void *buffer,
|
||||
std::size_t *size) noexcept {
|
||||
switch (addr.type()) {
|
||||
case FileAddressType::Inode:
|
||||
return readFileInodeRange(addr.getInode().value, readStart, readSize, buffer, size);
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Path:
|
||||
return readFilePathRange(addr.getPath().value, readStart, readSize, buffer, size);
|
||||
default:
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
Result<size_t> FileSystem::read(
|
||||
StringViewCR path,
|
||||
std::size_t const readStart,
|
||||
std::size_t const readSize,
|
||||
Span<char> buff) noexcept {
|
||||
size_t szOut{buff.size()};
|
||||
OX_RETURN_ERROR(readFilePathRange(path, readStart, readSize, buff.data(), &szOut));
|
||||
return szOut;
|
||||
}
|
||||
|
||||
Error FileSystem::write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType) noexcept {
|
||||
switch (addr.type()) {
|
||||
case FileAddressType::Inode:
|
||||
return writeFileInode(addr.getInode().value, buffer, size, fileType);
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Path:
|
||||
return writeFilePath(StringView(addr.getPath().value), buffer, size, fileType);
|
||||
default:
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
Result<FileStat> FileSystem::stat(const FileAddress &addr) const noexcept {
|
||||
switch (addr.type()) {
|
||||
case FileAddressType::Inode:
|
||||
return statInode(addr.getInode().value);
|
||||
case FileAddressType::ConstPath:
|
||||
case FileAddressType::Path:
|
||||
return statPath(StringView(addr.getPath().value));
|
||||
default:
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
template class FileSystemTemplate<FileStore16, Directory16>;
|
||||
template class FileSystemTemplate<FileStore32, Directory32>;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <ox/std/error.hpp>
|
||||
|
||||
#include <ox/fs/filesystem/passthroughfs.hpp>
|
||||
|
||||
#if defined(OX_HAS_PASSTHROUGHFS)
|
||||
|
||||
#include <fstream>
|
||||
#include <string_view>
|
||||
|
||||
namespace ox {
|
||||
|
||||
PassThroughFS::PassThroughFS(StringViewCR dirPath) {
|
||||
m_path = std::string_view(dirPath.data(), dirPath.bytes());
|
||||
}
|
||||
|
||||
PassThroughFS::~PassThroughFS() noexcept = default;
|
||||
|
||||
String PassThroughFS::basePath() const noexcept {
|
||||
return ox::String(m_path.string().c_str());
|
||||
}
|
||||
|
||||
Error PassThroughFS::mkdir(StringViewCR path, bool recursive) noexcept {
|
||||
bool success = false;
|
||||
const auto p = m_path / stripSlash(path);
|
||||
const auto u8p = p.u8string();
|
||||
oxTrace("ox.fs.PassThroughFS.mkdir", std::bit_cast<const char*>(u8p.c_str()));
|
||||
if (recursive) {
|
||||
std::error_code ec;
|
||||
const auto isDir = std::filesystem::is_directory(p, ec);
|
||||
if (isDir && !ec.value()) {
|
||||
success = true;
|
||||
} else {
|
||||
success = std::filesystem::create_directories(p, ec);
|
||||
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed"));
|
||||
}
|
||||
} else {
|
||||
std::error_code ec;
|
||||
const auto isDir = std::filesystem::is_directory(p, ec);
|
||||
if (isDir) {
|
||||
success = true;
|
||||
} else {
|
||||
success = std::filesystem::create_directory(p, ec);
|
||||
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed"));
|
||||
}
|
||||
}
|
||||
return ox::Error(success ? 0 : 1);
|
||||
}
|
||||
|
||||
Error PassThroughFS::move(StringViewCR src, StringViewCR dest) noexcept {
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(m_path / stripSlash(src), m_path / stripSlash(dest), ec);
|
||||
if (ec.value()) {
|
||||
return ox::Error(1);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<Vector<String>> PassThroughFS::ls(StringViewCR dir) const noexcept {
|
||||
Vector<String> out;
|
||||
std::error_code ec;
|
||||
const auto di = std::filesystem::directory_iterator(m_path / stripSlash(dir), ec);
|
||||
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: ls failed"));
|
||||
for (const auto &p : di) {
|
||||
const auto u8p = p.path().filename().u8string();
|
||||
out.emplace_back(reinterpret_cast<const char*>(u8p.c_str()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Error PassThroughFS::resize(uint64_t, void*) noexcept {
|
||||
// unsupported
|
||||
return ox::Error(1, "resize is not supported by PassThroughFS");
|
||||
}
|
||||
|
||||
Result<FileStat> PassThroughFS::statInode(uint64_t) const noexcept {
|
||||
// unsupported
|
||||
return ox::Error(1, "statInode(uint64_t) is not supported by PassThroughFS");
|
||||
}
|
||||
|
||||
Result<FileStat> PassThroughFS::statPath(StringViewCR path) const noexcept {
|
||||
std::error_code ec;
|
||||
const auto p = m_path / stripSlash(path);
|
||||
const FileType type = std::filesystem::is_directory(p, ec) ?
|
||||
FileType::Directory : FileType::NormalFile;
|
||||
oxTracef("ox.fs.PassThroughFS.statInode", "{} {}", ec.message(), path);
|
||||
const uint64_t size = type == FileType::Directory ? 0 : std::filesystem::file_size(p, ec);
|
||||
oxTracef("ox.fs.PassThroughFS.statInode.size", "{} {}", path, size);
|
||||
if (auto err = ec.value()) {
|
||||
return ox::Error{static_cast<ox::ErrorCode>(err), "PassThroughFS: stat failed"};
|
||||
}
|
||||
return FileStat{0, 0, size, type};
|
||||
}
|
||||
|
||||
uint64_t PassThroughFS::spaceNeeded(uint64_t size) const noexcept {
|
||||
return size;
|
||||
}
|
||||
|
||||
Result<uint64_t> PassThroughFS::available() const noexcept {
|
||||
std::error_code ec;
|
||||
const auto s = std::filesystem::space(m_path, ec);
|
||||
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: could not get FS size"));
|
||||
return s.available;
|
||||
}
|
||||
|
||||
Result<uint64_t> PassThroughFS::size() const noexcept {
|
||||
std::error_code ec;
|
||||
const auto s = std::filesystem::space(m_path, ec);
|
||||
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: could not get FS size"));
|
||||
return s.capacity;
|
||||
}
|
||||
|
||||
char *PassThroughFS::buff() noexcept {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Error PassThroughFS::walk(Error(*)(uint8_t, uint64_t, uint64_t)) noexcept {
|
||||
return ox::Error(1, "walk(Error(*)(uint8_t, uint64_t, uint64_t)) is not supported by PassThroughFS");
|
||||
}
|
||||
|
||||
bool PassThroughFS::valid() const noexcept {
|
||||
std::error_code ec;
|
||||
const auto out = std::filesystem::is_directory(m_path, ec);
|
||||
if (!ec.value()) {
|
||||
return out;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Error PassThroughFS::readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
|
||||
try {
|
||||
std::ifstream file((m_path / stripSlash(path)), std::ios::binary | std::ios::ate);
|
||||
const std::size_t size = static_cast<std::size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (size > buffSize) {
|
||||
oxTracef("ox.fs.PassThroughFS.read.error", "Read failed: Buffer too small: {}", path);
|
||||
return ox::Error(1);
|
||||
}
|
||||
file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(buffSize));
|
||||
} catch (const std::fstream::failure &f) {
|
||||
oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
|
||||
return ox::Error(2);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Error PassThroughFS::readFileInode(uint64_t, void*, std::size_t) noexcept {
|
||||
// unsupported
|
||||
return ox::Error(1, "readFileInode(uint64_t, void*, std::size_t) is not supported by PassThroughFS");
|
||||
}
|
||||
|
||||
Error PassThroughFS::readFilePathRange(
|
||||
StringViewCR path, size_t const readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
|
||||
try {
|
||||
std::ifstream file(m_path / stripSlash(path), std::ios::binary | std::ios::ate);
|
||||
auto const size = static_cast<size_t>(file.tellg());
|
||||
readSize = ox::min(readSize, size);
|
||||
file.seekg(static_cast<off_t>(readStart), std::ios::beg);
|
||||
if (readSize > *buffSize) {
|
||||
oxTracef("ox.fs.PassThroughFS.read.error", "Read failed: Buffer too small: {}", path);
|
||||
return ox::Error{1};
|
||||
}
|
||||
file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(readSize));
|
||||
return {};
|
||||
} catch (std::fstream::failure const &f) {
|
||||
oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
|
||||
return ox::Error{2};
|
||||
}
|
||||
}
|
||||
|
||||
Error PassThroughFS::readFileInodeRange(uint64_t, std::size_t, std::size_t, void*, std::size_t*) noexcept {
|
||||
// unsupported
|
||||
return ox::Error(1, "read(uint64_t, std::size_t, std::size_t, void*, std::size_t*) is not supported by PassThroughFS");
|
||||
}
|
||||
|
||||
Error PassThroughFS::removePath(StringViewCR path, bool const recursive) noexcept {
|
||||
if (recursive) {
|
||||
return ox::Error{std::filesystem::remove_all(m_path / stripSlash(path)) == 0};
|
||||
} else {
|
||||
return ox::Error{!std::filesystem::remove(m_path / stripSlash(path))};
|
||||
}
|
||||
}
|
||||
|
||||
Error PassThroughFS::writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType) noexcept {
|
||||
const auto p = (m_path / stripSlash(path));
|
||||
try {
|
||||
std::ofstream f(p, std::ios::binary);
|
||||
f.write(static_cast<const char*>(buffer), static_cast<std::streamsize>(size));
|
||||
} catch (const std::fstream::failure &f) {
|
||||
oxTracef("ox.fs.PassThroughFS.read.error", "Write of {} failed: {}", path, f.what());
|
||||
return ox::Error(1);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Error PassThroughFS::writeFileInode(uint64_t, const void*, uint64_t, FileType) noexcept {
|
||||
// unsupported
|
||||
return ox::Error(1, "writeFileInode(uint64_t, void*, uint64_t, uint8_t) is not supported by PassThroughFS");
|
||||
}
|
||||
|
||||
std::string_view PassThroughFS::stripSlash(StringView path) noexcept {
|
||||
for (auto i = 0u; i < path.size() && path[0] == '/'; i++) {
|
||||
path = substr(path, 1);
|
||||
}
|
||||
return {path.data(), path.bytes()};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <ox/std/memops.hpp>
|
||||
#include <ox/std/strops.hpp>
|
||||
#include <ox/std/trace.hpp>
|
||||
#include <ox/fs/filesystem/pathiterator.hpp>
|
||||
|
||||
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
|
||||
|
||||
namespace ox {
|
||||
|
||||
PathIterator::PathIterator(const char *path, std::size_t maxSize, std::size_t iterator) {
|
||||
m_path = path;
|
||||
m_maxSize = maxSize;
|
||||
m_iterator = iterator;
|
||||
}
|
||||
|
||||
PathIterator::PathIterator(const char *path): PathIterator(path, ox::strlen(path)) {
|
||||
}
|
||||
|
||||
PathIterator::PathIterator(StringViewCR path): PathIterator(path.data(), path.bytes()) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 if no error
|
||||
*/
|
||||
Error PathIterator::dirPath(char *out, std::size_t outSize) {
|
||||
const auto idx = ox::lastIndexOf(m_path, '/', m_maxSize);
|
||||
const auto size = static_cast<std::size_t>(idx) + 1;
|
||||
if (idx >= 0 && size < outSize) {
|
||||
ox::memcpy(out, m_path, size);
|
||||
out[size] = 0;
|
||||
return {};
|
||||
} else {
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the get item in the path
|
||||
Error PathIterator::get(StringView &fileName) {
|
||||
std::size_t size = 0;
|
||||
if (m_iterator >= m_maxSize) {
|
||||
oxTracef("ox.fs.PathIterator.get", "m_iterator ({}) >= m_maxSize ({})", m_iterator, m_maxSize);
|
||||
return ox::Error(1);
|
||||
}
|
||||
if (!ox::strlen(&m_path[m_iterator])) {
|
||||
oxTrace("ox.fs.PathIterator.get", "!ox::strlen(&m_path[m_iterator])");
|
||||
return ox::Error(1);
|
||||
}
|
||||
auto start = m_iterator;
|
||||
if (m_path[start] == '/') {
|
||||
start++;
|
||||
}
|
||||
// end is at the next /
|
||||
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
|
||||
// correct end if it is invalid, which happens if there is no next /
|
||||
if (!substr) {
|
||||
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
|
||||
}
|
||||
const auto end = static_cast<size_t>(substr - m_path);
|
||||
size = end - start;
|
||||
// cannot fit the output in the output parameter
|
||||
if (size >= MaxFileNameLength || size == 0) {
|
||||
return ox::Error(1);
|
||||
}
|
||||
fileName = ox::substr(m_path, start, start + size);
|
||||
// truncate trailing /
|
||||
if (size && fileName[size - 1] == '/') {
|
||||
fileName = ox::substr(m_path, start, start + size - 1);
|
||||
}
|
||||
oxAssert(fileName[fileName.size()-1] != '/', "name ends in /");
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 if no error
|
||||
*/
|
||||
Error PathIterator::next(StringView &fileName) {
|
||||
std::size_t size = 0;
|
||||
auto retval = ox::Error(1);
|
||||
if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) {
|
||||
retval = {};
|
||||
if (m_path[m_iterator] == '/') {
|
||||
m_iterator++;
|
||||
}
|
||||
const auto start = m_iterator;
|
||||
// end is at the next /
|
||||
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
|
||||
// correct end if it is invalid, which happens if there is no next /
|
||||
if (!substr) {
|
||||
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
|
||||
}
|
||||
const auto end = static_cast<size_t>(substr - m_path);
|
||||
size = end - start;
|
||||
// cannot fit the output in the output parameter
|
||||
if (size >= MaxFileNameLength) {
|
||||
return ox::Error(1);
|
||||
}
|
||||
fileName = ox::substr(m_path, start, start + size);
|
||||
// truncate trailing /
|
||||
while (fileName.size() && fileName[fileName.size() - 1] == '/') {
|
||||
fileName = ox::substr(m_path, start, start + size);
|
||||
}
|
||||
m_iterator += size;
|
||||
oxAssert(fileName.size() == 0 || fileName[fileName.size()-1] != '/', "name ends in /");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
Result<std::size_t> PathIterator::nextSize() const {
|
||||
std::size_t size = 0;
|
||||
auto retval = ox::Error(1);
|
||||
auto it = m_iterator;
|
||||
if (it < m_maxSize && ox::strlen(&m_path[it])) {
|
||||
retval = {};
|
||||
if (m_path[it] == '/') {
|
||||
it++;
|
||||
}
|
||||
const auto start = it;
|
||||
// end is at the next /
|
||||
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
|
||||
// correct end if it is invalid, which happens if there is no next /
|
||||
if (!substr) {
|
||||
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
|
||||
}
|
||||
const auto end = static_cast<std::size_t>(substr - m_path);
|
||||
size = end - start;
|
||||
}
|
||||
it += size;
|
||||
return {size, retval};
|
||||
}
|
||||
|
||||
bool PathIterator::hasNext() const {
|
||||
std::size_t size = 0;
|
||||
if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) {
|
||||
std::size_t start = m_iterator;
|
||||
if (m_path[start] == '/') {
|
||||
start++;
|
||||
}
|
||||
// end is at the next /
|
||||
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
|
||||
// correct end if it is invalid, which happens if there is no next /
|
||||
if (!substr) {
|
||||
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
|
||||
}
|
||||
const auto end = static_cast<std::size_t>(substr - m_path);
|
||||
size = end - start;
|
||||
}
|
||||
return size > 0;
|
||||
}
|
||||
|
||||
bool PathIterator::valid() const {
|
||||
return m_iterator < m_maxSize && m_path[m_iterator] != 0;
|
||||
}
|
||||
|
||||
PathIterator PathIterator::next() const {
|
||||
std::size_t size = 0;
|
||||
auto iterator = m_iterator;
|
||||
if (iterator < m_maxSize && ox::strlen(&m_path[iterator])) {
|
||||
if (m_path[iterator] == '/') {
|
||||
iterator++;
|
||||
}
|
||||
const auto start = iterator;
|
||||
// end is at the next /
|
||||
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
|
||||
// correct end if it is invalid, which happens if there is no next /
|
||||
if (!substr) {
|
||||
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
|
||||
}
|
||||
const auto end = static_cast<std::size_t>(substr - m_path);
|
||||
size = end - start;
|
||||
}
|
||||
iterator += size;
|
||||
return {m_path, m_maxSize, iterator + 1};
|
||||
}
|
||||
|
||||
const char *PathIterator::fullPath() const {
|
||||
return m_path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OX_CLANG_NOWARN_END
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
|
||||
struct Buff {
|
||||
char *data = nullptr;
|
||||
std::size_t size = 0;
|
||||
};
|
||||
|
||||
static ox::Result<Buff> loadFsBuff(const char *path) noexcept {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file.good()) {
|
||||
oxErrorf("Could not find OxFS file: {}", path);
|
||||
return ox::Error(1, "Could not find OxFS file");
|
||||
}
|
||||
try {
|
||||
const auto size = static_cast<std::size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
const auto buff = new char[size];
|
||||
file.read(buff, static_cast<std::streamsize>(size));
|
||||
return Buff{buff, size};
|
||||
} catch (const std::ios_base::failure &e) {
|
||||
oxErrorf("Could not read OxFS file: {}", e.what());
|
||||
return ox::Error(2, "Could not read OxFS file");
|
||||
}
|
||||
}
|
||||
|
||||
static ox::Result<ox::UPtr<ox::FileSystem>> loadFs(const char *path) noexcept {
|
||||
OX_REQUIRE(buff, loadFsBuff(path));
|
||||
return {ox::make_unique<ox::FileSystem32>(buff.data, buff.size)};
|
||||
}
|
||||
|
||||
static ox::Error runLs(ox::FileSystem *fs, ox::Span<const char*> args) noexcept {
|
||||
if (args.size() < 2) {
|
||||
oxErr("Must provide a directory to ls\n");
|
||||
return ox::Error(1);
|
||||
}
|
||||
OX_REQUIRE(files, fs->ls(args[1]));
|
||||
for (const auto &file : files) {
|
||||
oxOutf("{}\n", file);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error runRead(ox::FileSystem *fs, ox::Span<const char*> args) noexcept {
|
||||
if (args.size() < 2) {
|
||||
oxErr("Must provide a path to a file to read\n");
|
||||
return ox::Error(1);
|
||||
}
|
||||
OX_REQUIRE(buff, fs->read(ox::StringView(args[1])));
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
std::ignore = fwrite(buff.data(), sizeof(decltype(buff)::value_type), buff.size(), stdout);
|
||||
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error run(int argc, const char **argv) noexcept {
|
||||
if (argc < 3) {
|
||||
oxErr("OxFS file and subcommand arguments are required\n");
|
||||
return ox::Error(1);
|
||||
}
|
||||
auto const args = ox::Span{argv, static_cast<size_t>(argc)};
|
||||
auto const fsPath = args[1];
|
||||
ox::String subCmd(args[2]);
|
||||
OX_REQUIRE(fs, loadFs(fsPath));
|
||||
if (subCmd == "ls") {
|
||||
return runLs(fs.get(), args + 2);
|
||||
} else if (subCmd == "read") {
|
||||
return runRead(fs.get(), args + 2);
|
||||
}
|
||||
return ox::Error(1);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
auto err = run(argc, argv);
|
||||
oxAssert(err, "unhandled error");
|
||||
return static_cast<int>(err);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
add_executable(
|
||||
FSTests
|
||||
tests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
FSTests
|
||||
OxFS
|
||||
OxMetalClaw
|
||||
)
|
||||
|
||||
add_test("[ox/fs] PtrArith::setSize" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PtrArith::setSize)
|
||||
|
||||
add_test("[ox/fs] PathIterator::next1" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next1)
|
||||
add_test("[ox/fs] PathIterator::next2" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next2)
|
||||
add_test("[ox/fs] PathIterator::next3" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next3)
|
||||
add_test("[ox/fs] PathIterator::next4" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next4)
|
||||
add_test("[ox/fs] PathIterator::next5" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next5)
|
||||
add_test("[ox/fs] PathIterator::hasNext" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::hasNext)
|
||||
|
||||
add_test("[ox/fs] PathIterator::dirPath" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::dirPath)
|
||||
|
||||
add_test("[ox/fs] NodeBuffer::insert" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests "NodeBuffer::insert")
|
||||
add_test("[ox/fs] FileStore::readWrite" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests "FileStore::readWrite")
|
||||
|
||||
add_test("[ox/fs] Directory" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests "Directory")
|
||||
add_test("[ox/fs] FileSystem" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests "FileSystem")
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
// make sure asserts are enabled for the test file
|
||||
#undef NDEBUG
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
#include <ox/std/std.hpp>
|
||||
#include <ox/fs/ptrarith/nodebuffer.hpp>
|
||||
#include <ox/fs/filestore/filestoretemplate.hpp>
|
||||
#include <ox/fs/filesystem/filesystem.hpp>
|
||||
|
||||
template<typename T>
|
||||
struct OX_PACKED NodeType: public ox::ptrarith::Item<T> {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
size_t fullSize() const {
|
||||
return this->size() + sizeof(*this);
|
||||
}
|
||||
};
|
||||
|
||||
const std::map<ox::StringView, std::function<ox::Error(ox::StringView)>> tests = {
|
||||
{
|
||||
{
|
||||
"PtrArith::setSize",
|
||||
[](ox::StringView) {
|
||||
using BuffPtr_t = uint32_t;
|
||||
ox::Vector<char> buff(5 * ox::units::MB);
|
||||
auto buffer = new (buff.data()) ox::ptrarith::NodeBuffer<BuffPtr_t, NodeType<BuffPtr_t>>(buff.size());
|
||||
using String = ox::IString<6>;
|
||||
auto a1 = buffer->malloc(sizeof(String)).value;
|
||||
auto a2 = buffer->malloc(sizeof(String)).value;
|
||||
oxAssert(a1.valid(), "Allocation 1 failed.");
|
||||
oxAssert(a2.valid(), "Allocation 2 failed.");
|
||||
auto s1Buff = buffer->dataOf<String>(a1);
|
||||
auto s2Buff = buffer->dataOf<String>(a2);
|
||||
oxAssert(s1Buff.valid(), "s1 allocation 1 failed.");
|
||||
oxAssert(s2Buff.valid(), "s2 allocation 2 failed.");
|
||||
auto &s1 = *new (s1Buff) String("asdf");
|
||||
auto &s2 = *new (s2Buff) String("aoeu");
|
||||
oxTrace("test") << "s1: " << s1.c_str();
|
||||
oxTrace("test") << "s2: " << s2.c_str();
|
||||
oxAssert(s1 == "asdf", "Allocation 1 not as expected.");
|
||||
oxAssert(s2 == "aoeu", "Allocation 2 not as expected.");
|
||||
oxAssert(buffer->free(a1), "Free failed.");
|
||||
oxAssert(buffer->free(a2), "Free failed.");
|
||||
oxAssert(buffer->setSize(buffer->size() - buffer->available()), "Resize failed.");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"PathIterator::next1",
|
||||
[](ox::StringView) {
|
||||
auto constexpr path = ox::StringLiteral("/usr/share/charset.gbag");
|
||||
ox::PathIterator it(path.c_str(), path.size());
|
||||
ox::StringView buff;
|
||||
oxAssert(it.next(buff) == 0 && buff == "usr", "PathIterator shows wrong next");
|
||||
oxAssert(it.next(buff) == 0 && buff == "share", "PathIterator shows wrong next");
|
||||
oxAssert(it.next(buff) == 0 && buff == "charset.gbag", "PathIterator shows wrong next");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"PathIterator::next2",
|
||||
[](ox::StringView) {
|
||||
auto constexpr path = ox::StringView("/usr/share/");
|
||||
ox::PathIterator it(path);
|
||||
ox::StringView buff;
|
||||
oxAssert(it.next(buff), "PathIterator::next returned error");
|
||||
ox::expect(buff, "usr");
|
||||
oxAssert(it.next(buff), "PathIterator::next returned error");
|
||||
ox::expect(buff, "share");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"PathIterator::next3",
|
||||
[](ox::StringView) {
|
||||
auto const path = ox::String("/");
|
||||
ox::PathIterator it(path.c_str(), path.size());
|
||||
ox::StringView buff;
|
||||
oxAssert(it.next(buff) == 0 && buff == "\0", "PathIterator shows wrong next");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"PathIterator::next4",
|
||||
[](ox::StringView) {
|
||||
auto constexpr path = ox::StringLiteral("usr/share/charset.gbag");
|
||||
ox::PathIterator it(path);
|
||||
ox::StringView buff;
|
||||
oxAssert(it.next(buff) == 0 && buff == "usr", "PathIterator shows wrong next");
|
||||
oxAssert(it.next(buff) == 0 && buff == "share", "PathIterator shows wrong next");
|
||||
oxAssert(it.next(buff) == 0 && buff == "charset.gbag", "PathIterator shows wrong next");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"PathIterator::next5",
|
||||
[](ox::StringView) {
|
||||
auto const path = ox::String("usr/share/");
|
||||
ox::PathIterator it(path.c_str(), path.size());
|
||||
ox::StringView buff;
|
||||
oxAssert(it.next(buff) == 0 && buff == "usr", "PathIterator shows wrong next");
|
||||
oxAssert(it.next(buff) == 0 && buff == "share", "PathIterator shows wrong next");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"PathIterator::dirPath",
|
||||
[] (ox::StringView) {
|
||||
auto constexpr path = ox::StringLiteral("/usr/share/charset.gbag");
|
||||
ox::PathIterator it(path.c_str(), path.size());
|
||||
auto buff = static_cast<char*>(ox_alloca(path.size() + 1));
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
oxAssert(it.dirPath(buff, path.size()) == 0 && ox::strcmp(buff, "/usr/share/") == 0, "PathIterator shows incorrect dir path");
|
||||
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"PathIterator::hasNext",
|
||||
[](ox::StringView) {
|
||||
const auto path = "/file1";
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
ox::PathIterator it(path, ox::strlen(path));
|
||||
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||
oxAssert(it.hasNext(), "PathIterator shows incorrect hasNext");
|
||||
oxAssert(!it.next().hasNext(), "PathIterator shows incorrect hasNext");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"Ptr::subPtr",
|
||||
[](ox::StringView) {
|
||||
constexpr auto buffLen = 5000;
|
||||
ox::ptrarith::Ptr<uint8_t, uint32_t> p(ox_alloca(buffLen), buffLen, 500, 500);
|
||||
oxAssert(p.valid(), "Ptr::subPtr: Ptr p is invalid.");
|
||||
|
||||
auto subPtr = p.subPtr<uint64_t>(50);
|
||||
oxAssert(subPtr.valid(), "Ptr::subPtr: Ptr subPtr is invalid.");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"NodeBuffer::insert",
|
||||
[](ox::StringView) {
|
||||
constexpr auto buffLen = 5000;
|
||||
auto list = new (ox_alloca(buffLen)) ox::ptrarith::NodeBuffer<uint32_t, ox::FileStoreItem<uint32_t>>(buffLen);
|
||||
oxAssert(list->malloc(50).value.valid(), "NodeBuffer::insert: malloc 1 failed");
|
||||
oxAssert(list->malloc(50).value.valid(), "NodeBuffer::insert: malloc 2 failed");
|
||||
auto first = list->firstItem();
|
||||
oxAssert(first.valid(), "NodeBuffer::insert: Could not access first item");
|
||||
oxAssert(first->size() == 50, "NodeBuffer::insert: First item size invalid");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"FileStore::readWrite",
|
||||
[](ox::StringView) {
|
||||
constexpr auto buffLen = 5000;
|
||||
constexpr auto str1 = "Hello, World!";
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
constexpr auto str1Len = ox::strlen(str1) + 1;
|
||||
constexpr auto str2 = "Hello, Moon!";
|
||||
constexpr auto str2Len = ox::strlen(str2) + 1;
|
||||
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||
auto list = new (ox_alloca(buffLen)) ox::ptrarith::NodeBuffer<uint32_t, ox::FileStoreItem<uint32_t>>(buffLen);
|
||||
oxAssert(ox::FileStore32::format(list, buffLen), "FileStore::format failed.");
|
||||
ox::FileStore32 fileStore(list, buffLen);
|
||||
oxAssert(fileStore.write(4, str1, str1Len, 1), "FileStore::write 1 failed.");
|
||||
oxAssert(fileStore.write(5, str2, str2Len, 1), "FileStore::write 2 failed.");
|
||||
|
||||
char str1Read[str1Len];
|
||||
size_t str1ReadSize = 0;
|
||||
oxAssert(fileStore.read(4, reinterpret_cast<void*>(str1Read), str1Len, &str1ReadSize), "FileStore::read 1 failed.");
|
||||
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"Directory",
|
||||
[](ox::StringView) {
|
||||
ox::Vector<uint8_t> fsBuff(5000);
|
||||
oxAssert(ox::FileStore32::format(fsBuff.data(), fsBuff.size()), "FS format failed");
|
||||
ox::FileStore32 fileStore(fsBuff.data(), fsBuff.size());
|
||||
ox::Directory32 dir(fileStore, 105);
|
||||
|
||||
oxTrace("ox.fs.test.Directory") << "Init";
|
||||
oxAssert(dir.init(), "Init failed");
|
||||
|
||||
oxTrace("ox.fs.test.Directory") << "write 1";
|
||||
oxAssert(dir.write("/file1", 1), "Directory write of file1 failed");
|
||||
|
||||
oxTrace("ox.fs.test.Directory") << "find";
|
||||
oxAssert(dir.find("file1").error, "Could not find file1");
|
||||
oxAssert(dir.find("file1").value == 1, "Could not find file1");
|
||||
|
||||
oxTrace("ox.fs.test.Directory") << "write 2";
|
||||
oxAssert(dir.write("/file3", 3), "Directory write of file3 failed");
|
||||
|
||||
oxTrace("ox.fs.test.Directory") << "write 3";
|
||||
oxAssert(dir.write("/file2", 2), "Directory write of file2 failed");
|
||||
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
"FileSystem",
|
||||
[](ox::StringView) {
|
||||
ox::Vector<uint8_t> fsBuff(5000);
|
||||
oxTrace("ox.fs.test.FileSystem") << "format";
|
||||
oxAssert(ox::FileSystem32::format(fsBuff.data(), fsBuff.size()), "FileSystem format failed");
|
||||
ox::FileSystem32 fs(ox::FileStore32(fsBuff.data(), fsBuff.size()));
|
||||
oxTrace("ox.fs.test.FileSystem") << "mkdir";
|
||||
oxAssert(fs.mkdir("/dir", true), "mkdir failed");
|
||||
oxAssert(fs.stat("/dir").error, "mkdir failed");
|
||||
oxAssert(fs.mkdir("/l1d1/l2d1/l3d1", true), "mkdir failed");
|
||||
oxAssert(fs.stat("/l1d1/l2d1/l3d1").error, "mkdir failed");
|
||||
oxAssert(fs.mkdir("/l1d1/l2d2", true), "mkdir failed");
|
||||
oxAssert(fs.stat("/l1d1/l2d2").error, "mkdir failed");
|
||||
return ox::Error(0);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
if (argc < 2) {
|
||||
oxError("Must specify test to run");
|
||||
return -1;
|
||||
}
|
||||
auto const args = ox::Span{argv, static_cast<size_t>(argc)};
|
||||
ox::StringView const testName = args[1];
|
||||
ox::StringView const testArg = argc >= 3 ? args[2] : nullptr;
|
||||
auto const func = tests.find(testName);
|
||||
if (func != tests.end()) {
|
||||
oxAssert(func->second(testArg), "Test returned Error");
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user