aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-06-30 07:51:23 +0100
committerLexi Winter <lexi@le-fay.org>2025-06-30 07:51:23 +0100
commit034cd404a129103a8dd7747e6bd00ffd5550da93 (patch)
treed27946517d4d9333abd26ac50bbd4a436093e2ce
parent3e7902f7d790a486d3d9cb978df193f07f3a6ad9 (diff)
downloadnihil-034cd404a129103a8dd7747e6bd00ffd5550da93.tar.gz
nihil-034cd404a129103a8dd7747e6bd00ffd5550da93.tar.bz2
refactoring
-rw-r--r--.clang-format36
-rw-r--r--.clang-tidy33
-rw-r--r--clang-tidy.conf8
-rw-r--r--nihil.generator/CMakeLists.txt7
-rw-r--r--nihil.generator/byte_allocator.ccm27
-rw-r--r--nihil.generator/coroutine_traits.ccm70
-rw-r--r--nihil.generator/forward.ccm29
-rw-r--r--nihil.generator/generator.ccm717
-rw-r--r--nihil.generator/generator.test.cc (renamed from nihil.generator/test.cc)0
-rw-r--r--nihil.generator/generator_promise.ccm65
-rw-r--r--nihil.generator/generator_promise_base.ccm205
-rw-r--r--nihil.generator/manual_lifetime.ccm8
-rw-r--r--nihil.generator/nihil.generator.ccm7
-rw-r--r--nihil.guard/guard.ccm19
-rw-r--r--nihil.posix/CMakeLists.txt30
-rw-r--r--nihil.posix/argv.cc65
-rw-r--r--nihil.posix/argv.ccm97
-rw-r--r--nihil.posix/argv.test.cc256
-rw-r--r--nihil.posix/ensure_dir.cc30
-rw-r--r--nihil.posix/ensure_dir.ccm16
-rw-r--r--nihil.posix/exec.cc22
-rw-r--r--nihil.posix/exec.ccm53
-rw-r--r--nihil.posix/execl.ccm40
-rw-r--r--nihil.posix/execl.test.cc55
-rw-r--r--nihil.posix/execlp.ccm18
-rw-r--r--nihil.posix/execlp.test.cc67
-rw-r--r--nihil.posix/execshell.ccm21
-rw-r--r--nihil.posix/execshell.test.cc55
-rw-r--r--nihil.posix/execv.cc42
-rw-r--r--nihil.posix/execv.ccm38
-rw-r--r--nihil.posix/execv.test.cc55
-rw-r--r--nihil.posix/execvp.cc50
-rw-r--r--nihil.posix/execvp.ccm28
-rw-r--r--nihil.posix/fd.cc220
-rw-r--r--nihil.posix/fd.ccm278
-rw-r--r--nihil.posix/fd.test.cc (renamed from nihil.posix/test.fd.cc)53
-rw-r--r--nihil.posix/fexecv.cc47
-rw-r--r--nihil.posix/fexecv.ccm33
-rw-r--r--nihil.posix/fexecvp.ccm37
-rw-r--r--nihil.posix/find_in_path.cc52
-rw-r--r--nihil.posix/find_in_path.ccm43
-rw-r--r--nihil.posix/getenv.cc60
-rw-r--r--nihil.posix/getenv.ccm55
-rw-r--r--nihil.posix/getenv.test.cc (renamed from nihil.posix/test.getenv.cc)0
-rw-r--r--nihil.posix/open.cc31
-rw-r--r--nihil.posix/open.ccm85
-rw-r--r--nihil.posix/open_in_path.cc51
-rw-r--r--nihil.posix/open_in_path.ccm46
-rw-r--r--nihil.posix/posix.ccm16
-rw-r--r--nihil.posix/process.cc102
-rw-r--r--nihil.posix/process.ccm132
-rw-r--r--nihil.posix/read_file.ccm2
-rw-r--r--nihil.posix/rename.cc34
-rw-r--r--nihil.posix/rename.ccm21
-rw-r--r--nihil.posix/spawn.ccm15
-rw-r--r--nihil.posix/tempfile.cc128
-rw-r--r--nihil.posix/tempfile.ccm162
-rw-r--r--nihil.posix/tempfile.test.cc (renamed from nihil.posix/test.tempfile.cc)0
-rw-r--r--nihil.posix/test.spawn.cc40
-rw-r--r--nihil.posix/write_file.ccm2
60 files changed, 2153 insertions, 1861 deletions
diff --git a/.clang-format b/.clang-format
index aeed635..390acb9 100644
--- a/.clang-format
+++ b/.clang-format
@@ -4,9 +4,41 @@ PPIndentWidth: -1
UseTab: AlignWithSpaces
IndentPPDirectives: AfterHash
ColumnLimit: 100
+AccessModifierOffset: -8
+IndentRequiresClause: false
+RequiresClausePosition: OwnLine
+ContinuationIndentWidth: 8
-SeparateDefinitionBlocks: Always
+ReflowComments: Always
+
+SeparateDefinitionBlocks: Leave
+AllowShortFunctionsOnASingleLine: None
+
+BreakConstructorInitializers: BeforeComma
+ConstructorInitializerIndentWidth: 8
+
+BreakAfterReturnType: Automatic
+PenaltyReturnTypeOnItsOwnLine: 0
+BreakTemplateDeclarations: Yes
+
+BracedInitializerIndentWidth: 8
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: true
+ AfterControlStatement: Never
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: false
+ AfterStruct: true
+ AfterUnion: true
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ SplitEmptyNamespace: true
+ SplitEmptyRecord: true
NamespaceIndentation: None
FixNamespaceComments: true
-WrapNamespaceBodyWithEmptyLines: Always
+#WrapNamespaceBodyWithEmptyLines: Always
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..c84c230
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,33 @@
+---
+WarningsAsErrors: '*'
+Checks: >
+ -*,
+ clang-analyzer-*,
+ bugprone-*,
+ -bugprone-reserved-identifier,
+ -bugprone-easily-swappable-parameters,
+ cert-*,
+ concurrency-*,
+ cppcoreguidelines-*,
+ -cppcoreguidelines-avoid-do-while,
+ -cppcoreguidelines-avoid-magic-numbers,
+ -cppcoreguidelines-avoid-reference-coroutine-parameters,
+ -cppcoreguidelines-pro-type-*,
+ hicpp-*,
+ -hicpp-braces-around-statements,
+ -hicpp-named-parameter,
+ -hicpp-vararg,
+ misc-*,
+ modernize-*,
+ readability-*,
+ -readability-braces-around-statements,
+ -readability-convert-member-functions-to-static,
+ -readability-magic-numbers,
+ -readability-named-parameter,
+ -readability-identifier-length,
+ -readability-function-cognitive-complexity,
+ performance-*,
+
+CheckOptions:
+ performance-unnecessary-copy-initialization.AllowedTypes: 'nihil::open_flags;tempfile_flags'
+ performance-unnecessary-value-param.AllowedTypes: 'nihil::open_flags;tempfile_flags'
diff --git a/clang-tidy.conf b/clang-tidy.conf
deleted file mode 100644
index 92a59bc..0000000
--- a/clang-tidy.conf
+++ /dev/null
@@ -1,8 +0,0 @@
----
-WarningsAsErrors: '*'
-Checks: >
- -*,
- bugprone-*,
- -bugprone-reserved-identifier,
- -bugprone-easily-swappable-parameters,
- modernize-*,
diff --git a/nihil.generator/CMakeLists.txt b/nihil.generator/CMakeLists.txt
index 7464785..e521159 100644
--- a/nihil.generator/CMakeLists.txt
+++ b/nihil.generator/CMakeLists.txt
@@ -6,7 +6,12 @@ target_sources(nihil.generator
nihil.generator.ccm
generator.ccm
+ byte_allocator.ccm
+ coroutine_traits.ccm
elements_of.ccm
+ forward.ccm
+ generator_promise_base.ccm
+ generator_promise.ccm
manual_lifetime.ccm
promise_base_alloc.ccm
util.ccm
@@ -15,7 +20,7 @@ target_sources(nihil.generator
if(NIHIL_TESTS)
enable_testing()
- add_executable(nihil.generator.test test.cc)
+ add_executable(nihil.generator.test generator.test.cc)
target_link_libraries(nihil.generator.test PRIVATE
nihil.generator
Catch2::Catch2WithMain
diff --git a/nihil.generator/byte_allocator.ccm b/nihil.generator/byte_allocator.ccm
new file mode 100644
index 0000000..6d46ec6
--- /dev/null
+++ b/nihil.generator/byte_allocator.ccm
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <memory>
+#include <type_traits>
+
+export module nihil.generator:byte_allocator;
+
+namespace nihil {
+
+template <typename Alloc>
+using byte_allocator_t = typename std::allocator_traits<
+ std::remove_cvref_t<Alloc>>::template rebind_alloc<std::byte>;
+
+} // namespace nihil
diff --git a/nihil.generator/coroutine_traits.ccm b/nihil.generator/coroutine_traits.ccm
new file mode 100644
index 0000000..2a9d51d
--- /dev/null
+++ b/nihil.generator/coroutine_traits.ccm
@@ -0,0 +1,70 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <coroutine>
+#include <memory>
+
+export module nihil.generator:coroutine_traits;
+
+import :byte_allocator;
+import :forward;
+import :generator_promise;
+
+namespace std {
+
+// Type-erased allocator with default allocator behaviour.
+export template <typename Ref, typename Value, typename... Args>
+struct coroutine_traits<nihil::generator<Ref, Value>, Args...>
+{
+ using promise_type =
+ nihil::generator_promise<nihil::generator<Ref, Value>, std::allocator<std::byte>>;
+};
+
+// Type-erased allocator with std::allocator_arg parameter
+export template <typename Ref, typename Value, typename Alloc, typename... Args>
+struct coroutine_traits<nihil::generator<Ref, Value>, allocator_arg_t, Alloc, Args...>
+{
+private:
+ using byte_allocator = nihil::byte_allocator_t<Alloc>;
+
+public:
+ using promise_type = nihil::generator_promise<nihil::generator<Ref, Value>, byte_allocator,
+ true /*explicit Allocator*/>;
+};
+
+// Type-erased allocator with std::allocator_arg parameter (non-static member functions)
+export template <typename Ref, typename Value, typename This, typename Alloc, typename... Args>
+struct coroutine_traits<nihil::generator<Ref, Value>, This, allocator_arg_t, Alloc, Args...>
+{
+private:
+ using byte_allocator = nihil::byte_allocator_t<Alloc>;
+
+public:
+ using promise_type = nihil::generator_promise<nihil::generator<Ref, Value>, byte_allocator,
+ true /*explicit Allocator*/>;
+};
+
+// Generator with specified allocator type
+export template <typename Ref, typename Value, typename Alloc, typename... Args>
+struct coroutine_traits<nihil::generator<Ref, Value, Alloc>, Args...>
+{
+ using byte_allocator = nihil::byte_allocator_t<Alloc>;
+
+public:
+ using promise_type =
+ nihil::generator_promise<nihil::generator<Ref, Value, Alloc>, byte_allocator>;
+};
+
+} // namespace std
diff --git a/nihil.generator/forward.ccm b/nihil.generator/forward.ccm
new file mode 100644
index 0000000..8d5ca4d
--- /dev/null
+++ b/nihil.generator/forward.ccm
@@ -0,0 +1,29 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <type_traits>
+
+export module nihil.generator:forward;
+
+import :util;
+
+namespace nihil {
+
+export template <typename Ref,
+ typename Value = std::remove_cvref_t<Ref>,
+ typename Allocator = use_allocator_arg>
+class generator;
+
+} // namespace nihil
diff --git a/nihil.generator/generator.ccm b/nihil.generator/generator.ccm
index 27e8103..96790a8 100644
--- a/nihil.generator/generator.ccm
+++ b/nihil.generator/generator.ccm
@@ -23,485 +23,306 @@ module;
export module nihil.generator:generator;
+import :byte_allocator;
+import :coroutine_traits;
import :elements_of;
+import :forward;
+import :generator_promise_base;
+import :generator_promise;
import :manual_lifetime;
import :promise_base_alloc;
import :util;
namespace nihil {
-export template <typename _Ref,
- typename _Value = std::remove_cvref_t<_Ref>,
- typename _Allocator = use_allocator_arg>
-class generator;
-
-
-template<typename _Ref>
-struct __generator_promise_base
+// TODO : make layout compatible promise casts possible
+export template <typename Ref, typename Value, typename Alloc>
+class generator
{
- template <typename _Ref2, typename _Value, typename _Alloc>
- friend class generator;
-
- __generator_promise_base* __root_;
- std::coroutine_handle<> __parentOrLeaf_;
- // Note: Using manual_lifetime here to avoid extra calls to exception_ptr
- // constructor/destructor in cases where it is not needed (i.e. where this
- // generator coroutine is not used as a nested coroutine).
- // This member is lazily constructed by the __yield_sequence_awaiter::await_suspend()
- // method if this generator is used as a nested generator.
- manual_lifetime<std::exception_ptr> __exception_;
- manual_lifetime<_Ref> __value_;
-
- explicit __generator_promise_base(std::coroutine_handle<> thisCoro) noexcept
- : __root_(this)
- , __parentOrLeaf_(thisCoro)
- {}
-
- ~__generator_promise_base() {
- if (__root_ != this) {
- // This coroutine was used as a nested generator and so will
- // have constructed its __exception_ member which needs to be
- // destroyed here.
- __exception_.destruct();
- }
- }
-
- std::suspend_always initial_suspend() noexcept {
- return {};
- }
-
- void return_void() noexcept {}
-
- void unhandled_exception() {
- if (__root_ != this) {
- __exception_.get() = std::current_exception();
- } else {
- throw;
- }
- }
-
- // Transfers control back to the parent of a nested coroutine
- struct __final_awaiter {
- bool await_ready() noexcept {
- return false;
- }
-
- template <typename _Promise>
- std::coroutine_handle<>
- await_suspend(std::coroutine_handle<_Promise> __h) noexcept {
- _Promise& __promise = __h.promise();
- __generator_promise_base& __root = *__promise.__root_;
- if (&__root != &__promise) {
- auto __parent = __promise.__parentOrLeaf_;
- __root.__parentOrLeaf_ = __parent;
- return __parent;
- }
- return std::noop_coroutine();
- }
-
- void await_resume() noexcept {}
- };
-
- __final_awaiter final_suspend() noexcept {
- return {};
- }
-
- std::suspend_always yield_value(_Ref&& __x)
- noexcept(std::is_nothrow_move_constructible_v<_Ref>) {
- __root_->__value_.construct((_Ref&&)__x);
- return {};
- }
-
- template <typename _T>
- requires
- (!std::is_reference_v<_Ref>) &&
- std::is_convertible_v<_T, _Ref>
- std::suspend_always yield_value(_T&& __x)
- noexcept(std::is_nothrow_constructible_v<_Ref, _T>) {
- __root_->__value_.construct((_T&&)__x);
- return {};
- }
-
- template <typename _Gen>
- struct __yield_sequence_awaiter {
- _Gen __gen_;
-
- __yield_sequence_awaiter(_Gen&& __g) noexcept
- // Taking ownership of the generator ensures frame are destroyed
- // in the reverse order of their execution.
- : __gen_((_Gen&&)__g) {
- }
-
- bool await_ready() noexcept {
- return false;
- }
-
- // set the parent, root and exceptions pointer and
- // resume the nested
- template<typename _Promise>
- std::coroutine_handle<>
- await_suspend(std::coroutine_handle<_Promise> __h) noexcept {
- __generator_promise_base& __current = __h.promise();
- __generator_promise_base& __nested = *__gen_.__get_promise();
- __generator_promise_base& __root = *__current.__root_;
-
- __nested.__root_ = __current.__root_;
- __nested.__parentOrLeaf_ = __h;
-
- // Lazily construct the __exception_ member here now that we
- // know it will be used as a nested generator. This will be
- // destroyed by the promise destructor.
- __nested.__exception_.construct();
- __root.__parentOrLeaf_ = __gen_.__get_coro();
-
- // Immediately resume the nested coroutine (nested generator)
- return __gen_.__get_coro();
- }
-
- void await_resume() {
- __generator_promise_base& __nestedPromise = *__gen_.__get_promise();
- if (__nestedPromise.__exception_.get()) {
- std::rethrow_exception(std::move(__nestedPromise.__exception_.get()));
- }
- }
- };
-
- template <typename _OValue, typename _OAlloc>
- __yield_sequence_awaiter<generator<_Ref, _OValue, _OAlloc>>
- yield_value(nihil::elements_of<generator<_Ref, _OValue, _OAlloc>> __g) noexcept {
- return std::move(__g).get();
- }
-
- template <std::ranges::range _Rng, typename _Allocator>
- __yield_sequence_awaiter<generator<_Ref, std::remove_cvref_t<_Ref>, _Allocator>>
- yield_value(nihil::elements_of<_Rng, _Allocator> && __x) {
- return [](std::allocator_arg_t, _Allocator, auto && __rng) -> generator<_Ref, std::remove_cvref_t<_Ref>, _Allocator> {
- for(auto && e: __rng)
- co_yield static_cast<decltype(e)>(e);
- }(std::allocator_arg, __x.get_allocator(), std::forward<_Rng>(__x.get()));
- }
-
- void resume() {
- __parentOrLeaf_.resume();
- }
-
- // Disable use of co_await within this coroutine.
- void await_transform() = delete;
-};
+ using byte_allocator = byte_allocator_t<Alloc>;
-template<typename _Generator, typename _ByteAllocator, bool _ExplicitAllocator = false>
-struct __generator_promise;
-
-template<typename _Ref, typename _Value, typename _Alloc, typename _ByteAllocator, bool _ExplicitAllocator>
-struct __generator_promise<generator<_Ref, _Value, _Alloc>, _ByteAllocator, _ExplicitAllocator> final
- : public __generator_promise_base<_Ref>
- , public promise_base_alloc<_ByteAllocator> {
- __generator_promise() noexcept
- : __generator_promise_base<_Ref>(std::coroutine_handle<__generator_promise>::from_promise(*this))
- {}
-
- generator<_Ref, _Value, _Alloc> get_return_object() noexcept {
- return generator<_Ref, _Value, _Alloc>{
- std::coroutine_handle<__generator_promise>::from_promise(*this)
- };
- }
-
- using __generator_promise_base<_Ref>::yield_value;
-
- template <std::ranges::range _Rng>
- typename __generator_promise_base<_Ref>::template __yield_sequence_awaiter<generator<_Ref, _Value, _Alloc>>
- yield_value(nihil::elements_of<_Rng> && __x) {
- static_assert (!_ExplicitAllocator,
- "This coroutine has an explicit allocator specified with std::allocator_arg so an allocator needs to be passed "
- "explicitely to std::elements_of");
- return [](auto && __rng) -> generator<_Ref, _Value, _Alloc> {
- for(auto && e: __rng)
- co_yield static_cast<decltype(e)>(e);
- }(std::forward<_Rng>(__x.get()));
- }
-};
-
-template<typename _Alloc>
-using __byte_allocator_t = typename std::allocator_traits<std::remove_cvref_t<_Alloc>>::template rebind_alloc<std::byte>;
-
-} // namespace nihil
-
-namespace std {
-
-// Type-erased allocator with default allocator behaviour.
-export template<typename _Ref, typename _Value, typename... _Args>
-struct coroutine_traits<nihil::generator<_Ref, _Value>, _Args...> {
- using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, std::allocator<std::byte>>;
-};
-
-// Type-erased allocator with std::allocator_arg parameter
-export template<typename _Ref, typename _Value, typename _Alloc, typename... _Args>
-struct coroutine_traits<nihil::generator<_Ref, _Value>, allocator_arg_t, _Alloc, _Args...> {
-private:
- using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
public:
- using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, __byte_allocator, true /*explicit Allocator*/>;
-};
+ using promise_type = generator_promise<generator<Ref, Value, Alloc>, byte_allocator>;
+ friend promise_type;
-// Type-erased allocator with std::allocator_arg parameter (non-static member functions)
-export template<typename _Ref, typename _Value, typename _This, typename _Alloc, typename... _Args>
-struct coroutine_traits<nihil::generator<_Ref, _Value>, _This, allocator_arg_t, _Alloc, _Args...> {
private:
- using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
-public:
- using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, __byte_allocator, true /*explicit Allocator*/>;
-};
+ using coroutine_handle = std::coroutine_handle<promise_type>;
-// Generator with specified allocator type
-export template<typename _Ref, typename _Value, typename _Alloc, typename... _Args>
-struct coroutine_traits<nihil::generator<_Ref, _Value, _Alloc>, _Args...> {
- using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
public:
- using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value, _Alloc>, __byte_allocator>;
-};
-
-} // namespace std
-
-namespace nihil {
+ generator() noexcept = default;
+
+ generator(generator &&other) noexcept
+ : m_coro(std::exchange(other.m_coro, {}))
+ , m_started(std::exchange(other.m_started, false))
+ {
+ }
+
+ ~generator() noexcept
+ {
+ if (m_coro) {
+ if (m_started && !m_coro.done()) {
+ m_coro.promise().__value_.destruct();
+ }
+ m_coro.destroy();
+ }
+ }
+
+ auto operator=(generator &&g) noexcept -> generator &
+ {
+ swap(g);
+ return *this;
+ }
+
+ void swap(generator &other) noexcept
+ {
+ std::swap(m_coro, other.m_coro);
+ std::swap(m_started, other.m_started);
+ }
+
+ struct sentinel
+ {
+ };
+
+ struct iterator
+ {
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = Value;
+ using reference = Ref;
+ using pointer = std::add_pointer_t<Ref>;
+
+ iterator() noexcept = default;
+ iterator(const iterator &) = delete;
+
+ iterator(iterator &&other) noexcept
+ : m_coro(std::exchange(other.m_coro, {}))
+ {
+ }
+
+ auto operator=(iterator &&other) noexcept -> iterator &
+ {
+ std::swap(m_coro, other.m_coro);
+ return *this;
+ }
+
+ ~iterator() = default;
+
+ friend auto operator==(const iterator &it, sentinel) noexcept -> bool
+ {
+ return it.m_coro.done();
+ }
+
+ auto operator++() -> iterator &
+ {
+ m_coro.promise().m_value.destruct();
+ m_coro.promise().resume();
+ return *this;
+ }
+
+ void operator++(int)
+ {
+ (void)operator++();
+ }
+
+ auto operator*() const noexcept -> reference
+ {
+ return static_cast<reference>(m_coro.promise().m_value.get());
+ }
+
+ private:
+ friend generator;
+
+ explicit iterator(coroutine_handle coro) noexcept
+ : m_coro(coro)
+ {
+ }
+
+ coroutine_handle m_coro;
+ };
+
+ auto begin() -> iterator
+ {
+ assert(m_coro);
+ assert(!m_started);
+ m_started = true;
+ m_coro.resume();
+ return iterator{m_coro};
+ }
+
+ auto end() noexcept -> sentinel
+ {
+ return {};
+ }
-// TODO : make layout compatible promise casts possible
-export template <typename _Ref, typename _Value, typename _Alloc>
-class generator {
- using __byte_allocator = __byte_allocator_t<_Alloc>;
-public:
- using promise_type = __generator_promise<generator<_Ref, _Value, _Alloc>, __byte_allocator>;
- friend promise_type;
private:
- using __coroutine_handle = std::coroutine_handle<promise_type>;
-public:
-
- generator() noexcept = default;
-
- generator(generator&& __other) noexcept
- : __coro_(std::exchange(__other.__coro_, {}))
- , __started_(std::exchange(__other.__started_, false)) {
- }
-
- ~generator() noexcept {
- if (__coro_) {
- if (__started_ && !__coro_.done()) {
- __coro_.promise().__value_.destruct();
- }
- __coro_.destroy();
- }
- }
-
- generator& operator=(generator && g) noexcept {
- swap(g);
- return *this;
- }
-
- void swap(generator& __other) noexcept {
- std::swap(__coro_, __other.__coro_);
- std::swap(__started_, __other.__started_);
- }
-
- struct sentinel {};
-
- class iterator {
- public:
- using iterator_category = std::input_iterator_tag;
- using difference_type = std::ptrdiff_t;
- using value_type = _Value;
- using reference = _Ref;
- using pointer = std::add_pointer_t<_Ref>;
-
- iterator() noexcept = default;
- iterator(const iterator &) = delete;
-
- iterator(iterator&& __other) noexcept
- : __coro_(std::exchange(__other.__coro_, {})) {
- }
-
- iterator& operator=(iterator&& __other) {
- std::swap(__coro_, __other.__coro_);
- return *this;
- }
-
- ~iterator() {
- }
-
- friend bool operator==(const iterator &it, sentinel) noexcept {
- return it.__coro_.done();
- }
-
- iterator &operator++() {
- __coro_.promise().__value_.destruct();
- __coro_.promise().resume();
- return *this;
- }
- void operator++(int) {
- (void)operator++();
- }
-
- reference operator*() const noexcept {
- return static_cast<reference>(__coro_.promise().__value_.get());
- }
-
- private:
- friend generator;
-
- explicit iterator(__coroutine_handle __coro) noexcept
- : __coro_(__coro) {}
-
- __coroutine_handle __coro_;
- };
-
- iterator begin() {
- assert(__coro_);
- assert(!__started_);
- __started_ = true;
- __coro_.resume();
- return iterator{__coro_};
- }
-
- sentinel end() noexcept {
- return {};
- }
-
-private:
- explicit generator(__coroutine_handle __coro) noexcept
- : __coro_(__coro) {
- }
+ explicit generator(coroutine_handle coro) noexcept
+ : m_coro(coro)
+ {
+ }
public: // to get around access restrictions for __yield_sequence_awaitable
- std::coroutine_handle<> __get_coro() noexcept { return __coro_; }
- promise_type* __get_promise() noexcept { return std::addressof(__coro_.promise()); }
+ auto get_coro() noexcept -> std::coroutine_handle<>
+ {
+ return m_coro;
+ }
+ auto get_promise() noexcept -> promise_type *
+ {
+ return std::addressof(m_coro.promise());
+ }
private:
- __coroutine_handle __coro_;
- bool __started_ = false;
+ coroutine_handle m_coro;
+ bool m_started = false;
};
// Specialisation for type-erased allocator implementation.
-export template <typename _Ref, typename _Value>
-class generator<_Ref, _Value, use_allocator_arg> {
- using __promise_base = __generator_promise_base<_Ref>;
-public:
+export template <typename Ref, typename Value>
+class generator<Ref, Value, use_allocator_arg>
+{
+ using promise_base = generator_promise_base<Ref>;
- generator() noexcept
- : __promise_(nullptr)
- , __coro_()
- , __started_(false)
- {}
-
- generator(generator&& __other) noexcept
- : __promise_(std::exchange(__other.__promise_, nullptr))
- , __coro_(std::exchange(__other.__coro_, {}))
- , __started_(std::exchange(__other.__started_, false)) {
- }
-
- ~generator() noexcept {
- if (__coro_) {
- if (__started_ && !__coro_.done()) {
- __promise_->__value_.destruct();
- }
- __coro_.destroy();
- }
- }
-
- generator& operator=(generator g) noexcept {
- swap(g);
- return *this;
- }
-
- void swap(generator& __other) noexcept {
- std::swap(__promise_, __other.__promise_);
- std::swap(__coro_, __other.__coro_);
- std::swap(__started_, __other.__started_);
- }
-
- struct sentinel {};
-
- class iterator {
- public:
- using iterator_category = std::input_iterator_tag;
- using difference_type = std::ptrdiff_t;
- using value_type = _Value;
- using reference = _Ref;
- using pointer = std::add_pointer_t<_Ref>;
-
- iterator() noexcept = default;
- iterator(const iterator &) = delete;
-
- iterator(iterator&& __other) noexcept
- : __promise_(std::exchange(__other.__promise_, nullptr))
- , __coro_(std::exchange(__other.__coro_, {}))
- {}
-
- iterator& operator=(iterator&& __other) {
- __promise_ = std::exchange(__other.__promise_, nullptr);
- __coro_ = std::exchange(__other.__coro_, {});
- return *this;
- }
-
- ~iterator() = default;
-
- friend bool operator==(const iterator &it, sentinel) noexcept {
- return it.__coro_.done();
- }
-
- iterator& operator++() {
- __promise_->__value_.destruct();
- __promise_->resume();
- return *this;
- }
-
- void operator++(int) {
- (void)operator++();
- }
-
- reference operator*() const noexcept {
- return static_cast<reference>(__promise_->__value_.get());
- }
-
- private:
- friend generator;
-
- explicit iterator(__promise_base* __promise, std::coroutine_handle<> __coro) noexcept
- : __promise_(__promise)
- , __coro_(__coro)
- {}
-
- __promise_base* __promise_;
- std::coroutine_handle<> __coro_;
- };
-
- iterator begin() {
- assert(__coro_);
- assert(!__started_);
- __started_ = true;
- __coro_.resume();
- return iterator{__promise_, __coro_};
- }
-
- sentinel end() noexcept {
- return {};
- }
+public:
+ generator() noexcept
+ : m_promise(nullptr)
+ {
+ }
+
+ generator(generator &&other) noexcept
+ : m_promise(std::exchange(other.m_promise, nullptr))
+ , m_coro(std::exchange(other.m_coro, {}))
+ , m_started(std::exchange(other.m_started, false))
+ {
+ }
+
+ ~generator() noexcept
+ {
+ if (m_coro) {
+ if (m_started && !m_coro.done()) {
+ m_promise->m_value.destruct();
+ }
+ m_coro.destroy();
+ }
+ }
+
+ auto operator=(generator g) noexcept -> generator &
+ {
+ swap(g);
+ return *this;
+ }
+
+ void swap(generator &other) noexcept
+ {
+ std::swap(m_promise, other.m_promise);
+ std::swap(m_coro, other.m_coro);
+ std::swap(m_started, other.m_started);
+ }
+
+ struct sentinel
+ {
+ };
+
+ struct iterator
+ {
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = Value;
+ using reference = Ref;
+ using pointer = std::add_pointer_t<Ref>;
+
+ iterator() noexcept = default;
+ iterator(const iterator &) = delete;
+
+ iterator(iterator &&other) noexcept
+ : m_promise(std::exchange(other.m_promise, nullptr))
+ , m_coro(std::exchange(other.m_coro, {}))
+ {
+ }
+
+ auto operator=(iterator &&other) noexcept -> iterator &
+ {
+ m_promise = std::exchange(other.m_promise, nullptr);
+ m_coro = std::exchange(other.m_coro, {});
+ return *this;
+ }
+
+ ~iterator() = default;
+
+ friend auto operator==(const iterator &it, sentinel) noexcept -> bool
+ {
+ return it.m_coro.done();
+ }
+
+ auto operator++() -> iterator &
+ {
+ m_promise->m_value.destruct();
+ m_promise->resume();
+ return *this;
+ }
+
+ void operator++(int)
+ {
+ (void)operator++();
+ }
+
+ auto operator*() const noexcept -> reference
+ {
+ return static_cast<reference>(m_promise->m_value.get());
+ }
+
+ private:
+ friend generator;
+
+ explicit iterator(promise_base *promise,
+ std::coroutine_handle<> coro) noexcept
+ : m_promise(promise)
+ , m_coro(coro)
+ {
+ }
+
+ promise_base *m_promise;
+ std::coroutine_handle<> m_coro;
+ };
+
+ auto begin() -> iterator
+ {
+ assert(m_coro);
+ assert(!m_started);
+ m_started = true;
+ m_coro.resume();
+ return iterator{m_promise, m_coro};
+ }
+
+ auto end() noexcept -> sentinel
+ {
+ return {};
+ }
private:
- template<typename _Generator, typename _ByteAllocator, bool _ExplicitAllocator>
- friend struct __generator_promise;
+ template <typename Generator, typename ByteAllocator, bool ExplicitAllocator>
+ friend struct generator_promise;
- template<typename _Promise>
- explicit generator(std::coroutine_handle<_Promise> __coro) noexcept
- : __promise_(std::addressof(__coro.promise()))
- , __coro_(__coro)
- {}
+ template <typename Promise>
+ explicit generator(std::coroutine_handle<Promise> coro) noexcept
+ : m_promise(std::addressof(coro.promise()))
+ , m_coro(coro)
+ {
+ }
public: // to get around access restrictions for __yield_sequence_awaitable
- std::coroutine_handle<> __get_coro() noexcept { return __coro_; }
- __promise_base* __get_promise() noexcept { return __promise_; }
+ auto get_coro() noexcept -> std::coroutine_handle<>
+ {
+ return m_coro;
+ }
+
+ auto get_promise() noexcept -> promise_base *
+ {
+ return m_promise;
+ }
private:
- __promise_base* __promise_;
- std::coroutine_handle<> __coro_;
- bool __started_ = false;
+ promise_base *m_promise;
+ std::coroutine_handle<> m_coro;
+ bool m_started = false;
};
} // namespace nihil
diff --git a/nihil.generator/test.cc b/nihil.generator/generator.test.cc
index 49272b4..49272b4 100644
--- a/nihil.generator/test.cc
+++ b/nihil.generator/generator.test.cc
diff --git a/nihil.generator/generator_promise.ccm b/nihil.generator/generator_promise.ccm
new file mode 100644
index 0000000..b0fd4b1
--- /dev/null
+++ b/nihil.generator/generator_promise.ccm
@@ -0,0 +1,65 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <coroutine>
+#include <ranges>
+
+export module nihil.generator:generator_promise;
+
+import :forward;
+import :generator_promise_base;
+import :promise_base_alloc;
+
+namespace nihil {
+
+export template <typename Generator, typename ByteAllocator, bool ExplicitAllocator = false>
+struct generator_promise;
+
+export template <typename Ref, typename Value, typename Alloc, typename ByteAllocator,
+ bool ExplicitAllocator>
+struct generator_promise<generator<Ref, Value, Alloc>, ByteAllocator, ExplicitAllocator> final
+ : public generator_promise_base<Ref>,
+ public promise_base_alloc<ByteAllocator>
+{
+ generator_promise() noexcept
+ : generator_promise_base<Ref>(
+ std::coroutine_handle<generator_promise>::from_promise(*this))
+ {
+ }
+
+ auto get_return_object() noexcept -> generator<Ref, Value, Alloc>
+ {
+ return generator<Ref, Value, Alloc>{
+ std::coroutine_handle<generator_promise>::from_promise(*this)};
+ }
+
+ using generator_promise_base<Ref>::yield_value;
+
+ template <std::ranges::range Rng>
+ auto yield_value(nihil::elements_of<Rng> &&x) -> typename generator_promise_base<
+ Ref>::template yield_sequence_awaiter<generator<Ref, Value, Alloc>>
+ {
+ static_assert(!ExplicitAllocator,
+ "This coroutine has an explicit allocator specified with "
+ "std::allocator_arg so an allocator needs to be passed "
+ "explicitely to std::elements_of");
+ return [](auto &&rng) -> generator<Ref, Value, Alloc> {
+ for (auto &&e : rng)
+ co_yield static_cast<decltype(e)>(e);
+ }(std::forward<Rng>(x.get()));
+ }
+};
+
+} // namespace nihil
diff --git a/nihil.generator/generator_promise_base.ccm b/nihil.generator/generator_promise_base.ccm
new file mode 100644
index 0000000..fec9b1b
--- /dev/null
+++ b/nihil.generator/generator_promise_base.ccm
@@ -0,0 +1,205 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <coroutine>
+#include <exception>
+#include <memory>
+
+export module nihil.generator:generator_promise_base;
+
+import :elements_of;
+import :forward;
+import :manual_lifetime;
+
+namespace nihil {
+
+template <typename Ref>
+struct generator_promise_base
+{
+ template <typename Ref2, typename Value, typename Alloc>
+ friend class generator;
+
+ generator_promise_base *m_root;
+ std::coroutine_handle<> m_parent_or_leaf;
+
+ // Note: Using manual_lifetime here to avoid extra calls to exception_ptr
+ // constructor/destructor in cases where it is not needed (i.e. where this
+ // generator coroutine is not used as a nested coroutine).
+ // This member is lazily constructed by the __yield_sequence_awaiter::await_suspend()
+ // method if this generator is used as a nested generator.
+ manual_lifetime<std::exception_ptr> m_exception;
+ manual_lifetime<Ref> m_value;
+
+ explicit generator_promise_base(std::coroutine_handle<> this_coro) noexcept
+ : m_root(this)
+ , m_parent_or_leaf(this_coro)
+ {
+ }
+
+ ~generator_promise_base()
+ {
+ if (m_root != this) {
+ // This coroutine was used as a nested generator and so will
+ // have constructed its __exception_ member which needs to be
+ // destroyed here.
+ m_exception.destruct();
+ }
+ }
+
+ auto initial_suspend() noexcept -> std::suspend_always
+ {
+ return {};
+ }
+
+ auto return_void() noexcept -> void
+ {
+ }
+
+ auto unhandled_exception() -> void
+ {
+ if (m_root != this)
+ m_exception.get() = std::current_exception();
+ else
+ throw;
+ }
+
+ // Transfers control back to the parent of a nested coroutine
+ struct final_awaiter
+ {
+ auto await_ready() noexcept -> bool
+ {
+ return false;
+ }
+
+ template <typename Promise>
+ auto
+ await_suspend(std::coroutine_handle<Promise> h) noexcept -> std::coroutine_handle<>
+ {
+ Promise &promise = h.promise();
+ generator_promise_base &root = *promise.m_root;
+ if (&root != &promise) {
+ auto parent = promise.m_parent_or_leaf;
+ root.m_parent_or_leaf = parent;
+ return parent;
+ }
+ return std::noop_coroutine();
+ }
+
+ void await_resume() noexcept
+ {
+ }
+ };
+
+ auto final_suspend() noexcept -> final_awaiter
+ {
+ return {};
+ }
+
+ auto yield_value(Ref &&x) noexcept(std::is_nothrow_move_constructible_v<Ref>)
+ -> std::suspend_always
+ {
+ m_root->m_value.construct((Ref &&)x);
+ return {};
+ }
+
+ template <typename T>
+ requires(!std::is_reference_v<Ref>) && std::is_convertible_v<T, Ref>
+ auto
+ yield_value(T &&x) noexcept(std::is_nothrow_constructible_v<Ref, T>) -> std::suspend_always
+ {
+ m_root->m_value.construct((T &&)x);
+ return {};
+ }
+
+ template <typename Gen>
+ struct yield_sequence_awaiter
+ {
+ Gen m_gen;
+
+ yield_sequence_awaiter(Gen &&g) noexcept
+ // Taking ownership of the generator ensures frame are destroyed
+ // in the reverse order of their execution.
+ : m_gen((Gen &&)g)
+ {
+ }
+
+ auto await_ready() noexcept -> bool
+ {
+ return false;
+ }
+
+ // set the parent, root and exceptions pointer and
+ // resume the nested
+ template <typename Promise>
+ auto
+ await_suspend(std::coroutine_handle<Promise> h) noexcept -> std::coroutine_handle<>
+ {
+ generator_promise_base &current = h.promise();
+ generator_promise_base &nested = *m_gen.get_promise();
+ generator_promise_base &root = *current.m_root;
+
+ nested.m_root = current.m_root;
+ nested.m_parent_or_leaf = h;
+
+ // Lazily construct the __exception_ member here now that we
+ // know it will be used as a nested generator. This will be
+ // destroyed by the promise destructor.
+ nested.m_exception.construct();
+ root.m_parent_or_leaf = m_gen.get_coro();
+
+ // Immediately resume the nested coroutine (nested generator)
+ return m_gen.get_coro();
+ }
+
+ void await_resume()
+ {
+ generator_promise_base &nested_promise = *m_gen.get_promise();
+
+ if (nested_promise.m_exception.get()) {
+ std::rethrow_exception(nested_promise.m_exception.get());
+ }
+ }
+ };
+
+ template <typename OValue, typename OAlloc>
+ auto yield_value(nihil::elements_of<generator<Ref, OValue, OAlloc>> g) noexcept
+ -> yield_sequence_awaiter<generator<Ref, OValue, OAlloc>>
+
+ {
+ return std::move(g).get();
+ }
+
+ template <std::ranges::range Rng, typename Allocator>
+ auto yield_value(nihil::elements_of<Rng, Allocator> &&x)
+ -> yield_sequence_awaiter<generator<Ref, std::remove_cvref_t<Ref>, Allocator>>
+
+ {
+ return [](std::allocator_arg_t, Allocator,
+ auto &&rng) -> generator<Ref, std::remove_cvref_t<Ref>, Allocator> {
+ for (auto &&e : rng)
+ co_yield static_cast<decltype(e)>(e);
+ }(std::allocator_arg, x.get_allocator(), std::forward<Rng>(x.get()));
+ }
+
+ void resume()
+ {
+ m_parent_or_leaf.resume();
+ }
+
+ // Disable use of co_await within this coroutine.
+ void await_transform() = delete;
+};
+
+} // namespace nihil
diff --git a/nihil.generator/manual_lifetime.ccm b/nihil.generator/manual_lifetime.ccm
index d249e99..963e6c9 100644
--- a/nihil.generator/manual_lifetime.ccm
+++ b/nihil.generator/manual_lifetime.ccm
@@ -68,8 +68,8 @@ private:
template <typename T>
class manual_lifetime<T &> {
- manual_lifetime() noexcept {}
- ~manual_lifetime() {}
+ manual_lifetime() noexcept = default;
+ ~manual_lifetime() = default;
auto construct(this manual_lifetime &self, T &value) noexcept -> T &
{
@@ -92,8 +92,8 @@ private:
template <typename T>
class manual_lifetime<T &&> {
- manual_lifetime() noexcept {}
- ~manual_lifetime() {}
+ manual_lifetime() noexcept = default;
+ ~manual_lifetime() = default;
auto construct(this manual_lifetime &self, T &&value) noexcept -> T &&
{
diff --git a/nihil.generator/nihil.generator.ccm b/nihil.generator/nihil.generator.ccm
index 9eec5b4..ac9076b 100644
--- a/nihil.generator/nihil.generator.ccm
+++ b/nihil.generator/nihil.generator.ccm
@@ -17,16 +17,19 @@ module;
export module nihil.generator;
+export import :byte_allocator;
+export import :coroutine_traits;
export import :elements_of;
+export import :forward;
export import :generator;
+export import :generator_promise_base;
export import :manual_lifetime;
export import :promise_base_alloc;
export import :util;
-export namespace std::ranges {
+export namespace std::ranges { // NOLINT
template <typename T, typename U, typename Alloc>
constexpr inline bool enable_view<nihil::generator<T, U, Alloc>> = true;
} // namespace std::ranges
-
diff --git a/nihil.guard/guard.ccm b/nihil.guard/guard.ccm
index 7b6cf66..84ff401 100644
--- a/nihil.guard/guard.ccm
+++ b/nihil.guard/guard.ccm
@@ -1,7 +1,4 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <concepts>
@@ -13,19 +10,15 @@ export module nihil.guard;
namespace nihil {
-/*
- * guard: invoke a callable when this object is destroyed; this is similar to
- * scope_exit from the library fundamentals TS, which LLVM doesn't implement.
- */
+// guard: invoke a callable when this object is destroyed; this is similar to
+// scope_exit from the library fundamentals TS, which LLVM doesn't implement.
export template<std::invocable F>
struct guard final {
// Initialise the guard with a callable we will invoke later.
- guard(F func) : m_func(std::move(func)) {}
+ explicit guard(F func) : m_func(std::move(func)) {}
- /*
- * We are being destroyed, so call the callable.
- * If the callable throws, std::terminate() will be called.
- */
+ // We are being destroyed, so call the callable.
+ // If the callable throws, std::terminate() will be called.
~guard()
{
if (m_func)
diff --git a/nihil.posix/CMakeLists.txt b/nihil.posix/CMakeLists.txt
index b04018e..61e83df 100644
--- a/nihil.posix/CMakeLists.txt
+++ b/nihil.posix/CMakeLists.txt
@@ -10,13 +10,15 @@ target_sources(nihil.posix
argv.ccm
ensure_dir.ccm
- exec.ccm
+ execl.ccm
execlp.ccm
+ execshell.ccm
execv.ccm
execvp.ccm
executor.ccm
fd.ccm
fexecv.ccm
+ fexecvp.ccm
find_in_path.ccm
getenv.ccm
open.ccm
@@ -27,31 +29,21 @@ target_sources(nihil.posix
spawn.ccm
tempfile.ccm
write_file.ccm
-
- PRIVATE
- argv.cc
- ensure_dir.cc
- exec.cc
- execv.cc
- execvp.cc
- getenv.cc
- fd.cc
- find_in_path.cc
- open.cc
- open_in_path.cc
- process.cc
- rename.cc
- tempfile.cc
)
if(NIHIL_TESTS)
enable_testing()
add_executable(nihil.posix.test
- test.fd.cc
- test.getenv.cc
+ argv.test.cc
+ execl.test.cc
+ execlp.test.cc
+ execshell.test.cc
+ execv.test.cc
+ fd.test.cc
+ getenv.test.cc
test.spawn.cc
- test.tempfile.cc
+ tempfile.test.cc
)
target_link_libraries(nihil.posix.test PRIVATE
diff --git a/nihil.posix/argv.cc b/nihil.posix/argv.cc
deleted file mode 100644
index e6b1389..0000000
--- a/nihil.posix/argv.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <memory>
-#include <ranges>
-#include <string>
-#include <vector>
-
-module nihil.posix;
-
-namespace nihil {
-
-argv::argv() = default;
-argv::argv(argv &&) noexcept = default;
-auto argv::operator=(this argv &, argv &&) -> argv & = default;
-
-argv::~argv()
-{
- for (auto *arg : m_args)
- delete[] arg;
-}
-
-auto argv::data(this argv const &self) -> char const * const *
-{
- return self.m_args.data();
-}
-
-auto argv::data(this argv &self) -> char * const *
-{
- return self.m_args.data();
-}
-
-auto argv::size(this argv const &self)
-{
- return self.m_args.size();
-}
-
-auto argv::begin(this argv const &self)
-{
- return self.m_args.begin();
-}
-
-auto argv::end(this argv const &self)
-{
- return self.m_args.end();
-}
-
-
-auto argv::add_arg(this argv &self, std::string_view arg) -> void
-{
- // Create a nul-terminated C string.
- auto ptr = std::make_unique<char[]>(arg.size() + 1);
- std::ranges::copy(arg, ptr.get());
- ptr[arg.size()] = '\0';
-
- // Ensure we won't throw when emplacing the pointer.
- self.m_args.reserve(self.m_args.size() + 1);
- self.m_args.emplace_back(ptr.release());
-}
-
-} // namespace nihil
-
diff --git a/nihil.posix/argv.ccm b/nihil.posix/argv.ccm
index 6f60f4b..de75770 100644
--- a/nihil.posix/argv.ccm
+++ b/nihil.posix/argv.ccm
@@ -16,19 +16,35 @@ namespace nihil {
/*
* argv: stores a null-terminated array of nul-terminated C strings.
* argv::data() is suitable for passing to ::execv().
- *
- * Create an argv using argv::from_range(), which takes a range of
- * string-like objects.
*/
-export struct argv {
+export struct argv
+{
+ using value_type = char *;
+ using iterator = value_type const *;
+ using const_iterator = value_type const *;
+
/*
* Create a new argv from a range.
+ *
+ * Delegate to the default constructor to ensure the pointers in
+ * m_args are deleted if we throw when allocating.
*/
argv(std::from_range_t, std::ranges::range auto &&args)
+ : argv()
{
- for (auto &&arg : args)
- add_arg(std::string_view(arg));
+ for (auto &&arg : args) {
+ auto str = std::string_view(arg);
+
+ // Create a nul-terminated C string.
+ auto ptr = std::make_unique<char[]>(str.size() + 1); // NOLINT
+ std::ranges::copy(str, ptr.get());
+ ptr[str.size()] = '\0';
+
+ // Ensure we won't throw when emplacing the pointer.
+ m_args.reserve(m_args.size() + 1);
+ m_args.emplace_back(ptr.release());
+ }
m_args.push_back(nullptr);
}
@@ -36,43 +52,68 @@ export struct argv {
/*
* Create an argv from an initializer list.
*/
- template<typename T>
- explicit argv(std::initializer_list<T> &&args)
- : argv(std::from_range, std::forward<decltype(args)>(args))
+ template <typename T>
+ argv(std::initializer_list<T> const &args)
+ : argv(std::from_range, args)
{
}
+ template <typename T>
+ argv(std::initializer_list<T> &&args)
+ : argv(std::from_range, std::move(args))
+ {
+ }
+
+ // Destructor.
+ ~argv()
+ {
+ for (auto *arg : m_args)
+ delete[] arg; // NOLINT
+ }
+
// Movable.
- argv(argv &&) noexcept;
- auto operator=(this argv &, argv &&other) -> argv &;
+ argv(argv &&) noexcept = default;
+ auto operator=(argv &&other) noexcept -> argv & = default;
// Not copyable. TODO: for completeness, it probably should be.
argv(argv const &) = delete;
- auto operator=(this argv &, argv const &other) -> argv& = delete;
-
- ~argv();
+ auto operator=(argv const &other) -> argv & = delete;
// Access the stored arguments.
- [[nodiscard]] auto data(this argv const &self) -> char const * const *;
- [[nodiscard]] auto data(this argv &self) -> char * const *;
- [[nodiscard]] auto size(this argv const &self);
+ [[nodiscard]] auto data(this argv const &self) -> value_type const *
+ {
+ return self.m_args.data();
+ }
+
+ [[nodiscard]] auto size(this argv const &self) -> std::size_t
+ {
+ return self.m_args.size();
+ }
+
+ // Access an element by index. Throws std::out_of_range if the index is out of range.
+ [[nodiscard]] auto operator[](this argv const &self, std::size_t index) -> value_type
+ {
+ return self.m_args.at(index);
+ }
// Range access
- [[nodiscard]] auto begin(this argv const &self);
- [[nodiscard]] auto end(this argv const &self);
+ [[nodiscard]] auto begin(this argv const &self) -> const_iterator
+ {
+ return self.data();
+ }
+
+ [[nodiscard]] auto end(this argv const &self) -> const_iterator
+ {
+ return self.data() + self.size(); // NOLINT
+ }
private:
- // Use the from_range() factory method to create new instances.
- argv();
+ // Private since default-constructing an argv isn't useful.
+ argv() = default;
- // The argument pointers, including the null terminator.
- // This can't be a vector<unique_ptr> because we need an array of
- // char pointers to pass to exec.
+ // The argument pointers, including the null terminator. This can't be a
+ // vector<unique_ptr> because we need an array of char pointers to pass to exec.
std::vector<char *> m_args;
-
- // Add a new argument to the array.
- auto add_arg(this argv &self, std::string_view arg) -> void;
};
} // namespace nihil
-
diff --git a/nihil.posix/argv.test.cc b/nihil.posix/argv.test.cc
new file mode 100644
index 0000000..3cc218d
--- /dev/null
+++ b/nihil.posix/argv.test.cc
@@ -0,0 +1,256 @@
+/*
+ * This source code is released into the public domain
+ */
+
+#include <algorithm>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.posix;
+
+namespace {
+constexpr auto *test_tags = "[nihil][nihil.posix][nihil.posix.argv]";
+
+TEST_CASE("nihil.posix: argv: invariants", test_tags)
+{
+ static_assert(std::movable<nihil::argv>);
+ static_assert(std::move_constructible<nihil::argv>);
+ static_assert(std::is_nothrow_move_constructible_v<nihil::argv>);
+ static_assert(std::is_nothrow_move_assignable_v<nihil::argv>);
+
+ static_assert(!std::copy_constructible<nihil::argv>);
+
+ static_assert(std::destructible<nihil::argv>);
+
+ static_assert(std::swappable<nihil::argv>);
+ static_assert(std::is_nothrow_swappable_v<nihil::argv>);
+
+ static_assert(std::constructible_from<nihil::argv,
+ std::initializer_list<std::string_view>>);
+ static_assert(std::constructible_from<nihil::argv,
+ std::initializer_list<std::string>>);
+ static_assert(std::constructible_from<nihil::argv,
+ std::initializer_list<char const *>>);
+ static_assert(std::constructible_from<nihil::argv,
+ std::initializer_list<char *>>);
+
+ static_assert(std::ranges::sized_range<nihil::argv>);
+ static_assert(std::ranges::contiguous_range<nihil::argv>);
+}
+
+SCENARIO("nihil::argv::size() works")
+{
+ GIVEN("An argv constructed from 3 elements")
+ {
+ auto argv = nihil::argv{"one", "two", "three"};
+
+ THEN("size() returns 4")
+ {
+ REQUIRE(std::ranges::size(argv) == 4);
+ }
+ }
+}
+
+SCENARIO("nihil::argv::operator[] works")
+{
+ using namespace std::literals;
+
+ GIVEN("An argv constructed from 3 elements")
+ {
+ auto argv = nihil::argv{"one", "two", "three"};
+
+ THEN("operator[] returns the expected elements")
+ {
+ REQUIRE(argv[0] == "one"sv);
+ REQUIRE(argv[1] == "two"sv);
+ REQUIRE(argv[2] == "three"sv);
+ REQUIRE(argv[3] == nullptr);
+ }
+
+ AND_THEN("operator[] on a non-existent element throws std::out_of_range")
+ {
+ REQUIRE_THROWS_AS(argv[4], std::out_of_range);
+ }
+ }
+}
+
+SCENARIO("nihil::argv can be constructed from an initializer_list<char const *>")
+{
+ using namespace std::literals;
+
+ GIVEN("An argv constructed from an initializer_list")
+ {
+ auto argv = nihil::argv{"one", "two", "three"};
+
+ THEN("size() returns 4")
+ {
+ REQUIRE(std::ranges::size(argv) == 4);
+ }
+
+ AND_THEN("The stored values match the initializer_list")
+ {
+ REQUIRE(argv[0] == "one"sv);
+ REQUIRE(argv[1] == "two"sv);
+ REQUIRE(argv[2] == "three"sv);
+ REQUIRE(argv[3] == nullptr);
+ }
+ }
+}
+
+SCENARIO("nihil::argv can be constructed from an initializer_list<std::string_view>")
+{
+ using namespace std::literals;
+
+ GIVEN("An argv constructed from an initializer_list")
+ {
+ auto argv = nihil::argv{"one"sv, "two"sv, "three"sv};
+
+ THEN("size() returns 4")
+ {
+ REQUIRE(std::ranges::size(argv) == 4);
+ }
+
+ AND_THEN("The stored values match the initializer_list")
+ {
+ REQUIRE(argv[0] == "one"sv);
+ REQUIRE(argv[1] == "two"sv);
+ REQUIRE(argv[2] == "three"sv);
+ REQUIRE(argv[3] == nullptr);
+ }
+ }
+}
+
+SCENARIO("nihil::argv can be constructed from a range of std::string")
+{
+ using namespace std::literals;
+
+ GIVEN("An argv constructed from an initializer_list")
+ {
+ auto vec = std::vector{"one"s, "two"s, "three"s};
+ auto argv = nihil::argv(std::from_range, vec);
+
+ THEN("size() returns 4")
+ {
+ REQUIRE(std::ranges::size(argv) == 4);
+ }
+
+ AND_THEN("The stored values match the range")
+ {
+ REQUIRE(argv[0] == "one"sv);
+ REQUIRE(argv[1] == "two"sv);
+ REQUIRE(argv[2] == "three"sv);
+ REQUIRE(argv[3] == nullptr);
+ }
+ }
+}
+
+SCENARIO("nihil::data() returns the correct data")
+{
+ using namespace std::literals;
+
+ GIVEN("An argv")
+ {
+ auto argv = nihil::argv{"one", "two", "three"};
+
+ THEN("The values in data() match the provided data")
+ {
+ REQUIRE(argv.data()[0] == "one"sv);
+ REQUIRE(argv.data()[1] == "two"sv);
+ REQUIRE(argv.data()[2] == "three"sv);
+ REQUIRE(argv.data()[3] == nullptr);
+ }
+ }
+}
+
+SCENARIO("nihil::argv can be used as a range")
+{
+ using namespace std::literals;
+
+ GIVEN("An argv")
+ {
+ auto argv = nihil::argv{"one"sv, "two"sv, "three"sv};
+
+ WHEN("An std::vector is constructed from the argv")
+ {
+ auto vec = std::vector(std::from_range, argv);
+
+ THEN("The argv and the vector contain the same data")
+ {
+ REQUIRE(std::ranges::equal(argv, vec));
+ }
+ }
+
+ WHEN("The argv is copied to an std::vector using a range-based for")
+ {
+ auto vec = std::vector<char *>();
+
+ for (auto &&arg: argv)
+ vec.push_back(arg);
+
+ THEN("The argv and the vector contain the same data")
+ {
+ REQUIRE(std::ranges::equal(argv, vec));
+ }
+ }
+ }
+}
+
+SCENARIO("nihil::argv can be move-constructed")
+{
+ using namespace std::literals;
+
+ GIVEN("An argv object")
+ {
+ auto argv = nihil::argv{"one", "two", "three"};
+
+ WHEN("The argv is moved")
+ {
+ auto argv2 = std::move(argv);
+
+ THEN("The new object contains the data")
+ {
+ REQUIRE(argv2.size() == 4);
+ REQUIRE(argv2[0] == "one"sv);
+ }
+
+ AND_THEN("The old object does not")
+ {
+ REQUIRE(argv.size() == 0);
+ }
+ }
+ }
+}
+
+SCENARIO("nihil::argv can be move-assigned")
+{
+ using namespace std::literals;
+
+ GIVEN("Two argv objects")
+ {
+ auto argv1 = nihil::argv{"one", "two", "three"};
+ auto argv2 = nihil::argv{"x", "y"};
+
+ WHEN("argv2 is move-assigned to argv1")
+ {
+ argv1 = std::move(argv2);
+
+ THEN("argv1 contains the data from argv2")
+ {
+ REQUIRE(argv1.size() == 3);
+ REQUIRE(argv1[0] == "x"sv);
+ REQUIRE(argv1[1] == "y"sv);
+ REQUIRE(argv1[2] == nullptr);
+ }
+
+ AND_THEN("argv2 is empty")
+ {
+ REQUIRE(argv2.size() == 0);
+ }
+ }
+ }
+}
+
+} // anonymous namespace
diff --git a/nihil.posix/ensure_dir.cc b/nihil.posix/ensure_dir.cc
deleted file mode 100644
index 88e8898..0000000
--- a/nihil.posix/ensure_dir.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <expected>
-#include <filesystem>
-#include <format>
-#include <system_error>
-
-module nihil.posix;
-
-import nihil.error;
-
-namespace nihil {
-
-auto ensure_dir(std::filesystem::path const &dir) -> std::expected<void, error>
-{
- auto err = std::error_code();
-
- std::filesystem::create_directories(dir, err);
-
- if (err)
- return std::unexpected(error(err));
-
- return {};
-}
-
-} // namespace nihil
diff --git a/nihil.posix/ensure_dir.ccm b/nihil.posix/ensure_dir.ccm
index fa92a90..7eecea8 100644
--- a/nihil.posix/ensure_dir.ccm
+++ b/nihil.posix/ensure_dir.ccm
@@ -6,6 +6,7 @@ module;
#include <expected>
#include <filesystem>
+#include <system_error>
export module nihil.posix:ensure_dir;
@@ -16,8 +17,17 @@ namespace nihil {
/*
* Create the given directory and any parent directories.
*/
-export [[nodiscard]] auto ensure_dir(std::filesystem::path const &dir)
- -> std::expected<void, error>;
+export [[nodiscard]] auto
+ensure_dir(std::filesystem::path const &dir) -> std::expected<void, error>
+{
+ auto err = std::error_code();
-} // namespace nihil
+ std::filesystem::create_directories(dir, err);
+
+ if (err)
+ return std::unexpected(error(err));
+ return {};
+}
+
+} // namespace nihil
diff --git a/nihil.posix/exec.cc b/nihil.posix/exec.cc
deleted file mode 100644
index b4e8732..0000000
--- a/nihil.posix/exec.cc
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <expected>
-#include <format>
-
-module nihil.posix;
-
-import nihil.error;
-import nihil.monad;
-
-namespace nihil {
-
-auto shell(std::string_view const &command) -> std::expected<base_executor_type, error>
-{
- return execl("/bin/sh", "sh", "-c", command);
-}
-
-} // namespace nihil
diff --git a/nihil.posix/exec.ccm b/nihil.posix/exec.ccm
deleted file mode 100644
index cd03117..0000000
--- a/nihil.posix/exec.ccm
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-/*
- * Exec providers, mostly used for spawn().
- */
-
-#include <expected>
-#include <string>
-
-#include "nihil.hh"
-
-export module nihil.posix:exec;
-
-import nihil.error;
-import :execv;
-import :fexecv;
-import :argv;
-import :fd;
-
-namespace nihil {
-
-/*
- * The lowest-level executor type, which is returned by other executors.
- * Prefer fexecve() if it's available, otherwise use execve().
- */
-#ifdef NIHIL_HAVE_FEXECVE
-using base_executor_type = fexecv;
-#else
-using base_executor_type = execv;
-#endif
-
-/*
- * execl: equivalent to execv, except the arguments are passed as a
- * variadic pack of string-like objects.
- */
-export [[nodiscard]] auto execl(std::string_view path, auto &&...args)
- -> std::expected<base_executor_type, error>
-{
- return execv(path, argv({std::string_view(args)...}));
-}
-
-/*
- * shell: run the process by invoking /bin/sh -c with the single argument,
- * equivalent to system(3).
- */
-export [[nodiscard]] auto shell(std::string_view const &command)
- -> std::expected<base_executor_type, error>;
-
-} // namespace nihil
diff --git a/nihil.posix/execl.ccm b/nihil.posix/execl.ccm
new file mode 100644
index 0000000..f3cbf9a
--- /dev/null
+++ b/nihil.posix/execl.ccm
@@ -0,0 +1,40 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <expected>
+#include <string>
+
+#include "nihil.hh"
+
+export module nihil.posix:execl;
+
+import nihil.error;
+import :argv;
+import :execv;
+import :fexecv;
+
+namespace nihil {
+
+/*
+ * execl: equivalent to (f)execv, except the arguments are passed as a
+ * variadic pack of string-like objects.
+ */
+
+export [[nodiscard]] auto execl(std::string_view path, auto &&...args) -> execv
+{
+ return execv(path, argv({std::string_view(args)...}));
+}
+
+#ifdef NIHIL_HAVE_FEXECVE
+
+export [[nodiscard]] auto execl(fd &&executable, auto &&...args) -> fexecv
+{
+ return fexecv(std::move(executable), argv({std::string_view(args)...}));
+}
+
+#endif // NIHIL_HAVE_FEXECVE
+
+} // namespace nihil
diff --git a/nihil.posix/execl.test.cc b/nihil.posix/execl.test.cc
new file mode 100644
index 0000000..5aaaa25
--- /dev/null
+++ b/nihil.posix/execl.test.cc
@@ -0,0 +1,55 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.posix;
+
+namespace {
+
+SCENARIO("nihil::execl() can be used to spawn a shell")
+{
+ GIVEN("An execl object")
+ {
+ auto exec = nihil::execl("/bin/sh", "sh", "-c", "x=1; echo $x");
+
+ WHEN("The shell is executed")
+ {
+ auto output = std::string();
+ auto capture = nihil::make_capture(nihil::stdout_fileno, output).value();
+ auto status = nihil::spawn(exec, capture).value().wait().value();
+
+ THEN("The exit code is 0")
+ {
+ REQUIRE(status.status() == 0);
+ }
+ AND_THEN("The expected output was captured")
+ {
+ REQUIRE(output == "1\n");
+ }
+ }
+ }
+}
+
+SCENARIO("nihil::execl() returns the shell's exit code")
+{
+ GIVEN("An execshell object")
+ {
+ auto exec = nihil::execl("/bin/sh", "sh", "-c", "x=42; exit $x");
+
+ WHEN("The shell is executed")
+ {
+ auto output = std::string();
+ auto capture = nihil::make_capture(nihil::stdout_fileno, output).value();
+ auto status = nihil::spawn(exec, capture).value().wait().value();
+
+ THEN("The exit code is 1")
+ {
+ REQUIRE(status.status() == 42);
+ }
+ }
+ }
+}
+
+} // anonymous namespace
diff --git a/nihil.posix/execlp.ccm b/nihil.posix/execlp.ccm
index d0d88d5..ab3737c 100644
--- a/nihil.posix/execlp.ccm
+++ b/nihil.posix/execlp.ccm
@@ -1,28 +1,22 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
-#include <string>
#include <expected>
#include <format>
+#include <string>
export module nihil.posix:execlp;
import nihil.error;
-import :exec;
import :argv;
import :execvp;
namespace nihil {
-/*
- * execlp: equivalent to execvp, except the arguments are passed as a
- * variadic pack of string-like objects.
- */
-export [[nodiscard]] auto execlp(std::string_view file, auto &&...args)
- -> std::expected<base_executor_type, error>
+// execlp: equivalent to execvp, except the arguments are passed as a
+// variadic pack of string-like objects.
+export [[nodiscard]] auto
+execlp(std::string_view file, auto &&...args) -> std::expected<execv, error>
{
return execvp(file, argv({std::string_view(args)...}));
}
diff --git a/nihil.posix/execlp.test.cc b/nihil.posix/execlp.test.cc
new file mode 100644
index 0000000..cedf871
--- /dev/null
+++ b/nihil.posix/execlp.test.cc
@@ -0,0 +1,67 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.posix;
+
+namespace {
+
+SCENARIO("nihil::execlp() can be used to spawn a shell")
+{
+ GIVEN("An execlp object")
+ {
+ auto exec = nihil::execlp("sh", "sh", "-c", "x=1; echo $x");
+
+ THEN("sh was found in $PATH")
+ {
+ if (!exec)
+ FAIL(exec.error());
+ }
+
+ WHEN("The shell is executed")
+ {
+ auto output = std::string();
+ auto capture = nihil::make_capture(nihil::stdout_fileno, output).value();
+ auto status = nihil::spawn(exec.value(), capture).value().wait().value();
+
+ THEN("The exit code is 0")
+ {
+ REQUIRE(status.status() == 0);
+ }
+ AND_THEN("The expected output was captured")
+ {
+ REQUIRE(output == "1\n");
+ }
+ }
+ }
+}
+
+SCENARIO("nihil::execlp() returns the shell's exit code")
+{
+ GIVEN("An execlp object")
+ {
+ auto exec = nihil::execlp("sh", "sh", "-c", "x=42; exit $x");
+
+ THEN("sh was found in $PATH")
+ {
+ if (!exec)
+ FAIL(exec.error());
+ }
+
+ WHEN("The shell is executed")
+ {
+ auto output = std::string();
+ auto capture = nihil::make_capture(nihil::stdout_fileno, output).value();
+ auto status = nihil::spawn(exec.value(), capture).value().wait().value();
+
+ THEN("The exit code is 1")
+ {
+ REQUIRE(status.status() == 42);
+ }
+ }
+ }
+}
+
+} // anonymous namespace
diff --git a/nihil.posix/execshell.ccm b/nihil.posix/execshell.ccm
new file mode 100644
index 0000000..1fbfccf
--- /dev/null
+++ b/nihil.posix/execshell.ccm
@@ -0,0 +1,21 @@
+// This source code is released into the public domain.
+module;
+
+#include <string>
+
+export module nihil.posix:execshell;
+
+import nihil.error;
+import :execv;
+import :execl;
+
+namespace nihil {
+
+// execshell: a spawn executor which runs the process by invoking /bin/sh -c with the
+// single argument, equivalent to system(3).
+export [[nodiscard]] auto execshell(std::string_view command) -> execv
+{
+ return execl("/bin/sh", "sh", "-c", command);
+}
+
+} // namespace nihil
diff --git a/nihil.posix/execshell.test.cc b/nihil.posix/execshell.test.cc
new file mode 100644
index 0000000..b64953a
--- /dev/null
+++ b/nihil.posix/execshell.test.cc
@@ -0,0 +1,55 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.posix;
+
+namespace {
+
+SCENARIO("nihil::execshell() can be used to spawn a shell")
+{
+ GIVEN("An execshell object")
+ {
+ auto exec = nihil::execshell("x=1; echo $x");
+
+ WHEN("The shell is executed")
+ {
+ auto output = std::string();
+ auto capture = nihil::make_capture(nihil::stdout_fileno, output).value();
+ auto status = nihil::spawn(exec, capture).value().wait().value();
+
+ THEN("The exit code is 0")
+ {
+ REQUIRE(status.status() == 0);
+ }
+ AND_THEN("The expected output was captured")
+ {
+ REQUIRE(output == "1\n");
+ }
+ }
+ }
+}
+
+SCENARIO("nihil::execshell() returns the shell's exit code")
+{
+ GIVEN("An execshell object")
+ {
+ auto exec = nihil::execshell("x=42; exit $x");
+
+ WHEN("The shell is executed")
+ {
+ auto output = std::string();
+ auto capture = nihil::make_capture(nihil::stdout_fileno, output).value();
+ auto status = nihil::spawn(exec, capture).value().wait().value();
+
+ THEN("The exit code is 1")
+ {
+ REQUIRE(status.status() == 42);
+ }
+ }
+ }
+}
+
+} // anonymous namespace
diff --git a/nihil.posix/execv.cc b/nihil.posix/execv.cc
deleted file mode 100644
index 752a96d..0000000
--- a/nihil.posix/execv.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <coroutine>
-#include <expected>
-#include <filesystem>
-#include <format>
-#include <string>
-#include <utility>
-
-#include <err.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-extern char **environ;
-
-module nihil.posix;
-
-import nihil.error;
-import nihil.monad;
-
-namespace nihil {
-
-execv::execv(std::filesystem::path path, argv &&args) noexcept
- : m_path(std::move(path))
- , m_args(std::move(args))
-{
-}
-
-auto execv::exec(this execv &self) -> std::expected<void, error>
-{
- ::execve(self.m_path.string().c_str(), self.m_args.data(), environ);
- return std::unexpected(error("execve failed", error(std::errc(errno))));
-}
-
-execv::execv(execv &&) noexcept = default;
-auto execv::operator=(this execv &, execv &&) noexcept -> execv & = default;
-
-} // namespace nihil
diff --git a/nihil.posix/execv.ccm b/nihil.posix/execv.ccm
index d97754e..ef9d259 100644
--- a/nihil.posix/execv.ccm
+++ b/nihil.posix/execv.ccm
@@ -1,13 +1,12 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <expected>
#include <filesystem>
#include <string>
+#include <unistd.h>
+
export module nihil.posix:execv;
import nihil.error;
@@ -16,32 +15,37 @@ import :executor;
namespace nihil {
-/*
- * execv: use a filename and an argument vector to call ::execve().
- * This is the lowest-level executor which all others are implemented
- * in terms of, if fexecve is not available.
- *
- * TODO: Should have a way to pass the environment (envp).
- */
+// execv: use a filename and an argument vector to call ::execv().
export struct execv final
{
using tag = exec_tag;
- execv(std::filesystem::path, argv &&) noexcept;
+ execv(std::filesystem::path path, argv &&args) noexcept
+ : m_path(std::move(path))
+ , m_args(std::move(args))
+ {
+ }
- [[nodiscard]] auto exec(this execv &) -> std::expected<void, error>;
+ ~execv() = default;
// Movable
- execv(execv &&) noexcept;
- auto operator=(this execv &, execv &&) noexcept -> execv &;
+ execv(execv &&) noexcept = default;
+ auto operator=(execv &&) noexcept -> execv & = default;
// Not copyable (because m_args isn't copyable).
execv(execv const &) = delete;
auto operator=(this execv &, execv const &) -> execv & = delete;
+ // Perform the execv(). This only returns on failure.
+ [[nodiscard]] auto exec(this execv &self) -> std::expected<void, error>
+ {
+ ::execv(self.m_path.string().c_str(), self.m_args.data());
+ return std::unexpected(error("execve failed", error(std::errc(errno))));
+ }
+
private:
- std::filesystem::path m_path;
- argv m_args;
+ std::filesystem::path m_path;
+ argv m_args;
};
} // namespace nihil
diff --git a/nihil.posix/execv.test.cc b/nihil.posix/execv.test.cc
new file mode 100644
index 0000000..aaeead7
--- /dev/null
+++ b/nihil.posix/execv.test.cc
@@ -0,0 +1,55 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.posix;
+
+namespace {
+
+SCENARIO("nihil::execv() can be used to spawn a shell")
+{
+ GIVEN("An execv object")
+ {
+ auto exec = nihil::execv("/bin/sh", nihil::argv{"sh", "-c", "x=1; echo $x"});
+
+ WHEN("The shell is executed")
+ {
+ auto output = std::string();
+ auto capture = nihil::make_capture(nihil::stdout_fileno, output).value();
+ auto status = nihil::spawn(exec, capture).value().wait().value();
+
+ THEN("The exit code is 0")
+ {
+ REQUIRE(status.status() == 0);
+ }
+ AND_THEN("The expected output was captured")
+ {
+ REQUIRE(output == "1\n");
+ }
+ }
+ }
+}
+
+SCENARIO("nihil::execv() returns the shell's exit code")
+{
+ GIVEN("An execshell object")
+ {
+ auto exec = nihil::execv("/bin/sh", nihil::argv{"sh", "-c", "x=42; exit $x"});
+
+ WHEN("The shell is executed")
+ {
+ auto output = std::string();
+ auto capture = nihil::make_capture(nihil::stdout_fileno, output).value();
+ auto status = nihil::spawn(exec, capture).value().wait().value();
+
+ THEN("The exit code is 1")
+ {
+ REQUIRE(status.status() == 42);
+ }
+ }
+ }
+}
+
+} // anonymous namespace
diff --git a/nihil.posix/execvp.cc b/nihil.posix/execvp.cc
deleted file mode 100644
index 5eac315..0000000
--- a/nihil.posix/execvp.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <coroutine>
-#include <expected>
-#include <filesystem>
-#include <format>
-#include <string>
-#include <utility>
-
-#include <err.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include "nihil.hh"
-
-module nihil.posix;
-
-import nihil.error;
-import nihil.monad;
-
-namespace nihil {
-#ifdef NIHIL_HAVE_FEXECVE
-
-auto execvp(std::string_view file, argv &&argv) -> std::expected<fexecv, error>
-{
- auto execfd = open_in_path(file);
- if (!execfd)
- return std::unexpected(error(
- std::format("executable not found in path: {}", file)));
- return fexecv(std::move(*execfd), std::move(argv));
-}
-
-#else // !NIHIL_HAVE_FEXECVE
-
-auto execvp(std::string_view file, nihil::argv &&argv) -> std::expected<nihil::execv, nihil::error>
-{
- auto filename = nihil::find_in_path(file);
- if (!filename)
- return std::unexpected(nihil::error(
- std::format("executable not found in path: {}", file)));
- return execv(std::move(*filename), std::move(argv));
-}
-
-#endif // NIHIL_HAVE_FEXECVE
-
-} // namespace nihil
diff --git a/nihil.posix/execvp.ccm b/nihil.posix/execvp.ccm
index 688ecb6..680a13e 100644
--- a/nihil.posix/execvp.ccm
+++ b/nihil.posix/execvp.ccm
@@ -1,7 +1,4 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <string>
@@ -12,17 +9,22 @@ export module nihil.posix:execvp;
import nihil.error;
import :argv;
-import :exec;
+import :execv;
+import :find_in_path;
namespace nihil {
-/*
- * execvp: equivalent to fexecv(), except the command is passed as
- * a filename instead of a file descriptor. If the filename is not
- * an absolute path, it will be searched for in $PATH.
- */
-
-export [[nodiscard]] auto execvp(std::string_view file, argv &&argv)
- -> std::expected<base_executor_type, error>;
+// execvp: equivalent to execv, except the command is passed as
+// a filename instead of a file descriptor. If the filename is not
+// an absolute path, it will be searched for in $PATH.
+export [[nodiscard]] auto
+execvp(std::string_view file, argv &&argv) -> std::expected<execv, error>
+{
+ auto filename = nihil::find_in_path(file);
+ if (!filename)
+ return std::unexpected(nihil::error(
+ std::format("executable not found in path: {}", file)));
+ return execv(std::move(*filename), std::move(argv));
+}
} // namespace nihil
diff --git a/nihil.posix/fd.cc b/nihil.posix/fd.cc
deleted file mode 100644
index 6d5e54f..0000000
--- a/nihil.posix/fd.cc
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <coroutine>
-#include <expected>
-#include <format>
-#include <stdexcept>
-#include <system_error>
-
-module nihil.posix;
-
-import nihil.error;
-import nihil.monad;
-
-namespace nihil {
-
-fd::fd() noexcept = default;
-
-fd::fd(int fileno) noexcept
- : m_fileno(fileno)
-{
-}
-
-fd::~fd()
-{
- if (*this)
- std::ignore = this->close();
-}
-
-fd::fd(fd &&other) noexcept
- : m_fileno(std::exchange(other.m_fileno, invalid_fileno))
-{
-}
-
-auto fd::operator=(this fd &self, fd &&other) noexcept -> fd &
-{
- if (&self != &other)
- self.m_fileno = std::exchange(other.m_fileno, invalid_fileno);
- return self;
-}
-
-fd::operator bool(this fd const &self) noexcept
-{
- return self.m_fileno != invalid_fileno;
-}
-
-auto fd::close(this fd &self) -> std::expected<void, error>
-{
- auto const ret = ::close(self.get());
- self.m_fileno = invalid_fileno;
-
- if (ret == 0)
- return {};
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto fd::get(this fd const &self) -> int
-{
- if (self)
- return self.m_fileno;
- throw std::logic_error("Attempt to call get() on invalid fd");
-}
-
-auto fd::release(this fd &&self) -> int
-{
- if (self)
- return std::exchange(self.m_fileno, invalid_fileno);
- throw std::logic_error("Attempt to release an invalid fd");
-}
-
-auto dup(fd const &self) -> std::expected<fd, error>
-{
- auto const newfd = ::dup(self.get());
- if (newfd != -1)
- return newfd;
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto dup(fd const &self, int newfd) -> std::expected<fd, error>
-{
- auto const ret = ::dup2(self.get(), newfd);
- if (ret != -1)
- return newfd;
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto raw_dup(fd const &self) -> std::expected<int, error>
-{
- auto const newfd = ::dup(self.get());
- if (newfd != -1)
- return newfd;
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto raw_dup(fd const &self, int newfd) -> std::expected<int, error>
-{
- auto const ret = ::dup2(self.get(), newfd);
- if (ret != -1)
- return newfd;
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto getflags(fd const &self) -> std::expected<int, error>
-{
- auto const flags = ::fcntl(self.get(), F_GETFL);
- if (flags != -1)
- return flags;
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto replaceflags(fd &self, int newflags) -> std::expected<void, error>
-{
- auto const ret = ::fcntl(self.get(), F_SETFL, newflags);
- if (ret == 0)
- return {};
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto setflags(fd &self, int newflags) -> std::expected<int, error>
-{
- auto flags = co_await getflags(self);
-
- flags |= newflags;
- co_await replaceflags(self, flags);
-
- co_return flags;
-}
-
-auto clearflags(fd &self, int clrflags) -> std::expected<int, error>
-{
- auto flags = co_await getflags(self);
-
- flags &= ~clrflags;
- co_await replaceflags(self, flags);
-
- co_return flags;
-}
-
-auto getfdflags(fd const &self) -> std::expected<int, error>
-{
- auto const flags = ::fcntl(self.get(), F_GETFD);
- if (flags != -1)
- return flags;
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto replacefdflags(fd &self, int newflags) -> std::expected<void, error>
-{
- auto const ret = ::fcntl(self.get(), F_SETFD, newflags);
- if (ret != -1)
- return {};
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto setfdflags(fd &self, int newflags) -> std::expected<int, error>
-{
- auto flags = co_await getfdflags(self);
-
- flags |= newflags;
- co_await replacefdflags(self, flags);
-
- co_return flags;
-}
-
-auto clearfdflags(fd &self, int clrflags) -> std::expected<int, error>
-{
- auto flags = co_await getfdflags(self);
-
- flags &= ~clrflags;
- co_await replacefdflags(self, flags);
-
- co_return flags;
-}
-
-auto pipe() -> std::expected<std::pair<fd, fd>, error>
-{
- auto fds = std::array<int, 2>{};
-
- if (auto const ret = ::pipe(fds.data()); ret != 0)
- return std::unexpected(error(std::errc(errno)));
-
- return {{fd(fds[0]), fd(fds[1])}};
-}
-
-auto fd::write(this fd &self, std::span<std::byte const> buffer)
- -> std::expected<std::size_t, error>
-{
- auto const ret = ::write(self.get(), buffer.data(), buffer.size());
- if (ret >= 0)
- return ret;
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-auto fd::read(this fd &self, std::span<std::byte> buffer)
- -> std::expected<std::span<std::byte>, error>
-{
- auto const ret = ::read(self.get(), buffer.data(), buffer.size());
- if (ret >= 0)
- return buffer.subspan(0, ret);
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-} // namespace nihil
diff --git a/nihil.posix/fd.ccm b/nihil.posix/fd.ccm
index b937f46..7faf2f1 100644
--- a/nihil.posix/fd.ccm
+++ b/nihil.posix/fd.ccm
@@ -1,7 +1,4 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <coroutine>
@@ -11,56 +8,136 @@ module;
#include <stdexcept>
#include <system_error>
+#include <fcntl.h>
+#include <unistd.h>
+
export module nihil.posix:fd;
+import nihil.flagset;
import nihil.error;
import nihil.monad;
namespace nihil {
-/*
- * fd: a file descriptor.
- */
+// F_{GET,SET}FL flags
+struct fd_flags_tag
+{
+};
+export using fd_flags = nihil::flagset<int, fd_flags_tag>;
+
+export inline constexpr auto fd_none = fd_flags();
+export inline constexpr auto fd_nonblock = fd_flags::mask<O_NONBLOCK>();
+export inline constexpr auto fd_append = fd_flags::mask<O_APPEND>();
+export inline constexpr auto fd_async = fd_flags::mask<O_ASYNC>();
+export inline constexpr auto fd_sync = fd_flags::mask<O_SYNC>();
+export inline constexpr auto fd_dsync = fd_flags::mask<O_DSYNC>();
+
+#ifdef O_DIRECT
+export inline constexpr auto fl_direct = O_DIRECT;
+#endif
+
+// F_{GET,SET}FD flags
+struct fd_fdflags_tag
+{
+};
+export using fd_fdflags = nihil::flagset<int, fd_fdflags_tag>;
-export struct fd final {
+export inline constexpr auto fd_fd_none = fd_fdflags();
+export inline constexpr auto fd_fd_cloexec = fd_fdflags::mask<FD_CLOEXEC>();
+
+// fd: a file descriptor.
+export struct fd final
+{
// Construct an empty (invalid) fd.
- fd() noexcept;
+ fd() noexcept = default;
- // Construct an fd from an exising file destrictor, taking ownership.
- fd(int fd_) noexcept;
+ // Construct an fd from an exising file descriptor, taking ownership.
+ explicit fd(int fileno) noexcept
+ : m_fileno(fileno)
+ {
+ }
// Destructor. Close the fd, discarding any errors.
- ~fd();
+ ~fd()
+ {
+ if (*this)
+ std::ignore = this->close();
+ }
// Move from another fd, leaving the moved-from fd in an invalid state.
- fd(fd &&other) noexcept;
- auto operator=(this fd &, fd &&other) noexcept -> fd &;
+ fd(fd &&other) noexcept
+ : m_fileno(std::exchange(other.m_fileno, invalid_fileno))
+ {
+ }
+
+ auto operator=(this fd &self, fd &&other) noexcept -> fd &
+ {
+ if (&self != &other)
+ self.m_fileno = std::exchange(other.m_fileno, invalid_fileno);
+ return self; // NOLINT
+ }
// Not copyable.
fd(fd const &) = delete;
- fd& operator=(this fd &, fd const &) = delete;
+ auto operator=(this fd &, fd const &) -> fd & = delete;
// Return true if this fd is valid (open).
- [[nodiscard]] explicit operator bool(this fd const &self) noexcept;
+ [[nodiscard]] explicit operator bool(this fd const &self) noexcept
+ {
+ return self.m_fileno != invalid_fileno;
+ }
// Close the wrapped fd.
- [[nodiscard]] auto close(this fd &self) -> std::expected<void, error>;
+ [[nodiscard]] auto close(this fd &self) -> std::expected<void, error>
+ {
+ auto const ret = ::close(self.get());
+ self.m_fileno = invalid_fileno;
+
+ if (ret == 0)
+ return {};
+
+ return std::unexpected(error(std::errc(errno)));
+ }
// Return the stored fd.
- [[nodiscard]] auto get(this fd const &self) -> int;
+ [[nodiscard]] auto get(this fd const &self) -> int
+ {
+ if (self)
+ return self.m_fileno;
+ throw std::logic_error("Attempt to call get() on invalid fd");
+ }
// Release the stored fd and return it. The caller must close it.
- [[nodiscard]] auto release(this fd &&self) -> int;
+ [[nodiscard]] auto release(this fd &&self) -> int
+ {
+ if (self)
+ return std::exchange(self.m_fileno, invalid_fileno);
+ throw std::logic_error("Attempt to release an invalid fd");
+ }
// Write data from the provided buffer to the fd. Returns the
// number of bytes written.
- [[nodiscard]] auto write(this fd &self, std::span<std::byte const>)
- -> std::expected<std::size_t, error>;
+ [[nodiscard]] auto
+ write(this fd &self, std::span<std::byte const> buffer) -> std::expected<std::size_t, error>
+ {
+ auto const ret = ::write(self.get(), buffer.data(), buffer.size());
+ if (ret >= 0)
+ return ret;
+
+ return std::unexpected(error(std::errc(errno)));
+ }
// Read data from the fd to the provided buffer. Returns a
// subspan containing the data which was read.
- [[nodiscard]] auto read(this fd &self, std::span<std::byte>)
- -> std::expected<std::span<std::byte>, error>;
+ [[nodiscard]] auto read(this fd &self, std::span<std::byte> buffer)
+ -> std::expected<std::span<std::byte>, error>
+ {
+ auto const ret = ::read(self.get(), buffer.data(), buffer.size());
+ if (ret >= 0)
+ return buffer.subspan(0, ret);
+
+ return std::unexpected(error(std::errc(errno)));
+ }
private:
static constexpr int invalid_fileno = -1;
@@ -69,7 +146,14 @@ private:
};
// Create a copy of this fd by calling dup().
-export [[nodiscard]] auto dup(fd const &self) -> std::expected<fd, error>;
+export [[nodiscard]] auto dup(fd const &self) -> std::expected<fd, error>
+{
+ auto const newfd = ::dup(self.get());
+ if (newfd != -1)
+ return fd(newfd);
+
+ return std::unexpected(error(std::errc(errno)));
+}
// Create a copy of this fd by calling dup2(). Note that because this results
// in the existing fd and the new fd both being managed by an fd instance,
@@ -80,73 +164,137 @@ export [[nodiscard]] auto dup(fd const &self) -> std::expected<fd, error>;
//
// In both of these cases, either use raw_dup() instead, or immediately call
// release() on the returned fd to prevent the fd instance from closing it.
-export [[nodiscard]] auto dup(fd const &self, int newfd)
- -> std::expected<fd, error>;
+export [[nodiscard]] auto dup(fd const &self, int newfd) -> std::expected<fd, error>
+{
+ auto const ret = ::dup2(self.get(), newfd);
+ if (ret != -1)
+ return fd(newfd);
+
+ return std::unexpected(error(std::errc(errno)));
+}
// Create a copy of this fd by calling dup().
-export [[nodiscard]] auto raw_dup(fd const &self)
- -> std::expected<int, error>;
+export [[nodiscard]] auto raw_dup(fd const &self) -> std::expected<int, error>
+{
+ auto const newfd = ::dup(self.get());
+ if (newfd != -1)
+ return newfd;
+
+ return std::unexpected(error(std::errc(errno)));
+}
// Create a copy of this fd by calling dup2().
-export [[nodiscard]] auto raw_dup(fd const &self, int newfd)
- -> std::expected<int, error>;
+export [[nodiscard]] auto raw_dup(fd const &self, int newfd) -> std::expected<int, error>
+{
+ auto const ret = ::dup2(self.get(), newfd);
+ if (ret != -1)
+ return newfd;
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+// Call fcntl() on this fd. Prefer one of the type-safe wrappers to this, if available.
+export [[nodiscard]] auto fcntl(fd const &fd, int op, int arg = 0)
+ -> std::expected<int, error>
+{
+ auto const ret = ::fcntl(fd.get(), op, arg);
+ if (ret == -1)
+ return std::unexpected(error(std::errc(errno)));
+ return ret;
+}
// Return the fnctl flags for this fd.
-export [[nodiscard]] auto getflags(fd const &self)
- -> std::expected<int, error>;
+export [[nodiscard]] auto getflags(fd const &fd) -> std::expected<fd_flags, error>
+{
+ auto flags = co_await fcntl(fd, F_GETFL);
+ co_return fd_flags::from_int(flags);
+}
// Replace the fnctl flags for this fd.
-export [[nodiscard]] auto replaceflags(fd &self, int newflags)
- -> std::expected<void, error>;
+export [[nodiscard]] auto replaceflags(fd &fd, fd_flags newflags) -> std::expected<void, error>
+{
+ co_await fcntl(fd, F_SETFL, newflags.value());
+ co_return {};
+}
// Add bits to the fcntl flags for this fd. Returns the new flags.
-export [[nodiscard]] auto setflags(fd &self, int newflags)
- -> std::expected<int, error>;
+export [[nodiscard]] auto setflags(fd &fd, fd_flags newflags) -> std::expected<fd_flags, error>
+{
+ auto flags = co_await getflags(fd);
+
+ flags |= newflags;
+ co_await replaceflags(fd, flags);
+ co_return flags;
+}
// Remove bits from the fcntl flags for this fd. Returns the new flags.
-export [[nodiscard]] auto clearflags(fd &self, int clrflags)
- -> std::expected<int, error>;
+export [[nodiscard]] auto clearflags(fd &fd, fd_flags clrflags) -> std::expected<fd_flags, error>
+{
+ auto flags = co_await getflags(fd);
+
+ flags &= ~clrflags;
+ co_await replaceflags(fd, flags);
+ co_return flags;
+}
// Return the fd flags for this fd.
-export [[nodiscard]] auto getfdflags(fd const &self)
- -> std::expected<int, error>;
+export [[nodiscard]] auto getfdflags(fd const &fd) -> std::expected<fd_fdflags, error>
+{
+ auto const flags = co_await fcntl(fd, F_GETFD);
+ co_return fd_fdflags::from_int(flags);
+}
// Replace the fd flags for this fd.
-export [[nodiscard]] auto replacefdflags(fd &self, int newflags)
- -> std::expected<void, error>;
+export [[nodiscard]] auto replacefdflags(fd &fd, fd_fdflags newflags) -> std::expected<void, error>
+{
+ co_await fcntl(fd, F_SETFD, newflags.value());
+ co_return {};
+}
// Add bits to the fd flags for this fd. Returns the new flags.
-export [[nodiscard]] auto setfdflags(fd &self, int newflags)
- -> std::expected<int, error>;
+export [[nodiscard]] auto setfdflags(fd &fd, fd_fdflags newflags) -> std::expected<fd_fdflags, error>
+{
+ auto flags = co_await getfdflags(fd);
+
+ flags |= newflags;
+ co_await replacefdflags(fd, flags);
+ co_return flags;
+}
// Remove bits from the fd flags for this fd. Returns the new flags.
-export [[nodiscard]] auto clearfdflags(fd &self, int clrflags)
- -> std::expected<int, error>;
+export [[nodiscard]] auto clearfdflags(fd &fd, fd_fdflags clrflags) -> std::expected<fd_fdflags, error>
+{
+ auto flags = co_await getfdflags(fd);
+
+ flags &= ~clrflags;
+ co_await replacefdflags(fd, flags);
+ co_return flags;
+}
// Create two fds by calling pipe() and return them.
-export [[nodiscard]] auto pipe() -> std::expected<std::pair<fd, fd>, error>;
-
-/*
- * Write data to a file descriptor from the provided range. Returns the
- * number of bytes written.
- */
-export [[nodiscard]] auto write(fd &file,
- std::ranges::contiguous_range auto &&range)
- -> std::expected<std::size_t, error>
-requires(sizeof(std::ranges::range_value_t<decltype(range)>) == 1)
+export [[nodiscard]] auto pipe() -> std::expected<std::pair<fd, fd>, error>
+{
+ auto fds = std::array<int, 2>{};
+
+ if (auto const ret = ::pipe(fds.data()); ret != 0)
+ return std::unexpected(error(std::errc(errno)));
+
+ return {{fd(fds[0]), fd(fds[1])}};
+}
+
+// Write data to a file descriptor from the provided range. Returns the
+//number of bytes written.
+export [[nodiscard]] auto
+write(fd &file, std::ranges::contiguous_range auto &&range) -> std::expected<std::size_t, error>
+ requires(sizeof(std::ranges::range_value_t<decltype(range)>) == 1)
{
return file.write(as_bytes(std::span(range)));
}
-/*
- * Read data from a file descriptor into the provided buffer. Returns a
- * span containing the data that was read.
- */
-export [[nodiscard]] auto read(fd &file,
- std::ranges::contiguous_range auto &&range)
- -> std::expected<
- std::span<std::ranges::range_value_t<decltype(range)>>,
- error>
+// Read data from a file descriptor into the provided buffer. Returns a
+// span containing the data that was read.
+export [[nodiscard]] auto read(fd &file, std::ranges::contiguous_range auto &&range)
+ -> std::expected<std::span<std::ranges::range_value_t<decltype(range)>>, error>
requires(sizeof(std::ranges::range_value_t<decltype(range)>) == 1)
{
auto bspan = as_writable_bytes(std::span(range));
diff --git a/nihil.posix/test.fd.cc b/nihil.posix/fd.test.cc
index 5c282af..870ddde 100644
--- a/nihil.posix/test.fd.cc
+++ b/nihil.posix/fd.test.cc
@@ -2,10 +2,10 @@
* This source code is released into the public domain.
*/
+#include <coroutine>
#include <span>
#include <stdexcept>
-#include <stdio.h>
#include <fcntl.h>
#include <catch2/catch_test_macros.hpp>
@@ -23,10 +23,8 @@ auto fd_is_open(int fd) -> bool {
return ret == 0;
}
-} // anonymous namespace
-
TEST_CASE("fd: construct empty", "[fd]") {
- nihil::fd fd;
+ auto const fd = nihil::fd();
REQUIRE(!fd);
REQUIRE_THROWS_AS(fd.get(), std::logic_error);
@@ -111,17 +109,18 @@ TEST_CASE("fd: dup", "[fd]") {
}
TEST_CASE("fd: dup2", "[fd]") {
+ auto constexpr test_fd = 666;
auto file = ::open("/dev/null", O_RDONLY);
REQUIRE(file > 0);
- REQUIRE(!fd_is_open(666));
+ REQUIRE(!fd_is_open(test_fd));
auto fd = nihil::fd(file);
- auto fd2 = dup(fd, 666);
+ auto fd2 = dup(fd, test_fd);
REQUIRE(fd);
REQUIRE(fd2);
- REQUIRE(fd2->get() == 666);
+ REQUIRE(fd2->get() == test_fd);
}
TEST_CASE("fd: flags", "[fd]") {
@@ -131,27 +130,27 @@ TEST_CASE("fd: flags", "[fd]") {
auto fd = nihil::fd(file);
{
- auto const ret = replaceflags(fd, 0);
+ auto const ret = replaceflags(fd, nihil::fd_none);
REQUIRE(ret);
- REQUIRE(getflags(fd) == 0);
+ REQUIRE(getflags(fd) == nihil::fd_none);
}
{
- auto const ret = setflags(fd, O_NONBLOCK);
- REQUIRE(ret == O_NONBLOCK);
- REQUIRE(getflags(fd) == O_NONBLOCK);
+ auto const ret = setflags(fd, nihil::fd_nonblock);
+ REQUIRE(ret == nihil::fd_nonblock);
+ REQUIRE(getflags(fd) == nihil::fd_nonblock);
}
{
- auto const ret = setflags(fd, O_SYNC);
- REQUIRE(ret == (O_NONBLOCK|O_SYNC));
- REQUIRE(getflags(fd) == (O_NONBLOCK|O_SYNC));
+ auto const ret = setflags(fd, nihil::fd_sync);
+ REQUIRE(ret == (nihil::fd_nonblock | nihil::fd_sync));
+ REQUIRE(getflags(fd) == (nihil::fd_nonblock | nihil::fd_sync));
}
{
- auto const ret = clearflags(fd, O_NONBLOCK);
- REQUIRE(ret == O_SYNC);
- REQUIRE(getflags(fd) == O_SYNC);
+ auto const ret = clearflags(fd, nihil::fd_nonblock);
+ REQUIRE(ret == nihil::fd_sync);
+ REQUIRE(getflags(fd) == nihil::fd_sync);
}
}
@@ -162,21 +161,21 @@ TEST_CASE("fd: fdflags", "[fd]") {
auto fd = nihil::fd(file);
{
- auto const ret = replacefdflags(fd, 0);
+ auto const ret = replacefdflags(fd, nihil::fd_fd_none);
REQUIRE(ret);
- REQUIRE(getfdflags(fd) == 0);
+ REQUIRE(getfdflags(fd) == nihil::fd_fd_none);
}
{
- auto const ret = setfdflags(fd, FD_CLOEXEC);
- REQUIRE(ret == FD_CLOEXEC);
- REQUIRE(getfdflags(fd) == FD_CLOEXEC);
+ auto const ret = setfdflags(fd, nihil::fd_fd_cloexec);
+ REQUIRE(ret == nihil::fd_fd_cloexec);
+ REQUIRE(getfdflags(fd) == nihil::fd_fd_cloexec);
}
{
- auto const ret = clearfdflags(fd, FD_CLOEXEC);
- REQUIRE(ret == 0);
- REQUIRE(getfdflags(fd) == 0);
+ auto const ret = clearfdflags(fd, nihil::fd_fd_cloexec);
+ REQUIRE(ret == nihil::fd_fd_none);
+ REQUIRE(getfdflags(fd) == nihil::fd_fd_none);
}
}
@@ -202,3 +201,5 @@ TEST_CASE("fd: pipe, read, write", "[fd]") {
REQUIRE(read_buf);
REQUIRE(std::string_view(*read_buf) == test_string);
}
+
+} // anonymous namespace
diff --git a/nihil.posix/fexecv.cc b/nihil.posix/fexecv.cc
deleted file mode 100644
index ad57d14..0000000
--- a/nihil.posix/fexecv.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <coroutine>
-#include <expected>
-#include <format>
-#include <string>
-#include <utility>
-
-#include <err.h>
-
-#include "nihil.h"
-
-#ifdef NIHIL_HAVE_FEXECVE
-
-extern char **environ;
-
-module nihil.posix;
-
-import nihil.error;
-import nihil.monad;
-
-namespace nihil {
-
-fexecv::fexecv(fd &&execfd, argv &&args) noexcept
- : m_execfd(std::move(execfd))
- , m_args(std::move(args))
-{
-}
-
-auto fexecv::exec(this fexecv &self)
- -> std::expected<void, error>
-{
- ::fexecve(self.m_execfd.get(), self.m_args.data(), environ);
- return std::unexpected(error("fexecve failed",
- error(std::errc(errno))));
-}
-
-fexecv::fexecv(fexecv &&) noexcept = default;
-auto fexecv::operator=(this fexecv &, fexecv &&) noexcept -> fexecv& = default;
-
-} // namespace nihil
-
-#endif // NIHIL_HAVE_FEXECVE
diff --git a/nihil.posix/fexecv.ccm b/nihil.posix/fexecv.ccm
index 1fe57a8..4001726 100644
--- a/nihil.posix/fexecv.ccm
+++ b/nihil.posix/fexecv.ccm
@@ -1,7 +1,4 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <expected>
@@ -27,27 +24,35 @@ namespace nihil {
*
* TODO: Should have a way to pass the environment (envp).
*/
-export struct fexecv final {
+export struct fexecv final
+{
using tag = exec_tag;
- fexecv(fd &&execfd, argv &&args) noexcept;
+ fexecv(fd &&execfd, argv &&args) noexcept
+ : m_execfd(std::move(execfd))
+ , m_args(std::move(args))
+ {
+ }
- [[nodiscard]] auto exec(this fexecv &self)
- -> std::expected<void, error>;
+ [[nodiscard]] auto exec(this fexecv &self) -> std::expected<void, error>
+ {
+ ::fexecve(self.m_execfd.get(), self.m_args.data(), environ);
+ return std::unexpected(error("fexecve failed", error(std::errc(errno))));
+ }
// Movable
- fexecv(fexecv &&) noexcept;
- auto operator=(this fexecv &, fexecv &&) noexcept -> fexecv&;
+ fexecv(fexecv &&) noexcept = default;
+ auto operator=(this fexecv &, fexecv &&) noexcept -> fexecv & = default;
// Not copyable (because we hold the open fd object)
fexecv(fexecv const &) = delete;
- auto operator=(this fexecv &, fexecv const &) -> fexecv& = delete;
+ auto operator=(this fexecv &, fexecv const &) -> fexecv & = delete;
private:
- fd m_execfd;
- argv m_args;
+ fd m_execfd;
+ argv m_args;
};
-#endif // NIHIL_HAVE_FEXECVE
+#endif // NIHIL_HAVE_FEXECVE
} // namespace nihil
diff --git a/nihil.posix/fexecvp.ccm b/nihil.posix/fexecvp.ccm
new file mode 100644
index 0000000..d61240c
--- /dev/null
+++ b/nihil.posix/fexecvp.ccm
@@ -0,0 +1,37 @@
+// This source code is released into the public domain.
+module;
+
+#include <expected>
+#include <filesystem>
+#include <format>
+#include <string>
+
+#include "nihil.hh"
+
+export module nihil.posix:fexecvp;
+
+#ifdef NIHIL_HAVE_FEXECVE
+
+import nihil.error;
+import :argv;
+import :execv;
+import :open_in_path;
+
+namespace nihil {
+
+// execvp: equivalent to execv, except the command is passed as
+// a filename instead of a file descriptor. If the filename is not
+// an absolute path, it will be searched for in $PATH.
+export [[nodiscard]] auto
+fexecvp(std::filesystem::path const &file, argv &&argv) -> std::expected<execv, error>
+{
+ auto execfd = open_in_path(file);
+ if (!execfd)
+ return std::unexpected(error(
+ std::format("executable not found in path: {}", file)));
+ return fexecv(std::move(*execfd), std::move(argv));
+}
+
+} // namespace nihil
+
+#endif // NIHIL_HAVE_FEXECVE
diff --git a/nihil.posix/find_in_path.cc b/nihil.posix/find_in_path.cc
deleted file mode 100644
index 7b03faa..0000000
--- a/nihil.posix/find_in_path.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <filesystem>
-#include <optional>
-#include <ranges>
-#include <string>
-
-#include <fcntl.h>
-#include <paths.h>
-#include <unistd.h>
-
-module nihil.posix;
-
-namespace nihil {
-
-auto find_in_path(std::filesystem::path const &file) -> std::optional<std::filesystem::path>
-{
- using namespace std::literals;
-
- auto try_return = [](std::filesystem::path file)
- -> std::optional<std::filesystem::path>
- {
- auto ret = ::access(file.string().c_str(), X_OK);
- if (ret == 0)
- return {std::move(file)};
- return {};
- };
-
- // Absolute pathname skips the search.
- if (file.is_absolute())
- return try_return(file);
-
- auto path = getenv("PATH").value_or(_PATH_DEFPATH);
-
- for (auto &&dir : path | std::views::split(':')) {
- // An empty $PATH element means cwd.
- auto sdir = dir.empty()
- ? std::filesystem::path(".")
- : std::filesystem::path(std::string_view(dir));
-
- if (auto ret = try_return(sdir / file); ret)
- return ret;
- }
-
- return {};
-}
-
-} // namespace nihil
diff --git a/nihil.posix/find_in_path.ccm b/nihil.posix/find_in_path.ccm
index 4988a12..7bfa3b9 100644
--- a/nihil.posix/find_in_path.ccm
+++ b/nihil.posix/find_in_path.ccm
@@ -1,24 +1,53 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <filesystem>
#include <optional>
+#include <ranges>
+
+#include <paths.h>
+#include <unistd.h>
export module nihil.posix:find_in_path;
import nihil.error;
import :fd;
+import :getenv;
namespace nihil {
/*
* Find an executable in $PATH and return the full path. If $PATH is not set, uses _PATH_DEFPATH.
- * If the file can't be found or opened, returns std::nullopt.
+ * If the file can't be found or is not executable, returns std::nullopt.
*/
-export [[nodiscard]] auto find_in_path(std::filesystem::path const &file)
- -> std::optional<std::filesystem::path>;
+export [[nodiscard]] auto
+find_in_path(std::filesystem::path const &file) -> std::optional<std::filesystem::path>
+{
+ using namespace std::literals;
+
+ auto try_return = [](std::filesystem::path file) -> std::optional<std::filesystem::path> {
+ auto ret = ::access(file.string().c_str(), X_OK);
+ if (ret == 0)
+ return {std::move(file)};
+ return {};
+ };
+
+ // Absolute pathname skips the search.
+ if (file.is_absolute())
+ return try_return(file);
+
+ auto const path = getenv("PATH").value_or(_PATH_DEFPATH); // NOLINT
+
+ for (auto &&dir : path | std::views::split(':')) {
+ // An empty $PATH element means cwd.
+ auto sdir = dir.empty() ? std::filesystem::path(".")
+ : std::filesystem::path(std::string_view(dir));
+
+ if (auto ret = try_return(sdir / file); ret)
+ return {ret};
+ }
+
+ return {};
+}
} // namespace nihil
diff --git a/nihil.posix/getenv.cc b/nihil.posix/getenv.cc
deleted file mode 100644
index c596902..0000000
--- a/nihil.posix/getenv.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <cstdint>
-#include <expected>
-#include <string>
-#include <system_error>
-#include <vector>
-
-#include <unistd.h>
-
-#include "nihil.hh"
-
-module nihil.posix;
-
-import nihil.error;
-
-namespace nihil {
-
-auto getenv(std::string_view varname) -> std::expected<std::string, error>
-{
- auto cvarname = std::string(varname);
-
-#ifdef NIHIL_HAVE_GETENV_R
- // Start with a buffer of this size, and double it every iteration.
- constexpr auto bufinc = std::size_t{1024};
-
- auto buf = std::vector<char>(bufinc);
- for (;;) {
- auto const ret = ::getenv_r(cvarname.c_str(),
- buf.data(), buf.size());
-
- if (ret == 0)
- return {std::string(buf.data())};
-
- if (ret == -1 && errno == ERANGE) {
- buf.resize(buf.size() * 2);
- continue;
- }
-
- return std::unexpected(error(std::errc(errno)));
- }
-#else // NIHIL_HAVE_GETENV_R
- errno = 0;
- auto *v = ::getenv(cvarname.c_str());
-
- if (v != nullptr)
- return {std::string(v)};
-
- if (errno != 0)
- return std::unexpected(error(std::errc(errno)));
-
- return std::unexpected(error(std::errc::no_such_file_or_directory));
-#endif // NIHIL_HAVE_GETENV_R
-}
-
-} // namespace nihil
diff --git a/nihil.posix/getenv.ccm b/nihil.posix/getenv.ccm
index 465f7e7..5967bf7 100644
--- a/nihil.posix/getenv.ccm
+++ b/nihil.posix/getenv.ccm
@@ -1,11 +1,15 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
+#include <cerrno>
#include <expected>
#include <string>
+#include <system_error>
+#include <vector>
+
+#include <unistd.h>
+
+#include "nihil.hh"
export module nihil.posix:getenv;
@@ -13,11 +17,44 @@ import nihil.error;
namespace nihil {
-/*
- * Find a variable by the given name in the environment by calling getenv_r().
- */
-export [[nodiscard]] auto getenv(std::string_view varname)
- -> std::expected<std::string, error>;
+// Find a variable by the given name in the environment by calling getenv_r() if available,
+// or getenv() if not. In either case the value is copied, so will not be affected by
+// future calls to setenv().
+export [[nodiscard]] auto getenv(std::string_view varname) -> std::expected<std::string, error>
+{
+ auto cvarname = std::string(varname);
+
+#ifdef NIHIL_HAVE_GETENV_R
+ // Start with a buffer of this size, and double it every iteration.
+ constexpr auto bufinc = std::size_t{1024};
+
+ auto buf = std::vector<char>(bufinc);
+ for (;;) {
+ auto const ret = ::getenv_r(cvarname.c_str(), buf.data(), buf.size());
+
+ if (ret == 0)
+ return {std::string(buf.data())};
+
+ if (ret == -1 && errno == ERANGE) {
+ buf.resize(buf.size() * 2);
+ continue;
+ }
+
+ return std::unexpected(error(std::errc(errno)));
+ }
+#else // NIHIL_HAVE_GETENV_R
+ errno = 0;
+ auto *v = ::getenv(cvarname.c_str()); // NOLINT
+
+ if (v != nullptr)
+ return {std::string(v)};
+
+ if (errno != 0)
+ return std::unexpected(error(std::errc(errno)));
+
+ return std::unexpected(error(std::errc::no_such_file_or_directory));
+#endif // NIHIL_HAVE_GETENV_R
+}
} // namespace nihil
diff --git a/nihil.posix/test.getenv.cc b/nihil.posix/getenv.test.cc
index 9e10c16..9e10c16 100644
--- a/nihil.posix/test.getenv.cc
+++ b/nihil.posix/getenv.test.cc
diff --git a/nihil.posix/open.cc b/nihil.posix/open.cc
deleted file mode 100644
index 9ef6538..0000000
--- a/nihil.posix/open.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <expected>
-#include <filesystem>
-#include <system_error>
-
-#include <fcntl.h>
-#include <unistd.h>
-
-module nihil.posix;
-
-import nihil.error;
-import :fd;
-
-namespace nihil {
-
-auto open(std::filesystem::path const &filename, int flags, int mode)
- -> std::expected<fd, error>
-{
- auto fdno = ::open(filename.c_str(), flags, mode);
- if (fdno != -1)
- return fd(fdno);
-
- return std::unexpected(error(std::errc(errno)));
-}
-
-} // namespace nihil
diff --git a/nihil.posix/open.ccm b/nihil.posix/open.ccm
index eaedacd..59f80af 100644
--- a/nihil.posix/open.ccm
+++ b/nihil.posix/open.ccm
@@ -1,24 +1,87 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <expected>
#include <filesystem>
+#include <fcntl.h>
+#include <unistd.h>
+
export module nihil.posix:open;
import nihil.error;
+import nihil.flagset;
import :fd;
-export namespace nihil {
+namespace nihil {
+
+struct open_flags_tag
+{
+};
+export using open_flags = nihil::flagset<int, open_flags_tag>;
+
+export inline constexpr auto open_none = open_flags();
+
+// Basic flags, exactly one of these is required.
+export inline constexpr auto open_read = open_flags::mask<O_RDONLY>();
+export inline constexpr auto open_write = open_flags::mask<O_WRONLY>();
+export inline constexpr auto open_readwrite = open_flags::mask<O_RDWR>();
+export inline constexpr auto open_search = open_flags::mask<O_SEARCH>();
+export inline constexpr auto open_exec = open_flags::mask<O_EXEC>();
+
+// Modifiers
+export inline constexpr auto open_nonblock = open_flags::mask<O_NONBLOCK>();
+export inline constexpr auto open_append = open_flags::mask<O_APPEND>();
+export inline constexpr auto open_create = open_flags::mask<O_CREAT>();
+export inline constexpr auto open_truncate = open_flags::mask<O_TRUNC>();
+export inline constexpr auto open_exclusive = open_flags::mask<O_EXCL>();
+export inline constexpr auto open_shared_lock = open_flags::mask<O_SHLOCK>();
+export inline constexpr auto open_exclusive_lock = open_flags::mask<O_EXLOCK>();
+export inline constexpr auto open_directory = open_flags::mask<O_DIRECTORY>();
+export inline constexpr auto open_nofollow = open_flags::mask<O_NOFOLLOW>();
+export inline constexpr auto open_nofollow_any = open_flags::mask<O_NOFOLLOW_ANY>();
+export inline constexpr auto open_symlink = open_flags::mask<O_SYMLINK>();
+export inline constexpr auto open_eventonly = open_flags::mask<O_EVTONLY>();
+export inline constexpr auto open_close_on_exec = open_flags::mask<O_CLOEXEC>();
+export inline constexpr auto open_resolve_beneath = open_flags::mask<O_RESOLVE_BENEATH>();
+
+// FreeBSD
+#ifdef O_DIRECT
+export inline constexpr auto open_direct = open_flags::mask<O_DIRECT>();
+#endif
+
+#ifdef O_VERIFY
+export inline constexpr auto open_verify = open_flags::mask<O_VERIFY>();
+#endif
+
+#ifdef O_PATH
+export inline constexpr auto open_path = open_flags::mask<O_PATH>();
+#endif
+
+#ifdef O_EMPTY_PATH
+export inline constexpr auto open_empty_path = open_flags::mask<O_EMPTY_PATH>();
+#endif
+
+// Open the given file and return an fd for it.
+export [[nodiscard]] auto open(std::filesystem::path const &filename, open_flags flags,
+ int mode = 0777) -> std::expected<fd, error>
+{
+ auto fdno = ::open(filename.c_str(), flags.value(), mode);
+ if (fdno != -1)
+ return fd(fdno);
+
+ return std::unexpected(error(std::errc(errno)));
+}
+
+// Like open(), but resolve relative to an open file descriptor, which must refer to a directory.
+export [[nodiscard]] auto openat(fd &where, std::filesystem::path const &filename, open_flags flags,
+ int mode = 0777) -> std::expected<fd, error>
+{
+ auto fdno = ::openat(where.get(), filename.c_str(), flags.value(), mode);
+ if (fdno != -1)
+ return fd(fdno);
-/*
- * Open the given file and return an fd for it.
- */
-[[nodiscard]] auto open(std::filesystem::path const &filename,
- int flags, int mode = 0777)
- -> std::expected<fd, error>;
+ return std::unexpected(error(std::errc(errno)));
+}
} // namespace nihil
diff --git a/nihil.posix/open_in_path.cc b/nihil.posix/open_in_path.cc
deleted file mode 100644
index 30021ca..0000000
--- a/nihil.posix/open_in_path.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <filesystem>
-#include <optional>
-#include <ranges>
-#include <string>
-
-#include <fcntl.h>
-#include <paths.h>
-
-module nihil.posix;
-
-namespace nihil {
-
-auto open_in_path(std::filesystem::path const &file) -> std::optional<fd>
-{
- using namespace std::literals;
-
- auto try_open =
- [](std::filesystem::path const &file) -> std::optional<fd>
- {
- auto ret = open(file, O_EXEC);
- if (ret)
- return {std::move(*ret)};
- return {};
- };
-
- // Absolute pathname skips the search.
- if (file.is_absolute())
- return try_open(file);
-
- auto path = getenv("PATH").value_or(_PATH_DEFPATH);
-
- for (auto &&dir : path | std::views::split(':')) {
- // An empty $PATH element means cwd.
- auto sdir = dir.empty()
- ? std::filesystem::path(".")
- : std::filesystem::path(std::string_view(dir));
-
- if (auto ret = try_open(sdir / file); ret)
- return ret;
- }
-
- return {};
-}
-
-} // namespace nihil
diff --git a/nihil.posix/open_in_path.ccm b/nihil.posix/open_in_path.ccm
index d4c090d..7ff5812 100644
--- a/nihil.posix/open_in_path.ccm
+++ b/nihil.posix/open_in_path.ccm
@@ -1,23 +1,51 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <filesystem>
#include <optional>
+#include <ranges>
+#include <string>
+
+#include <paths.h>
export module nihil.posix:open_in_path;
import nihil.error;
import :fd;
+import :getenv;
+import :open;
namespace nihil {
-/*
- * Find an executable in $PATH and open it with O_EXEC. If $PATH is not set, uses _PATH_DEFPATH.
- * If the file can't be found or opened, returns std::nullopt.
- */
-export [[nodiscard]] auto open_in_path(std::filesystem::path const &file) -> std::optional<fd>;
+// Find an executable in $PATH and open it with O_EXEC. If $PATH is not set, uses _PATH_DEFPATH.
+// If the file can't be found or opened, returns std::nullopt.
+export [[nodiscard]] auto open_in_path(std::filesystem::path const &file) -> std::optional<fd>
+{
+ using namespace std::literals;
+
+ auto try_open = [](std::filesystem::path const &file) -> std::optional<fd> {
+ auto ret = open(file, open_exec);
+ if (ret)
+ return {std::move(*ret)};
+ return {};
+ };
+
+ // Absolute pathname skips the search.
+ if (file.is_absolute())
+ return try_open(file);
+
+ auto path = getenv("PATH").value_or(_PATH_DEFPATH); // NOLINT
+
+ for (auto &&dir : path | std::views::split(':')) {
+ // An empty $PATH element means cwd.
+ auto sdir = dir.empty() ? std::filesystem::path(".")
+ : std::filesystem::path(std::string_view(dir));
+
+ if (auto ret = try_open(sdir / file); ret)
+ return ret;
+ }
+
+ return {};
+}
} // namespace nihil
diff --git a/nihil.posix/posix.ccm b/nihil.posix/posix.ccm
index 2ebc1b8..3e13d5a 100644
--- a/nihil.posix/posix.ccm
+++ b/nihil.posix/posix.ccm
@@ -1,28 +1,20 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
-#include <expected>
-#include <filesystem>
-#include <optional>
-#include <string>
-
-#include "nihil.hh"
-
export module nihil.posix;
import nihil.error;
export import :argv;
export import :ensure_dir;
-export import :exec;
+export import :execl;
export import :execlp;
+export import :execshell;
export import :execv;
export import :execvp;
export import :fd;
export import :fexecv;
+export import :fexecvp;
export import :find_in_path;
export import :getenv;
export import :open;
diff --git a/nihil.posix/process.cc b/nihil.posix/process.cc
deleted file mode 100644
index 02642bc..0000000
--- a/nihil.posix/process.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <cerrno>
-#include <cstring>
-#include <expected>
-#include <format>
-#include <optional>
-#include <system_error>
-#include <utility>
-
-#include <sys/types.h>
-#include <sys/wait.h>
-
-module nihil.posix;
-
-import nihil.error;
-
-namespace nihil {
-
-auto wait_result::okay(this wait_result const &self) -> bool
-{
- return self.status() == 0;
-}
-
-wait_result::operator bool(this wait_result const &self)
-{
- return self.okay();
-}
-
-auto wait_result::status(this wait_result const &self) -> std::optional<int>
-{
- if (WIFEXITED(self._status))
- return WEXITSTATUS(self._status);
- return {};
-}
-
-auto wait_result::signal(this wait_result const &self) -> std::optional<int>
-{
- if (WIFSIGNALED(self._status))
- return WTERMSIG(self._status);
- return {};
-}
-
-wait_result::wait_result(int status)
- : _status(status)
-{}
-
-process::process(::pid_t pid)
- : m_pid(pid)
-{}
-
-process::~process() {
- if (m_pid == -1)
- return;
-
- auto status = int{};
- std::ignore = waitpid(m_pid, &status, WEXITED);
-}
-
-process::process(process &&other) noexcept
- : m_pid(std::exchange(other.m_pid, -1))
-{
-}
-
-auto process::operator=(this process &self, process &&other) noexcept
- -> process &
-{
- if (&self != &other) {
- self.m_pid = std::exchange(other.m_pid, -1);
- }
-
- return self;
-}
-
-// Get the child's process id.
-auto process::pid(this process const &self) noexcept -> ::pid_t
-{
- return self.m_pid;
-}
-
-auto process::wait(this process &&self) -> std::expected<wait_result, error>
-{
- auto status = int{};
- auto ret = waitpid(self.m_pid, &status, 0);
- if (ret == -1)
- return std::unexpected(error(std::errc(errno)));
-
- return wait_result(status);
-}
-
-auto process::release(this process &&self) -> ::pid_t
-{
- auto const ret = self.pid();
- self.m_pid = -1;
- return ret;
-}
-
-} // namespace nihil
diff --git a/nihil.posix/process.ccm b/nihil.posix/process.ccm
index 425deac..ee7de15 100644
--- a/nihil.posix/process.ccm
+++ b/nihil.posix/process.ccm
@@ -1,7 +1,4 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <expected>
@@ -9,7 +6,7 @@ module;
#include <system_error>
#include <utility>
-#include <sys/types.h>
+#include <sys/wait.h>
export module nihil.posix:process;
@@ -17,72 +14,119 @@ import nihil.error;
namespace nihil {
-/*
- * wait_result: the exit status of a process.
- */
-export struct wait_result final {
+// wait_result: the exit status of a process.
+export struct wait_result final
+{
// Return true if the process exited normally with an exit code of
// zero, otherwise false.
- [[nodiscard]] auto okay(this wait_result const &self) -> bool;
- [[nodiscard]] explicit operator bool(this wait_result const &self);
+ [[nodiscard]] auto okay(this wait_result const &self) -> bool
+ {
+ return self.status() == 0;
+ }
+
+ [[nodiscard]] explicit operator bool(this wait_result const &self)
+ {
+ return self.okay();
+ }
// Return the exit status, if any.
- [[nodiscard]] auto status(this wait_result const &self)
- -> std::optional<int>;
+ [[nodiscard]] auto status(this wait_result const &self) -> std::optional<int>
+ {
+ if (WIFEXITED(self.m_status))
+ return WEXITSTATUS(self.m_status);
+ return {};
+ }
// Return the exit signal, if any.
- [[nodiscard]] auto signal(this wait_result const &self)
- -> std::optional<int>;
+ [[nodiscard]] auto signal(this wait_result const &self) -> std::optional<int>
+ {
+ if (WIFSIGNALED(self.m_status))
+ return WTERMSIG(self.m_status);
+ return {};
+ }
private:
friend struct process;
- int _status;
+ int m_status;
// Construct a new wait_result from the output of waitpid().
- wait_result(int status);
+ explicit wait_result(int status)
+ : m_status(status)
+ {
+ }
};
-/*
- * process: represents a process we created, which can be waited for.
- */
-export struct process final {
+// Represents a process we created, which can be waited for.
+export struct process final
+{
process() = delete;
- /*
- * Create a new process from a pid, which must be a child of the
- * current process.
- */
- process(::pid_t pid);
+ // Create a new process from a pid, which must be a child of the
+ // current process.
+ explicit process(::pid_t pid)
+ : m_pid(pid)
+ {
+ }
// When destroyed, we automatically wait for the process to
// avoid creating zombie processes.
- ~process();
+ ~process()
+ {
+ if (m_pid == -1)
+ return;
+
+ auto status = int{};
+ std::ignore = ::waitpid(m_pid, &status, WEXITED);
+ }
// Movable.
- process(process &&) noexcept;
- auto operator=(this process &, process &&) noexcept -> process &;
+ process(process &&other) noexcept
+ : m_pid(std::exchange(other.m_pid, -1))
+ {}
+
+ auto operator=(this process &self, process &&other) noexcept -> process &
+ {
+ if (&self != &other) {
+ self.m_pid = std::exchange(other.m_pid, -1);
+ }
+
+ return self; // NOLINT
+ }
// Not copyable.
process(process const &) = delete;
auto operator=(this process &, process const &) -> process & = delete;
// Get the child's process id.
- [[nodiscard]] auto pid(this process const &self) noexcept -> ::pid_t;
-
- /*
- * Wait for this process to exit (by calling waitpid()) and return
- * its exit status. This destroys the process state, leaving this
- * object in a moved-from state.
- */
- [[nodiscard]] auto wait(this process &&self)
- -> std::expected<wait_result, error>;
-
- /*
- * Release this process so we won't try to wait for it when
- * destroying this object.
- */
- [[nodiscard]] auto release(this process &&self) -> ::pid_t;
+ [[nodiscard]] auto pid(this process const &self) noexcept -> ::pid_t
+ {
+ return self.m_pid;
+ }
+
+ // Wait for this process to exit (by calling waitpid()) and return
+ // its exit status. This destroys the process state, leaving this
+ // object in a moved-from state.
+ [[nodiscard]] auto wait(this process &&self) // NOLINT
+ -> std::expected<wait_result, error>
+ {
+ auto status = int{};
+ auto ret = waitpid(self.m_pid, &status, 0);
+ if (ret == -1)
+ return std::unexpected(error(std::errc(errno)));
+
+ return wait_result(status);
+ }
+
+ // Release this process so we won't try to wait for it when
+ // destroying this object. This will leave a zombie process
+ // unless the wait is done another way.
+ [[nodiscard]] auto release(this process &&self) -> ::pid_t // NOLINT
+ {
+ auto const ret = self.pid();
+ self.m_pid = -1;
+ return ret;
+ }
private:
::pid_t m_pid;
diff --git a/nihil.posix/read_file.ccm b/nihil.posix/read_file.ccm
index be9e102..3b4fd5b 100644
--- a/nihil.posix/read_file.ccm
+++ b/nihil.posix/read_file.ccm
@@ -32,7 +32,7 @@ read_file(std::filesystem::path const &filename,
std::output_iterator<char> auto &&iter)
-> std::expected<void, error>
{
- auto file = co_await open(filename, O_RDONLY);
+ auto file = co_await open(filename, open_read);
auto constexpr bufsize = std::size_t{1024};
auto buffer = std::array<char, bufsize>{};
diff --git a/nihil.posix/rename.cc b/nihil.posix/rename.cc
deleted file mode 100644
index 9203d08..0000000
--- a/nihil.posix/rename.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <expected>
-#include <filesystem>
-
-module nihil.posix;
-
-import nihil.error;
-
-namespace nihil {
-
-/*
- * Rename a file.
- */
-auto rename_file(std::filesystem::path const &oldp,
- std::filesystem::path const &newp)
- -> std::expected<void, error>
-{
- auto err = std::error_code();
-
- std::filesystem::rename(oldp, newp, err);
-
- if (err)
- return std::unexpected(error(err));
-
- return {};
-}
-
-
-} // namespace nihil
diff --git a/nihil.posix/rename.ccm b/nihil.posix/rename.ccm
index 796ec5b..a1b292e 100644
--- a/nihil.posix/rename.ccm
+++ b/nihil.posix/rename.ccm
@@ -1,7 +1,4 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
#include <expected>
@@ -13,11 +10,19 @@ import nihil.error;
namespace nihil {
-/*
- * Rename a file (or directory).
- */
+// Rename a file (or directory).
export [[nodiscard]] auto
rename(std::filesystem::path const &oldp, std::filesystem::path const &newp)
- -> std::expected<void, error>;
+ -> std::expected<void, error>
+{
+ auto err = std::error_code();
+
+ std::filesystem::rename(oldp, newp, err);
+
+ if (err)
+ return std::unexpected(error(err));
+
+ return {};
+}
} // namespace nihil
diff --git a/nihil.posix/spawn.ccm b/nihil.posix/spawn.ccm
index 9fa24e3..a185bb3 100644
--- a/nihil.posix/spawn.ccm
+++ b/nihil.posix/spawn.ccm
@@ -1,7 +1,4 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
/*
@@ -29,7 +26,7 @@ export module nihil.posix:spawn;
import nihil.monad;
import :argv;
-import :exec;
+import :executor;
import :open;
import :process;
@@ -152,7 +149,7 @@ private:
export [[nodiscard]] auto
make_fd_file(int fdno, std::filesystem::path const &file,
- int flags, int mode = 0777)
+ open_flags flags, int mode = 0777)
-> std::expected<fd_file, error>
{
auto fd = co_await open(file, flags, mode);
@@ -166,19 +163,19 @@ make_fd_file(int fdno, std::filesystem::path const &file,
export [[nodiscard]] inline auto
stdin_devnull() -> std::expected<fd_file, error>
{
- return make_fd_file(stdin_fileno, "/dev/null", O_RDONLY);
+ return make_fd_file(stdin_fileno, "/dev/null", open_read);
}
export [[nodiscard]] inline auto
stdout_devnull() -> std::expected<fd_file, error>
{
- return make_fd_file(stdout_fileno, "/dev/null", O_WRONLY);
+ return make_fd_file(stdout_fileno, "/dev/null", open_write);
}
export [[nodiscard]] inline auto
stderr_devnull() -> std::expected<fd_file, error>
{
- return make_fd_file(stderr_fileno, "/dev/null", O_WRONLY);
+ return make_fd_file(stderr_fileno, "/dev/null", open_write);
}
/*
diff --git a/nihil.posix/tempfile.cc b/nihil.posix/tempfile.cc
deleted file mode 100644
index b1d3dee..0000000
--- a/nihil.posix/tempfile.cc
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <algorithm>
-#include <coroutine>
-#include <expected>
-#include <filesystem>
-#include <random>
-#include <string>
-
-#include <fcntl.h>
-#include <unistd.h>
-
-module nihil.posix;
-
-import nihil.flagset;
-import :getenv;
-import :open;
-
-namespace nihil {
-
-temporary_file::temporary_file(nihil::fd &&fd,
- std::filesystem::path path) noexcept
- : m_fd(std::move(fd))
- , m_path(std::move(path))
-{
-}
-
-temporary_file::temporary_file(nihil::fd &&fd) noexcept
- : m_fd(std::move(fd))
-{
-}
-
-temporary_file::temporary_file(temporary_file &&other) noexcept
- : m_fd(std::move(other.m_fd))
- , m_path(std::move(other.m_path))
-{
-}
-
-temporary_file::~temporary_file() //NOLINT(bugprone-exception-escape)
-{
- if (m_fd)
- release();
-}
-
-auto temporary_file::release(this temporary_file &self) -> void
-{
- if (!self.m_fd)
- throw std::logic_error(
- "release() called on already-released tempfile");
-
- if (!self.m_path.empty()) {
- auto ec = std::error_code(); // ignore errors
- remove(self.path(), ec);
-
- self.m_path.clear();
- }
-
- std::ignore = self.m_fd.close();
-}
-
-auto temporary_file::path(this temporary_file const &self)
- -> std::filesystem::path const &
-{
- if (self.m_path.empty())
- throw std::logic_error(
- "path() called on unlinked temporary_file");
-
- return self.m_path;
-}
-
-auto temporary_file::fd(this temporary_file &self) -> nihil::fd &
-{
- if (!self.m_fd)
- throw std::logic_error("fd() called on empty temporary_file");
-
- return self.m_fd;
-}
-
-auto tempfile(tempfile_flags_t flags) -> std::expected<temporary_file, error>
-{
- auto rng = std::default_random_engine(std::random_device{}());
-
- auto random_name = [&] -> std::string {
- auto constexpr length = std::size_t{12};
- auto constexpr randchars = std::string_view(
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789");
-
- auto dist = std::uniform_int_distribution<>(
- 0, randchars.size() - 1);
- auto ret = std::string(length, 0);
- std::ranges::generate_n(ret.begin(), length,
- [&] -> char {
- return randchars[dist(rng)];
- });
- return ret;
- };
-
- auto dir = std::filesystem::path(getenv("TMPDIR").value_or("/tmp"));
-
- // Keep trying until we don't get EEXIST.
- for (;;) {
- auto filename = dir / (random_name() + ".tmp");
- auto fd = nihil::open(filename, O_RDWR | O_CREAT | O_EXCL,
- 0600);
- if (!fd) {
- if (fd.error() == std::errc::file_exists)
- continue;
- return std::unexpected(fd.error());
- }
-
- if (flags & tempfile_unlink) {
- auto ec = std::error_code();
- remove(filename, ec);
- return temporary_file(std::move(*fd));
- } else {
- return temporary_file(std::move(*fd),
- std::move(filename));
- }
- }
-}
-
-} // namespace nihil
diff --git a/nihil.posix/tempfile.ccm b/nihil.posix/tempfile.ccm
index 82f3be4..e1510e5 100644
--- a/nihil.posix/tempfile.ccm
+++ b/nihil.posix/tempfile.ccm
@@ -1,16 +1,13 @@
-/*
- * This source code is released into the public domain.
- */
-
+// This source code is released into the public domain.
module;
-/*
- * tempfile: create a temporary file.
- */
+// tempfile: create a temporary file.
+#include <algorithm>
#include <cstdint>
#include <expected>
#include <filesystem>
+#include <random>
#include <string>
export module nihil.posix:tempfile;
@@ -18,70 +15,139 @@ export module nihil.posix:tempfile;
import nihil.error;
import nihil.flagset;
import :fd;
+import :getenv;
+import :open;
namespace nihil {
-struct tempfile_flags_tag {};
-export using tempfile_flags_t = flagset<std::uint8_t, tempfile_flags_tag>;
+struct tempfile_flags_tag
+{
+};
+export using tempfile_flags = flagset<std::uint8_t, tempfile_flags_tag>;
// No flags.
-export inline constexpr auto tempfile_none = tempfile_flags_t();
+export inline constexpr auto tempfile_none = tempfile_flags();
// Unlink the tempfile immediately after creating it
-export inline constexpr auto tempfile_unlink = tempfile_flags_t::bit<0>();
-
-export struct temporary_file final {
- /*
- * Fetch the file's fd.
- */
- [[nodiscard]] auto fd(this temporary_file &) -> nihil::fd &;
-
- /*
- * Fetch the name of this file. If tempfile_unlink was specified,
- * throws std::logic_error.
- */
- [[nodiscard]] auto path(this temporary_file const &)
- -> std::filesystem::path const &;
-
- /*
- * Release this temporary file, causing it to be deleted immediately.
- * Throws std::logic_error if the file has already been released.
- */
- auto release(this temporary_file &) -> void;
-
- /*
- * Destructor; unlink the file if we didn't already.
- */
- ~temporary_file();
+export inline constexpr auto tempfile_unlink = tempfile_flags::bit<0>();
+
+export struct temporary_file final
+{
+ // Fetch the file's fd.
+ [[nodiscard]] auto fd(this temporary_file &self) -> nihil::fd &
+ {
+ if (!self.m_fd)
+ throw std::logic_error("fd() called on empty temporary_file");
+
+ return self.m_fd;
+ }
+
+ // Fetch the name of this file. If tempfile_unlink was specified,
+ // throws std::logic_error.
+ [[nodiscard]] auto path(this temporary_file const &self) -> std::filesystem::path const &
+ {
+ if (self.m_path.empty())
+ throw std::logic_error("path() called on unlinked temporary_file");
+
+ return self.m_path;
+ }
+
+ // Release this temporary file, causing it to be closed and deleted immediately
+ // Throws std::logic_error if the file has already been released.
+ auto release(this temporary_file &self) -> void
+ {
+ if (!self.m_fd)
+ throw std::logic_error("release() called on already-released tempfile");
+
+ if (!self.m_path.empty()) {
+ auto ec = std::error_code(); // ignore errors
+ remove(self.path(), ec);
+
+ self.m_path.clear();
+ }
+
+ std::ignore = self.m_fd.close();
+ }
+
+ // Destructor; unlink the file if we didn't already.
+ ~temporary_file() // NOLINT
+ {
+ if (m_fd)
+ release();
+ }
// Not copyable.
temporary_file(temporary_file const &) = delete;
// Movable.
- temporary_file(temporary_file &&other) noexcept;
+ temporary_file(temporary_file &&other) noexcept = default;
// Not assignable.
- auto operator=(this temporary_file &, temporary_file const &)
- -> temporary_file & = delete;
- auto operator=(this temporary_file &, temporary_file &&) noexcept
- -> temporary_file & = delete;
+ auto operator=(this temporary_file &, temporary_file const &) -> temporary_file & = delete;
+ auto
+ operator=(this temporary_file &, temporary_file &&) noexcept -> temporary_file & = delete;
private:
// The file descriptor for the file.
- nihil::fd m_fd;
- std::filesystem::path m_path;
+ nihil::fd m_fd;
+ std::filesystem::path m_path;
+
+ temporary_file(nihil::fd &&fd, std::filesystem::path path) noexcept
+ : m_fd(std::move(fd))
+ , m_path(std::move(path))
+ {
+ }
- temporary_file(nihil::fd &&fd, std::filesystem::path) noexcept;
- temporary_file(nihil::fd &&fd) noexcept;
+ explicit temporary_file(nihil::fd &&fd) noexcept
+ : m_fd(std::move(fd))
+ {
+ }
- friend auto tempfile(tempfile_flags_t flags)
- -> std::expected<temporary_file, error>;
+ friend auto tempfile(tempfile_flags flags) -> std::expected<temporary_file, error>;
};
/*
* Create a temporary file and return it.
*/
-export [[nodiscard]] auto tempfile(tempfile_flags_t flags = tempfile_none)
- -> std::expected<temporary_file, error>;
+export [[nodiscard]] auto
+tempfile(tempfile_flags flags = tempfile_none) -> std::expected<temporary_file, error>
+{
+ auto rng = std::default_random_engine(std::random_device{}());
+
+ auto random_name = [&] -> std::string {
+ auto constexpr length = std::size_t{12};
+ auto constexpr randchars = std::string_view("abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789");
+
+ auto dist = std::uniform_int_distribution<>(0, randchars.size() - 1);
+ auto ret = std::string(length, 0);
+ std::ranges::generate_n(ret.begin(), length,
+ [&] -> char { return randchars[dist(rng)]; });
+ return ret;
+ };
+
+ auto dir = std::filesystem::path(getenv("TMPDIR").value_or("/tmp")); // NOLINT
+
+ // Keep trying until we don't get EEXIST.
+ for (;;) {
+ auto filename = dir / (random_name() + ".tmp");
+ auto fd =
+ nihil::open(filename, open_readwrite | open_create | open_exclusive, 0600);
+ if (!fd) {
+ if (fd.error() == std::errc::file_exists)
+ continue;
+ return std::unexpected(fd.error());
+ }
+
+ if (flags & tempfile_unlink) {
+ auto ec = std::error_code();
+ remove(filename, ec);
+ return temporary_file(std::move(*fd));
+ }
+
+ return temporary_file(std::move(*fd), std::move(filename));
+ }
+}
} // namespace nihil
diff --git a/nihil.posix/test.tempfile.cc b/nihil.posix/tempfile.test.cc
index b1c7604..b1c7604 100644
--- a/nihil.posix/test.tempfile.cc
+++ b/nihil.posix/tempfile.test.cc
diff --git a/nihil.posix/test.spawn.cc b/nihil.posix/test.spawn.cc
index ca6c076..c5b4f53 100644
--- a/nihil.posix/test.spawn.cc
+++ b/nihil.posix/test.spawn.cc
@@ -6,27 +6,6 @@
import nihil.posix;
-TEST_CASE("spawn: system", "[spawn]")
-{
- using namespace nihil;
-
- auto exec = shell("x=1; echo $x");
- REQUIRE(exec);
-
- auto output = std::string();
- auto capture = make_capture(stdout_fileno, output);
- REQUIRE(capture);
-
- auto proc = spawn(*exec, *capture);
- REQUIRE(proc);
-
- auto status = std::move(*proc).wait();
- REQUIRE(status);
-
- REQUIRE(status->okay());
- REQUIRE(output == "1\n");
-}
-
TEST_CASE("spawn: execv", "[spawn]") {
using namespace nihil;
@@ -69,25 +48,6 @@ TEST_CASE("spawn: execvp", "[spawn]") {
REQUIRE(output == "1\n");
}
-TEST_CASE("spawn: execl", "[spawn]") {
- using namespace nihil;
-
- auto exec = execl("/bin/sh", "sh", "-c", "x=1; echo $x");
- REQUIRE(exec);
-
- auto output = std::string();
- auto capture = make_capture(stdout_fileno, output);
- REQUIRE(capture);
-
- auto proc = spawn(*exec, *capture);
- REQUIRE(proc);
-
- auto status = std::move(*proc).wait();
- REQUIRE(status);
-
- REQUIRE(status->okay());
- REQUIRE(output == "1\n");
-}
TEST_CASE("spawn: execlp", "[spawn]") {
using namespace nihil;
diff --git a/nihil.posix/write_file.ccm b/nihil.posix/write_file.ccm
index 867e0db..ce21e6b 100644
--- a/nihil.posix/write_file.ccm
+++ b/nihil.posix/write_file.ccm
@@ -35,7 +35,7 @@ auto write_file(std::filesystem::path const &filename,
int mode = 0777)
-> std::expected<std::size_t, error>
{
- auto file = co_await open(filename, O_CREAT|O_WRONLY, mode);
+ auto file = co_await open(filename, open_write | open_create, mode);
auto nbytes = co_await write(file, range);
co_return nbytes;
}