340 lines
11 KiB
C++
340 lines
11 KiB
C++
/*
|
|
* 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.len()));
|
|
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.len() + 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
|