From 95edfa1d039a4549e86227e7faaed7cfe85de6dd Mon Sep 17 00:00:00 2001 From: Qyriad Date: Thu, 4 Jul 2024 15:27:14 -0600 Subject: [PATCH] WIP: add Result type Change-Id: Id60e7cd1d0ea67e4ecc7115bf250b4788c13e176 --- src/libutil/file-system.cc | 46 +++++++ src/libutil/file-system.hh | 5 + src/libutil/result.hh | 247 +++++++++++++++++++++++++++++++++++++ src/nix/run.cc | 2 +- 4 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 src/libutil/result.hh diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index 9278ea368..a4cc12850 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -1,7 +1,9 @@ #include #include #include +#include +#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 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 maybeLstat(const Path & path) { std::optional st{std::in_place}; @@ -230,6 +241,26 @@ Path readLink(const Path & path) } } +Result tryReadlink(Path const & path) +{ + checkInterrupt(); + std::vector 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 readFileSource(const Path & path) return make_box_ptr(std::move(fd)); } +Result, 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>(make_box_ptr(std::move(fd))); +} void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync) { diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh index 17f5da062..03478752f 100644 --- a/src/libutil/file-system.hh +++ b/src/libutil/file-system.hh @@ -8,6 +8,7 @@ #include "box_ptr.hh" #include "types.hh" #include "file-descriptor.hh" +#include "result.hh" #include #include @@ -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 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 tryReadlink(Path const & path); + bool isLink(const Path & path); /** diff --git a/src/libutil/result.hh b/src/libutil/result.hh new file mode 100644 index 000000000..34b701ddd --- /dev/null +++ b/src/libutil/result.hh @@ -0,0 +1,247 @@ +#pragma once +/// @file + +#include +#include +#include +#include + +template +struct Result +{ + using Self = Result; + + 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(value.ok); + } else { + this->inner.err = std::forward(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 mk_fallback) & + { + if (this->is_ok()) { + this->inner.ok; + } else { + return mk_fallback(); + } + } + + T && unwrap_or_else(std::function 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 + Result map(std::function pred) + { + if (this->is_ok()) { + return Result::Ok(Tag::Ok, pred().inner); + } else { + Result::Err(Tag::Err, this->inner); + } + } + +public: + // Boilerplate stuff + + // Copy constructor. + Result(Result const & other) + : tag(other.tag), inner(other.inner) + { } + + // Move constructor. + Result(Result && 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. +template +struct Ok +{ + T && v; + + //Ok(T const &) = delete; + + Ok(T && v) : v(std::forward(v)) { } + + template + operator Result() && + { + return Result::Ok(std::move(this->v)); + } +}; + +/// Helper type for constructing a Result from a trivially copyable lvalue. +template +requires std::is_trivially_copyable_v +struct OkVal +{ + T v; + OkVal(T v) : v(v) { } + + template + operator Result() && + { + return Result::Ok(this->v); + } +}; + +//template +//static Ok OkMove(T && v) +//{ +// return Ok(std::forward(v)); +//} + +//Ok(std::string &&) -> Ok; + +/// 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 +struct Err +{ + E && e; + + Err(E && e) : e(std::move(e)) { } + + template + operator Result() && + { + return Result::Err(std::move(this->e)); + } +}; diff --git a/src/nix/run.cc b/src/nix/run.cc index fd50e5465..9c9a50e8f 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -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);