/* * This source code is released into the public domain. */ module; #include #include #include #include #include #include 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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::error_code> { auto fds = std::array{}; 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 { 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 { 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