fd: Implement managed file descriptor support
Change-Id: I0c5c438aa2c31ae52e115951b3fb1e85df182fd1
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/meson_options.txt b/meson_options.txt
index 86bc7fc..2914f5c 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,2 +1,3 @@
+option('fd', type: 'feature', description: 'Managed file descriptor support')
option('tests', type: 'feature', description: 'Build tests')
option('examples', type: 'boolean', value: true, description: 'Build examples')
diff --git a/src/meson.build b/src/meson.build
index 09d8b0e..dca95ce 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -36,10 +36,36 @@
span_dep,
]
+has_fd = false
+if not get_option('fd').disabled() and has_span
+ has_fd = true
+
+ fd_srcs = [
+ 'stdplus/fd/create.cpp',
+ 'stdplus/fd/dupable.cpp',
+ 'stdplus/fd/impl.cpp',
+ 'stdplus/fd/managed.cpp',
+ 'stdplus/fd/ops.cpp',
+ ]
+
+ install_headers(
+ 'stdplus/fd/create.hpp',
+ 'stdplus/fd/dupable.hpp',
+ 'stdplus/fd/gmock.hpp',
+ 'stdplus/fd/impl.hpp',
+ 'stdplus/fd/intf.hpp',
+ 'stdplus/fd/managed.hpp',
+ 'stdplus/fd/ops.hpp',
+ subdir: 'stdplus/fd')
+elif get_option('fd').enabled()
+ error('File descriptor support required')
+endif
+
stdplus_lib = library(
'stdplus',
'stdplus/exception.cpp',
'stdplus/signal.cpp',
+ fd_srcs,
include_directories: stdplus_headers,
implicit_include_directories: false,
dependencies: stdplus_deps,
@@ -66,6 +92,7 @@
install_headers(
'stdplus/exception.hpp',
+ 'stdplus/flags.hpp',
'stdplus/raw.hpp',
'stdplus/signal.hpp',
'stdplus/types.hpp',
diff --git a/src/stdplus/fd/create.cpp b/src/stdplus/fd/create.cpp
new file mode 100644
index 0000000..2240a98
--- /dev/null
+++ b/src/stdplus/fd/create.cpp
@@ -0,0 +1,28 @@
+#include <fcntl.h>
+#include <fmt/format.h>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/util/cexec.hpp>
+#include <sys/socket.h>
+
+namespace stdplus
+{
+namespace fd
+{
+
+DupableFd open(const char* pathname, OpenFlags flags, mode_t mode)
+{
+ return DupableFd(
+ CHECK_ERRNO(::open(pathname, static_cast<int>(flags), mode),
+ fmt::format("open `{}`", pathname)));
+}
+
+DupableFd socket(SocketDomain domain, SocketType type, SocketProto protocol)
+{
+ return DupableFd(
+ CHECK_ERRNO(::socket(static_cast<int>(domain), static_cast<int>(type),
+ static_cast<int>(protocol)),
+ "socket"));
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/src/stdplus/fd/create.hpp b/src/stdplus/fd/create.hpp
new file mode 100644
index 0000000..574b4f4
--- /dev/null
+++ b/src/stdplus/fd/create.hpp
@@ -0,0 +1,76 @@
+#pragma once
+#include <fcntl.h>
+#include <stdplus/fd/dupable.hpp>
+#include <stdplus/flags.hpp>
+#include <string>
+
+namespace stdplus
+{
+namespace fd
+{
+
+enum class OpenAccess : int
+{
+ ReadOnly = O_RDONLY,
+ WriteOnly = O_WRONLY,
+ ReadWrite = O_RDWR,
+};
+
+enum class OpenFlag : int
+{
+ Append = O_APPEND,
+ Async = O_ASYNC,
+ CloseOnExec = O_CLOEXEC,
+ Create = O_CREAT,
+ Direct = O_DIRECT,
+ Directory = O_DIRECTORY,
+ Dsync = O_DSYNC,
+ EnsureCreate = O_EXCL,
+ LargeFile = O_LARGEFILE,
+ NoAtime = O_NOATIME,
+ NoCtty = O_NOCTTY,
+ NoFollow = O_NOFOLLOW,
+ NonBlock = O_NONBLOCK,
+ Path = O_PATH,
+ Sync = O_SYNC,
+ TmpFile = O_TMPFILE,
+ Trunc = O_TRUNC,
+};
+
+class OpenFlags : public BitFlags<int, OpenFlag>
+{
+ public:
+ inline OpenFlags(OpenAccess access) :
+ BitFlags<int, OpenFlag>(static_cast<int>(access))
+ {
+ }
+
+ inline OpenFlags(BitFlags<int, OpenFlag> flags) :
+ BitFlags<int, OpenFlag>(flags)
+ {
+ }
+};
+
+DupableFd open(const char* pathname, OpenFlags flags, mode_t mode = 0);
+inline DupableFd open(const std::string& pathname, OpenFlags flags,
+ mode_t mode = 0)
+{
+ return open(pathname.c_str(), flags, mode);
+}
+
+enum class SocketDomain : int
+{
+};
+
+enum class SocketType : int
+{
+};
+
+enum class SocketProto : int
+{
+};
+
+DupableFd socket(SocketDomain domain, SocketType type, SocketProto protocol);
+
+} // namespace fd
+} // namespace stdplus
diff --git a/src/stdplus/fd/dupable.cpp b/src/stdplus/fd/dupable.cpp
new file mode 100644
index 0000000..96e347f
--- /dev/null
+++ b/src/stdplus/fd/dupable.cpp
@@ -0,0 +1,41 @@
+#include <fcntl.h>
+#include <stdplus/fd/dupable.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/util/cexec.hpp>
+#include <utility>
+
+namespace stdplus
+{
+namespace fd
+{
+namespace detail
+{
+
+int ref(const int& fd)
+{
+ return CHECK_ERRNO(fcntl(fd, F_DUPFD_CLOEXEC, fd), "fcntl dupfd_cloexec");
+}
+
+} // namespace detail
+
+DupableFd::DupableFd(const int& fd) : handle(fd)
+{
+}
+
+DupableFd::DupableFd(int&& fd) : handle(std::move(fd))
+{
+ fd::setFdFlags(*this, fd::getFdFlags(*this).set(fd::FdFlag::CloseOnExec));
+}
+
+int DupableFd::release()
+{
+ return handle.release();
+}
+
+int DupableFd::get() const
+{
+ return handle.value();
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/src/stdplus/fd/dupable.hpp b/src/stdplus/fd/dupable.hpp
new file mode 100644
index 0000000..b008ce0
--- /dev/null
+++ b/src/stdplus/fd/dupable.hpp
@@ -0,0 +1,59 @@
+#pragma once
+#include <stdplus/fd/impl.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/handle/copyable.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+namespace detail
+{
+
+int ref(const int& fd);
+
+using DupableFdHandle = Copyable<int>::Handle<drop, ref>;
+
+} // namespace detail
+
+/** @class DupableFd
+ * @brief Holds references to file descriptors which can be dup'd
+ * @details Provides RAII semantics for file descriptors
+ */
+class DupableFd : public FdImpl
+{
+ public:
+ /** @brief Duplicates and holds a file descriptor
+ * Does not automatically close the input descriptor
+ *
+ * @param[in] fd - File descriptor being duplicated
+ * @throws std::system_error for underlying syscall failures
+ */
+ explicit DupableFd(const int& fd);
+
+ /** @brief Holds the input file descriptor
+ * Becomes the sole owner of the file descriptor
+ *
+ * @param[in] fd - File descriptor to hold
+ */
+ explicit DupableFd(int&& fd);
+
+ /** @brief Unmanages the file descriptor and returns the value with
+ * ownership to the caller.
+ *
+ * @return The file descriptor number
+ */
+ [[nodiscard]] int release();
+
+ int get() const override;
+
+ private:
+ friend class ManagedFd;
+ detail::DupableFdHandle handle;
+};
+
+} // namespace fd
+
+using fd::DupableFd;
+
+} // namespace stdplus
diff --git a/src/stdplus/fd/gmock.hpp b/src/stdplus/fd/gmock.hpp
new file mode 100644
index 0000000..bd57b51
--- /dev/null
+++ b/src/stdplus/fd/gmock.hpp
@@ -0,0 +1,39 @@
+#pragma once
+#include <gmock/gmock.h>
+#include <stdplus/fd/intf.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+class FdMock : public Fd
+{
+ public:
+ MOCK_METHOD(span<std::byte>, read, (span<std::byte> buf), (override));
+ MOCK_METHOD(span<std::byte>, recv, (span<std::byte> buf, RecvFlags flags),
+ (override));
+ MOCK_METHOD(span<const std::byte>, write, (span<const std::byte> data),
+ (override));
+ MOCK_METHOD(span<const std::byte>, send,
+ (span<const std::byte> data, SendFlags flags), (override));
+ MOCK_METHOD(size_t, lseek, (off_t offset, Whence whence), (override));
+ MOCK_METHOD(void, truncate, (off_t size), (override));
+ MOCK_METHOD(void, bind, (span<const std::byte> sockaddr), (override));
+ MOCK_METHOD(void, setsockopt,
+ (SockLevel level, SockOpt optname, span<const std::byte> opt),
+ (override));
+ MOCK_METHOD(int, ioctl, (unsigned long id, void* data), (override));
+ MOCK_METHOD(int, constIoctl, (unsigned long id, void* data),
+ (const, override));
+ MOCK_METHOD(void, fcntlSetfd, (FdFlags flags), (override));
+ MOCK_METHOD(FdFlags, fcntlGetfd, (), (const, override));
+ MOCK_METHOD(void, fcntlSetfl, (FileFlags flags), (override));
+ MOCK_METHOD(FileFlags, fcntlGetfl, (), (const, override));
+};
+
+} // namespace fd
+
+using fd::FdMock;
+
+} // namespace stdplus
diff --git a/src/stdplus/fd/impl.cpp b/src/stdplus/fd/impl.cpp
new file mode 100644
index 0000000..511bda4
--- /dev/null
+++ b/src/stdplus/fd/impl.cpp
@@ -0,0 +1,160 @@
+#include <fcntl.h>
+#include <fmt/format.h>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/impl.hpp>
+#include <stdplus/util/cexec.hpp>
+#include <string_view>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+namespace stdplus
+{
+namespace fd
+{
+
+using namespace std::literals::string_view_literals;
+
+span<std::byte> FdImpl::read(span<std::byte> buf)
+{
+ ssize_t amt = ::read(get(), buf.data(), buf.size());
+ if (amt == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ {
+ return {};
+ }
+ throw util::makeSystemError(errno, "read");
+ }
+ else if (amt == 0)
+ {
+ throw exception::Eof("read");
+ }
+ return buf.subspan(0, amt);
+}
+
+span<std::byte> FdImpl::recv(span<std::byte> buf, RecvFlags flags)
+{
+ ssize_t amt =
+ ::recv(get(), buf.data(), buf.size(), static_cast<int>(flags));
+ if (amt == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ {
+ return {};
+ }
+ throw util::makeSystemError(errno, "recv");
+ }
+ else if (amt == 0)
+ {
+ throw exception::Eof("recv");
+ }
+ return buf.subspan(0, amt);
+}
+
+span<const std::byte> FdImpl::write(span<const std::byte> data)
+{
+ ssize_t amt = ::write(get(), data.data(), data.size());
+ if (amt == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ {
+ return {};
+ }
+ throw util::makeSystemError(errno, "write");
+ }
+ return data.subspan(0, amt);
+}
+
+span<const std::byte> FdImpl::send(span<const std::byte> data, SendFlags flags)
+{
+ ssize_t amt =
+ ::send(get(), data.data(), data.size(), static_cast<int>(flags));
+ if (amt == -1)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ {
+ return {};
+ }
+ throw util::makeSystemError(errno, "send");
+ }
+ return data.subspan(0, amt);
+}
+
+static std::string_view whenceStr(Whence whence)
+{
+ switch (whence)
+ {
+ case Whence::Set:
+ return "set"sv;
+ case Whence::Cur:
+ return "cur"sv;
+ case Whence::End:
+ return "end"sv;
+ default:
+ return "Unknown whence"sv;
+ }
+}
+
+size_t FdImpl::lseek(off_t offset, Whence whence)
+{
+ return CHECK_ERRNO(::lseek(get(), offset, static_cast<int>(whence)),
+ fmt::format("lseek {}B {}", offset, whenceStr(whence)));
+}
+
+void FdImpl::truncate(off_t size)
+{
+ CHECK_ERRNO(::ftruncate(get(), size), fmt::format("ftruncate {}B", size));
+}
+
+void FdImpl::bind(span<const std::byte> sockaddr)
+{
+ CHECK_ERRNO(
+ ::bind(get(), reinterpret_cast<const struct sockaddr*>(sockaddr.data()),
+ sockaddr.size()),
+ "bind");
+}
+
+void FdImpl::setsockopt(SockLevel level, SockOpt optname,
+ span<const std::byte> opt)
+{
+ CHECK_ERRNO(::setsockopt(get(), static_cast<int>(level),
+ static_cast<int>(optname), opt.data(), opt.size()),
+ "setsockopt");
+}
+
+int FdImpl::ioctl(unsigned long id, void* data)
+{
+ return constIoctl(id, data);
+}
+
+int FdImpl::constIoctl(unsigned long id, void* data) const
+{
+ return CHECK_ERRNO(::ioctl(get(), id, data),
+ fmt::format("ioctl {:#x}", id));
+}
+
+void FdImpl::fcntlSetfd(FdFlags flags)
+{
+ CHECK_ERRNO(::fcntl(get(), F_SETFD, static_cast<int>(flags)),
+ "fcntl setfd");
+}
+
+FdFlags FdImpl::fcntlGetfd() const
+{
+ return FdFlags(CHECK_ERRNO(::fcntl(get(), F_GETFD), "fcntl getfd"));
+}
+
+void FdImpl::fcntlSetfl(FileFlags flags)
+{
+ CHECK_ERRNO(::fcntl(get(), F_SETFL, static_cast<int>(flags)),
+ "fcntl setfl");
+}
+
+FileFlags FdImpl::fcntlGetfl() const
+{
+ return FileFlags(CHECK_ERRNO(::fcntl(get(), F_GETFL), "fcntl getfl"));
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/src/stdplus/fd/impl.hpp b/src/stdplus/fd/impl.hpp
new file mode 100644
index 0000000..b321dfb
--- /dev/null
+++ b/src/stdplus/fd/impl.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include <stdplus/fd/intf.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+class FdImpl : public Fd
+{
+ public:
+ virtual int get() const = 0;
+
+ span<std::byte> read(span<std::byte> buf) override;
+ span<std::byte> recv(span<std::byte> buf, RecvFlags flags) override;
+ span<const std::byte> write(span<const std::byte> data) override;
+ span<const std::byte> send(span<const std::byte> data,
+ SendFlags flags) override;
+ size_t lseek(off_t offset, Whence whence) override;
+ void truncate(off_t size) override;
+ void bind(span<const std::byte> sockaddr) override;
+ void setsockopt(SockLevel level, SockOpt optname,
+ span<const std::byte> opt) override;
+ int ioctl(unsigned long id, void* data) override;
+ int constIoctl(unsigned long id, void* data) const override;
+ void fcntlSetfd(FdFlags flags) override;
+ FdFlags fcntlGetfd() const override;
+ void fcntlSetfl(FileFlags flags) override;
+ FileFlags fcntlGetfl() const override;
+};
+
+} // namespace fd
+
+using fd::FdImpl;
+
+} // namespace stdplus
diff --git a/src/stdplus/fd/intf.hpp b/src/stdplus/fd/intf.hpp
new file mode 100644
index 0000000..aac85a2
--- /dev/null
+++ b/src/stdplus/fd/intf.hpp
@@ -0,0 +1,108 @@
+#pragma once
+#include <cstddef>
+#include <fcntl.h>
+#include <stdplus/flags.hpp>
+#include <stdplus/types.hpp>
+#include <sys/socket.h>
+
+namespace stdplus
+{
+namespace fd
+{
+
+enum class RecvFlag : int
+{
+ DontWait = MSG_DONTWAIT,
+ ErrQueue = MSG_ERRQUEUE,
+ OutOfBounds = MSG_OOB,
+ Peek = MSG_PEEK,
+ Trunc = MSG_TRUNC,
+ WaitAll = MSG_WAITALL,
+};
+using RecvFlags = BitFlags<int, RecvFlag>;
+
+enum class SendFlag : int
+{
+ Confirm = MSG_CONFIRM,
+ DontRoute = MSG_DONTROUTE,
+ DontWait = MSG_DONTWAIT,
+ EndOfRecord = MSG_EOR,
+ More = MSG_MORE,
+ NoSignal = MSG_NOSIGNAL,
+ OutOfBounds = MSG_OOB,
+};
+using SendFlags = BitFlags<int, SendFlag>;
+
+enum class Whence : int
+{
+ Set = SEEK_SET,
+ Cur = SEEK_CUR,
+ End = SEEK_END,
+};
+
+enum class SockLevel : int
+{
+ Socket = SOL_SOCKET,
+};
+
+enum class SockOpt : int
+{
+ Debug = SO_DEBUG,
+ Broadcast = SO_BROADCAST,
+ ReuseAddr = SO_REUSEADDR,
+ KeepAlive = SO_KEEPALIVE,
+ Linger = SO_LINGER,
+ OOBInline = SO_OOBINLINE,
+ SendBuf = SO_SNDBUF,
+ RecvBuf = SO_RCVBUF,
+ DontRoute = SO_DONTROUTE,
+ RecvLowWait = SO_RCVLOWAT,
+ RecvTimeout = SO_RCVTIMEO,
+ SendLowWait = SO_SNDLOWAT,
+ SendTimeout = SO_SNDTIMEO,
+};
+
+enum class FdFlag : int
+{
+ CloseOnExec = FD_CLOEXEC,
+};
+using FdFlags = BitFlags<int, FdFlag>;
+
+enum class FileFlag : int
+{
+ Append = O_APPEND,
+ Async = O_ASYNC,
+ Direct = O_DIRECT,
+ NoAtime = O_NOATIME,
+ NonBlock = O_NONBLOCK,
+};
+using FileFlags = BitFlags<int, FileFlag>;
+
+class Fd
+{
+ public:
+ virtual ~Fd() = default;
+
+ virtual span<std::byte> read(span<std::byte> buf) = 0;
+ virtual span<std::byte> recv(span<std::byte> buf, RecvFlags flags) = 0;
+ virtual span<const std::byte> write(span<const std::byte> data) = 0;
+ virtual span<const std::byte> send(span<const std::byte> data,
+ SendFlags flags) = 0;
+ virtual size_t lseek(off_t offset, Whence whence) = 0;
+ virtual void truncate(off_t size) = 0;
+ virtual void bind(span<const std::byte> sockaddr) = 0;
+ virtual void setsockopt(SockLevel level, SockOpt optname,
+ span<const std::byte> opt) = 0;
+ virtual int ioctl(unsigned long id, void* data) = 0;
+ virtual int constIoctl(unsigned long id, void* data) const = 0;
+ virtual void fcntlSetfd(FdFlags flags) = 0;
+ virtual FdFlags fcntlGetfd() const = 0;
+ virtual void fcntlSetfl(FileFlags flags) = 0;
+ virtual FileFlags fcntlGetfl() const = 0;
+};
+
+} // namespace fd
+
+using fd::Fd;
+
+} // namespace stdplus
diff --git a/src/stdplus/fd/managed.cpp b/src/stdplus/fd/managed.cpp
new file mode 100644
index 0000000..4611143
--- /dev/null
+++ b/src/stdplus/fd/managed.cpp
@@ -0,0 +1,59 @@
+#include <fmt/format.h>
+#include <stdplus/fd/dupable.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/util/cexec.hpp>
+#include <unistd.h>
+#include <utility>
+
+namespace stdplus
+{
+namespace fd
+{
+namespace detail
+{
+
+void drop(int&& fd)
+{
+ CHECK_ERRNO(close(fd), "close");
+}
+
+} // namespace detail
+
+ManagedFd::ManagedFd(int&& fd) : handle(std::move(fd))
+{
+ fd::setFdFlags(*this, fd::getFdFlags(*this).set(fd::FdFlag::CloseOnExec));
+}
+
+ManagedFd::ManagedFd(DupableFd&& other) noexcept :
+ handle(static_cast<detail::ManagedFdHandle&&>(other.handle))
+{
+}
+
+ManagedFd::ManagedFd(const DupableFd& other) : ManagedFd(DupableFd(other))
+{
+}
+
+ManagedFd& ManagedFd::operator=(DupableFd&& other) noexcept
+{
+ handle = static_cast<detail::ManagedFdHandle&&>(other.handle);
+ return *this;
+}
+
+ManagedFd& ManagedFd::operator=(const DupableFd& other)
+{
+ return *this = DupableFd(other);
+}
+
+int ManagedFd::release()
+{
+ return handle.release();
+}
+
+int ManagedFd::get() const
+{
+ return handle.value();
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/src/stdplus/fd/managed.hpp b/src/stdplus/fd/managed.hpp
new file mode 100644
index 0000000..3b9dde3
--- /dev/null
+++ b/src/stdplus/fd/managed.hpp
@@ -0,0 +1,60 @@
+#pragma once
+#include <stdplus/fd/impl.hpp>
+#include <stdplus/handle/managed.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+namespace detail
+{
+
+/** @brief Closes the file descriptor when dropping an Fd handle
+ *
+ * @param[in] fd - File descriptor to close
+ */
+void drop(int&& fd);
+
+using ManagedFdHandle = Managed<int>::Handle<drop>;
+
+} // namespace detail
+
+class DupableFd;
+
+/** @class ManagedFd
+ * @brief Holds references to unique, non-dupable file descriptors
+ * @details Provides RAII semantics for file descriptors
+ */
+class ManagedFd : public FdImpl
+{
+ public:
+ /** @brief Holds the input file descriptor
+ * Becomes the sole owner of the file descriptor
+ *
+ * @param[in] fd - File descriptor to hold
+ */
+ explicit ManagedFd(int&& fd);
+
+ ManagedFd(DupableFd&& other) noexcept;
+ ManagedFd(const DupableFd& other);
+ ManagedFd& operator=(DupableFd&& other) noexcept;
+ ManagedFd& operator=(const DupableFd& other);
+
+ /** @brief Unmanages the file descriptor and returns the value with
+ * ownership to the caller.
+ *
+ * @return The file descriptor number
+ */
+ [[nodiscard]] int release();
+
+ int get() const override;
+
+ private:
+ detail::ManagedFdHandle handle;
+};
+
+} // namespace fd
+
+using fd::ManagedFd;
+
+} // namespace stdplus
diff --git a/src/stdplus/fd/ops.cpp b/src/stdplus/fd/ops.cpp
new file mode 100644
index 0000000..d96dbb5
--- /dev/null
+++ b/src/stdplus/fd/ops.cpp
@@ -0,0 +1,93 @@
+#include <fmt/format.h>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <utility>
+
+namespace stdplus
+{
+namespace fd
+{
+namespace detail
+{
+
+template <typename Fun, typename Byte, typename... Args>
+static void opExact(const char* name, Fun&& fun, Fd& fd, span<Byte> data,
+ Args&&... args)
+{
+ while (data.size() > 0)
+ {
+ auto ret = (fd.*fun)(data, std::forward<Args>(args)...);
+ if (ret.size() == 0)
+ {
+ throw exception::WouldBlock(
+ fmt::format("{} missing {}B", name, data.size()));
+ }
+ data = data.subspan(ret.size());
+ }
+}
+
+void readExact(Fd& fd, span<std::byte> data)
+{
+ opExact("readExact", &Fd::read, fd, data);
+}
+
+void recvExact(Fd& fd, span<std::byte> data, RecvFlags flags)
+{
+ opExact("recvExact", &Fd::recv, fd, data, flags);
+}
+
+void writeExact(Fd& fd, span<const std::byte> data)
+{
+ opExact("writeExact", &Fd::write, fd, data);
+}
+
+void sendExact(Fd& fd, span<const std::byte> data, SendFlags flags)
+{
+ opExact("sendExact", &Fd::send, fd, data, flags);
+}
+
+template <typename Fun, typename Byte, typename... Args>
+static span<Byte> opAligned(const char* name, Fun&& fun, Fd& fd, size_t align,
+ span<Byte> data, Args&&... args)
+{
+ span<Byte> ret;
+ do
+ {
+ auto r =
+ (fd.*fun)(data.subspan(ret.size()), std::forward<Args>(args)...);
+ if (ret.size() != 0 && r.size() == 0)
+ {
+ throw exception::WouldBlock(
+ fmt::format("{} is {}B/{}B", name, ret.size() % align, align));
+ }
+ ret = data.subspan(0, ret.size() + r.size());
+ } while (ret.size() % align != 0);
+ return ret;
+}
+
+span<std::byte> readAligned(Fd& fd, size_t align, span<std::byte> buf)
+{
+ return opAligned("readAligned", &Fd::read, fd, align, buf);
+}
+
+span<std::byte> recvAligned(Fd& fd, size_t align, span<std::byte> buf,
+ RecvFlags flags)
+{
+ return opAligned("recvAligned", &Fd::recv, fd, align, buf, flags);
+}
+
+span<const std::byte> writeAligned(Fd& fd, size_t align,
+ span<const std::byte> data)
+{
+ return opAligned("writeAligned", &Fd::write, fd, align, data);
+}
+
+span<const std::byte> sendAligned(Fd& fd, size_t align,
+ span<const std::byte> data, SendFlags flags)
+{
+ return opAligned("sendAligned", &Fd::send, fd, align, data, flags);
+}
+
+} // namespace detail
+} // namespace fd
+} // namespace stdplus
diff --git a/src/stdplus/fd/ops.hpp b/src/stdplus/fd/ops.hpp
new file mode 100644
index 0000000..317eb30
--- /dev/null
+++ b/src/stdplus/fd/ops.hpp
@@ -0,0 +1,145 @@
+#pragma once
+#include <stdplus/fd/intf.hpp>
+#include <stdplus/raw.hpp>
+#include <stdplus/types.hpp>
+#include <utility>
+
+namespace stdplus
+{
+namespace fd
+{
+namespace detail
+{
+
+void readExact(Fd& fd, span<std::byte> data);
+void recvExact(Fd& fd, span<std::byte> data, RecvFlags flags);
+void writeExact(Fd& fd, span<const std::byte> data);
+void sendExact(Fd& fd, span<const std::byte> data, SendFlags flags);
+
+span<std::byte> readAligned(Fd& fd, size_t align, span<std::byte> buf);
+span<std::byte> recvAligned(Fd& fd, size_t align, span<std::byte> buf,
+ RecvFlags flags);
+span<const std::byte> writeAligned(Fd& fd, size_t align,
+ span<const std::byte> data);
+span<const std::byte> sendAligned(Fd& fd, size_t align,
+ span<const std::byte> data, SendFlags flags);
+
+template <typename Fun, typename Container, typename... Args>
+auto alignedOp(Fun&& fun, Fd& fd, Container&& c, Args&&... args)
+{
+ using Data = raw::detail::dataType<Container>;
+ auto ret = fun(fd, sizeof(Data), raw::asSpan<std::byte>(c),
+ std::forward<Args>(args)...);
+ return span<Data>(std::begin(c), ret.size() / sizeof(Data));
+}
+
+} // namespace detail
+
+template <typename Container>
+inline auto read(Fd& fd, Container&& c)
+{
+ return detail::alignedOp(detail::readAligned, fd,
+ std::forward<Container>(c));
+}
+
+template <typename Container>
+inline auto recv(Fd& fd, Container&& c, RecvFlags flags)
+{
+ return detail::alignedOp(detail::recvAligned, fd,
+ std::forward<Container>(c), flags);
+}
+
+template <typename Container>
+inline auto write(Fd& fd, Container&& c)
+{
+ return detail::alignedOp(detail::writeAligned, fd,
+ std::forward<Container>(c));
+}
+
+template <typename Container>
+inline auto send(Fd& fd, Container&& c, SendFlags flags)
+{
+ return detail::alignedOp(detail::sendAligned, fd,
+ std::forward<Container>(c), flags);
+}
+
+template <typename T>
+inline void readExact(Fd& fd, T&& t)
+{
+ detail::readExact(fd, raw::asSpan<std::byte>(t));
+}
+
+template <typename T>
+inline void recvExact(Fd& fd, T&& t, RecvFlags flags)
+{
+ detail::recvExact(fd, raw::asSpan<std::byte>(t), flags);
+}
+
+template <typename T>
+inline void writeExact(Fd& fd, T&& t)
+{
+ detail::writeExact(fd, raw::asSpan<std::byte>(t));
+}
+
+template <typename T>
+inline void sendExact(Fd& fd, T&& t, SendFlags flags)
+{
+ detail::sendExact(fd, raw::asSpan<std::byte>(t), flags);
+}
+
+inline size_t lseek(Fd& fd, off_t offset, Whence whence)
+{
+ return fd.lseek(offset, whence);
+}
+
+inline void truncate(Fd& fd, off_t size)
+{
+ return fd.truncate(size);
+}
+
+template <typename SockAddr>
+inline void bind(Fd& fd, SockAddr&& sockaddr)
+{
+ return fd.bind(raw::asSpan<std::byte>(sockaddr));
+}
+
+template <typename Opt>
+inline void setsockopt(Fd& fd, SockLevel level, SockOpt optname, Opt&& opt)
+{
+ return fd.setsockopt(level, optname, raw::asSpan<std::byte>(opt));
+}
+
+template <typename Data>
+inline int constIoctl(const Fd& fd, unsigned long id, Data&& data)
+{
+ return fd.constIoctl(id, raw::asSpan<std::byte>(data).data());
+}
+
+template <typename Data>
+inline int ioctl(Fd& fd, unsigned long id, Data&& data)
+{
+ return fd.ioctl(id, raw::asSpan<std::byte>(data).data());
+}
+
+inline FdFlags getFdFlags(const Fd& fd)
+{
+ return fd.fcntlGetfd();
+}
+
+inline void setFdFlags(Fd& fd, FdFlags flags)
+{
+ return fd.fcntlSetfd(flags);
+}
+
+inline FileFlags getFileFlags(const Fd& fd)
+{
+ return fd.fcntlGetfl();
+}
+
+inline void setFileFlags(Fd& fd, FileFlags flags)
+{
+ return fd.fcntlSetfl(flags);
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/src/stdplus/flags.hpp b/src/stdplus/flags.hpp
new file mode 100644
index 0000000..047c776
--- /dev/null
+++ b/src/stdplus/flags.hpp
@@ -0,0 +1,46 @@
+#pragma once
+#include <utility>
+
+namespace stdplus
+{
+
+template <typename Int, typename Flag = Int>
+class BitFlags
+{
+ public:
+ inline explicit BitFlags(Int val = 0) noexcept : val(val)
+ {
+ }
+
+ inline BitFlags& set(Flag flag) & noexcept
+ {
+ val |= static_cast<Int>(flag);
+ return *this;
+ }
+ inline BitFlags&& set(Flag flag) && noexcept
+ {
+ val |= static_cast<Int>(flag);
+ return std::move(*this);
+ }
+
+ inline BitFlags& unset(Flag flag) & noexcept
+ {
+ val &= ~static_cast<Int>(flag);
+ return *this;
+ }
+ inline BitFlags&& unset(Flag flag) && noexcept
+ {
+ val &= ~static_cast<Int>(flag);
+ return std::move(*this);
+ }
+
+ explicit inline operator Int() const noexcept
+ {
+ return val;
+ }
+
+ private:
+ Int val;
+};
+
+} // namespace stdplus
diff --git a/test/fd/dupable.cpp b/test/fd/dupable.cpp
new file mode 100644
index 0000000..2b34f9f
--- /dev/null
+++ b/test/fd/dupable.cpp
@@ -0,0 +1,14 @@
+#include <gtest/gtest.h>
+#include <stdplus/fd/dupable.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+TEST(DupableFd, Noop)
+{
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/test/fd/impl.cpp b/test/fd/impl.cpp
new file mode 100644
index 0000000..f2336f5
--- /dev/null
+++ b/test/fd/impl.cpp
@@ -0,0 +1,14 @@
+#include <gtest/gtest.h>
+#include <stdplus/fd/impl.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+TEST(DupableFd, Noop)
+{
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/test/fd/intf.cpp b/test/fd/intf.cpp
new file mode 100644
index 0000000..d4cc8c2
--- /dev/null
+++ b/test/fd/intf.cpp
@@ -0,0 +1,4 @@
+#include <stdplus/fd/intf.hpp>
+int main(int, char*[])
+{
+}
diff --git a/test/fd/managed.cpp b/test/fd/managed.cpp
new file mode 100644
index 0000000..003c4e3
--- /dev/null
+++ b/test/fd/managed.cpp
@@ -0,0 +1,14 @@
+#include <gtest/gtest.h>
+#include <stdplus/fd/managed.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+TEST(DupableFd, Noop)
+{
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/test/fd/mock.cpp b/test/fd/mock.cpp
new file mode 100644
index 0000000..0cc6dfd
--- /dev/null
+++ b/test/fd/mock.cpp
@@ -0,0 +1,12 @@
+#include <gtest/gtest.h>
+#include <stdplus/fd/gmock.hpp>
+
+namespace stdplus
+{
+
+TEST(Mock, Instantiate)
+{
+ FdMock fd;
+}
+
+} // namespace stdplus
diff --git a/test/fd/ops.cpp b/test/fd/ops.cpp
new file mode 100644
index 0000000..2d52101
--- /dev/null
+++ b/test/fd/ops.cpp
@@ -0,0 +1,17 @@
+#include <gtest/gtest.h>
+#include <stdplus/fd/ops.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+TEST(Flags, Flags)
+{
+ FdFlags f = FdFlags(0).set(FdFlag::CloseOnExec).unset(FdFlag::CloseOnExec);
+ f.set(FdFlag::CloseOnExec).unset(FdFlag::CloseOnExec);
+ EXPECT_EQ(0, static_cast<int>(f));
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index 0de7168..9a9b060 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -27,6 +27,19 @@
'util/string',
]
+if has_fd
+ gtests += [
+ 'fd/dupable',
+ 'fd/managed',
+ 'fd/intf',
+ 'fd/impl',
+ 'fd/mock',
+ 'fd/ops',
+ ]
+else
+ warning('Not testing file descriptor feature')
+endif
+
if gtest.found() and gmock.found()
foreach t : gtests
test(t, executable(t.underscorify(), t + '.cpp',