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