build: Refactor directory structure to separate headers

This makes the structure similar to other OpenBMC projects.

Change-Id: I5b76fe7439dba9b68244ee1494f2266b6351e498
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
new file mode 100644
index 0000000..5d870ac
--- /dev/null
+++ b/include/meson.build
@@ -0,0 +1,44 @@
+stdplus_headers = include_directories('.')
+
+if has_dl
+  install_headers(
+    'stdplus/dl.hpp',
+    subdir: 'stdplus')
+endif
+
+if has_fd
+  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')
+endif
+
+if has_io_uring
+  install_headers(
+    'stdplus/io_uring.hpp',
+    subdir: 'stdplus')
+endif
+
+install_headers(
+  'stdplus/cancel.hpp',
+  'stdplus/exception.hpp',
+  'stdplus/flags.hpp',
+  'stdplus/raw.hpp',
+  'stdplus/signal.hpp',
+  subdir: 'stdplus')
+
+install_headers(
+  'stdplus/handle/copyable.hpp',
+  'stdplus/handle/managed.hpp',
+  subdir: 'stdplus/handle')
+
+install_headers(
+  'stdplus/util/cexec.hpp',
+  'stdplus/util/string.hpp',
+  subdir: 'stdplus/util')
diff --git a/include/stdplus/cancel.hpp b/include/stdplus/cancel.hpp
new file mode 100644
index 0000000..cb40c87
--- /dev/null
+++ b/include/stdplus/cancel.hpp
@@ -0,0 +1,136 @@
+#pragma once
+#include <stdplus/handle/managed.hpp>
+
+#include <tuple>
+#include <utility>
+
+namespace stdplus
+{
+
+struct Cancelable
+{
+    virtual ~Cancelable() = default;
+    virtual void cancel() noexcept = 0;
+};
+
+namespace detail
+{
+
+struct CancelableF
+{
+    inline void operator()(Cancelable* c) noexcept
+    {
+        c->cancel();
+    }
+};
+
+using CancelHandle = stdplus::Managed<Cancelable*>::HandleF<CancelableF>;
+
+} // namespace detail
+
+struct Cancel : detail::CancelHandle
+{
+    Cancel() : detail::CancelHandle(std::nullopt)
+    {
+    }
+    template <typename T>
+    explicit Cancel(T&& t) : detail::CancelHandle(t)
+    {
+    }
+};
+
+namespace detail
+{
+
+struct fAny
+{
+};
+struct fPtr : fAny
+{
+};
+
+template <typename>
+struct validator
+{
+    typedef int type;
+};
+
+template <typename F,
+          typename validator<decltype(std::declval<F>() == nullptr)>::type = 0>
+inline bool fPop(const F& f, fPtr)
+{
+    return !(f == nullptr);
+}
+
+template <typename F>
+inline bool fPop(const F&, fAny)
+{
+    return true;
+}
+
+template <typename F>
+inline bool fPop(const F& f)
+{
+    return fPop(f, fPtr());
+}
+
+} // namespace detail
+
+template <typename F, typename... DefaultArgs>
+class AlwaysCallOnce
+{
+  public:
+    template <typename Fi, typename... Vs>
+    explicit AlwaysCallOnce(Fi&& fi, Vs&&... default_args) :
+        f(std::forward<Fi>(fi)), default_args(std::forward<Vs>(default_args)...)
+    {
+    }
+    AlwaysCallOnce(const AlwaysCallOnce&) = delete;
+    AlwaysCallOnce(AlwaysCallOnce&& other) noexcept :
+        f(std::move(other.f)), default_args(std::move(other.default_args)),
+        called(std::exchange(other.called, true))
+    {
+    }
+    AlwaysCallOnce& operator=(const AlwaysCallOnce&) = delete;
+    AlwaysCallOnce& operator=(AlwaysCallOnce&& other) noexcept
+    {
+        finalCall();
+        f = std::move(other.f);
+        default_args = std::move(other.default_args);
+        called = std::exchange(other.called, true);
+        return *this;
+    }
+    ~AlwaysCallOnce()
+    {
+        finalCall();
+    }
+
+    template <typename... Args>
+    auto operator()(Args&&... args) noexcept
+    {
+        called = true;
+        return f(std::forward<Args>(args)...);
+    }
+
+  private:
+    F f;
+    std::tuple<DefaultArgs...> default_args;
+    bool called = false;
+
+    void finalCall() noexcept
+    {
+        if (!called && detail::fPop(f))
+        {
+            std::apply(f, default_args);
+        }
+    }
+};
+
+template <typename... Args>
+inline auto alwaysCallOnce(Args&&... args)
+{
+    return AlwaysCallOnce<std::remove_cv_t<std::remove_reference_t<Args>>...>(
+        std::forward<Args>(args)...);
+}
+
+} // namespace stdplus
diff --git a/include/stdplus/dl.hpp b/include/stdplus/dl.hpp
new file mode 100644
index 0000000..0526391
--- /dev/null
+++ b/include/stdplus/dl.hpp
@@ -0,0 +1,55 @@
+#pragma once
+#include <dlfcn.h>
+#include <link.h>
+
+#include <stdplus/flags.hpp>
+#include <stdplus/handle/managed.hpp>
+
+namespace stdplus
+{
+
+enum class DlOpenType : int
+{
+    Lazy = RTLD_LAZY,
+    Now = RTLD_NOW,
+};
+
+enum class DlOpenFlag : int
+{
+    Global = RTLD_GLOBAL,
+    Local = RTLD_LOCAL,
+    NoDelete = RTLD_NODELETE,
+    NoLoad = RTLD_NOLOAD,
+    DeepBind = RTLD_DEEPBIND,
+};
+
+class DlOpenFlags : public stdplus::BitFlags<int, DlOpenFlag>
+{
+  public:
+    inline DlOpenFlags(DlOpenType type) :
+        BitFlags<int, DlOpenFlag>(static_cast<int>(type))
+    {
+    }
+
+    inline DlOpenFlags(BitFlags<int, DlOpenFlag> flags) :
+        BitFlags<int, DlOpenFlag>(flags)
+    {
+    }
+};
+
+class Dl
+{
+  public:
+    Dl(const char* filename, DlOpenFlags flags);
+
+    struct link_map* linkMap();
+
+  private:
+    void info(int request, void* info);
+
+    static void* open(const char* filename, int flags);
+    static void close(void*&& handle);
+    stdplus::Managed<void*>::Handle<close> handle;
+};
+
+} // namespace stdplus
diff --git a/include/stdplus/exception.hpp b/include/stdplus/exception.hpp
new file mode 100644
index 0000000..a7dbe8b
--- /dev/null
+++ b/include/stdplus/exception.hpp
@@ -0,0 +1,79 @@
+#pragma once
+#include <fmt/format.h>
+#include <system_error>
+#include <utility>
+
+// Forward declare builtins in case they are unsupported
+#if !__has_builtin(__builtin_LINE)
+int __builtin_LINE();
+const char* __builtin_FILE();
+const char* __builtin_FUNCTION();
+#endif
+
+namespace stdplus
+{
+namespace exception
+{
+
+struct WouldBlock : public std::system_error
+{
+    WouldBlock(const char* what);
+    WouldBlock(const std::string& what);
+};
+
+struct Eof : public std::system_error
+{
+    Eof(const char* what);
+    Eof(const std::string& what);
+};
+
+template <typename F>
+auto ignore(F&& f, const char* file = __builtin_FILE(),
+            int line = __builtin_LINE(),
+            const char* func = __builtin_FUNCTION()) noexcept
+{
+    return
+        [f = std::move(f), file, line, func](auto&&... args) mutable noexcept {
+            try
+            {
+                return f(std::forward<decltype(args)>(args)...);
+            }
+            catch (const std::exception& e)
+            {
+                fmt::print(stderr, "Ignoring({}:{} {}): {}\n", file, line, func,
+                           e.what());
+            }
+            catch (...)
+            {
+                fmt::print(stderr, "Ignoring({}:{} {}): Invalid Error\n", file,
+                           line, func);
+            }
+            using Ret = std::invoke_result_t<decltype(f), decltype(args)...>;
+            if constexpr (!std::is_same_v<void, Ret>)
+            {
+                return Ret();
+            }
+        };
+}
+
+template <typename F>
+auto ignoreQuiet(F&& f) noexcept
+{
+    return [f = std::move(f)](auto&&... args) mutable noexcept {
+        try
+        {
+            return f(std::forward<decltype(args)>(args)...);
+        }
+        catch (...)
+        {
+        }
+        using Ret = std::invoke_result_t<decltype(f), decltype(args)...>;
+        if constexpr (!std::is_same_v<void, Ret>)
+        {
+            return Ret();
+        }
+    };
+}
+
+} // namespace exception
+} // namespace stdplus
diff --git a/include/stdplus/fd/create.hpp b/include/stdplus/fd/create.hpp
new file mode 100644
index 0000000..83aa2ba
--- /dev/null
+++ b/include/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/stdplus/fd/dupable.hpp b/include/stdplus/fd/dupable.hpp
new file mode 100644
index 0000000..0f810cf
--- /dev/null
+++ b/include/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/stdplus/fd/gmock.hpp b/include/stdplus/fd/gmock.hpp
new file mode 100644
index 0000000..ca3b2af
--- /dev/null
+++ b/include/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/stdplus/fd/impl.hpp b/include/stdplus/fd/impl.hpp
new file mode 100644
index 0000000..9d5acaf
--- /dev/null
+++ b/include/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/stdplus/fd/intf.hpp b/include/stdplus/fd/intf.hpp
new file mode 100644
index 0000000..4d9553d
--- /dev/null
+++ b/include/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/stdplus/fd/managed.hpp b/include/stdplus/fd/managed.hpp
new file mode 100644
index 0000000..a188421
--- /dev/null
+++ b/include/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/stdplus/fd/mmap.hpp b/include/stdplus/fd/mmap.hpp
new file mode 100644
index 0000000..06b7944
--- /dev/null
+++ b/include/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/stdplus/fd/ops.hpp b/include/stdplus/fd/ops.hpp
new file mode 100644
index 0000000..8f3735b
--- /dev/null
+++ b/include/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
diff --git a/include/stdplus/flags.hpp b/include/stdplus/flags.hpp
new file mode 100644
index 0000000..047c776
--- /dev/null
+++ b/include/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/include/stdplus/handle/copyable.hpp b/include/stdplus/handle/copyable.hpp
new file mode 100644
index 0000000..86fc8b7
--- /dev/null
+++ b/include/stdplus/handle/copyable.hpp
@@ -0,0 +1,153 @@
+#pragma once
+#include <optional>
+#include <stdplus/handle/managed.hpp>
+#include <type_traits>
+#include <utility>
+
+namespace stdplus
+{
+
+/** @brief Similar to the Managed Handle, but also allows for copying
+ *         and performs an operation during that copy.
+ */
+template <typename T, typename... As>
+struct Copyable
+{
+    template <typename Drop, typename Ref>
+    class HandleF : public Managed<T, As...>::template HandleF<Drop>
+    {
+      protected:
+        static constexpr bool ref_noexcept =
+            noexcept(Ref()(std::declval<T>(), std::declval<As&>()...));
+
+      public:
+        using MHandleF = typename Managed<T, As...>::template HandleF<Drop>;
+
+        /** @brief Creates a handle referencing the object
+         *
+         *  @param[in] maybeV - Optional object being managed
+         */
+        template <typename... Vs>
+        constexpr explicit HandleF(const std::optional<T>& maybeV, Vs&&... vs) noexcept(
+            noexcept(MHandleF(std::nullopt, std::declval<Vs>()...)) && noexcept(
+                std::declval<HandleF>().reset(
+                    std::declval<const std::optional<T>&>()))) :
+            MHandleF(std::nullopt, std::forward<Vs>(vs)...)
+        {
+            reset(maybeV);
+        }
+        template <typename... Vs>
+        constexpr explicit HandleF(const T& maybeV, Vs&&... vs) noexcept(
+            noexcept(MHandleF(std::nullopt, std::declval<Vs>()...)) && noexcept(
+                std::declval<HandleF>().reset(std::declval<const T&>()))) :
+            MHandleF(std::nullopt, std::forward<Vs>(vs)...)
+        {
+            reset(maybeV);
+        }
+
+        /** @brief Creates a handle owning the object
+         *
+         *  @param[in] maybeV - Maybe the object being managed
+         */
+        template <typename... Vs>
+        constexpr explicit HandleF(
+            std::optional<T>&& maybeV,
+            Vs&&... vs) noexcept(noexcept(MHandleF(std::
+                                                       declval<std::optional<
+                                                           T>&&>(),
+                                                   std::declval<Vs>()...))) :
+            MHandleF(std::move(maybeV), std::forward<Vs>(vs)...)
+        {
+        }
+        template <typename... Vs>
+        constexpr explicit HandleF(T&& maybeV, Vs&&... vs) noexcept(
+            noexcept(MHandleF(std::declval<T&&>(), std::declval<Vs>()...))) :
+            MHandleF(std::move(maybeV), std::forward<Vs>(vs)...)
+        {
+        }
+
+        constexpr HandleF(const HandleF& other) noexcept(noexcept(MHandleF(
+            std::nullopt,
+            std::declval<const std::tuple<
+                As...>&>())) && noexcept(std::declval<HandleF>()
+                                             .reset(std::declval<
+                                                    const std::optional<
+                                                        T>&>()))) :
+            MHandleF(std::nullopt, other.as)
+        {
+            reset(other.maybe_value());
+        }
+
+        constexpr HandleF(HandleF&& other) noexcept(
+            std::is_nothrow_move_constructible_v<MHandleF>) :
+            MHandleF(std::move(other))
+        {
+        }
+
+        constexpr HandleF& operator=(const HandleF& other) noexcept(
+            noexcept(std::declval<HandleF>().reset()) &&
+            std::is_nothrow_copy_constructible_v<std::tuple<As...>>&& noexcept(
+                std::declval<HandleF>().reset(
+                    std::declval<const std::optional<T>&>())))
+        {
+            if (this != &other)
+            {
+                reset();
+                this->as = other.as;
+                reset(other.maybe_value());
+            }
+            return *this;
+        }
+
+        constexpr HandleF& operator=(HandleF&& other) noexcept(
+            std::is_nothrow_move_assignable_v<MHandleF>)
+        {
+            MHandleF::operator=(std::move(other));
+            return *this;
+        }
+
+        using MHandleF::reset;
+        constexpr void reset(const std::optional<T>& maybeV) noexcept(
+            ref_noexcept&& noexcept(std::declval<HandleF>().reset(
+                std::declval<T>())) && noexcept(std::declval<HandleF>()
+                                                    .reset()))
+        {
+            if (maybeV)
+            {
+                reset(doRef(*maybeV, std::index_sequence_for<As...>()));
+            }
+            else
+            {
+                reset();
+            }
+        }
+        constexpr void reset(const T& maybeV) noexcept(ref_noexcept&& noexcept(
+            std::declval<HandleF>().reset(std::declval<T>())))
+        {
+            reset(doRef(maybeV, std::index_sequence_for<As...>()));
+        }
+
+      private:
+        template <size_t... Indices>
+        T doRef(const T& v,
+                std::index_sequence<Indices...>) noexcept(ref_noexcept)
+        {
+            return Ref()(v, std::get<Indices>(this->as)...);
+        }
+    };
+
+    template <T (*ref)(const T&, As&...)>
+    struct Reffer
+    {
+        T operator()(const T& t, As&... as) noexcept(noexcept(ref))
+        {
+            return ref(t, as...);
+        }
+    };
+
+    template <void (*drop)(T&&, As&...), T (*ref)(const T&, As&...)>
+    using Handle = HandleF<typename Managed<T, As...>::template Dropper<drop>,
+                           Reffer<ref>>;
+};
+
+} // namespace stdplus
diff --git a/include/stdplus/handle/managed.hpp b/include/stdplus/handle/managed.hpp
new file mode 100644
index 0000000..08cac4f
--- /dev/null
+++ b/include/stdplus/handle/managed.hpp
@@ -0,0 +1,249 @@
+#pragma once
+#include <cstdlib>
+#include <optional>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+namespace stdplus
+{
+
+/** @brief   An RAII handle which takes an object and calls a user specified
+ *           function on the object when it should be cleaned up.
+ *  @details This is useful for adding RAII semantics to non-RAII things like
+ *           file descriptors, or c structs allocated with special routines.
+ *           We could make a simple file descriptor wrapper that is
+ *           automatically closed:
+ *
+ *           struct CloseFd {
+ *               void operator()(int&& fd) { close(fd); }
+ *           };
+ *           using Fd = Managed<int>::HandleF<closefd>;
+ *
+ *           void some_func()
+ *           {
+ *               Fd fd(open("somefile", 0));
+ *               char buf[4096];
+ *               int amt = read(*fd, buf, sizeof(buf));
+ *               printf("%.*s\n", amt, data);
+ *           }
+ */
+template <typename T, typename... As>
+struct Managed
+{
+
+    template <typename Drop>
+    class HandleF
+    {
+      protected:
+        static constexpr bool drop_noexcept =
+            noexcept(Drop()(std::declval<T>(), std::declval<As&>()...));
+
+      public:
+        /** @brief Creates a handle owning the object
+         *
+         *  @param[in] maybeV - Maybe the object being managed
+         */
+        template <typename... Vs>
+        constexpr explicit HandleF(std::optional<T>&& maybeV, Vs&&... vs) noexcept(
+            std::is_nothrow_move_constructible_v<std::optional<T>>&& noexcept(
+                std::tuple<As...>(std::declval<Vs>()...))) :
+            as(std::forward<Vs>(vs)...),
+            maybeT(std::move(maybeV))
+        {
+        }
+        template <typename... Vs>
+        constexpr explicit HandleF(T&& maybeV, Vs&&... vs) noexcept(
+            std::is_nothrow_move_constructible_v<std::optional<T>>&& noexcept(
+                std::tuple<As...>(std::declval<Vs>()...))) :
+            as(std::forward<Vs>(vs)...),
+            maybeT(std::move(maybeV))
+        {
+        }
+
+        HandleF(const HandleF& other) = delete;
+        HandleF& operator=(const HandleF& other) = delete;
+
+        constexpr HandleF(HandleF&& other) noexcept(
+            std::is_nothrow_move_constructible_v<std::tuple<As...>>&&
+                std::is_nothrow_move_constructible_v<std::optional<T>>) :
+            as(std::move(other.as)),
+            maybeT(std::move(other.maybeT))
+        {
+            other.maybeT = std::nullopt;
+        }
+
+        constexpr HandleF& operator=(HandleF&& other) noexcept(
+            std::is_nothrow_move_assignable_v<std::tuple<As...>>&& noexcept(
+                std::declval<HandleF>().reset(
+                    std::declval<std::optional<T>>())))
+        {
+            if (this != &other)
+            {
+                reset(std::move(other.maybeT));
+                as = std::move(other.as);
+                other.maybeT = std::nullopt;
+            }
+            return *this;
+        }
+
+        virtual ~HandleF() noexcept(
+            std::is_nothrow_destructible_v<std::tuple<As...>>&&
+                std::is_nothrow_destructible_v<std::optional<T>>&& noexcept(
+                    std::declval<HandleF>().reset()))
+        {
+            reset();
+        }
+
+        /** @brief Gets the managed object
+         *
+         *  @return A pointer to the object
+         */
+        constexpr const T* operator->() const noexcept
+        {
+            return &(*maybeT);
+        }
+
+        /** @brief Gets the managed object
+         *
+         *  @return A reference to the object
+         */
+        constexpr const T& operator*() const& noexcept
+        {
+            return *maybeT;
+        }
+
+        /** @brief Determine if we are managing an object
+         *
+         *  @return Do we currently have an object
+         */
+        constexpr explicit operator bool() const noexcept
+        {
+            return static_cast<bool>(maybeT);
+        }
+
+        /** @brief Determine if we are managing an object
+         *
+         *  @return Do we currently have an object
+         */
+        constexpr bool has_value() const noexcept
+        {
+            return maybeT.has_value();
+        }
+
+        /** @brief Gets the managed object
+         *
+         *  @throws std::bad_optional_access if it has no object
+         *  @return A reference to the managed object
+         */
+        constexpr const T& value() const&
+        {
+            return maybeT.value();
+        }
+
+        /** @brief Gets the managed object if it exists
+         *
+         *  @throws std::bad_optional_access if it has no object
+         *  @return A reference to the managed object
+         */
+        constexpr const std::optional<T>& maybe_value() const& noexcept
+        {
+            return maybeT;
+        }
+
+        /** @brief Resets the managed value to a new value
+         *         The container takes ownership of the value
+         *
+         *  @param[in] maybeV - Maybe the new value
+         */
+        constexpr void reset(std::optional<T>&& maybeV) noexcept(
+            drop_noexcept&& std::is_nothrow_move_assignable_v<std::optional<T>>)
+        {
+            maybeDrop(std::index_sequence_for<As...>());
+            maybeT = std::move(maybeV);
+        }
+        constexpr void reset(T&& maybeV) noexcept(
+            drop_noexcept&& std::is_nothrow_move_assignable_v<std::optional<T>>)
+        {
+            maybeDrop(std::index_sequence_for<As...>());
+            maybeT = std::move(maybeV);
+        }
+
+        /** @brief A shorthand reset function for convenience
+         *         Same as calling reset(std::nullopt)
+         */
+        constexpr void reset() noexcept(drop_noexcept)
+        {
+            reset(std::nullopt);
+        }
+
+        /** @brief Releases the managed value and transfers ownership
+         *         to the caller.
+         *
+         *  @throws std::bad_optional_access if it has no object
+         *  @return The value that was managed
+         */
+        [[nodiscard]] constexpr T release()
+        {
+            T ret = std::move(maybeT.value());
+            maybeT = std::nullopt;
+            return ret;
+        }
+
+        /** @brief Releases the managed value and transfers ownership
+         *         to the caller.
+         *
+         *  @return Maybe the value that was managed
+         */
+        [[nodiscard]] constexpr std::optional<T> maybe_release() noexcept
+        {
+            std::optional<T> ret = std::move(maybeT);
+            maybeT = std::nullopt;
+            return ret;
+        }
+
+        /** @brief Reference the contained data
+         *
+         *  @return A reference to the contained data
+         */
+        constexpr const std::tuple<As...>& data() const noexcept
+        {
+            return as;
+        }
+        constexpr std::tuple<As...>& data() noexcept
+        {
+            return as;
+        }
+
+      protected:
+        /* Hold the data parameterized for this container */
+        std::tuple<As...> as;
+
+      private:
+        /* Stores the managed object if we have one */
+        std::optional<T> maybeT;
+
+        template <size_t... Indices>
+        void maybeDrop(std::index_sequence<Indices...>) noexcept(drop_noexcept)
+        {
+            if (maybeT)
+            {
+                Drop()(std::move(*maybeT), std::get<Indices>(as)...);
+            }
+        }
+    };
+
+    template <void (*drop)(T&&, As&...)>
+    struct Dropper
+    {
+        void operator()(T&& t, As&... as) noexcept(noexcept(drop))
+        {
+            drop(std::move(t), as...);
+        }
+    };
+
+    template <void (*drop)(T&&, As&...)>
+    using Handle = HandleF<Dropper<drop>>;
+};
+
+} // namespace stdplus
diff --git a/include/stdplus/io_uring.hpp b/include/stdplus/io_uring.hpp
new file mode 100644
index 0000000..6572e04
--- /dev/null
+++ b/include/stdplus/io_uring.hpp
@@ -0,0 +1,140 @@
+#pragma once
+
+#include <liburing.h>
+
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/handle/managed.hpp>
+
+#include <chrono>
+#include <optional>
+#include <vector>
+
+namespace stdplus
+{
+
+/** @brief Converts a chrono duration into a kernel duration */
+__kernel_timespec chronoToKTS(std::chrono::nanoseconds t) noexcept;
+
+class IoUring
+{
+  public:
+    struct CQEHandler
+    {
+        CQEHandler() = default;
+        CQEHandler(CQEHandler&&) = delete;
+        CQEHandler& operator=(CQEHandler&&) = delete;
+        CQEHandler(const CQEHandler&) = delete;
+        CQEHandler& operator=(const CQEHandler&) = delete;
+        virtual ~CQEHandler() = default;
+
+        virtual void handleCQE(io_uring_cqe&) noexcept = 0;
+    };
+
+    class FileHandle
+    {
+      public:
+        inline operator int() const
+        {
+            return *slot;
+        }
+
+      private:
+        explicit FileHandle(unsigned slot, IoUring& ring);
+
+        static void drop(unsigned&& slot, IoUring*& ring);
+
+        Managed<unsigned, IoUring*>::Handle<drop> slot;
+
+        friend class IoUring;
+    };
+
+    explicit IoUring(size_t queue_size = 10, int flags = 0);
+    explicit IoUring(size_t queue_size, io_uring_params& params);
+
+    IoUring(IoUring&&) = delete;
+    IoUring& operator=(IoUring&&) = delete;
+    IoUring(const IoUring&) = delete;
+    IoUring& operator=(const IoUring&) = delete;
+    ~IoUring();
+
+    /** @brief Reserves an additional number of file descriptor slots
+     *
+     *  @param[in] num - The number of slots to register
+     *  @throws std::system_error if the allocation fails
+     */
+    void reserveFiles(size_t num);
+
+    /** @brief Registers a file descriptor with a slot on the ring
+     *
+     *  @param[in] fd - The file descriptor to register
+     *  @throws std::system_error if the allocation fails
+     *  @return A handle to the registered file on the ring
+     */
+    [[nodiscard]] FileHandle registerFile(int fd);
+
+    /** @brief Get current list of files descriptors registered on the ring.
+     * Note this view potentially expires when registrations change. */
+    inline std::span<const int> getFiles() const noexcept
+    {
+        return files;
+    }
+
+    /** @brief Gets an unused SQE from the ring
+     *
+     *  @throws std::system_error if the allocation fails
+     *  @return An SQE on the ring
+     */
+    io_uring_sqe& getSQE();
+
+    /** @brief Associates the SQE with a user provided callback handler
+     *
+     *  @param[in] sqe - The SQE that we want to register
+     *  @param[in] h   - The handler which will be run when the CQE comes back
+     */
+    void setHandler(io_uring_sqe& sqe, CQEHandler* h) noexcept;
+
+    /** @brief Cancels the outstanding request associated with a handler
+     *
+     *  @param[in] h - The handler associated with the request
+     */
+    void cancelHandler(CQEHandler& h);
+
+    /** @brief Submits all outstanding SQEs to the kernel
+     *
+     *  @throws std::system_error if the submission fails
+     */
+    void submit();
+
+    /** @brief Non-blocking process all outstanding CQEs */
+    void process() noexcept;
+
+    /** @brief Waits for new CQEs to become available */
+    void wait();
+    void wait(std::chrono::nanoseconds timeout);
+
+    /** @brief Returns the EventFD associated with the ring
+     *         A new descriptor is created if it does not yet exist
+     *
+     *  @throws std::system_error if constructing the event fd fails
+     *  @return A reference to the descriptor
+     */
+    stdplus::ManagedFd& getEventFd();
+
+    /** @brief Non-blocking process all outstanding eventFd events
+     *         Should be used instead of process() to clear eventFd events.
+     */
+    void processEvents();
+
+  private:
+    io_uring ring;
+    std::optional<stdplus::ManagedFd> event_fd;
+    std::vector<CQEHandler*> handlers;
+    std::vector<int> files;
+    size_t filesAllocated = 0;
+
+    void dropHandler(CQEHandler* h, io_uring_cqe& cqe) noexcept;
+    void setFile(unsigned slot, int fd) noexcept;
+    void updateFile(unsigned slot, int fd);
+};
+
+} // namespace stdplus
diff --git a/include/stdplus/raw.hpp b/include/stdplus/raw.hpp
new file mode 100644
index 0000000..7da4b92
--- /dev/null
+++ b/include/stdplus/raw.hpp
@@ -0,0 +1,203 @@
+#pragma once
+#include <fmt/format.h>
+#include <span>
+#include <stdexcept>
+#include <string_view>
+#include <type_traits>
+
+namespace stdplus
+{
+namespace raw
+{
+
+namespace detail
+{
+
+/** @brief Gets the datatype referenced in a container
+ */
+template <typename Container>
+using dataType = std::remove_pointer_t<decltype(std::data(
+    std::declval<std::add_lvalue_reference_t<Container>>()))>;
+
+/** @brief Gets the sizetype referenced in a container
+ */
+template <typename Container>
+using sizeType =
+    decltype(std::size(std::declval<std::add_lvalue_reference_t<Container>>()));
+
+/** @brief Determines if the container holds trivially copyable data
+ */
+template <typename Container>
+inline constexpr bool trivialContainer =
+    std::is_trivially_copyable_v<dataType<Container>>;
+
+/** @brief Adds const to A if B is const
+ */
+template <typename A, typename B>
+using copyConst =
+    std::conditional_t<std::is_const_v<B>, std::add_const_t<A>, A>;
+
+/** @brief Determines if a type is a container of data
+ */
+template <typename, typename = void>
+inline constexpr bool hasData = false;
+template <typename T>
+inline constexpr bool hasData<T, std::void_t<dataType<T>, sizeType<T>>> = true;
+
+} // namespace detail
+
+/** @brief Compares two containers to see if their raw bytes are equal
+ *
+ *  @param[in] a - The first container
+ *  @param[in] b - The second container
+ *  @return True if they are the same, false otherwise
+ */
+template <typename A, typename B>
+bool equal(const A& a, const B& b) noexcept
+{
+    static_assert(std::is_trivially_copyable_v<A>);
+    static_assert(std::is_trivially_copyable_v<B>);
+    static_assert(sizeof(A) == sizeof(B));
+    return memcmp(&a, &b, sizeof(A)) == 0;
+}
+
+/** @brief Copies data from a buffer into a copyable type
+ *
+ *  @param[in] data - The data buffer being copied from
+ *  @return The copyable type with data populated
+ */
+template <typename T, typename Container>
+T copyFrom(const Container& c)
+{
+    static_assert(std::is_trivially_copyable_v<T>);
+    static_assert(detail::trivialContainer<Container>);
+    T ret;
+    const size_t bytes = std::size(c) * sizeof(*std::data(c));
+    if (bytes < sizeof(ret))
+    {
+        throw std::runtime_error(
+            fmt::format("CopyFrom: {} < {}", bytes, sizeof(ret)));
+    }
+    std::memcpy(&ret, std::data(c), sizeof(ret));
+    return ret;
+}
+
+/** @brief References the data from a buffer if aligned
+ *
+ *  @param[in] data - The data buffer being referenced
+ *  @return The reference to the data in the new type
+ */
+template <typename T, typename Container,
+          typename Tp = detail::copyConst<T, detail::dataType<Container>>>
+Tp& refFrom(Container&& c)
+{
+    static_assert(std::is_trivially_copyable_v<Tp>);
+    static_assert(detail::trivialContainer<Container>);
+    static_assert(sizeof(*std::data(c)) % alignof(Tp) == 0);
+    const size_t bytes = std::size(c) * sizeof(*std::data(c));
+    if (bytes < sizeof(Tp))
+    {
+        throw std::runtime_error(
+            fmt::format("RefFrom: {} < {}", bytes, sizeof(Tp)));
+    }
+    return *reinterpret_cast<Tp*>(std::data(c));
+}
+
+/** @brief Extracts data from a buffer into a copyable type
+ *         Updates the data buffer to show that data was removed
+ *
+ *  @param[in,out] data - The data buffer being extracted from
+ *  @return The copyable type with data populated
+ */
+template <typename T, typename CharT>
+T extract(std::basic_string_view<CharT>& data)
+{
+    T ret = copyFrom<T>(data);
+    static_assert(sizeof(T) % sizeof(CharT) == 0);
+    data.remove_prefix(sizeof(T) / sizeof(CharT));
+    return ret;
+}
+template <typename T, typename IntT,
+          typename = std::enable_if_t<std::is_trivially_copyable_v<IntT>>>
+T extract(std::span<IntT>& data)
+{
+    T ret = copyFrom<T>(data);
+    static_assert(sizeof(T) % sizeof(IntT) == 0);
+    data = data.subspan(sizeof(T) / sizeof(IntT));
+    return ret;
+}
+
+/** @brief Extracts data from a buffer as a reference if aligned
+ *         Updates the data buffer to show that data was removed
+ *
+ *  @param[in,out] data - The data buffer being extracted from
+ *  @return A reference to the data
+ */
+template <typename T, typename CharT>
+const T& extractRef(std::basic_string_view<CharT>& data)
+{
+    const T& ret = refFrom<T>(data);
+    static_assert(sizeof(T) % sizeof(CharT) == 0);
+    data.remove_prefix(sizeof(T) / sizeof(CharT));
+    return ret;
+}
+template <typename T, typename IntT,
+          typename = std::enable_if_t<std::is_trivially_copyable_v<IntT>>,
+          typename Tp = detail::copyConst<T, IntT>>
+Tp& extractRef(std::span<IntT>& data)
+{
+    Tp& ret = refFrom<Tp>(data);
+    static_assert(sizeof(Tp) % sizeof(IntT) == 0);
+    data = data.subspan(sizeof(Tp) / sizeof(IntT));
+    return ret;
+}
+
+/** @brief Returns the std::span referencing the data of the raw trivial type
+ *         or of trivial types in a contiguous container.
+ *
+ *  @param[in] t - The trivial raw data
+ *  @return A view over the input with the given output integral type
+ */
+template <typename CharT, typename T>
+std::enable_if_t<!detail::hasData<T>, std::basic_string_view<CharT>>
+    asView(const T& t) noexcept
+{
+    static_assert(std::is_trivially_copyable_v<T>);
+    static_assert(sizeof(T) % sizeof(CharT) == 0);
+    return {reinterpret_cast<const CharT*>(&t), sizeof(T) / sizeof(CharT)};
+}
+
+template <typename CharT, typename Container>
+std::enable_if_t<detail::hasData<Container>, std::basic_string_view<CharT>>
+    asView(const Container& c) noexcept
+{
+    static_assert(detail::trivialContainer<Container>);
+    static_assert(sizeof(*std::data(c)) % sizeof(CharT) == 0);
+    return {reinterpret_cast<const CharT*>(std::data(c)),
+            std::size(c) * sizeof(*std::data(c)) / sizeof(CharT)};
+}
+
+template <typename IntT, typename T,
+          typename = std::enable_if_t<std::is_trivially_copyable_v<IntT>>,
+          typename = std::enable_if_t<!detail::hasData<T>>,
+          typename IntTp = detail::copyConst<IntT, T>>
+std::span<IntTp> asSpan(T& t) noexcept
+{
+    static_assert(std::is_trivially_copyable_v<T>);
+    static_assert(sizeof(T) % sizeof(IntTp) == 0);
+    return {reinterpret_cast<IntTp*>(&t), sizeof(T) / sizeof(IntTp)};
+}
+template <typename IntT, typename Container,
+          typename = std::enable_if_t<std::is_trivially_copyable_v<IntT>>,
+          typename = std::enable_if_t<detail::hasData<Container>>,
+          typename IntTp = detail::copyConst<IntT, detail::dataType<Container>>>
+std::span<IntTp> asSpan(Container&& c) noexcept
+{
+    static_assert(detail::trivialContainer<Container>);
+    static_assert(sizeof(*std::data(c)) % sizeof(IntTp) == 0);
+    return {reinterpret_cast<IntTp*>(std::data(c)),
+            std::size(c) * sizeof(*std::data(c)) / sizeof(IntTp)};
+}
+
+} // namespace raw
+} // namespace stdplus
diff --git a/include/stdplus/signal.hpp b/include/stdplus/signal.hpp
new file mode 100644
index 0000000..465181b
--- /dev/null
+++ b/include/stdplus/signal.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+namespace stdplus
+{
+namespace signal
+{
+
+/** @brief Blocks the signal from being handled by the designated
+ *         sigaction. If the signal is already blocked this does nothing.
+ *
+ *  @param[in] signum - The int representing the signal to block
+ *  @throws std::system_error if any underlying error occurs.
+ */
+void block(int signum);
+
+} // namespace signal
+} // namespace stdplus
diff --git a/include/stdplus/util/cexec.hpp b/include/stdplus/util/cexec.hpp
new file mode 100644
index 0000000..daf402c
--- /dev/null
+++ b/include/stdplus/util/cexec.hpp
@@ -0,0 +1,227 @@
+#pragma once
+#include <functional>
+#include <string>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+
+/** @brief   Wraps common c style error handling for exception throwing
+ *           This requires the callee to set errno on error.
+ *  @details We often have a pattern in our code for checking errors and
+ *           propagating up exceptions:
+ *
+ *           int c_call(const char* path);
+ *
+ *           int our_cpp_call(const char* path)
+ *           {
+ *               int r = c_call(path);
+ *               if (r < 0)
+ *               {
+ *                   throw std::system_error(errno, std::generic_category(),
+ *                                           "our msg");
+ *               }
+ *               return r;
+ *           }
+ *
+ *           To make that more succinct, we can use CHECK_ERRNO:
+ *
+ *           int our_cpp_call(const char* path)
+ *           {
+ *               return CHECK_ERRNO(c_call(path), "our msg");
+ *           }
+ *
+ *  @param[in] expr          - The expression returning an errno value
+ *  @param[in] error_handler - Passed to the doError function
+ *  @throws std::system_error (depends on error_handler) for an error case
+ *  @return A successful return value based on the function type
+ */
+#define CHECK_ERRNO(expr, error_handler)                                       \
+    [&](auto check_errno_ret) {                                                \
+        if constexpr (::std::is_pointer_v<decltype(check_errno_ret)>)          \
+        {                                                                      \
+            if (check_errno_ret == nullptr)                                    \
+                ::stdplus::util::doError(errno, (error_handler));              \
+        }                                                                      \
+        else if constexpr (::std::is_signed_v<decltype(check_errno_ret)>)      \
+        {                                                                      \
+            if (check_errno_ret < 0)                                           \
+                ::stdplus::util::doError(errno, (error_handler));              \
+        }                                                                      \
+        else                                                                   \
+        {                                                                      \
+            static_assert(::std::is_same_v<decltype(check_errno_ret), int>,    \
+                          "Unimplemented check routine");                      \
+        }                                                                      \
+        return check_errno_ret;                                                \
+    }((expr))
+
+/** @brief   Wraps common c style error handling for exception throwing
+ *           This requires the callee to provide error information in -r.
+ *           See CHECK_ERRNO() for details.
+ *
+ *  @param[in] expr          - The expression returning an negative error value
+ *  @param[in] error_handler - Passed to the doError function
+ *  @throws std::system_error (depends on error_handler) for an error case
+ *  @return A successful return value based on the function type
+ */
+#define CHECK_RET(expr, error_handler)                                         \
+    [&](auto check_ret_ret) {                                                  \
+        if constexpr (::std::is_signed_v<decltype(check_ret_ret)>)             \
+        {                                                                      \
+            if (check_ret_ret < 0)                                             \
+                ::stdplus::util::doError(-check_ret_ret, (error_handler));     \
+        }                                                                      \
+        else                                                                   \
+        {                                                                      \
+            static_assert(::std::is_same_v<decltype(check_ret_ret), int>,      \
+                          "Unimplemented check routine");                      \
+        }                                                                      \
+        return check_ret_ret;                                                  \
+    }((expr))
+
+namespace stdplus
+{
+namespace util
+{
+
+/** @brief   Common pattern used by default for constructing a system exception
+ *  @details Most libc or system calls will want to return a generic
+ *           system_error when detecting an error in a call. This function
+ *           creates that error from the errno and message.
+ *
+ *  @param[in] error -
+ *  @param[in] msg   -
+ *  @return The exception passed to a `throw` call.
+ */
+inline auto makeSystemError(int error, const char* msg)
+{
+    return std::system_error(error, std::generic_category(), msg);
+}
+
+/** @brief Turns errors into exceptions for the given error handler
+ *
+ *  @param[in] error - The numeric system error code
+ *  @param[in] msg   - The string used to describe the error
+ *  @throws A system exception with the given error and msg
+ */
+inline void doError(int error, const char* msg)
+{
+    throw makeSystemError(error, msg);
+}
+inline void doError(int error, const std::string& msg)
+{
+    throw makeSystemError(error, msg.c_str());
+}
+
+/** @brief Turns errors into exceptions for the given error handler
+ *
+ *  @param[in] error - The numeric system error code
+ *  @param[in] fun   - the function used to throw the error
+ */
+template <typename Fun>
+inline std::enable_if_t<std::is_invocable_v<Fun, int>, void> doError(int error,
+                                                                     Fun&& fun)
+{
+    fun(error);
+}
+
+/** @brief   Wraps common c style error handling for exception throwing
+ *           This requires the callee to set errno on error.
+ *  @details We often have a pattern in our code for checking errors and
+ *           propagating up exceptions:
+ *
+ *           int c_call(const char* path);
+ *
+ *           int our_cpp_call(const char* path)
+ *           {
+ *               int r = c_call(path);
+ *               if (r < 0)
+ *               {
+ *                   throw std::system_error(errno, std::generic_category(),
+ *                                           "our msg");
+ *               }
+ *               return r;
+ *           }
+ *
+ *           To make that more succinct, we can use callCheckErrno:
+ *
+ *           int our_cpp_call(const char* path)
+ *           {
+ *               return callCheckErrno("our msg", c_call, path);
+ *           }
+ *
+ *  @param[in] msg     - The error message displayed when errno is set.
+ *  @param[in] func    - The wrapped function we invoke
+ *  @param[in] args... - The arguments passed to the function
+ *  @throws std::system_error for an error case.
+ *  @return A successful return value based on the function type
+ */
+template <auto(*makeError)(int, const char*) = makeSystemError,
+          typename... Args>
+inline auto callCheckErrno(const char* msg, Args&&... args)
+{
+    using Ret = typename std::invoke_result<Args...>::type;
+
+    if constexpr (std::is_integral_v<Ret> && std::is_signed_v<Ret>)
+    {
+        Ret r = std::invoke(std::forward<Args>(args)...);
+        if (r < 0)
+            throw makeError(errno, msg);
+        return r;
+    }
+    else if constexpr (std::is_pointer_v<Ret>)
+    {
+        Ret r = std::invoke(std::forward<Args>(args)...);
+        if (r == nullptr)
+            throw makeError(errno, msg);
+        return r;
+    }
+    else
+    {
+        static_assert(std::is_same_v<Ret, int>, "Unimplemented check routine");
+    }
+}
+template <auto(*makeError)(int, const char*) = makeSystemError,
+          typename... Args>
+inline auto callCheckErrno(const std::string& msg, Args&&... args)
+{
+    return callCheckErrno(msg.c_str(), std::forward<Args>(args)...);
+}
+
+/** @brief   Wraps common c style error handling for exception throwing
+ *           This requires the callee to provide error information in -r.
+ *           See callCheckErrno() for details.
+ *
+ *  @param[in] msg     - The error message displayed when errno is set.
+ *  @param[in] func    - The wrapped function we invoke
+ *  @param[in] args... - The arguments passed to the function
+ *  @throws std::system_error for an error case.
+ *  @return A successful return value based on the function type
+ */
+template <auto(*makeError)(int, const char*) = makeSystemError,
+          typename... Args>
+inline auto callCheckRet(const char* msg, Args&&... args)
+{
+    using Ret = typename std::invoke_result<Args...>::type;
+
+    if constexpr (std::is_integral_v<Ret> && std::is_signed_v<Ret>)
+    {
+        Ret r = std::invoke(std::forward<Args>(args)...);
+        if (r < 0)
+            throw makeError(-r, msg);
+        return r;
+    }
+    else
+    {
+        static_assert(std::is_same_v<Ret, int>, "Unimplemented check routine");
+    }
+}
+template <auto(*makeError)(int, const char*) = makeSystemError,
+          typename... Args>
+inline auto callCheckRet(const std::string& msg, Args&&... args)
+{
+    return callCheckRet(msg.c_str(), std::forward<Args>(args)...);
+}
+
+} // namespace util
+} // namespace stdplus
diff --git a/include/stdplus/util/string.hpp b/include/stdplus/util/string.hpp
new file mode 100644
index 0000000..fad0d7a
--- /dev/null
+++ b/include/stdplus/util/string.hpp
@@ -0,0 +1,78 @@
+#pragma once
+#include <cstring>
+#include <string>
+#include <string_view>
+#include <utility>
+
+namespace stdplus
+{
+namespace util
+{
+namespace detail
+{
+
+template <typename... Views>
+void strAppendViews(std::string& dst, Views... views)
+{
+    dst.reserve((dst.size() + ... + views.size()));
+    (dst.append(views), ...);
+}
+
+} // namespace detail
+
+/** @brief Converts the string into its underlying nul-terminated c-str
+ *
+ *  @param[in] str - The string reference
+ *  @return The c-str
+ */
+template <typename Str, typename = std::enable_if_t<
+                            std::is_same_v<std::remove_cv_t<Str>, std::string>>>
+auto cStr(Str& str)
+{
+    return str.data();
+}
+template <
+    typename Str,
+    typename = std::enable_if_t<
+        std::is_pointer_v<Str> &&
+        std::is_same_v<std::remove_cv_t<std::remove_pointer_t<Str>>, char>>>
+auto cStr(Str str)
+{
+    return str;
+}
+
+/** @brief Appends multiple strings to the end of the destination string
+ *         in the most optimal way for the given inputs.
+ *
+ *  @param[in, out] dst - The string being appended to
+ *  @param[in] ...strs  - An arbitrary number of strings to concatenate
+ */
+template <typename... Strs>
+void strAppend(std::string& dst, const Strs&... strs)
+{
+    detail::strAppendViews(dst, std::string_view(strs)...);
+}
+
+/** @brief Concatenates multiple strings together in the most optimal
+ *         way for the given inputs.
+ *
+ *  @param[in] ...strs - An arbitrary number of strings to concatenate
+ *  @return The concatenated result string
+ */
+template <typename... Strs>
+std::string strCat(const Strs&... strs)
+{
+    std::string ret;
+    strAppend(ret, strs...);
+    return ret;
+}
+template <typename... Strs>
+std::string strCat(std::string&& in, const Strs&... strs)
+{
+    std::string ret = std::move(in);
+    strAppend(ret, strs...);
+    return ret;
+}
+
+} // namespace util
+} // namespace stdplus