forked from lix-project/lix
WIP: add Result<T, E> type
Change-Id: Id60e7cd1d0ea67e4ecc7115bf250b4788c13e176
This commit is contained in:
parent
4347f4ef70
commit
95edfa1d03
|
@ -1,7 +1,9 @@
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "box_ptr.hh"
|
||||||
#include "environment-variables.hh"
|
#include "environment-variables.hh"
|
||||||
#include "file-descriptor.hh"
|
#include "file-descriptor.hh"
|
||||||
#include "file-system.hh"
|
#include "file-system.hh"
|
||||||
|
@ -183,6 +185,15 @@ struct stat lstat(const Path & path)
|
||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<struct stat, SysError> tryLstat(Path const & path)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
if (lstat(path.c_str(), &st)) {
|
||||||
|
return Err(SysError(errno, "getting status of '%s'", path));
|
||||||
|
}
|
||||||
|
return OkVal(st);
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<struct stat> maybeLstat(const Path & path)
|
std::optional<struct stat> maybeLstat(const Path & path)
|
||||||
{
|
{
|
||||||
std::optional<struct stat> st{std::in_place};
|
std::optional<struct stat> st{std::in_place};
|
||||||
|
@ -230,6 +241,26 @@ Path readLink(const Path & path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<Path, SysError> tryReadlink(Path const & path)
|
||||||
|
{
|
||||||
|
checkInterrupt();
|
||||||
|
std::vector<char> buf;
|
||||||
|
for (ssize_t bufSize = PATH_MAX / 4; true; bufSize += bufSize / 2) {
|
||||||
|
buf.resize(bufSize);
|
||||||
|
ssize_t rlSize = readlink(path.c_str(), buf.data(), bufSize);
|
||||||
|
error_t saved = errno;
|
||||||
|
if (rlSize == -1) {
|
||||||
|
if (saved == EINVAL) {
|
||||||
|
return Err(SysError(saved, "'%s' is not a symlink", path));
|
||||||
|
} else {
|
||||||
|
return Err(SysError(saved, "reading symbolic link '%s'", path));
|
||||||
|
}
|
||||||
|
} else if (rlSize < bufSize) {
|
||||||
|
return Ok(std::string(buf.data(), rlSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool isLink(const Path & path)
|
bool isLink(const Path & path)
|
||||||
{
|
{
|
||||||
|
@ -302,6 +333,21 @@ box_ptr<Source> readFileSource(const Path & path)
|
||||||
return make_box_ptr<FileSource>(std::move(fd));
|
return make_box_ptr<FileSource>(std::move(fd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<box_ptr<Source>, SysError> tryReadFileSource(Path const & path)
|
||||||
|
{
|
||||||
|
AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)};
|
||||||
|
error_t saved = errno;
|
||||||
|
if (!fd) {
|
||||||
|
return Err(SysError(saved, "opening file '%s'", path));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FileSource : FdSource {
|
||||||
|
AutoCloseFD fd;
|
||||||
|
explicit FileSource(AutoCloseFD fd) : FdSource(fd.get()), fd(std::move(fd)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok<box_ptr<Source>>(make_box_ptr<FileSource>(std::move(fd)));
|
||||||
|
}
|
||||||
|
|
||||||
void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
|
void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "box_ptr.hh"
|
#include "box_ptr.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "file-descriptor.hh"
|
#include "file-descriptor.hh"
|
||||||
|
#include "result.hh"
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
@ -90,6 +91,8 @@ bool isDirOrInDir(std::string_view path, std::string_view dir);
|
||||||
struct stat stat(const Path & path);
|
struct stat stat(const Path & path);
|
||||||
struct stat lstat(const Path & path);
|
struct stat lstat(const Path & path);
|
||||||
|
|
||||||
|
Result<struct stat, SysError> tryLstat(Path const & path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `lstat` the given path if it exists.
|
* `lstat` the given path if it exists.
|
||||||
* @return std::nullopt if the path doesn't exist, or an optional containing the result of `lstat` otherwise
|
* @return std::nullopt if the path doesn't exist, or an optional containing the result of `lstat` otherwise
|
||||||
|
@ -115,6 +118,8 @@ bool pathAccessible(const Path & path);
|
||||||
*/
|
*/
|
||||||
Path readLink(const Path & path);
|
Path readLink(const Path & path);
|
||||||
|
|
||||||
|
Result<Path, SysError> tryReadlink(Path const & path);
|
||||||
|
|
||||||
bool isLink(const Path & path);
|
bool isLink(const Path & path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
247
src/libutil/result.hh
Normal file
247
src/libutil/result.hh
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
#pragma once
|
||||||
|
/// @file
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
template<typename T, typename E>
|
||||||
|
struct Result
|
||||||
|
{
|
||||||
|
using Self = Result<T, E>;
|
||||||
|
|
||||||
|
union Inner
|
||||||
|
{
|
||||||
|
// Here for default construction only.
|
||||||
|
// std::monostate does not have the same effect, for some reason,
|
||||||
|
// but we're never going to have a T or E smaller than sizeof(char) anyway.
|
||||||
|
unsigned char _default;
|
||||||
|
T ok;
|
||||||
|
E err;
|
||||||
|
|
||||||
|
// Make default construction zero-initialize.
|
||||||
|
constexpr Inner() noexcept : _default(0) { }
|
||||||
|
~Inner() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Tag
|
||||||
|
{
|
||||||
|
Ok,
|
||||||
|
Err,
|
||||||
|
};
|
||||||
|
|
||||||
|
Tag tag;
|
||||||
|
Inner inner;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Result() = delete;
|
||||||
|
|
||||||
|
constexpr Result(Inner && value, Tag tag)
|
||||||
|
: tag(tag), inner()
|
||||||
|
{
|
||||||
|
if (this->tag == Tag::Ok) {
|
||||||
|
this->inner.ok = std::forward<T>(value.ok);
|
||||||
|
} else {
|
||||||
|
this->inner.err = std::forward<E>(value.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr Self Ok(T const & ok)
|
||||||
|
{
|
||||||
|
Inner inner;
|
||||||
|
inner.ok = ok;
|
||||||
|
return Self{std::move(inner), Tag::Ok};
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Self Ok(T && ok)
|
||||||
|
{
|
||||||
|
Inner inner;
|
||||||
|
inner.ok = std::move(ok);
|
||||||
|
return Self{std::move(inner), Tag::Ok};
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Self Err(T const & err)
|
||||||
|
{
|
||||||
|
Inner inner;
|
||||||
|
inner.err = err;
|
||||||
|
return Self{std::move(inner), Tag::Err};
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Self Err(E && err)
|
||||||
|
{
|
||||||
|
Inner inner;
|
||||||
|
inner.err = std::move(err);
|
||||||
|
return Self(std::move(inner), Tag::Err);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_ok() const
|
||||||
|
{
|
||||||
|
return this->tag == Tag::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_err() const
|
||||||
|
{
|
||||||
|
return this->tag == Tag::Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
T & unwrap() &
|
||||||
|
{
|
||||||
|
assert(this->is_ok());
|
||||||
|
return this->inner.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
T && unwrap() &&
|
||||||
|
{
|
||||||
|
assert(this->is_ok());
|
||||||
|
return std::move(this->inner.ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
E && unwrap_err() &&
|
||||||
|
{
|
||||||
|
assert(this->is_err());
|
||||||
|
return std::move(this->inner.err);
|
||||||
|
}
|
||||||
|
|
||||||
|
T & unwrap_or(T & fallback) &
|
||||||
|
{
|
||||||
|
if (this->is_err()) {
|
||||||
|
return this->inner.ok;
|
||||||
|
} else {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T && unwrap_or(T fallback) &&
|
||||||
|
{
|
||||||
|
if (this->is_ok()) {
|
||||||
|
return std::move(this->inner.ok);
|
||||||
|
} else {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T & unwrap_or_else(std::function<T &()> mk_fallback) &
|
||||||
|
{
|
||||||
|
if (this->is_ok()) {
|
||||||
|
this->inner.ok;
|
||||||
|
} else {
|
||||||
|
return mk_fallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T && unwrap_or_else(std::function<T &&()> mk_fallback) &&
|
||||||
|
{
|
||||||
|
if (this->is_ok()) {
|
||||||
|
return std::move(this->inner.ok);
|
||||||
|
} else {
|
||||||
|
return mk_fallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T & unwrap_or_throw() &
|
||||||
|
{
|
||||||
|
if (this->is_err()) {
|
||||||
|
throw this->inner.err;
|
||||||
|
} else {
|
||||||
|
return this->inner.ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T && unwrap_or_throw() &&
|
||||||
|
{
|
||||||
|
if (this->is_err()) {
|
||||||
|
throw std::move(this->inner.err);
|
||||||
|
} else {
|
||||||
|
return std::move(this->inner.ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename NewT>
|
||||||
|
Result<NewT, E> map(std::function<NewT(T)> pred)
|
||||||
|
{
|
||||||
|
if (this->is_ok()) {
|
||||||
|
return Result<NewT, E>::Ok(Tag::Ok, pred().inner);
|
||||||
|
} else {
|
||||||
|
Result<NewT, E>::Err(Tag::Err, this->inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Boilerplate stuff
|
||||||
|
|
||||||
|
// Copy constructor.
|
||||||
|
Result(Result<T, E> const & other)
|
||||||
|
: tag(other.tag), inner(other.inner)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
// Move constructor.
|
||||||
|
Result(Result<T, E> && other)
|
||||||
|
: tag(other.tag), inner(std::move(other.inner))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
// Please do not make me add copy or move assignment operators.
|
||||||
|
|
||||||
|
~Result()
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper type for constructing a Result<T, _>.
|
||||||
|
template<typename T>
|
||||||
|
struct Ok
|
||||||
|
{
|
||||||
|
T && v;
|
||||||
|
|
||||||
|
//Ok(T const &) = delete;
|
||||||
|
|
||||||
|
Ok(T && v) : v(std::forward<T>(v)) { }
|
||||||
|
|
||||||
|
template<typename E>
|
||||||
|
operator Result<T, E>() &&
|
||||||
|
{
|
||||||
|
return Result<T, E>::Ok(std::move(this->v));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper type for constructing a Result<T, _> from a trivially copyable lvalue.
|
||||||
|
template<typename T>
|
||||||
|
requires std::is_trivially_copyable_v<T>
|
||||||
|
struct OkVal
|
||||||
|
{
|
||||||
|
T v;
|
||||||
|
OkVal(T v) : v(v) { }
|
||||||
|
|
||||||
|
template<typename E>
|
||||||
|
operator Result<T, E>() &&
|
||||||
|
{
|
||||||
|
return Result<T, E>::Ok(this->v);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//template<typename T>
|
||||||
|
//static Ok<T> OkMove(T && v)
|
||||||
|
//{
|
||||||
|
// return Ok<T>(std::forward<T>(v));
|
||||||
|
//}
|
||||||
|
|
||||||
|
//Ok(std::string &&) -> Ok<std::string>;
|
||||||
|
|
||||||
|
/// Helper type for constructing a Result<_, E>
|
||||||
|
// The type deduction here is optimized for non-aggregate types,
|
||||||
|
// which makes it so you can use this type without any additional boilerplate for
|
||||||
|
// using any of our Error objects inline, but means that if your error type is an,
|
||||||
|
// aggregate you're going to have to std::move() it in the constructor, sorry.
|
||||||
|
template<typename E>
|
||||||
|
struct Err
|
||||||
|
{
|
||||||
|
E && e;
|
||||||
|
|
||||||
|
Err(E && e) : e(std::move(e)) { }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
operator Result<T, E>() &&
|
||||||
|
{
|
||||||
|
return Result<T, E>::Err(std::move(this->e));
|
||||||
|
}
|
||||||
|
};
|
|
@ -251,7 +251,7 @@ void chrootHelper(int argc, char * * argv)
|
||||||
auto src = "/" + entry.name;
|
auto src = "/" + entry.name;
|
||||||
Path dst = tmpDir + "/" + entry.name;
|
Path dst = tmpDir + "/" + entry.name;
|
||||||
if (pathExists(dst)) continue;
|
if (pathExists(dst)) continue;
|
||||||
auto st = lstat(src);
|
struct stat st = tryLstat(src).unwrap_or_throw();
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
if (mkdir(dst.c_str(), 0700) == -1)
|
if (mkdir(dst.c_str(), 0700) == -1)
|
||||||
throw SysError("creating directory '%s'", dst);
|
throw SysError("creating directory '%s'", dst);
|
||||||
|
|
Loading…
Reference in a new issue