/*
Userspace Virtual Filesystem
Copyright (C) 2015 Sebastian Herbord. All rights reserved.
This file is part of usvfs.
usvfs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
usvfs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with usvfs. If not, see .
*/
#pragma once
#include "exceptionex.h"
#include "logging.h"
#include "shared_memory.h"
#include "stringutils.h"
#include "wildcard.h"
// simplify unit tests by allowing access to private members
#ifndef PRIVATE
#define PRIVATE private
#endif // PRIVATE
namespace usvfs::shared
{
template
struct SHMDataCreator
{
static T create(const U& source, const VoidAllocatorT& allocator)
{
return T(source, allocator);
}
};
template
T createData(const U& source, const VoidAllocatorT& allocator)
{
return SHMDataCreator::create(source, allocator);
}
template
T createDataEmpty(const VoidAllocatorT& allocator);
template
void dataAssign(T& destination, const T& source);
// crappy little workaround for fs::path iterating over path separators
fs::path::iterator nextIter(const fs::path::iterator& iter,
const fs::path::iterator& end);
void advanceIter(fs::path::iterator& iter, const fs::path::iterator& end);
// decomposes a path into its components
//
class DecomposablePath
{
public:
explicit DecomposablePath(std::string s) : m_s(std::move(s)), m_begin(0), m_end(0)
{
m_end = nextSeparator(m_begin);
}
// move to the next component, returns false when there are no more components
//
bool next()
{
for (;;) {
if (m_end >= m_s.size()) {
// done
m_begin = m_end;
return false;
}
// move begin to one past the last separator found
m_begin = m_end + 1;
// find the next separator
m_end = nextSeparator(m_begin);
// check for components that should be ignored:
// - empty, happens when the path ends with a separator
// - slashes, happens with consecutive separators
// - dot, unnecessary
const auto c = current();
if (!c.empty() && c != "\\" && c != "/" && c != ".") {
return true;
}
}
}
// checks if next() would return false
//
bool peekNext() const
{
auto copy = *this;
return copy.next();
}
// the current component, empty when next() returned false
//
std::string_view current() const { return {m_s.data() + m_begin, m_end - m_begin}; }
private:
const std::string m_s;
std::size_t m_begin, m_end;
// finds the next path separator
//
std::size_t nextSeparator(std::size_t from) const
{
while (from < m_s.size()) {
if (m_s[from] == '/' || m_s[from] == '\\') {
break;
}
++from;
}
return from;
}
};
namespace bi = boost::interprocess;
namespace bmi = boost::multi_index;
typedef uint8_t TreeFlags;
static const TreeFlags FLAG_DIRECTORY = 0x01;
static const TreeFlags FLAG_DUMMY = 0x02;
static const TreeFlags FLAG_FIRSTUSERFLAG = 0x10;
struct MissingThrowT
{};
static const MissingThrowT MissingThrow = MissingThrowT();
template
class TreeContainer;
template
struct mutable_pair
{
typedef T1 first_type;
typedef T2 second_type;
mutable_pair(Alloc alloc) : first(T1(alloc)), second(T2(alloc)) {}
mutable_pair(const T1& f, const T2& s) : first(f), second(s) {}
mutable_pair(const std::pair& p) : first(p.first), second(p.second) {}
T1 first;
mutable T2 second;
};
template >
using mimap = bmi::multi_index_container<
Element,
bmi::indexed_by<
bmi::ordered_unique, Compare>>,
typename Allocator::template rebind::other>;
/**
* a representation of a directory tree in memory.
* This class is designed to be stored in shared memory.
*/
template
class DirectoryTree
{
template
friend class TreeContainer;
public:
struct CILess
{
template
bool operator()(const U& lhs, const V& rhs) const
{
const size_t lhsLength = getLength(lhs);
const size_t rhsLength = getLength(rhs);
const auto r =
_strnicmp(getCharPtr(lhs), getCharPtr(rhs), std::min(lhsLength, rhsLength));
if (r == 0) {
return lhsLength < rhsLength;
}
return (r < 0);
}
private:
const char* getCharPtr(const StringT& s) const { return s.c_str(); }
const char* getCharPtr(const std::string& s) const { return s.c_str(); }
const char* getCharPtr(const char* s) const { return s; }
const char* getCharPtr(std::string_view s) const { return s.data(); }
size_t getLength(const StringT& s) const { return s.size(); }
size_t getLength(const std::string& s) const { return s.size(); }
size_t getLength(const char* s) const { return strlen(s); }
size_t getLength(std::string_view s) const { return s.size(); }
};
typedef DirectoryTree NodeT;
typedef bi::deleter DeleterT;
typedef NodeDataT DataT;
typedef bi::shared_ptr NodePtrT;
typedef bi::weak_ptr WeakPtrT;
typedef bi::allocator, SegmentManagerT>
NodeEntryAllocatorT;
typedef mimap NodeMapT;
typedef typename NodeMapT::iterator file_iterator;
typedef typename NodeMapT::const_iterator const_file_iterator;
typedef std::function VisitorFunction;
DirectoryTree() = delete;
DirectoryTree(const NodeT& reference) = delete;
DirectoryTree(NodeT&& reference) = delete;
NodeT& operator=(NodeT reference) = delete;
/**
* @brief construct a new node to be inserted in an existing tree
**/
DirectoryTree(std::string_view name, TreeFlags flags, const NodePtrT& parent,
const NodeDataT& data, const VoidAllocatorT& allocator)
: m_Parent(parent), m_Name(name.begin(), name.end(), allocator), m_Data(data),
m_Nodes(allocator), m_Flags(flags)
{}
~DirectoryTree() { m_Nodes.clear(); }
/**
* @return parent node
*/
NodePtrT parent() const { return m_Parent.lock(); }
/**
* @return the full path to the node
*/
fs::path path() const
{
if (m_Parent.lock().get() == nullptr) {
if (m_Name.size() == 0) {
return fs::path();
} else {
return fs::path(m_Name.c_str()) / "\\";
}
} else {
return m_Parent.lock()->path() / m_Name.c_str();
}
}
/**
* @return data connected to this node
**/
const NodeDataT& data() const { return m_Data; }
/**
* @return name of this node
*/
std::string name() const { return m_Name.c_str(); }
/**
* @brief setFlag change a flag for this node
* @param enabled new state for the specified flag
*/
void setFlag(TreeFlags flag, bool enabled = true)
{
m_Flags = enabled ? m_Flags | flag : m_Flags & ~flag;
}
/**
* @return true if the specified flag is set, false otherwise
*/
bool hasFlag(TreeFlags flag) const { return (m_Flags & flag) != 0; }
/**
* @return true if this node is a directory, false if it's a regular file
*/
bool isDirectory() const { return hasFlag(FLAG_DIRECTORY); }
/**
* @return the number of subnodes (directly) below this one
*/
size_t numNodes() const { return m_Nodes.size(); }
/**
* @return number of nodes in this (sub-)tree including this one
*/
size_t numNodesRecursive() const
{
size_t result = numNodes() + 1;
for (const auto& node : m_Nodes) {
result += node.second->numNodesRecursive();
}
return result;
}
/**
* @brief find a node by its path
* @param path the path to look up
* @return a pointer to the node or a null ptr
*/
NodePtrT findNode(const fs::path& path)
{
fs::path::iterator iter = path.begin();
if (iter == path.end()) {
return NodePtrT();
}
return findNode(path, iter);
}
/**
* @brief find a node by its path
* @param path the path to look up
* @return a pointer to the node or a null ptr
*/
const NodePtrT findNode(const fs::path& path) const
{
fs::path::iterator iter = path.begin();
if (iter == path.end()) {
return NodePtrT();
}
return findNode(path, iter);
}
/**
* @brief visit the nodes along the specified path (in order) calling the visitor for
* each
* @param path the path to visit
* @param visitor a function called for each node
*/
void visitPath(const fs::path& path, const VisitorFunction& visitor) const
{
fs::path::iterator iter = path.begin();
if (iter == path.end()) {
return;
}
visitPath(path, iter, visitor);
}
/**
* @brief retrieve a node by the specified name
* @param name name of the node
* @return the node found or an empty pointer if no such node was found
*/
NodePtrT node(std::string_view name, MissingThrowT) const
{
auto iter = m_Nodes.find(name);
if (iter != m_Nodes.end()) {
return iter->second;
} else {
USVFS_THROW_EXCEPTION(node_missing_error());
}
}
/**
* @brief retrieve a node by the specified name
* @param name name of the node
* @return the node found or an empty pointer if no such node was found
*/
NodePtrT node(std::string_view name)
{
auto iter = m_Nodes.find(name);
if (iter != m_Nodes.end()) {
return iter->second;
} else {
return NodePtrT();
}
}
/**
* @brief retrieve a node by the specified name
* @param name name of the node
* @return the node found or an empty pointer if no such node was found
*/
const NodePtrT node(std::string_view name, MissingThrowT)
{
auto iter = m_Nodes.find(name);
if (iter != m_Nodes.end()) {
return iter->second;
} else {
USVFS_THROW_EXCEPTION(node_missing_error());
}
}
/**
* @brief retrieve a node by the specified name
* @param name name of the node
* @return the node found or an empty pointer if no such node was found
*/
const NodePtrT node(std::string_view name) const
{
auto iter = m_Nodes.find(name);
if (iter != m_Nodes.end()) {
return iter->second;
} else {
return NodePtrT();
}
}
/**
* @brief test if a node by the specified name exists
* @param name name of the node
* @return true if the node exists, false otherwise
*/
bool exists(std::string_view name) const
{
return m_Nodes.find(name) != m_Nodes.end();
}
/**
* @brief find all matches for a pattern
* @param pattern the pattern to look for
* @return a vector of the found nodes
*/
std::vector find(const std::string& pattern) const
{
// determine if there is a prefix in the pattern that indicates a specific
// directory.
size_t fixedPart = pattern.find_first_of("*?");
if (fixedPart == 0)
fixedPart = std::string::npos;
if (fixedPart != std::string::npos)
fixedPart = pattern.find_last_of(R"(\/)", fixedPart);
std::vector result;
if (fixedPart != std::string::npos) {
// if there is a prefix, search for the node representing that path and
// search only on that
NodePtrT node = findNode(fs::path(pattern.substr(0, fixedPart)));
if (node.get() != nullptr) {
node->findLocal(result, pattern.substr(fixedPart + 1));
}
} else {
findLocal(result, pattern);
}
return result;
}
/**
* @return an iterator to the first leaf
**/
file_iterator filesBegin() { return m_Nodes.begin(); }
/**
* @return a const iterator to the first leaf
**/
const_file_iterator filesBegin() const { return m_Nodes.begin(); }
/**
* @return an iterator one past the last leaf
**/
file_iterator filesEnd() { return m_Nodes.end(); }
/**
* @return a const iterator one past the last leaf
**/
const_file_iterator filesEnd() const { return m_Nodes.end(); }
/**
* @brief erase the leaf at the specified iterator
* @return an iterator to the following file
**/
file_iterator erase(file_iterator iter) { return m_Nodes.erase(iter); }
/**
* @brief clear all nodes
*/
void clear() { m_Nodes.clear(); }
void removeFromTree()
{
if (auto par = parent()) {
spdlog::get("usvfs")->info("remove from tree {}", m_Name.c_str());
auto self = par->m_Nodes.find(m_Name.c_str());
if (self != par->m_Nodes.end()) {
par->erase(self);
} else {
// trying to remove a node that does not exist, most likely because it was
// already removed in a lower level call. this is known to happen when MoveFile
// has the MOVEFILE_COPY_ALLOWED flag and moving a mapped file.
spdlog::get("usvfs")->warn("Failed to remove inexisting node from tree: {}",
m_Name.c_str());
}
}
}
PRIVATE : void set(StringT key, const NodePtrT& value)
{
auto res = m_Nodes.emplace(std::move(key), value);
if (!res.second) {
res.first->second = value;
}
}
WeakPtrT findRoot() const
{
if (m_Parent.lock().get() == nullptr) {
return m_Self;
} else {
return m_Parent.lock()->findRoot();
}
}
NodePtrT findNode(const fs::path& name, fs::path::iterator& iter)
{
if (iter == name.end()) {
return NodePtrT();
}
std::string l = iter->string();
auto subNode = m_Nodes.find(iter->string());
advanceIter(iter, name.end());
if (iter == name.end()) {
// last name component, should be a local node
if (subNode != m_Nodes.end()) {
return subNode->second;
} else {
return NodePtrT();
}
} else {
if (subNode != m_Nodes.end()) {
return subNode->second->findNode(name, iter);
} else {
return NodePtrT();
}
}
}
const NodePtrT findNode(const fs::path& name, fs::path::iterator& iter) const
{
if (iter == name.end()) {
return NodePtrT();
}
auto subNode = m_Nodes.find(iter->string());
advanceIter(iter, name.end());
if (iter == name.end()) {
// last name component, should be a local node
if (subNode != m_Nodes.end()) {
return subNode->second;
} else {
return NodePtrT();
}
} else {
if (subNode != m_Nodes.end()) {
return subNode->second->findNode(name, iter);
} else {
return NodePtrT();
}
}
}
void visitPath(const fs::path& path, fs::path::iterator& iter,
const VisitorFunction& visitor) const
{
if (iter == path.end()) {
return;
}
auto subNode = m_Nodes.find(iter->string());
if (subNode != m_Nodes.end()) {
visitor(subNode->second);
advanceIter(iter, path.end());
if (iter != path.end()) {
subNode->second->visitPath(path, iter, visitor);
}
}
}
void findLocal(std::vector& output, const std::string& pattern) const
{
for (auto iter = m_Nodes.begin(); iter != m_Nodes.end(); ++iter) {
LPCSTR remainder = nullptr;
if (pattern.size() > 1 && (pattern[0] == '*') &&
((pattern[1] == '/') || (pattern[1] == '\\')) &&
iter->second->isDirectory()) {
// the star may represent a directory (one directory level, not
// multiple!), search in subdirectory
iter->second->findLocal(output, pattern.substr(1));
} else if ((remainder = wildcard::PartialMatch(iter->second->name().c_str(),
pattern.c_str())) != nullptr) {
if ((*remainder == '\0') || (strcmp(remainder, "*") == 0)) {
NodePtrT node = iter->second;
output.push_back(node);
}
if (iter->second->isDirectory()) {
iter->second->findLocal(output, remainder);
}
}
}
}
PRIVATE : TreeFlags m_Flags;
WeakPtrT m_Parent;
WeakPtrT m_Self;
StringT m_Name;
NodeDataT m_Data;
NodeMapT m_Nodes;
};
template
void dumpTree(std::ostream& stream, const DirectoryTree& tree, int level = 0)
{
stream << std::string(level, ' ') << tree.name() << " -> " << tree.data() << "\n";
for (auto iter = tree.filesBegin(); iter != tree.filesEnd(); ++iter) {
dumpTree(stream, *iter->second, level + 1);
}
}
} // namespace usvfs::shared