// This source code is released into the public domain. module; #include #include #include #include #include #include #include #include export module nihil.posix:fd; import nihil.flagset; import nihil.error; import nihil.monad; namespace nihil { // F_{GET,SET}FL flags struct fd_flags_tag { }; export using fd_flags = nihil::flagset; export inline constexpr auto fd_none = fd_flags(); export inline constexpr auto fd_nonblock = fd_flags::mask(); export inline constexpr auto fd_append = fd_flags::mask(); export inline constexpr auto fd_async = fd_flags::mask(); export inline constexpr auto fd_sync = fd_flags::mask(); export inline constexpr auto fd_dsync = fd_flags::mask(); #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; export inline constexpr auto fd_fd_none = fd_fdflags(); export inline constexpr auto fd_fd_cloexec = fd_fdflags::mask(); // fd: a file descriptor. export struct fd final { // Construct an empty (invalid) fd. fd() noexcept = default; // 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() { if (*this) std::ignore = this->close(); } // Move from another fd, leaving the moved-from fd in an invalid state. 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; 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 { return self.m_fileno != invalid_fileno; } // Close the wrapped fd. [[nodiscard]] auto close(this fd &self) -> std::expected { 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 { 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 { 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 buffer) -> std::expected { 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 buffer) -> std::expected, 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; int m_fileno = invalid_fileno; }; // Create a copy of this fd by calling dup(). export [[nodiscard]] auto dup(fd const &self) -> std::expected { 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, // 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 [[nodiscard]] auto dup(fd const &self, int newfd) -> std::expected { 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 { 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 { 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 { 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 &fd) -> std::expected { 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 &fd, fd_flags newflags) -> std::expected { 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 &fd, fd_flags newflags) -> std::expected { 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 &fd, fd_flags clrflags) -> std::expected { 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 &fd) -> std::expected { 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 &fd, fd_fdflags newflags) -> std::expected { 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 &fd, fd_fdflags newflags) -> std::expected { 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 &fd, fd_fdflags clrflags) -> std::expected { 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, error> { auto fds = std::array{}; 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 requires(sizeof(std::ranges::range_value_t) == 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>, error> requires(sizeof(std::ranges::range_value_t) == 1) { auto bspan = as_writable_bytes(std::span(range)); auto rspan = co_await file.read(bspan); co_return std::span(range).subspan(0, rspan.size()); } } // namespace nihil