build: Split up features into separate libraries
This makes it much more obvious when a feature is missing for a user.
Change-Id: Ibb17d7ab1f185a1976a32f48933c01a252450dd1
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include-fd/meson.build b/include-fd/meson.build
new file mode 100644
index 0000000..33d021e
--- /dev/null
+++ b/include-fd/meson.build
@@ -0,0 +1,12 @@
+stdplus_headers += include_directories('.')
+
+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/mmap.hpp',
+ 'stdplus/fd/ops.hpp',
+ subdir: 'stdplus/fd')
diff --git a/include-fd/stdplus/fd/create.hpp b/include-fd/stdplus/fd/create.hpp
new file mode 100644
index 0000000..83aa2ba
--- /dev/null
+++ b/include-fd/stdplus/fd/create.hpp
@@ -0,0 +1,90 @@
+#pragma once
+#include <fcntl.h>
+#include <netinet/ip.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
+{
+ INet = AF_INET,
+ INet6 = AF_INET6,
+ Netlink = AF_NETLINK,
+ Packet = AF_PACKET,
+ UNIX = AF_UNIX,
+};
+
+enum class SocketType : int
+{
+ Datagram = SOCK_DGRAM,
+ Raw = SOCK_RAW,
+ Stream = SOCK_STREAM,
+};
+
+enum class SocketProto : int
+{
+ ICMP = IPPROTO_ICMP,
+ IP = IPPROTO_IP,
+ Raw = IPPROTO_RAW,
+ TCP = IPPROTO_TCP,
+ UDP = IPPROTO_UDP,
+};
+
+DupableFd socket(SocketDomain domain, SocketType type, SocketProto protocol);
+
+} // namespace fd
+} // namespace stdplus
diff --git a/include-fd/stdplus/fd/dupable.hpp b/include-fd/stdplus/fd/dupable.hpp
new file mode 100644
index 0000000..0f810cf
--- /dev/null
+++ b/include-fd/stdplus/fd/dupable.hpp
@@ -0,0 +1,62 @@
+#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 Constructs an empty file descriptor */
+ DupableFd() noexcept;
+
+ /** @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/include-fd/stdplus/fd/gmock.hpp b/include-fd/stdplus/fd/gmock.hpp
new file mode 100644
index 0000000..ca3b2af
--- /dev/null
+++ b/include-fd/stdplus/fd/gmock.hpp
@@ -0,0 +1,49 @@
+#pragma once
+#include <gmock/gmock.h>
+#include <stdplus/fd/intf.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+class FdMock : public Fd
+{
+ public:
+ MOCK_METHOD(std::span<std::byte>, read, (std::span<std::byte> buf),
+ (override));
+ MOCK_METHOD(std::span<std::byte>, recv,
+ (std::span<std::byte> buf, RecvFlags flags), (override));
+ MOCK_METHOD(std::span<const std::byte>, write,
+ (std::span<const std::byte> data), (override));
+ MOCK_METHOD(std::span<const std::byte>, send,
+ (std::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, (std::span<const std::byte> sockaddr), (override));
+ MOCK_METHOD(void, listen, (int backlog), (override));
+ MOCK_METHOD((std::tuple<std::optional<int>, std::span<std::byte>>), accept,
+ (std::span<std::byte> sockaddr), (override));
+ MOCK_METHOD(void, setsockopt,
+ (SockLevel level, SockOpt optname,
+ std::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));
+ MOCK_METHOD(std::span<std::byte>, mmap,
+ (std::span<std::byte> window, ProtFlags prot, MMapFlags flags,
+ off_t offset),
+ (override));
+ MOCK_METHOD(void, munmap, (std::span<std::byte> window), (override));
+};
+
+} // namespace fd
+
+using fd::FdMock;
+
+} // namespace stdplus
diff --git a/include-fd/stdplus/fd/impl.hpp b/include-fd/stdplus/fd/impl.hpp
new file mode 100644
index 0000000..9d5acaf
--- /dev/null
+++ b/include-fd/stdplus/fd/impl.hpp
@@ -0,0 +1,45 @@
+#pragma once
+#include <stdplus/fd/intf.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+class FdImpl : public Fd
+{
+ public:
+ virtual int get() const = 0;
+
+ std::span<std::byte> read(std::span<std::byte> buf) override;
+ std::span<std::byte> recv(std::span<std::byte> buf,
+ RecvFlags flags) override;
+ std::span<const std::byte> write(std::span<const std::byte> data) override;
+ std::span<const std::byte> send(std::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(std::span<const std::byte> sockaddr) override;
+ void listen(int backlog) override;
+ std::tuple<std::optional<int>, std::span<std::byte>>
+ accept(std::span<std::byte> sockaddr) override;
+ void setsockopt(SockLevel level, SockOpt optname,
+ std::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;
+
+ protected:
+ std::span<std::byte> mmap(std::span<std::byte> window, ProtFlags prot,
+ MMapFlags flags, off_t offset) override;
+ void munmap(std::span<std::byte> window) override;
+};
+
+} // namespace fd
+
+using fd::FdImpl;
+
+} // namespace stdplus
diff --git a/include-fd/stdplus/fd/intf.hpp b/include-fd/stdplus/fd/intf.hpp
new file mode 100644
index 0000000..4d9553d
--- /dev/null
+++ b/include-fd/stdplus/fd/intf.hpp
@@ -0,0 +1,157 @@
+#pragma once
+#include <cstddef>
+#include <fcntl.h>
+#include <optional>
+#include <span>
+#include <stdplus/flags.hpp>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <tuple>
+
+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>;
+
+enum class ProtFlag : int
+{
+ Exec = PROT_EXEC,
+ Read = PROT_READ,
+ Write = PROT_WRITE,
+};
+using ProtFlags = BitFlags<int, ProtFlag>;
+
+enum class MMapAccess : int
+{
+ Shared = MAP_SHARED,
+ Private = MAP_PRIVATE,
+};
+
+enum class MMapFlag : int
+{
+};
+
+class MMapFlags : public BitFlags<int, MMapFlag>
+{
+ public:
+ inline MMapFlags(MMapAccess access) :
+ BitFlags<int, MMapFlag>(static_cast<int>(access))
+ {
+ }
+
+ inline MMapFlags(BitFlags<int, MMapFlag> flags) :
+ BitFlags<int, MMapFlag>(flags)
+ {
+ }
+};
+
+class MMap;
+
+class Fd
+{
+ public:
+ virtual ~Fd() = default;
+
+ virtual std::span<std::byte> read(std::span<std::byte> buf) = 0;
+ virtual std::span<std::byte> recv(std::span<std::byte> buf,
+ RecvFlags flags) = 0;
+ virtual std::span<const std::byte>
+ write(std::span<const std::byte> data) = 0;
+ virtual std::span<const std::byte> send(std::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(std::span<const std::byte> sockaddr) = 0;
+ virtual void listen(int backlog) = 0;
+ virtual std::tuple<std::optional<int>, std::span<std::byte>>
+ accept(std::span<std::byte> sockaddr) = 0;
+ virtual void setsockopt(SockLevel level, SockOpt optname,
+ std::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;
+
+ protected:
+ virtual std::span<std::byte> mmap(std::span<std::byte> window,
+ ProtFlags prot, MMapFlags flags,
+ off_t offset) = 0;
+ virtual void munmap(std::span<std::byte> window) = 0;
+ friend class MMap;
+};
+
+} // namespace fd
+
+using fd::Fd;
+
+} // namespace stdplus
diff --git a/include-fd/stdplus/fd/managed.hpp b/include-fd/stdplus/fd/managed.hpp
new file mode 100644
index 0000000..a188421
--- /dev/null
+++ b/include-fd/stdplus/fd/managed.hpp
@@ -0,0 +1,63 @@
+#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 Constructs an empty file descriptor */
+ ManagedFd() noexcept;
+
+ /** @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/include-fd/stdplus/fd/mmap.hpp b/include-fd/stdplus/fd/mmap.hpp
new file mode 100644
index 0000000..06b7944
--- /dev/null
+++ b/include-fd/stdplus/fd/mmap.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include <cstddef>
+#include <functional>
+#include <span>
+#include <stdplus/fd/intf.hpp>
+#include <stdplus/handle/managed.hpp>
+
+namespace stdplus
+{
+namespace fd
+{
+
+class MMap
+{
+ public:
+ inline MMap(Fd& fd, size_t window_size, ProtFlags prot, MMapFlags flags,
+ off_t offset) :
+ MMap(
+ fd,
+ std::span<std::byte>{static_cast<std::byte*>(nullptr), window_size},
+ prot, flags, offset)
+ {
+ }
+ MMap(Fd& fd, std::span<std::byte> window, ProtFlags prot, MMapFlags flags,
+ off_t offset);
+
+ std::span<std::byte> get() const;
+
+ private:
+ static void drop(std::span<std::byte>&&, std::reference_wrapper<Fd>&);
+ Managed<std::span<std::byte>, std::reference_wrapper<Fd>>::Handle<drop>
+ mapping;
+};
+
+} // namespace fd
+} // namespace stdplus
diff --git a/include-fd/stdplus/fd/ops.hpp b/include-fd/stdplus/fd/ops.hpp
new file mode 100644
index 0000000..8f3735b
--- /dev/null
+++ b/include-fd/stdplus/fd/ops.hpp
@@ -0,0 +1,179 @@
+#pragma once
+#include <span>
+#include <stdexcept>
+#include <stdplus/fd/dupable.hpp>
+#include <stdplus/fd/intf.hpp>
+#include <stdplus/raw.hpp>
+#include <utility>
+
+namespace stdplus
+{
+namespace fd
+{
+namespace detail
+{
+
+void readExact(Fd& fd, std::span<std::byte> data);
+void recvExact(Fd& fd, std::span<std::byte> data, RecvFlags flags);
+void writeExact(Fd& fd, std::span<const std::byte> data);
+void sendExact(Fd& fd, std::span<const std::byte> data, SendFlags flags);
+
+std::span<std::byte> readAligned(Fd& fd, size_t align,
+ std::span<std::byte> buf);
+std::span<std::byte> recvAligned(Fd& fd, size_t align, std::span<std::byte> buf,
+ RecvFlags flags);
+std::span<const std::byte> writeAligned(Fd& fd, size_t align,
+ std::span<const std::byte> data);
+std::span<const std::byte> sendAligned(Fd& fd, size_t align,
+ std::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 std::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));
+}
+
+inline void listen(Fd& fd, int backlog)
+{
+ return fd.listen(backlog);
+}
+
+template <typename SockAddr>
+inline std::optional<stdplus::DupableFd> accept(Fd& fd, SockAddr&& sockaddr)
+{
+ auto ret = fd.accept(raw::asSpan<std::byte>(sockaddr));
+ if (!std::get<0>(ret))
+ {
+ return std::nullopt;
+ }
+ if (std::get<1>(ret).size() != sizeof(sockaddr))
+ {
+ throw std::runtime_error("Invalid sockaddr type for accept");
+ }
+ return stdplus::DupableFd(std::move(*std::get<0>(ret)));
+}
+
+inline std::optional<stdplus::DupableFd> accept(Fd& fd)
+{
+ auto ret = std::get<0>(fd.accept(std::span<std::byte>{}));
+ if (!ret)
+ {
+ return std::nullopt;
+ }
+ return stdplus::DupableFd(std::move(*ret));
+}
+
+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