2022-12-12 20:57:32 +02:00
|
|
|
#pragma once
|
2023-04-01 06:18:41 +03:00
|
|
|
///@file
|
2022-12-12 20:57:32 +02:00
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <optional>
|
|
|
|
#include <cassert>
|
|
|
|
#include <iostream>
|
|
|
|
#include <set>
|
2023-11-14 15:47:17 +02:00
|
|
|
#include <vector>
|
2022-12-12 20:57:32 +02:00
|
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* A canonical representation of a path. It ensures the following:
|
|
|
|
*
|
|
|
|
* - It always starts with a slash.
|
|
|
|
*
|
|
|
|
* - It never ends with a slash, except if the path is "/".
|
|
|
|
*
|
|
|
|
* - A slash is never followed by a slash (i.e. no empty components).
|
|
|
|
*
|
|
|
|
* - There are no components equal to '.' or '..'.
|
|
|
|
*
|
2024-01-13 08:11:49 +02:00
|
|
|
* `CanonPath` are "virtual" Nix paths for abstract file system objects;
|
|
|
|
* they are always Unix-style paths, regardless of what OS Nix is
|
|
|
|
* running on. The `/` root doesn't denote the ambient host file system
|
|
|
|
* root, but some virtual FS root.
|
|
|
|
*
|
|
|
|
* @note It might be useful to compare `openat(some_fd, "foo/bar")` on
|
|
|
|
* Unix. `"foo/bar"` is a relative path because an absolute path would
|
|
|
|
* "override" the `some_fd` directory file descriptor and escape to the
|
|
|
|
* "system root". Conversely, Nix's abstract file operations *never* escape the
|
|
|
|
* designated virtual file system (i.e. `SourceAccessor` or
|
|
|
|
* `ParseSink`), so `CanonPath` does not need an absolute/relative
|
|
|
|
* distinction.
|
|
|
|
*
|
|
|
|
* @note The path does not need to correspond to an actually existing
|
|
|
|
* path, and the path may or may not have unresolved symlinks.
|
2023-03-27 04:12:25 +03:00
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
class CanonPath
|
|
|
|
{
|
|
|
|
std::string path;
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Construct a canon path from a non-canonical path. Any '.', '..'
|
|
|
|
* or empty components are removed.
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
CanonPath(std::string_view raw);
|
|
|
|
|
|
|
|
explicit CanonPath(const char * raw)
|
|
|
|
: CanonPath(std::string_view(raw))
|
|
|
|
{ }
|
|
|
|
|
|
|
|
struct unchecked_t { };
|
|
|
|
|
|
|
|
CanonPath(unchecked_t _, std::string path)
|
|
|
|
: path(std::move(path))
|
|
|
|
{ }
|
|
|
|
|
2023-11-14 15:47:17 +02:00
|
|
|
/**
|
|
|
|
* Construct a canon path from a vector of elements.
|
|
|
|
*/
|
|
|
|
CanonPath(const std::vector<std::string> & elems);
|
|
|
|
|
2022-12-12 20:57:32 +02:00
|
|
|
static CanonPath root;
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* If `raw` starts with a slash, return
|
|
|
|
* `CanonPath(raw)`. Otherwise return a `CanonPath` representing
|
|
|
|
* `root + "/" + raw`.
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
CanonPath(std::string_view raw, const CanonPath & root);
|
|
|
|
|
|
|
|
bool isRoot() const
|
|
|
|
{ return path.size() <= 1; }
|
|
|
|
|
|
|
|
explicit operator std::string_view() const
|
|
|
|
{ return path; }
|
|
|
|
|
|
|
|
const std::string & abs() const
|
|
|
|
{ return path; }
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Like abs(), but return an empty string if this path is
|
|
|
|
* '/'. Thus the returned string never ends in a slash.
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
const std::string & absOrEmpty() const
|
|
|
|
{
|
|
|
|
const static std::string epsilon;
|
|
|
|
return isRoot() ? epsilon : path;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char * c_str() const
|
|
|
|
{ return path.c_str(); }
|
|
|
|
|
|
|
|
std::string_view rel() const
|
|
|
|
{ return ((std::string_view) path).substr(1); }
|
|
|
|
|
2024-01-12 23:55:37 +02:00
|
|
|
const char * rel_c_str() const
|
|
|
|
{
|
|
|
|
auto cs = path.c_str();
|
|
|
|
assert(cs[0]); // for safety if invariant is broken
|
|
|
|
return &cs[1];
|
|
|
|
}
|
|
|
|
|
2022-12-12 20:57:32 +02:00
|
|
|
struct Iterator
|
|
|
|
{
|
|
|
|
std::string_view remaining;
|
|
|
|
size_t slash;
|
|
|
|
|
|
|
|
Iterator(std::string_view remaining)
|
|
|
|
: remaining(remaining)
|
|
|
|
, slash(remaining.find('/'))
|
|
|
|
{ }
|
|
|
|
|
|
|
|
bool operator != (const Iterator & x) const
|
|
|
|
{ return remaining.data() != x.remaining.data(); }
|
|
|
|
|
2023-03-30 16:48:39 +03:00
|
|
|
bool operator == (const Iterator & x) const
|
|
|
|
{ return !(*this != x); }
|
|
|
|
|
2022-12-12 20:57:32 +02:00
|
|
|
const std::string_view operator * () const
|
|
|
|
{ return remaining.substr(0, slash); }
|
|
|
|
|
|
|
|
void operator ++ ()
|
|
|
|
{
|
|
|
|
if (slash == remaining.npos)
|
|
|
|
remaining = remaining.substr(remaining.size());
|
|
|
|
else {
|
|
|
|
remaining = remaining.substr(slash + 1);
|
|
|
|
slash = remaining.find('/');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Iterator begin() const { return Iterator(rel()); }
|
|
|
|
Iterator end() const { return Iterator(rel().substr(path.size() - 1)); }
|
|
|
|
|
|
|
|
std::optional<CanonPath> parent() const;
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Remove the last component. Panics if this path is the root.
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
void pop();
|
|
|
|
|
|
|
|
std::optional<std::string_view> dirOf() const
|
|
|
|
{
|
|
|
|
if (isRoot()) return std::nullopt;
|
2022-12-23 16:32:54 +02:00
|
|
|
return ((std::string_view) path).substr(0, path.rfind('/'));
|
2022-12-12 20:57:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<std::string_view> baseName() const
|
|
|
|
{
|
|
|
|
if (isRoot()) return std::nullopt;
|
|
|
|
return ((std::string_view) path).substr(path.rfind('/') + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator == (const CanonPath & x) const
|
|
|
|
{ return path == x.path; }
|
|
|
|
|
|
|
|
bool operator != (const CanonPath & x) const
|
|
|
|
{ return path != x.path; }
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Compare paths lexicographically except that path separators
|
|
|
|
* are sorted before any other character. That is, in the sorted order
|
|
|
|
* a directory is always followed directly by its children. For
|
|
|
|
* instance, 'foo' < 'foo/bar' < 'foo!'.
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
bool operator < (const CanonPath & x) const
|
|
|
|
{
|
|
|
|
auto i = path.begin();
|
|
|
|
auto j = x.path.begin();
|
|
|
|
for ( ; i != path.end() && j != x.path.end(); ++i, ++j) {
|
|
|
|
auto c_i = *i;
|
|
|
|
if (c_i == '/') c_i = 0;
|
|
|
|
auto c_j = *j;
|
|
|
|
if (c_j == '/') c_j = 0;
|
|
|
|
if (c_i < c_j) return true;
|
|
|
|
if (c_i > c_j) return false;
|
|
|
|
}
|
|
|
|
return i == path.end() && j != x.path.end();
|
|
|
|
}
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Return true if `this` is equal to `parent` or a child of
|
|
|
|
* `parent`.
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
bool isWithin(const CanonPath & parent) const;
|
|
|
|
|
|
|
|
CanonPath removePrefix(const CanonPath & prefix) const;
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Append another path to this one.
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
void extend(const CanonPath & x);
|
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Concatenate two paths.
|
|
|
|
*/
|
2024-02-05 16:13:11 +02:00
|
|
|
CanonPath operator / (const CanonPath & x) const;
|
2022-12-12 20:57:32 +02:00
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Add a path component to this one. It must not contain any slashes.
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
void push(std::string_view c);
|
|
|
|
|
2024-02-05 16:13:11 +02:00
|
|
|
CanonPath operator / (std::string_view c) const;
|
2022-12-12 20:57:32 +02:00
|
|
|
|
2023-03-27 04:12:25 +03:00
|
|
|
/**
|
|
|
|
* Check whether access to this path is allowed, which is the case
|
|
|
|
* if 1) `this` is within any of the `allowed` paths; or 2) any of
|
|
|
|
* the `allowed` paths are within `this`. (The latter condition
|
|
|
|
* ensures access to the parents of allowed paths.)
|
|
|
|
*/
|
2022-12-12 20:57:32 +02:00
|
|
|
bool isAllowed(const std::set<CanonPath> & allowed) const;
|
2023-03-30 16:48:39 +03:00
|
|
|
|
2023-04-07 16:55:28 +03:00
|
|
|
/**
|
|
|
|
* Return a representation `x` of `path` relative to `this`, i.e.
|
|
|
|
* `CanonPath(this.makeRelative(x), this) == path`.
|
|
|
|
*/
|
2023-03-30 16:48:39 +03:00
|
|
|
std::string makeRelative(const CanonPath & path) const;
|
2023-12-06 14:45:59 +02:00
|
|
|
|
|
|
|
friend class std::hash<CanonPath>;
|
2022-12-12 20:57:32 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
std::ostream & operator << (std::ostream & stream, const CanonPath & path);
|
|
|
|
|
|
|
|
}
|
2023-12-06 14:45:59 +02:00
|
|
|
|
|
|
|
template<>
|
|
|
|
struct std::hash<nix::CanonPath>
|
|
|
|
{
|
|
|
|
std::size_t operator ()(const nix::CanonPath & s) const noexcept
|
|
|
|
{
|
|
|
|
return std::hash<std::string>{}(s.path);
|
|
|
|
}
|
|
|
|
};
|