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 <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)
|
||||
{
|
||||
|
|
|
@ -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
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;
|
||||
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);
|
||||
|
|
Loading…
Reference in a new issue