aboutsummaryrefslogtreecommitdiffstats
path: root/modules/fd.ccm
diff options
context:
space:
mode:
Diffstat (limited to 'modules/fd.ccm')
-rw-r--r--modules/fd.ccm309
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