WIP: add Result<T, E> type

Change-Id: Id60e7cd1d0ea67e4ecc7115bf250b4788c13e176
This commit is contained in:
Qyriad 2024-07-04 15:27:14 -06:00
parent 4347f4ef70
commit 95edfa1d03
4 changed files with 299 additions and 1 deletions

View file

@ -1,7 +1,9 @@
#include <sys/time.h>
#include <filesystem>
#include <atomic>
#include <fcntl.h>
#include "box_ptr.hh"
#include "environment-variables.hh"
#include "file-descriptor.hh"
#include "file-system.hh"
@ -183,6 +185,15 @@ struct stat lstat(const Path & path)
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> 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)
{
@ -302,6 +333,21 @@ box_ptr<Source> readFileSource(const Path & path)
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)
{

View file

@ -8,6 +8,7 @@
#include "box_ptr.hh"
#include "types.hh"
#include "file-descriptor.hh"
#include "result.hh"
#include <sys/types.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 lstat(const Path & path);
Result<struct stat, SysError> tryLstat(Path const & path);
/**
* `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
@ -115,6 +118,8 @@ bool pathAccessible(const Path & path);
*/
Path readLink(const Path & path);
Result<Path, SysError> tryReadlink(Path const & path);
bool isLink(const Path & path);
/**

247
src/libutil/result.hh Normal file
View 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));
}
};

View file

@ -251,7 +251,7 @@ void chrootHelper(int argc, char * * argv)
auto src = "/" + entry.name;
Path dst = tmpDir + "/" + entry.name;
if (pathExists(dst)) continue;
auto st = lstat(src);
struct stat st = tryLstat(src).unwrap_or_throw();
if (S_ISDIR(st.st_mode)) {
if (mkdir(dst.c_str(), 0700) == -1)
throw SysError("creating directory '%s'", dst);