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