Skip to content

Instantly share code, notes, and snippets.

@JonathanCline
Created July 9, 2022 01:00
Show Gist options
  • Save JonathanCline/a54d65551448e132917c4a7847a7f7b7 to your computer and use it in GitHub Desktop.
Save JonathanCline/a54d65551448e132917c4a7847a7f7b7 to your computer and use it in GitHub Desktop.
#include <asx/vfs.hpp>
#include <asx/fs.hpp>
#include <asx/assert.hpp>
#include <nlohmann/json.hpp>
#include <filesystem>
#include <iostream>
namespace asx::vfs::impl
{
bool AnyFileStorage::good() const noexcept
{
return this->file_ != nullptr;
};
void AnyFileStorage::reset()
{
if (this->good())
{
switch (this->type())
{
case FileType::directory:
delete (this->get<DirectoryData>());
break;
case FileType::file:
delete (this->get<FileData>());
break;
case FileType::null:
ASX_FAIL("SHOULD NEVER HAPPEN, NULL TYPE BUT POINTER IS NOT NULL REEEE");
break;
default:
ASX_FAIL("unknown file type");
break;
};
(void)this->release();
};
};
FileBase* AnyFileStorage::release() noexcept
{
const auto o = this->file_;
this->file_ = nullptr;
this->type_ = FileType::null;
return o;
};
DirectoryData* AnyFileStorage::unset_parent()
{
if (this->good())
{
auto& _file = this->file_;
auto _parent = _file->parent_;
if (_parent)
{
_file->parent_ = nullptr;
};
return _parent;
};
return nullptr;
};
AnyFileStorage AnyFileStorage::create(FileName _name, FileType _type, DirectoryData* _parent)
{
FileBase* _file = nullptr;
switch (_type)
{
case FileType::file:
_file = new FileData(_name, _parent);
break;
case FileType::directory:
_file = new DirectoryData(_name, _parent);
break;
case FileType::null:
return AnyFileStorage(nullptr, _type);
default:
ASX_FAIL("unknown file type");
break;
};
ASX_CHECK(_file && "failed to create file");
return AnyFileStorage(_file, _type);
};
AnyFileStorage AnyFileStorage::create_directory(FileName _name, DirectoryData* _parent)
{
return AnyFileStorage::create(_name, FileType::directory, _parent);
};
AnyFileStorage::AnyFileStorage(std::nullptr_t) :
AnyFileStorage(nullptr, FileType::null)
{};
AnyFileStorage::AnyFileStorage(FileData* _data) :
AnyFileStorage(_data, FileType::file)
{};
AnyFileStorage::AnyFileStorage(DirectoryData* _data) :
AnyFileStorage(_data, FileType::directory)
{};
AnyFileStorage::AnyFileStorage(FileBase* _data, FileType _type) :
file_(_data), type_(_type)
{
// Sanity check
if (_data)
{
ASX_CHECK(_type != FileType::null);
}
else
{
ASX_CHECK(_type == FileType::null);
};
};
AnyFileStorage::AnyFileStorage(AnyFileStorage&& other) noexcept :
file_(std::exchange(other.file_, nullptr)),
type_(std::exchange(other.type_, FileType::null))
{};
AnyFileStorage& AnyFileStorage::operator=(AnyFileStorage&& other) noexcept
{
this->reset();
this->type_ = std::exchange(other.type_, FileType::null);
this->file_ = std::exchange(other.file_, nullptr);
return *this;
};
AnyFileStorage::~AnyFileStorage()
{
this->reset();
};
};
namespace asx::vfs
{
inline void recursive_serialize(nlohmann::json& _parent, const Path& _parentPath, const Filesystem& _filesystem)
{
using json = nlohmann::json;
auto _contents = _filesystem.contents(_parentPath);
for (auto& v : _contents)
{
auto _node = json::object();
_node["name"] = v;
auto _path = _parentPath + v;
_node["path"] = _path;
if (_filesystem.is_directory(_path))
{
auto nn = json::array();
recursive_serialize(nn, _path, _filesystem);
_node["contents"] = nn;
}
else
{
const auto bn = _filesystem.dump(_path);
_node["data"] = asx::encode_base64(bn);
};
_parent.push_back(_node);
};
};
std::vector<std::byte> serialize(const Filesystem& _filesystem)
{
using json = nlohmann::json;
auto _json = json::object();
_json["name"] = "/";
_json["path"] = "/";
{
auto nn = json::array();
recursive_serialize(nn, "/", _filesystem);
_json["contents"] = std::move(nn);
};
const auto s = _json.dump();
auto o = std::vector<std::byte>(s.size());
std::ranges::copy(s, reinterpret_cast<char*>(o.data()));
return o;
};
inline bool recursive_deserialize(const nlohmann::json& _json, Filesystem& _filesystem)
{
const Path _path = _json.at("path");
if (_json.contains("contents"))
{
if (_path != "/" && !_filesystem.create_directory(_path))
{
return false;
};
for (auto& v : _json.at("contents"))
{
if (!recursive_deserialize(v, _filesystem))
{
return false;
};
};
}
else
{
std::string_view _dataStr = _json.at("data");
const auto bn = asx::decode_base64(_dataStr);
_filesystem.upload(_path, bn);
};
return true;
};
bool deserialize(std::span<const std::byte> _data, Filesystem& _filesystem)
{
using json = nlohmann::json;
auto s = std::string(_data.size(), '\0');
std::ranges::copy(_data, reinterpret_cast<std::byte*>(s.data()));
auto _json = json::parse(s);
return recursive_deserialize(_json, _filesystem);
};
Filesystem copy_directory_as_vfs(const PathView& _pathStr)
{
auto _path = std::filesystem::path(_pathStr);
auto _filesystem = Filesystem{};
for (auto& p : std::filesystem::recursive_directory_iterator(_path))
{
auto _vPath = "/" + std::filesystem::relative(p, _path).generic_string();
if (p.is_directory())
{
_filesystem.create_directories(_vPath);
}
else if (p.is_regular_file())
{
const auto _fileData = readfile(p.path().string());
_filesystem.upload(_vPath, _fileData);
};
};
return _filesystem;
};
};
#pragma once
/** @file */
#include <asx/str.hpp>
#include <asx/assert.hpp>
#include <asx/concepts.hpp>
#include <asx/algorithm.hpp>
#include <asx/type_traits.hpp>
#include <jclib/memory.h>
#include <jclib/functional.h>
#include <map>
#include <span>
#include <string>
#include <vector>
#include <cstddef>
#include <variant>
#include <optional>
#include <functional>
#include <unordered_map>
namespace asx::vfs
{
using FileName = std::string;
using FileNameView = std::string_view;
using Path = FileName;
using PathView = FileNameView;
enum class FileType : uint8_t
{
// Invalid file / null
null = 0,
// Regular file
file = 1,
// Directory
directory = 2,
};
namespace impl
{
class FileData;
class DirectoryData;
template <typename T>
struct filedata_type;
template <> struct filedata_type<FileData> : cxconstant<FileType::file> { };
template <> struct filedata_type<DirectoryData> : cxconstant<FileType::directory> { };
struct FileBase
{
public:
int fd() const noexcept { return this->open_fd_; };
bool has_open_fd() const { return this->fd() != 0; };
void close_fd() { this->open_fd_ = 0; };
void set_fd(int _fd) { this->open_fd_ = _fd; };
FileBase(const FileName& _name, DirectoryData* _parent = nullptr) :
name_(_name), parent_(_parent)
{};
FileName name_;
DirectoryData* parent_ = nullptr;
private:
int open_fd_ = 0;
};
class AnyFileStorage
{
public:
bool good() const noexcept;
explicit operator bool() const noexcept
{
return this->good();
};
void reset();
FileBase* release() noexcept;
FileType type() const noexcept
{
return this->type_;
};
template <jc::cx_any_of<FileData, DirectoryData> T>
T* get() const &
{
ASX_CHECK(this->type() == filedata_type<T>::value);
return static_cast<T*>(this->file_);
};
auto get() && = delete;
template <jc::cx_any_of<FileData, DirectoryData> T>
T& as() const&
{
ASX_CHECK(this->good());
return *this->get<T>();
};
auto as() && = delete;
/**
* @brief Only call if you know what you are doing. Doesn't remove the file from the parent's
* child contents.
*
* @return The old parent directory pointer.
*/
DirectoryData* unset_parent();
const FileName& name() const
{
return this->file_->name_;
};
const DirectoryData* parent() const
{
return this->file_->parent_;
};
static AnyFileStorage create(FileName _name, FileType _type = FileType::file, DirectoryData* _parent = nullptr);
static AnyFileStorage create_directory(FileName _name, DirectoryData* _parent = nullptr);
int fd() const noexcept
{
return this->file_->fd();
};
bool has_open_fd() const
{
return this->file_->has_open_fd();
};
void close_fd()
{
this->file_->close_fd();
};
void set_fd(int _fd)
{
this->file_->set_fd(_fd);
};
AnyFileStorage(AnyFileStorage&& other) noexcept;
AnyFileStorage& operator=(AnyFileStorage&& other) noexcept;
~AnyFileStorage();
protected:
explicit AnyFileStorage(std::nullptr_t);
explicit AnyFileStorage(FileData* _data);
explicit AnyFileStorage(DirectoryData* _data);
explicit AnyFileStorage(FileBase* _data, FileType _type);
private:
FileBase* file_;
FileType type_;
AnyFileStorage(const AnyFileStorage&) = delete;
AnyFileStorage& operator=(const AnyFileStorage&) = delete;
};
class FileData : public FileBase
{
public:
ASX_DEFINE_ITERATOR_FUNCTIONS(this->data_);
void clear() noexcept
{
this->data_.clear();
};
auto size() const
{
return this->data_.size();
};
size_t write(std::span<const std::byte> _data)
{
const auto _count = _data.size();
this->data_.insert(this->end(), _data.begin(), _data.end());
return _count;
};
size_t write(std::span<const std::byte> _data, size_t _pos)
{
const auto _count = _data.size();
const auto _size = this->size();
auto it = this->begin() + std::min(_pos, _size);
this->data_.insert(it, _data.begin(), _data.end());
return _count;
};
size_t write(std::span<const char> _data)
{
return this->write(std::as_bytes(_data));
};
size_t write(std::span<const char> _data, size_t _pos)
{
return this->write(std::as_bytes(_data), _pos);
};
std::span<const std::byte> read_bytes(size_t _count) const
{
const auto _size = this->size();
auto _end = this->begin() + std::min(_count, _size);
return std::span<const std::byte>(this->begin(), _end);
};
std::span<const std::byte> read_bytes(size_t _count, size_t _offset)
{
const auto _size = this->size();
_offset = std::min(_offset, _size);
auto _begin = this->begin() + _offset;
auto _end = _begin + std::min(_count, _size - _offset);
return std::span<const std::byte>(_begin, _end);
};
std::span<const char> read(size_t _count) const
{
const auto p0 = reinterpret_cast<const char*>(this->data_.data());
const auto _size = this->size();
const auto _readCount = std::min(_count, _size);
const auto p1 = p0 + _readCount;
return std::span<const char>(p0, p1);
};
std::span<const char> read(size_t _count, size_t _offset)
{
_offset = std::min(_offset, this->data_.size());
const auto p0 = reinterpret_cast<const char*>(this->data_.data()) + _offset;
const auto _size = this->size() - _offset;
const auto _readCount = std::min(_count, _size);
const auto p1 = p0 + _readCount;
return std::span<const char>(p0, p1);
};
using FileBase::FileBase;
std::vector<std::byte> data_;
};
struct DirectoryContentsHasher : public jc::operator_tag
{
auto operator()(const std::string& _str) const
{
return std::hash<std::string_view>{}(_str);
};
auto operator()(const std::string_view& _str) const
{
return std::hash<std::string_view>{}(_str);
};
};
class DirectoryData : public FileBase
{
private:
using container_type = std::unordered_map<FileName, AnyFileStorage,
jc::transparent<impl::DirectoryContentsHasher>, jc::transparent<jc::equals_t>>;
public:
bool create(const FileName& _name, FileType _type = FileType::file)
{
const auto [it, _good] = this->contents_.insert({ _name,
impl::AnyFileStorage::create(_name, _type, this) });
return _good;
};
void remove(const FileNameView& _name)
{
auto& _contents = this->contents_;
if (const auto it = _contents.find(_name); it != _contents.end())
{
auto& [_name, _file] = *it;
if (_file.parent() == this)
{
_file.unset_parent();
};
_contents.erase(it);
};
};
void remove_all()
{
for (auto it = this->contents_.begin(); it != this->contents_.end(); ++it)
{
auto& [_name, _file] = *it;
if (_file.parent() == this)
{
_file.unset_parent();
};
};
this->contents_.clear();
};
AnyFileStorage* child(const FileNameView& _name)
{
if (const auto it = this->contents_.find(_name); it != this->contents_.end())
{
return &it->second;
}
else
{
return nullptr;
};
};
auto child_paths() const
{
return this->contents_ | std::views::keys;
};
/**
* @brief Gets the number of files within this directory.
* @return Number of files.
*/
size_t child_count() const noexcept
{
return this->contents_.size();
};
using FileBase::FileBase;
~DirectoryData()
{
this->remove_all();
};
private:
container_type contents_;
};
};
enum class Openmode
{
r = 0,
w = 1,
};
/**
* @brief Represents a virtual filesystem held in memory.
*/
class Filesystem
{
private:
static impl::FileData& as_file(const impl::AnyFileStorage& _file)
{
return _file.as<impl::FileData>();
};
static impl::DirectoryData& as_directory(const impl::AnyFileStorage& _file)
{
return _file.as<impl::DirectoryData>();
};
impl::DirectoryData& root() const
{
return this->as_directory(this->root_);
};
constexpr static auto seperator_chars_v = std::string_view("/\\");
struct PathSegmentReader
{
explicit operator bool() const
{
return this->head_ != this->path_.size();
};
PathView next()
{
auto& _path = this->path_;
const auto _pathSize = _path.size();
auto& _head = this->head_;
if (_head == _pathSize)
{
return {};
}
else if (_head == 0 && _path.front() == '/')
{
// Special handling to allow the root directory "/" to be a segment
const auto _segment = _path.substr(0, 1);
const auto _nextHeadPos =
std::min(_path.find_first_not_of(seperator_chars_v), _pathSize);
this->head_ = _nextHeadPos;
return _segment;
};
// Find the next seperator position.
const auto _nextSeperatorPos = _path.find_first_of(seperator_chars_v, _head);
auto _pos = std::min(_nextSeperatorPos, _pathSize);
// If this points to a directory, it should end with a "/".
if (_nextSeperatorPos != _pathSize)
{
++_pos;
};
// Grab the segment.
auto _segment = _path.substr(_head, _pos - _head);
// Move the reader head.
if (_pos != _pathSize)
{
const auto _nextHeadPos =
std::min(_path.find_first_not_of(seperator_chars_v, _nextSeperatorPos), _pathSize);
this->head_ = _nextHeadPos;
}
else
{
this->head_ = _pathSize;
};
return _segment;
};
PathSegmentReader(PathView _path) :
path_(_path)
{};
PathView path_;
size_t head_ = 0;
};
// Exists early if function returns false
// _op(<segment>, <previous segments>/<segment>) -> bool
//
// Returns the number of segments iterated over.
static size_t foreach_segment(const PathView& _path,
cx_invocable_with_result<bool, std::string_view, std::string_view> auto&& _op)
{
size_t n = 0;
if (_path.empty())
{
return n;
}
else
{
auto _segmentReader = PathSegmentReader(_path);
while (_segmentReader)
{
// Invoke function with the parsed segment.
const auto _segment = _segmentReader.next();
++n;
if (!std::invoke(_op, _segment, _path.substr(0, _segmentReader.head_)))
{
break;
};
};
return n;
};
};
impl::AnyFileStorage* get(const PathView& _path) const
{
if (_path.empty())
{
return nullptr;
}
else
{
auto* _directory = &this->root();
auto _segmentReader = PathSegmentReader(_path);
while (_segmentReader)
{
const auto _segment = _segmentReader.next();
// If this is the first segment and it is the root directory
// path ("/") then we can skip it.
if (_directory == &this->root() && _segment == "/")
{
_directory = &this->root();
// If that is the end of the path, return the root directory
if (!_segmentReader)
{
return &this->root_;
}
else
{
continue;
};
};
auto _child = _directory->child(_segment);
if (!_child)
{
// Child not found, invalid path
return nullptr;
}
else
{
if (!_segmentReader)
{
// Hit the bottom, return child
return _child;
}
else
{
// Keep digging
if (_child->type() == FileType::directory)
{
_directory = &this->as_directory(*_child);
}
else
{
// Not a directory
return nullptr;
};
};
};
};
return nullptr;
};
};
template <bool PreserveFinalSeperator = false>
PathView rstrip_seperators(const PathView& _path) const
{
auto _result = asx::rstrip_any(_path, seperator_chars_v);
// If the path given is all seperator characters, return the root path
if (!_path.empty() && _result.empty())
{
return _path.substr(0, 1);
}
else
{
if constexpr (PreserveFinalSeperator)
{
if (_path.back() == '/')
{
return _path.substr(0, _result.size() + 1);
};
};
return _result;
};
};
PathView rstrip_segment(const PathView& _path) const
{
// /foo//bar// -> /foo//bar
// /foo -> /foo
auto _cleaned = rstrip_seperators(_path);
if (_cleaned.empty()) { return _cleaned; };
// /foo//bar
// ^
// |
// /foo
// ^
// |
const auto _pos = _cleaned.find_last_of(seperator_chars_v);
if (_pos == _cleaned.npos && _pos != _path.size())
{
return {};
};
// /foo//bar -> /foo// -> /foo
// /foo -> / -> /
const auto _parent = _cleaned.substr(0, _pos + 1);
return _parent;
};
struct FileHandle
{
bool eof() const
{
return this->head_ == as_file(*this->file_).size();
};
std::optional<std::string_view> read(const size_t _max)
{
if (this->mode_ != Openmode::r || !this->file_)
{
return std::nullopt;
};
if (this->eof())
{
return std::nullopt;
};
auto& _file = as_file(*this->file_);
const auto _data = _file.read(_max, this->head_);
this->head_ += _data.size();
return _data;
};
size_t write(const std::string_view _data)
{
if (!this->file_ || this->mode_ != Openmode::w) { return 0; };
auto& _file = as_file(*this->file_);
return _file.write(_data);
};
void close()
{
if (this->file_)
{
this->file_->close_fd();
this->file_ = nullptr;
};
};
impl::AnyFileStorage* file_;
Openmode mode_;
size_t head_ = 0;
};
public:
FileType type(const PathView& _path) const
{
const auto _file = this->get(_path);
if (_file)
{
return _file->type();
}
else
{
return FileType::null;
};
};
bool exists(const PathView& _path) const
{
auto _file = this->get(_path);
return _file != nullptr;
};
bool is_directory(const PathView& _path) const
{
return this->type(_path) == FileType::directory;
};
bool is_regular_file(const PathView& _path) const
{
return this->type(_path) == FileType::file;
};
PathView parent_path(const PathView& _path) const
{
return rstrip_segment(_path);
};
PathView filename(const PathView& _path) const
{
const auto _cleaned = this->rstrip_seperators<true>(_path);
if (_cleaned.empty())
{
return PathView();
}
else
{
auto _pos = asx::find_last_of(_cleaned, seperator_chars_v, 1);
if (_pos == _cleaned.npos)
{
_pos = 0;
}
else
{
++_pos;
};
return _cleaned.substr(_pos, _cleaned.size() - _pos);
};
};
bool create(const PathView& _path, FileType _type)
{
// Find the parent path to create the directory in.
const auto _parentPath = this->parent_path(_path);
if (_parentPath.empty() || _parentPath == _path)
{
// Invalid path
return false;
};
if (const auto _type = this->type(_parentPath);
_type == FileType::null)
{
// Parent directory does not exist.
return false;
}
else if (_type != FileType::directory)
{
// Parent path is not a directory.
return false;
};
// Grab the filename from the path.
// /foo/bin -> bin
// /foo/bin/ -> bin
const auto _filename = ((_type == FileType::file)?
Path(this->rstrip_seperators(this->filename(_path))) :
Path(this->rstrip_seperators<true>(this->filename(_path))));
ASX_CHECK(!_filename.empty());
// Try to create the child directory
auto _parent = this->get(_parentPath);
ASX_CHECK(_parent);
auto& _parentDir = this->as_directory(*_parent);
return _parentDir.create(_filename, _type);
};
bool create_directory(const PathView& _path)
{
return this->create(_path, FileType::directory);
};
bool create_directories(const PathView& _path)
{
bool _good = false;
const auto n = this->foreach_segment(_path,
[this, &_good](const PathView& _segment, const PathView& _path) -> bool
{
bool _result = false;
if (const auto _type = this->type(_path); _type == FileType::null)
{
if (this->create_directory(_path))
{
_result = true;
_good = _result;
}
else
{
_result = false;
_good = _result;
};
}
else
{
if (_type == FileType::directory)
{
_result = true;
}
else
{
_result = false;
_good = _result;
};
};
return _result;
});
return _good;
};
bool remove_all(const PathView& _path)
{
auto _file = this->get(_path);
if (!_file) { return false; };
if (_file->type() != FileType::directory) { return false; };
this->as_directory(*_file).remove_all();
return true;
};
bool remove_all()
{
this->root().remove_all();
return true;
};
std::vector<FileName> contents(const PathView& _path) const
{
// Grab the directory file.
auto _file = this->get(_path);
// Check yoself
if (!_file || _file->type() != FileType::directory) { return {}; };
// View as a directory.
auto& _directory = this->as_directory(*_file);
// Convert into output type.
auto _contents = std::vector<FileName>(_directory.child_count());
std::ranges::copy(_directory.child_paths(), _contents.begin());
return _contents;
};
bool touch(const PathView& _path)
{
auto _parentPath = this->parent_path(_path);
if (_parentPath.empty())
{
_parentPath = this->root().name_;
}
else
{
if (!this->is_directory(_parentPath))
{
return false;
};
};
auto& _parentDirectory = this->as_directory(*this->get(_parentPath));
return _parentDirectory.create(FileName(this->filename(_path)), FileType::file);
};
int open(const PathView& _path, Openmode _mode)
{
auto _file = this->get(_path);
if (!_file)
{
if (_mode == Openmode::w)
{
if (!this->touch(_path))
{
return -1;
}
else
{
_file = this->get(_path);
if (!_file)
{
return -1;
};
};
}
else
{
return -1;
};
}
else if (_file->has_open_fd())
{
return -1;
}
else if (_file->type() != FileType::file)
{
return -1;
}
else
{
// Clear the file contents if opening with write
if (_mode == Openmode::w)
{
as_file(*_file).clear();
};
};
const auto _fd = ++this->fd_count_;
auto _fHandle = FileHandle{};
_fHandle.file_ = _file;
_fHandle.mode_ = _mode;
_file->set_fd(_fd);
this->fds_.insert_or_assign(_fd, _fHandle);
return _fd;
};
void close(int _fd)
{
if (const auto it = this->fds_.find(_fd); it != this->fds_.end())
{
it->second.close();
this->fds_.erase(it);
};
};
/**
* @brief Attempts to read some data from a file.
* @param _fd File handle.
* @param _max Max chars to read.
* @return String read from file, or null on error.
*/
std::optional<std::string_view> read(int _fd, const size_t _max)
{
auto& _fileHandles = this->fds_;
if (auto it = _fileHandles.find(_fd); it != _fileHandles.end())
{
// Read from the file
auto& _file = it->second;
return _file.read(_max);
}
else
{
// Invalid file handle
return std::nullopt;
};
};
/**
* @brief Attempts to write some data to the file.
* @param _fd File handle.
* @param _data The data to write.
* @return Number of bytes written, returns -1 on error.
*/
int write(int _fd, const std::string_view _data)
{
auto& _fileHandles = this->fds_;
if (auto it = _fileHandles.find(_fd); it != _fileHandles.end())
{
// Write to the file
auto& _file = it->second;
const auto _writeCount = static_cast<int>(_file.write(_data));
// Return the number of bytes written.
return _writeCount;
}
else
{
// Invalid file handle
return -1;
};
};
std::vector<std::byte> dump(const PathView& _path) const
{
const auto _file = this->get(_path);
if (!_file) { return {}; };
auto& f = this->as_file(*_file);
return f.data_;
};
void upload(const PathView& _path, std::span<const std::byte> _data)
{
this->create(_path, FileType::file);
const auto _file = this->get(_path);
if (!_file) {
return;
};
auto& f = this->as_file(*_file);
f.data_.assign(_data.begin(), _data.end());
};
/**
* @brief Resets the filesystem to only contain a root directory.
*/
void clear() noexcept
{
this->as_directory(this->root_).remove_all();
};
void close_open_handles()
{
for (auto& [_fd, _fh] : this->fds_)
{
_fh.close();
};
this->fds_.clear();
};
Filesystem() :
root_(impl::AnyFileStorage::create_directory("/"))
{};
private:
mutable impl::AnyFileStorage root_;
/**
* @brief Open file handles.
*/
std::map<int, FileHandle> fds_;
int fd_count_ = 6959;
};
std::vector<std::byte> serialize(const Filesystem& _filesystem);
bool deserialize(std::span<const std::byte> _data, Filesystem& _filesystem);
Filesystem copy_directory_as_vfs(const PathView& _path);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment