diff options
Diffstat (limited to 'modules/fd.ccm')
| -rw-r--r-- | modules/fd.ccm | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/modules/fd.ccm b/modules/fd.ccm new file mode 100644 index 0000000..ad96ea7 --- /dev/null +++ b/modules/fd.ccm @@ -0,0 +1,309 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <fcntl.h> +#include <unistd.h> + +#include <expected> +#include <format> +#include <stdexcept> +#include <system_error> + +export module nihil:fd; + +import :generic_error; + +namespace nihil { + +/* + * Exception thrown when an internal fd error occurs. This is not supposed + * to be caught, since it indicates an internal logic error in the caller. + */ +export struct fd_logic_error final : std::logic_error { + fd_logic_error(std::string what) + : std::logic_error(std::move(what)) + {} +}; + +/* + * fd: a file descriptor. + */ + +export struct fd final { + // Construct an empty (invalid) fd. + fd() noexcept = default; + + // Construct an fd from an exising file destrictor, taking ownership. + fd(int fd_) noexcept : _fd(fd_) {} + + // Destructor. Close the fd, discarding any errors. + ~fd() + { + if (*this) + this->close(); + } + + // Move from another fd, leaving the moved-from fd in an invalid state. + fd(fd &&other) noexcept + : _fd(std::exchange(other._fd, _invalid_fd)) + {} + + // Move assign from another fd. + auto operator=(fd &&other) noexcept -> fd & + { + if (this != &other) + _fd = std::exchange(other._fd, _invalid_fd); + return *this; + } + + // Not copyable. + fd(fd const &) = delete; + fd& operator=(fd const &) = delete; + + // Return true if this fd is valid (open). + explicit operator bool(this fd const &self) noexcept + { + return self._fd != _invalid_fd; + } + + // Close the wrapped fd. + auto close(this fd &self) -> std::expected<void, std::error_code> + { + auto const ret = ::close(self.get()); + self._fd = _invalid_fd; + + if (ret == 0) + return {}; + + return std::unexpected(std::make_error_code(std::errc(errno))); + } + + // Return the stored fd. + auto get(this fd const &self) -> int { + if (self) + return self._fd; + throw fd_logic_error("Attempt to call get() on invalid fd"); + } + + + // Release the stored fd and return it. The caller must close it. + auto release(this fd &&self) -> int { + if (self) + return std::exchange(self._fd, self._invalid_fd); + throw fd_logic_error("Attempt to release an invalid fd"); + } + +private: + static constexpr int _invalid_fd = -1; + + int _fd = _invalid_fd; +}; + +// Create a copy of this fd by calling dup(). +export auto dup(fd const &self) -> std::expected<fd, std::error_code> +{ + auto thisfd = self.get(); + + auto const newfd = ::dup(thisfd); + if (newfd != -1) + return {newfd}; + + return std::unexpected(std::make_error_code(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, +// there are two potential cases that can cause problems: +// +// - dup()ing an fd to itself (a no-op) +// - dup()ing an fd to an fd which is already managed by an fd instance +// +// 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 auto dup(fd const &self, int newfd) + -> std::expected<fd, std::error_code> +{ + auto thisfd = self.get(); + + auto const ret = ::dup2(thisfd, newfd); + if (ret != -1) + return {newfd}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +// Create a copy of this fd by calling dup(). +export auto raw_dup(fd const &self) -> std::expected<int, std::error_code> +{ + auto thisfd = self.get(); + + auto const newfd = ::dup(thisfd); + if (newfd != -1) + return {newfd}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +// Create a copy of this fd by calling dup2(). +export auto raw_dup(fd const &self, int newfd) + -> std::expected<int, std::error_code> +{ + auto thisfd = self.get(); + + auto const ret = ::dup2(thisfd, newfd); + if (ret != -1) + return {newfd}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +// Return the fnctl flags for this fd. +export auto getflags(fd const &self) -> std::expected<int, std::error_code> +{ + auto const flags = ::fcntl(self.get(), F_GETFL); + if (flags != -1) + return {flags}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +// Replace the fnctl flags for this fd. +export auto replaceflags(fd &self, int newflags) + -> std::expected<void, std::error_code> +{ + auto const ret = ::fcntl(self.get(), F_SETFL, newflags); + if (ret == 0) + return {}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +// Add bits to the fcntl flags for this fd. Returns the new flags. +export auto setflags(fd &self, int newflags) + -> std::expected<int, std::error_code> +{ + auto flags = getflags(self); + if (!flags) + return flags; + + *flags |= newflags; + auto const ret = replaceflags(self, *flags); + if (!ret) + return std::unexpected(ret.error()); + + return {*flags}; +} + +// Remove bits from the fcntl flags for this fd. Returns the new flags. +export auto clearflags(fd &self, int clrflags) + -> std::expected<int, std::error_code> +{ + auto flags = getflags(self); + if (!flags) + return flags; + + *flags &= ~clrflags; + auto const ret = replaceflags(self, *flags); + if (!ret) + return std::unexpected(ret.error()); + + return {*flags}; +} + +// Return the fd flags for this fd. +export auto getfdflags(fd const &self) -> std::expected<int, std::error_code> +{ + auto const flags = ::fcntl(self.get(), F_GETFD); + if (flags != -1) + return {flags}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +// Replace the fd flags for this fd. +export auto replacefdflags(fd &self, int newflags) + -> std::expected<void, std::error_code> +{ + auto const ret = ::fcntl(self.get(), F_SETFD, newflags); + if (ret != -1) + return {}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +// Add bits to the fd flags for this fd. Returns the new flags. +export auto setfdflags(fd &self, int newflags) + -> std::expected<int, std::error_code> +{ + auto flags = getfdflags(self); + if (!flags) + return flags; + + *flags |= newflags; + auto const ret = replacefdflags(self, *flags); + if (!ret) + return std::unexpected(ret.error()); + + return {*flags}; +} + +// Remove bits from the fd flags for this fd. Returns the new flags. +export auto clearfdflags(fd &self, int clrflags) + -> std::expected<int, std::error_code> +{ + auto flags = getfdflags(self); + if (!flags) + return flags; + + *flags &= ~clrflags; + auto ret = replacefdflags(self, *flags); + if (!ret) + return std::unexpected(ret.error()); + + return {*flags}; +} + +// Create two fds by calling pipe() and return them. +export auto pipe() -> std::expected<std::pair<fd, fd>, std::error_code> { + auto fds = std::array<int, 2>{}; + + if (auto const ret = ::pipe(fds.data()); ret != 0) + return std::unexpected(std::make_error_code(std::errc(errno))); + + return {{fd(fds[0]), fd(fds[1])}}; +} + +/* + * Write data to a file descriptor from the provided buffer. Returns the + * number of bytes (not objects) written. Incomplete writes may cause a + * partial object to be written. + */ +export auto write(fd &file, std::ranges::contiguous_range auto &&range) + -> std::expected<std::size_t, std::error_code> +{ + auto const ret = ::write(file.get(), std::ranges::data(range), + std::ranges::size(range)); + if (ret >= 0) + return ret; + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +/* + * Read data from a file descriptor into the provided buffer. Returns the + * number of bytes (not objects) read. Incomplete reads may cause a partial + * object to be read. + */ +export auto read(fd &file, std::ranges::contiguous_range auto &&range) + -> std::expected<std::size_t, std::error_code> +{ + auto const ret = ::read(file.get(), std::ranges::data(range), + std::ranges::size(range)); + if (ret >= 0) + return ret; + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +} // namespace nihil |
