diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-07-01 17:07:04 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-07-01 17:07:04 +0100 |
| commit | 2e2d1bd3b6c7776b77c33b94f30ead89367a71e6 (patch) | |
| tree | 54d37ffadf8e677938d9b7a28e4e9b71be1e75c1 /nihil.posix | |
| parent | 36427c0966faa7aecd586b397ed9b845f18172f5 (diff) | |
| download | nihil-2e2d1bd3b6c7776b77c33b94f30ead89367a71e6.tar.gz nihil-2e2d1bd3b6c7776b77c33b94f30ead89367a71e6.tar.bz2 | |
add nihil.std
Diffstat (limited to 'nihil.posix')
39 files changed, 308 insertions, 458 deletions
diff --git a/nihil.posix/CMakeLists.txt b/nihil.posix/CMakeLists.txt index 1fcc365..d76ae4d 100644 --- a/nihil.posix/CMakeLists.txt +++ b/nihil.posix/CMakeLists.txt @@ -2,7 +2,14 @@ add_library(nihil.posix STATIC) target_link_libraries(nihil.posix PRIVATE - nihil.core nihil.error nihil.flagset nihil.guard nihil.monad) + nihil.std + nihil.core + nihil.error + nihil.flagset + nihil.guard + nihil.monad + nihil.util +) target_sources(nihil.posix PUBLIC FILE_SET modules TYPE CXX_MODULES FILES @@ -17,18 +24,19 @@ target_sources(nihil.posix execvp.ccm executor.ccm fd.ccm - fexecv.ccm - fexecvp.ccm find_in_path.ccm getenv.ccm open.ccm open_in_path.ccm + paths.ccm process.ccm read_file.ccm rename.ccm spawn.ccm stat.ccm tempfile.ccm + unistd.ccm + unlink.ccm write_file.ccm ) diff --git a/nihil.posix/argv.ccm b/nihil.posix/argv.ccm index de75770..230ae9a 100644 --- a/nihil.posix/argv.ccm +++ b/nihil.posix/argv.ccm @@ -1,16 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <memory> -#include <ranges> -#include <string> -#include <vector> - +// This source code is released into the public domain. export module nihil.posix:argv; +import nihil.std; + namespace nihil { /* diff --git a/nihil.posix/argv.test.cc b/nihil.posix/argv.test.cc index 3cc218d..e71c3da 100644 --- a/nihil.posix/argv.test.cc +++ b/nihil.posix/argv.test.cc @@ -1,14 +1,8 @@ -/* - * This source code is released into the public domain - */ - -#include <algorithm> -#include <string> -#include <type_traits> -#include <vector> +// This source code is released into the public domain #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/ensure_dir.ccm b/nihil.posix/ensure_dir.ccm index 7eecea8..8d3e7a8 100644 --- a/nihil.posix/ensure_dir.ccm +++ b/nihil.posix/ensure_dir.ccm @@ -1,15 +1,7 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <expected> -#include <filesystem> -#include <system_error> - +// This source code is released into the public domain. export module nihil.posix:ensure_dir; +import nihil.std; import nihil.error; namespace nihil { diff --git a/nihil.posix/execl.ccm b/nihil.posix/execl.ccm index f3cbf9a..99b9169 100644 --- a/nihil.posix/execl.ccm +++ b/nihil.posix/execl.ccm @@ -1,40 +1,25 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <expected> -#include <string> - -#include "nihil.hh" - +// This source code is released into the public domain. export module nihil.posix:execl; +import nihil.std; import nihil.error; import :argv; import :execv; -import :fexecv; +import :fd; namespace nihil { -/* - * execl: equivalent to (f)execv, except the arguments are passed as a - * variadic pack of string-like objects. - */ +// 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) -> execv +export [[nodiscard]] auto execl(std::filesystem::path path, auto &&...args) -> execv { - return execv(path, argv({std::string_view(args)...})); + return execv(std::move(path), argv({std::string_view(args)...})); } -#ifdef NIHIL_HAVE_FEXECVE - -export [[nodiscard]] auto execl(fd &&executable, auto &&...args) -> fexecv +export [[nodiscard]] auto execl(fd &&executable, auto &&...args) -> execv { - return fexecv(std::move(executable), argv({std::string_view(args)...})); + return execv(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 index 5aaaa25..51c2bb9 100644 --- a/nihil.posix/execl.test.cc +++ b/nihil.posix/execl.test.cc @@ -1,9 +1,8 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/execlp.ccm b/nihil.posix/execlp.ccm index ab3737c..12f2c24 100644 --- a/nihil.posix/execlp.ccm +++ b/nihil.posix/execlp.ccm @@ -1,12 +1,7 @@ // This source code is released into the public domain. -module; - -#include <expected> -#include <format> -#include <string> - export module nihil.posix:execlp; +import nihil.std; import nihil.error; import :argv; import :execvp; @@ -15,8 +10,9 @@ 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<execv, error> +execlp(std::filesystem::path const &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 index aa32253..6fdccea 100644 --- a/nihil.posix/execlp.test.cc +++ b/nihil.posix/execlp.test.cc @@ -1,9 +1,8 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/execshell.ccm b/nihil.posix/execshell.ccm index 1fbfccf..e0263e5 100644 --- a/nihil.posix/execshell.ccm +++ b/nihil.posix/execshell.ccm @@ -1,10 +1,7 @@ // This source code is released into the public domain. -module; - -#include <string> - export module nihil.posix:execshell; +import nihil.std; import nihil.error; import :execv; import :execl; diff --git a/nihil.posix/execshell.test.cc b/nihil.posix/execshell.test.cc index b64953a..47e3313 100644 --- a/nihil.posix/execshell.test.cc +++ b/nihil.posix/execshell.test.cc @@ -1,9 +1,8 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/executor.ccm b/nihil.posix/executor.ccm index f348dc8..d9bde88 100644 --- a/nihil.posix/executor.ccm +++ b/nihil.posix/executor.ccm @@ -1,19 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <concepts> -#include <type_traits> - +// This source code is released into the public domain. export module nihil.posix:executor; +import nihil.std; + namespace nihil { -/* - * A concept to mark spawn executors. - */ +// A concept to mark spawn executors, which should contain: +// using tag = exec_tag; export struct exec_tag{}; diff --git a/nihil.posix/execv.ccm b/nihil.posix/execv.ccm index ef9d259..d598d94 100644 --- a/nihil.posix/execv.ccm +++ b/nihil.posix/execv.ccm @@ -1,17 +1,17 @@ // This source code is released into the public domain. module; -#include <expected> -#include <filesystem> -#include <string> - -#include <unistd.h> +#include <unistd.h> // execv() export module nihil.posix:execv; +import nihil.std; import nihil.error; +import nihil.match; +import nihil.util; import :argv; import :executor; +import :fd; namespace nihil { @@ -20,12 +20,19 @@ export struct execv final { using tag = exec_tag; + // Construct an execv from a filename. execv(std::filesystem::path path, argv &&args) noexcept - : m_path(std::move(path)) + : m_executable(std::move(path)) , m_args(std::move(args)) { } + // Construct an execv from a file descriptor + execv(fd &&executable, argv &&argv) noexcept + : m_executable(std::move(executable)) + , m_args(std::move(argv)) + {} + ~execv() = default; // Movable @@ -39,12 +46,30 @@ export struct execv final // 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)))); + auto guard = save_errno(); + + return self.m_executable | match { + [&] (std::filesystem::path const &path) { + ::execv(path.string().c_str(), self.m_args.data()); + return std::unexpected(error("execve failed", error(sys_error()))); + }, + + [&] (fd const &file) { +#if NIHIL_HAVE_FEXECVE == 1 + ::fexecv(file.get(), self.m_args.data()); + return std::unexpected(error("execve failed", error(sys_error()))); +#else + std::ignore = file; + return std::unexpected(error(std::errc::function_not_supported)); +#endif + } + }; } private: - std::filesystem::path m_path; + // The thing we will execute. + std::variant<std::filesystem::path, fd> m_executable; + // Arguments to pass to the thing. argv m_args; }; diff --git a/nihil.posix/execv.test.cc b/nihil.posix/execv.test.cc index aaeead7..8c3ef0c 100644 --- a/nihil.posix/execv.test.cc +++ b/nihil.posix/execv.test.cc @@ -1,9 +1,8 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/execvp.ccm b/nihil.posix/execvp.ccm index 270e311..14e548e 100644 --- a/nihil.posix/execvp.ccm +++ b/nihil.posix/execvp.ccm @@ -1,29 +1,26 @@ // This source code is released into the public domain. -module; - -#include <coroutine> -#include <string> -#include <expected> -#include <format> - export module nihil.posix:execvp; +import nihil.std; +import nihil.core; import nihil.error; import nihil.monad; import :argv; import :execv; import :find_in_path; +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. +// execvp: equivalent to execv, except the command will be searched for in $PATH. + export [[nodiscard]] auto -execvp(std::string_view file, argv &&argv) -> std::expected<execv, error> +execvp(std::filesystem::path const &file, argv &&args) -> std::expected<execv, error> { - auto filename = co_await find_in_path(file); - co_return execv(std::move(filename), std::move(argv)); + if constexpr (features::fexecve) + co_return execv(co_await open_in_path(file), std::move(args)); + else + co_return execv(co_await find_in_path(file), std::move(args)); } } // namespace nihil diff --git a/nihil.posix/execvp.test.cc b/nihil.posix/execvp.test.cc index e34823d..5f1b979 100644 --- a/nihil.posix/execvp.test.cc +++ b/nihil.posix/execvp.test.cc @@ -2,6 +2,7 @@ #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/fd.ccm b/nihil.posix/fd.ccm index 7faf2f1..8210b6d 100644 --- a/nihil.posix/fd.ccm +++ b/nihil.posix/fd.ccm @@ -1,24 +1,23 @@ // This source code is released into the public domain. module; -#include <coroutine> -#include <expected> -#include <ranges> -#include <span> -#include <stdexcept> -#include <system_error> - #include <fcntl.h> #include <unistd.h> export module nihil.posix:fd; +import nihil.std; import nihil.flagset; import nihil.error; import nihil.monad; namespace nihil { +// Useful constants +export inline int constexpr stdin_fileno = STDIN_FILENO; +export inline int constexpr stdout_fileno = STDOUT_FILENO; +export inline int constexpr stderr_fileno = STDERR_FILENO; + // F_{GET,SET}FL flags struct fd_flags_tag { @@ -96,7 +95,7 @@ export struct fd final if (ret == 0) return {}; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Return the stored fd. @@ -124,7 +123,7 @@ export struct fd final if (ret >= 0) return ret; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Read data from the fd to the provided buffer. Returns a @@ -136,7 +135,7 @@ export struct fd final if (ret >= 0) return buffer.subspan(0, ret); - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } private: @@ -152,7 +151,7 @@ export [[nodiscard]] auto dup(fd const &self) -> std::expected<fd, error> if (newfd != -1) return fd(newfd); - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Create a copy of this fd by calling dup2(). Note that because this results @@ -170,7 +169,7 @@ export [[nodiscard]] auto dup(fd const &self, int newfd) -> std::expected<fd, er if (ret != -1) return fd(newfd); - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Create a copy of this fd by calling dup(). @@ -180,7 +179,7 @@ export [[nodiscard]] auto raw_dup(fd const &self) -> std::expected<int, error> if (newfd != -1) return newfd; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Create a copy of this fd by calling dup2(). @@ -190,7 +189,7 @@ export [[nodiscard]] auto raw_dup(fd const &self, int newfd) -> std::expected<in if (ret != -1) return newfd; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Call fcntl() on this fd. Prefer one of the type-safe wrappers to this, if available. @@ -199,7 +198,7 @@ export [[nodiscard]] auto fcntl(fd const &fd, int op, int arg = 0) { auto const ret = ::fcntl(fd.get(), op, arg); if (ret == -1) - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); return ret; } @@ -277,7 +276,7 @@ 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 std::unexpected(error(sys_error())); return {{fd(fds[0]), fd(fds[1])}}; } diff --git a/nihil.posix/fd.test.cc b/nihil.posix/fd.test.cc index 870ddde..65b2ad3 100644 --- a/nihil.posix/fd.test.cc +++ b/nihil.posix/fd.test.cc @@ -1,15 +1,11 @@ -/* - * This source code is released into the public domain. - */ - -#include <coroutine> -#include <span> -#include <stdexcept> +// This source code is released into the public domain. #include <fcntl.h> +#include <unistd.h> #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.error; import nihil.posix; diff --git a/nihil.posix/fexecv.ccm b/nihil.posix/fexecv.ccm deleted file mode 100644 index 4001726..0000000 --- a/nihil.posix/fexecv.ccm +++ /dev/null @@ -1,58 +0,0 @@ -// This source code is released into the public domain. -module; - -#include <expected> -#include <string> - -#include "nihil.hh" - -export module nihil.posix:fexecv; - -import nihil.error; -import :argv; -import :executor; -import :fd; - -namespace nihil { - -#ifdef NIHIL_HAVE_FEXECVE - -/* - * fexecv: use a file descriptor and an argument vector to call ::fexecve(). - * This is the lowest-level executor which all others are implemented - * in terms of (if it's available). - * - * TODO: Should have a way to pass the environment (envp). - */ -export struct fexecv final -{ - using tag = exec_tag; - - 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> - { - ::fexecve(self.m_execfd.get(), self.m_args.data(), environ); - return std::unexpected(error("fexecve failed", error(std::errc(errno)))); - } - - // Movable - 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; - -private: - fd m_execfd; - argv m_args; -}; - -#endif // NIHIL_HAVE_FEXECVE - -} // namespace nihil diff --git a/nihil.posix/fexecvp.ccm b/nihil.posix/fexecvp.ccm deleted file mode 100644 index d61240c..0000000 --- a/nihil.posix/fexecvp.ccm +++ /dev/null @@ -1,37 +0,0 @@ -// 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.ccm b/nihil.posix/find_in_path.ccm index 61df669..dabe358 100644 --- a/nihil.posix/find_in_path.ccm +++ b/nihil.posix/find_in_path.ccm @@ -1,19 +1,15 @@ // This source code is released into the public domain. module; -#include <expected> -#include <filesystem> -#include <optional> -#include <ranges> - -#include <paths.h> -#include <unistd.h> +#include <unistd.h> // access() export module nihil.posix:find_in_path; +import nihil.std; import nihil.error; import :fd; import :getenv; +import :paths; namespace nihil { @@ -28,7 +24,7 @@ export [[nodiscard]] auto find_in_path(std::filesystem::path const &file, std::s auto ret = ::access(file.string().c_str(), X_OK); if (ret == 0) return {std::move(file)}; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); }; // Absolute pathname skips the search. @@ -59,7 +55,7 @@ export [[nodiscard]] auto find_in_path(std::filesystem::path const &file, std::s export [[nodiscard]] auto find_in_path(std::filesystem::path const &file) -> std::expected<std::filesystem::path, error> { - auto const path = getenv("PATH").value_or(_PATH_DEFPATH); // NOLINT + auto const path = getenv("PATH").value_or(std::string(paths::defpath)); // NOLINT return find_in_path(file, path); } diff --git a/nihil.posix/getenv.ccm b/nihil.posix/getenv.ccm index 5967bf7..ddffeb3 100644 --- a/nihil.posix/getenv.ccm +++ b/nihil.posix/getenv.ccm @@ -2,18 +2,15 @@ module; #include <cerrno> -#include <expected> -#include <string> -#include <system_error> -#include <vector> - -#include <unistd.h> +#include <stdlib.h> // NOLINT: getenv_r #include "nihil.hh" export module nihil.posix:getenv; +import nihil.std; import nihil.error; +import nihil.util; namespace nihil { @@ -23,9 +20,10 @@ namespace nihil { // future calls to setenv(). export [[nodiscard]] auto getenv(std::string_view varname) -> std::expected<std::string, error> { + auto errno_guard = save_errno(); auto cvarname = std::string(varname); -#ifdef NIHIL_HAVE_GETENV_R +#if NIHIL_HAVE_GETENV_R == 1 // Start with a buffer of this size, and double it every iteration. constexpr auto bufinc = std::size_t{1024}; @@ -44,16 +42,14 @@ export [[nodiscard]] auto getenv(std::string_view varname) -> std::expected<std: return std::unexpected(error(std::errc(errno))); } #else // NIHIL_HAVE_GETENV_R - errno = 0; - auto *v = ::getenv(cvarname.c_str()); // NOLINT + // getenv() may not set errno on failure, so initialise it to a reasonable value. + errno = ENOENT; + auto const *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)); + return error(sys_error()); #endif // NIHIL_HAVE_GETENV_R } diff --git a/nihil.posix/getenv.test.cc b/nihil.posix/getenv.test.cc index 9e10c16..3ba1d94 100644 --- a/nihil.posix/getenv.test.cc +++ b/nihil.posix/getenv.test.cc @@ -1,18 +1,14 @@ -/* - * This source code is released into the public domain. - */ - -#include <ranges> -#include <string> -#include <system_error> +// This source code is released into the public domain. #include <unistd.h> #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.error; import nihil.posix; +namespace { TEST_CASE("getenv: existing value", "[getenv]") { auto constexpr *name = "NIHIL_TEST_VAR"; @@ -48,3 +44,5 @@ TEST_CASE("getenv: long value") REQUIRE(s); REQUIRE(*s == value); } + +} // anonymous namespace diff --git a/nihil.posix/open.ccm b/nihil.posix/open.ccm index 59f80af..f2f5ecd 100644 --- a/nihil.posix/open.ccm +++ b/nihil.posix/open.ccm @@ -1,20 +1,20 @@ // 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.std; import nihil.error; import nihil.flagset; +import nihil.util; import :fd; namespace nihil { +// Flags which can be passed to open(). struct open_flags_tag { }; @@ -70,7 +70,7 @@ export [[nodiscard]] auto open(std::filesystem::path const &filename, open_flags if (fdno != -1) return fd(fdno); - return std::unexpected(error(std::errc(errno))); + return error(sys_error()); } // Like open(), but resolve relative to an open file descriptor, which must refer to a directory. @@ -81,7 +81,7 @@ export [[nodiscard]] auto openat(fd &where, std::filesystem::path const &filenam if (fdno != -1) return fd(fdno); - return std::unexpected(error(std::errc(errno))); + return error(sys_error()); } } // namespace nihil diff --git a/nihil.posix/open.test.cc b/nihil.posix/open.test.cc index bb8bcc9..e49f4c4 100644 --- a/nihil.posix/open.test.cc +++ b/nihil.posix/open.test.cc @@ -2,6 +2,7 @@ #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.error; import nihil.posix; diff --git a/nihil.posix/open_in_path.ccm b/nihil.posix/open_in_path.ccm index e8c1761..0733c8d 100644 --- a/nihil.posix/open_in_path.ccm +++ b/nihil.posix/open_in_path.ccm @@ -1,20 +1,12 @@ // This source code is released into the public domain. -module; - -#include <expected> -#include <filesystem> -#include <optional> -#include <ranges> -#include <string> - -#include <paths.h> - export module nihil.posix:open_in_path; +import nihil.std; import nihil.error; import :fd; import :getenv; import :open; +import :paths; namespace nihil { @@ -51,7 +43,7 @@ open_in_path(std::filesystem::path const &file, std::string_view path) -> std::e export [[nodiscard]] auto open_in_path(std::filesystem::path const &file) -> std::expected<fd, error> { - auto const path = getenv("PATH").value_or(_PATH_DEFPATH); // NOLINT + auto const path = getenv("PATH").value_or(std::string(paths::defpath)); // NOLINT return open_in_path(file, path); } diff --git a/nihil.posix/open_in_path.test.cc b/nihil.posix/open_in_path.test.cc index 13d6b49..ebd1405 100644 --- a/nihil.posix/open_in_path.test.cc +++ b/nihil.posix/open_in_path.test.cc @@ -2,6 +2,7 @@ #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.error; import nihil.posix; diff --git a/nihil.posix/paths.ccm b/nihil.posix/paths.ccm new file mode 100644 index 0000000..775e566 --- /dev/null +++ b/nihil.posix/paths.ccm @@ -0,0 +1,27 @@ +// This source code is released into the public domain. +module; + +#include <paths.h> + +export module nihil.posix:paths; + +import nihil.std; + +namespace nihil::paths { + +export inline constexpr auto defpath = std::string_view(_PATH_DEFPATH); +export inline constexpr auto stdpath = std::string_view(_PATH_STDPATH); + +#ifdef _PATH_LOCALBASE +export inline constexpr auto localbase = std::string_view(_PATH_LOCALBASE); +#else +export inline constexpr auto localbase = std::string_view("/usr/local"); +#endif + +#ifdef _PATH_SYSPATH +export inline constexpr auto syspath = std::string_view(_PATH_SYSPATH); +#else +export inline constexpr auto syspath = std::string_view("/sbin:/usr/sbin"); +#endif + +} // namespace nihil::paths diff --git a/nihil.posix/posix.ccm b/nihil.posix/posix.ccm index c49a992..aa21649 100644 --- a/nihil.posix/posix.ccm +++ b/nihil.posix/posix.ccm @@ -13,16 +13,16 @@ 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; export import :open_in_path; +export import :paths; export import :process; export import :read_file; export import :rename; export import :spawn; export import :stat; export import :tempfile; +export import :unlink; export import :write_file; diff --git a/nihil.posix/process.ccm b/nihil.posix/process.ccm index ee7de15..9fbf34c 100644 --- a/nihil.posix/process.ccm +++ b/nihil.posix/process.ccm @@ -1,19 +1,17 @@ // This source code is released into the public domain. module; -#include <expected> -#include <optional> -#include <system_error> -#include <utility> - #include <sys/wait.h> export module nihil.posix:process; +import nihil.std; import nihil.error; namespace nihil { +export struct process; + // wait_result: the exit status of a process. export struct wait_result final { @@ -58,7 +56,7 @@ private: }; // Represents a process we created, which can be waited for. -export struct process final +struct process final { process() = delete; @@ -83,7 +81,8 @@ export struct process final // Movable. process(process &&other) noexcept : m_pid(std::exchange(other.m_pid, -1)) - {} + { + } auto operator=(this process &self, process &&other) noexcept -> process & { @@ -113,7 +112,7 @@ export struct process final auto status = int{}; auto ret = waitpid(self.m_pid, &status, 0); if (ret == -1) - return std::unexpected(error(std::errc(errno))); + return error(sys_error()); return wait_result(status); } diff --git a/nihil.posix/read_file.ccm b/nihil.posix/read_file.ccm index 3b4fd5b..61c5085 100644 --- a/nihil.posix/read_file.ccm +++ b/nihil.posix/read_file.ccm @@ -1,20 +1,4 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <algorithm> -#include <expected> -#include <filesystem> -#include <iterator> -#include <ranges> -#include <span> -#include <system_error> - -#include <fcntl.h> -#include <unistd.h> - +// This source code is released into the public domain. export module nihil.posix:read_file; import nihil.error; @@ -24,12 +8,9 @@ import :open; namespace nihil { -/* - * Read the contents of a file into an output iterator. - */ +// Read the contents of a file into an output iterator. export [[nodiscard]] auto -read_file(std::filesystem::path const &filename, - std::output_iterator<char> auto &&iter) +read_file(std::filesystem::path const &filename, std::output_iterator<char> auto &&iter) -> std::expected<void, error> { auto file = co_await open(filename, open_read); @@ -38,7 +19,7 @@ read_file(std::filesystem::path const &filename, auto buffer = std::array<char, bufsize>{}; for (;;) { - auto read_buf = co_await(read(file, buffer)); + auto read_buf = co_await (read(file, buffer)); if (read_buf.empty()) co_return {}; diff --git a/nihil.posix/rename.ccm b/nihil.posix/rename.ccm index a1b292e..c46005e 100644 --- a/nihil.posix/rename.ccm +++ b/nihil.posix/rename.ccm @@ -1,12 +1,9 @@ // This source code is released into the public domain. -module; - -#include <expected> -#include <filesystem> - export module nihil.posix:rename; +import nihil.std; import nihil.error; +import nihil.util; namespace nihil { @@ -15,12 +12,11 @@ export [[nodiscard]] auto rename(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); + auto guard = save_errno(); - if (err) - return std::unexpected(error(err)); + auto const ret = std::rename(oldp.string().c_str(), newp.string().c_str()); + if (ret == -1) + return std::unexpected(error(sys_error())); return {}; } diff --git a/nihil.posix/spawn.ccm b/nihil.posix/spawn.ccm index a185bb3..1e4102a 100644 --- a/nihil.posix/spawn.ccm +++ b/nihil.posix/spawn.ccm @@ -1,52 +1,27 @@ // This source code is released into the public domain. -module; - -/* - * spawn(): fork and execute a child process. - */ - -#include <algorithm> -#include <cerrno> -#include <coroutine> -#include <expected> -#include <filesystem> -#include <format> -#include <iterator> -#include <print> -#include <string> -#include <utility> - -#include <sys/types.h> -#include <sys/wait.h> - -#include <fcntl.h> -#include <unistd.h> - export module nihil.posix:spawn; +// spawn(): fork and execute a child process. + import nihil.monad; import :argv; import :executor; +import :fd; import :open; import :process; +import :unistd; namespace nihil { -// Useful constants -export inline int constexpr stdin_fileno = STDIN_FILENO; -export inline int constexpr stdout_fileno = STDOUT_FILENO; -export inline int constexpr stderr_fileno = STDERR_FILENO; - -/* - * fd_{read,write}_pipe: create a pipe with one end in the child and the other in the parent. - * The child's side will be dup2()'d to the provided fd number. - * The parent side fd can be retrieved via parent_fd(). - * - * fd_read_pipe() puts the reading side in the child, while fd_write_pipe() puts the writing - * side in the child. - */ +// fd_{read,write}_pipe: create a pipe with one end in the child and the other in the parent. +// The child's side will be dup2()'d to the provided fd number. +// The parent side fd can be retrieved via parent_fd(). +// +// fd_read_pipe() puts the reading side in the child, while fd_write_pipe() puts the writing +// side in the child. -struct fd_pipe_base { +struct fd_pipe_base +{ fd_pipe_base(int fdno, fd &&child_fd, fd &&parent_fd) : m_fdno(fdno) , m_child_fd(std::move(child_fd)) @@ -58,8 +33,8 @@ struct fd_pipe_base { { auto err = raw_dup(self.m_child_fd, self.m_fdno); if (!err) { - std::print("dup: {}\n", err.error()); - _exit(1); + std::println("dup: {}", err.error()); + std::quick_exit(1); } /* @@ -81,35 +56,34 @@ struct fd_pipe_base { } private: - int m_fdno; - fd m_child_fd; - fd m_parent_fd; - + int m_fdno; + fd m_child_fd; + fd m_parent_fd; }; -export struct fd_read_pipe final : fd_pipe_base { +export struct fd_read_pipe final : fd_pipe_base +{ fd_read_pipe(int fdno, fd &&read_fd, fd &&write_fd) : fd_pipe_base(fdno, std::move(read_fd), std::move(write_fd)) { } }; -export struct fd_write_pipe final : fd_pipe_base { +export struct fd_write_pipe final : fd_pipe_base +{ fd_write_pipe(int fdno, fd &&read_fd, fd &&write_fd) : fd_pipe_base(fdno, std::move(write_fd), std::move(read_fd)) { } }; -export [[nodiscard]] auto -make_fd_read_pipe(int fdno) -> std::expected<fd_read_pipe, error> +export [[nodiscard]] auto make_fd_read_pipe(int fdno) -> std::expected<fd_read_pipe, error> { auto fds = co_await pipe(); co_return fd_read_pipe(fdno, std::move(fds.first), std::move(fds.second)); } -export [[nodiscard]] auto -make_fd_write_pipe(int fdno) -> std::expected<fd_write_pipe, error> +export [[nodiscard]] auto make_fd_write_pipe(int fdno) -> std::expected<fd_write_pipe, error> { auto fds = co_await pipe(); co_return fd_write_pipe(fdno, std::move(fds.first), std::move(fds.second)); @@ -119,7 +93,8 @@ make_fd_write_pipe(int fdno) -> std::expected<fd_write_pipe, error> * fd_file: open a file and provide it to the child as a file descriptor. * open_flags and open_mode are as for ::open(). */ -export struct fd_file final { +export struct fd_file final +{ fd_file(int fdno, fd &&file_fd) : m_fdno(fdno) , m_file_fd(std::move(file_fd)) @@ -136,20 +111,19 @@ export struct fd_file final { auto err = raw_dup(self.m_file_fd, self.m_fdno); if (!err) { std::print("dup: {}\n", err.error()); - _exit(1); + std::quick_exit(1); } std::ignore = self.m_file_fd.close(); } private: - int m_fdno; - fd m_file_fd; + int m_fdno; + fd m_file_fd; }; export [[nodiscard]] auto -make_fd_file(int fdno, std::filesystem::path const &file, - open_flags flags, int mode = 0777) +make_fd_file(int fdno, std::filesystem::path const &file, open_flags flags, int mode = 0777) -> std::expected<fd_file, error> { auto fd = co_await open(file, flags, mode); @@ -160,20 +134,17 @@ make_fd_file(int fdno, std::filesystem::path const &file, * Shorthand for fd_file with /dev/null as the file. */ -export [[nodiscard]] inline auto -stdin_devnull() -> std::expected<fd_file, error> +export [[nodiscard]] inline auto stdin_devnull() -> std::expected<fd_file, error> { return make_fd_file(stdin_fileno, "/dev/null", open_read); } -export [[nodiscard]] inline auto -stdout_devnull() -> std::expected<fd_file, error> +export [[nodiscard]] inline auto stdout_devnull() -> std::expected<fd_file, error> { return make_fd_file(stdout_fileno, "/dev/null", open_write); } -export [[nodiscard]] inline auto -stderr_devnull() -> std::expected<fd_file, error> +export [[nodiscard]] inline auto stderr_devnull() -> std::expected<fd_file, error> { return make_fd_file(stderr_fileno, "/dev/null", open_write); } @@ -182,8 +153,9 @@ stderr_devnull() -> std::expected<fd_file, error> * Capture the output of a pipe in the parent and read it into an * output iterator. */ -export template<std::output_iterator<char> Iterator> -struct fd_capture final { +export template <std::output_iterator<char> Iterator> +struct fd_capture final +{ fd_capture(fd_write_pipe &&pipe, Iterator it) : m_pipe(std::move(pipe)) , m_iterator(std::move(it)) @@ -204,7 +176,7 @@ struct fd_capture final { auto &fd = self.m_pipe.parent_fd(); for (;;) { auto ret = read(fd, buffer); - if (!ret || ret->size() == 0) + if (!ret || ret->empty()) break; std::ranges::copy(*ret, self.m_iterator); @@ -215,21 +187,18 @@ struct fd_capture final { } private: - fd_write_pipe m_pipe; - Iterator m_iterator; + fd_write_pipe m_pipe; + Iterator m_iterator; }; -export [[nodiscard]] auto -make_capture(int fdno, std::output_iterator<char> auto &&it) +export [[nodiscard]] auto make_capture(int fdno, std::output_iterator<char> auto &&it) -> std::expected<fd_capture<decltype(it)>, error> { auto pipe = co_await make_fd_write_pipe(fdno); - co_return fd_capture(std::move(pipe), - std::forward<decltype(it)>(it)); + co_return fd_capture(std::move(pipe), std::forward<decltype(it)>(it)); } -export [[nodiscard]] auto -make_capture(int fdno, std::string &str) +export [[nodiscard]] auto make_capture(int fdno, std::string &str) -> std::expected<fd_capture<decltype(std::back_inserter(str))>, error> { auto pipe = co_await make_fd_write_pipe(fdno); @@ -241,14 +210,9 @@ make_capture(int fdno, std::string &str) * Throws exec_error() on failure. */ export [[nodiscard]] auto -spawn(executor auto &&executor, auto &&...actions) - -> std::expected<process, error> +spawn(executor auto &&executor, auto &&...actions) -> std::expected<process, error> { - auto const pid = ::fork(); - if (pid == -1) - return std::unexpected(error("fork failed", - error(std::errc(errno)))); - + auto const pid = co_await fork(); auto proc = process(pid); if (pid == 0) { @@ -257,13 +221,13 @@ spawn(executor auto &&executor, auto &&...actions) std::ignore = std::move(proc).release(); auto err = executor.exec(); - std::print("{}\n", err.error()); - _exit(1); + std::println("{}", err.error()); + std::quick_exit(1); } (actions.run_in_parent(proc), ...); - return proc; + co_return proc; } } // namespace nihil diff --git a/nihil.posix/stat.ccm b/nihil.posix/stat.ccm index 6a0cabf..ee8113b 100644 --- a/nihil.posix/stat.ccm +++ b/nihil.posix/stat.ccm @@ -3,13 +3,12 @@ module; // Basic wrappers around stat() and fstat(). -#include <expected> -#include <filesystem> - #include <sys/stat.h> export module nihil.posix:stat; +import nihil.std; +import nihil.util; import :fd; namespace nihil { @@ -17,19 +16,23 @@ namespace nihil { export [[nodiscard]] auto stat(std::filesystem::path const &path) -> std::expected<struct ::stat, error> { + auto guard = save_errno(); + struct ::stat sb{}; auto ret = ::stat(path.string().c_str(), &sb); if (ret == -1) - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); return sb; } export [[nodiscard]] auto stat(fd const &fd) -> std::expected<struct ::stat, error> { + auto guard = save_errno(); + struct ::stat sb{}; auto ret = ::fstat(fd.get(), &sb); if (ret == -1) - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); return sb; } diff --git a/nihil.posix/stat.test.cc b/nihil.posix/stat.test.cc index cf1e29c..535273b 100644 --- a/nihil.posix/stat.test.cc +++ b/nihil.posix/stat.test.cc @@ -1,5 +1,6 @@ // This source code is released into the public domain. +import nihil.std; import nihil.error; import nihil.posix; diff --git a/nihil.posix/tempfile.ccm b/nihil.posix/tempfile.ccm index e1510e5..121c636 100644 --- a/nihil.posix/tempfile.ccm +++ b/nihil.posix/tempfile.ccm @@ -1,22 +1,15 @@ // This source code is released into the public domain. -module; - -// tempfile: create a temporary file. - -#include <algorithm> -#include <cstdint> -#include <expected> -#include <filesystem> -#include <random> -#include <string> - export module nihil.posix:tempfile; +import nihil.std; import nihil.error; import nihil.flagset; import :fd; import :getenv; import :open; +import :unlink; + +// tempfile: create a temporary file. namespace nihil { @@ -60,9 +53,7 @@ export struct temporary_file final 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); - + std::ignore = unlink(self.path()); self.m_path.clear(); } @@ -141,8 +132,7 @@ tempfile(tempfile_flags flags = tempfile_none) -> std::expected<temporary_file, } if (flags & tempfile_unlink) { - auto ec = std::error_code(); - remove(filename, ec); + std::ignore = unlink(filename); return temporary_file(std::move(*fd)); } diff --git a/nihil.posix/tempfile.test.cc b/nihil.posix/tempfile.test.cc index b1c7604..fcaafdc 100644 --- a/nihil.posix/tempfile.test.cc +++ b/nihil.posix/tempfile.test.cc @@ -1,11 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include <filesystem> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.posix; TEST_CASE("posix.tempfile: create", "[nihil][nihil.posix]") diff --git a/nihil.posix/unistd.ccm b/nihil.posix/unistd.ccm new file mode 100644 index 0000000..14c19ee --- /dev/null +++ b/nihil.posix/unistd.ccm @@ -0,0 +1,23 @@ +// This source code is released into the public domain. +module; + +#include <unistd.h> + +export module nihil.posix:unistd; + +import nihil.std; +import nihil.error; + +// Symbols from unistd.h that might be useful. + +namespace nihil { + +export [[nodiscard]] auto fork() -> std::expected<::pid_t, error> +{ + auto const pid = ::fork(); + if (pid == -1) + return std::unexpected(error(sys_error())); + return pid; +} + +}; diff --git a/nihil.posix/unlink.ccm b/nihil.posix/unlink.ccm new file mode 100644 index 0000000..d6c47cd --- /dev/null +++ b/nihil.posix/unlink.ccm @@ -0,0 +1,28 @@ +// This source code is released into the public domain. +module; + +#include <unistd.h> + +export module nihil.posix:unlink; + +// unlink: simple wrapper around ::unlink() + +import nihil.std; +import nihil.error; +import nihil.util; + +namespace nihil { + +export [[nodiscard]] auto unlink(std::filesystem::path const &path) + -> std::expected<void, error> +{ + auto guard = save_errno(); + + auto const ret = ::unlink(path.string().c_str()); + if (ret == 0) + return {}; + + return std::unexpected(error(sys_error())); +} + +} // namespace nihil diff --git a/nihil.posix/write_file.ccm b/nihil.posix/write_file.ccm index ce21e6b..4bdd6e2 100644 --- a/nihil.posix/write_file.ccm +++ b/nihil.posix/write_file.ccm @@ -1,34 +1,19 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include <coroutine> -#include <expected> -#include <filesystem> -#include <ranges> -#include <system_error> -#include <vector> - -#include <fcntl.h> -#include <unistd.h> - +// This source code is released into the public domain. export module nihil.posix:write_file; +import nihil.std; import nihil.error; import nihil.guard; import nihil.monad; import :fd; import :open; import :rename; +import :unlink; namespace nihil { -/* - * Write the contents of a range to a file. Returns the number of bytes - * written. - */ +// Write the contents of a range to a file. Returns the number of bytes +// written. export [[nodiscard]] auto write_file(std::filesystem::path const &filename, std::ranges::contiguous_range auto &&range, @@ -40,9 +25,7 @@ auto write_file(std::filesystem::path const &filename, co_return nbytes; } -/* - * Utility wrapper for non-contiguous ranges. - */ +// Utility wrapper for non-contiguous ranges. export [[nodiscard]] auto write_file(std::filesystem::path const &filename, std::ranges::range auto &&range) @@ -52,12 +35,10 @@ requires(!std::ranges::contiguous_range<decltype(range)>) return write_file(filename, std::vector(std::from_range, range)); } -/* - * Write the contents of a range to a file safely. The data will be written - * to "<filename>.tmp", and if the write succeeds, the temporary file will be - * renamed to the target filename. If an error occurs, the target file will - * not be modified. - */ +// Write the contents of a range to a file safely. The data will be written +// to "<filename>.tmp", and if the write succeeds, the temporary file will be +// renamed to the target filename. If an error occurs, the target file will +// not be modified. export [[nodiscard]] auto safe_write_file(std::filesystem::path const &filename, std::ranges::range auto &&range) @@ -68,11 +49,11 @@ auto safe_write_file(std::filesystem::path const &filename, tmpfile /= (filename.filename().native() + ".tmp"); auto tmpfile_guard = guard([&tmpfile] { - ::unlink(tmpfile.c_str()); + std::ignore = unlink(tmpfile.c_str()); }); co_await write_file(tmpfile, range); - co_await nihil::rename(tmpfile, filename); + co_await rename(tmpfile, filename); tmpfile_guard.release(); co_return {}; |
