event: add a simple wrapper around sd-event

In order to facilitate stop-conditions on the async::context,
we need to be able to escape the wait on the dbus fd.  sd-event
will allow us to do this and also build other co-routine primitives
in the future (such as `co_await async::delay(1s)` backed by sd-event
timers).  Define a simple wrapper around sd-event here which can
be leveraged by the async framework.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I945416cbce3c98d98135c42cb7ca249a57cb9a60
diff --git a/src/event.cpp b/src/event.cpp
new file mode 100644
index 0000000..e9c11af
--- /dev/null
+++ b/src/event.cpp
@@ -0,0 +1,176 @@
+#include <sys/eventfd.h>
+
+#include <sdbusplus/event.hpp>
+#include <sdbusplus/exception.hpp>
+
+namespace sdbusplus::event
+{
+
+source::source(source&& s)
+{
+    if (&s == this)
+    {
+        return;
+    }
+    ev = std::exchange(s.ev, nullptr);
+    sourcep = std::exchange(s.sourcep, nullptr);
+}
+
+source& source::operator=(source&& s)
+{
+    if (nullptr != sourcep)
+    {
+        auto l = ev->obtain_lock();
+        sd_event_source_unref(sourcep);
+    }
+    ev = std::exchange(s.ev, nullptr);
+    sourcep = std::exchange(s.sourcep, nullptr);
+
+    return *this;
+}
+
+source::~source()
+{
+    if (nullptr != sourcep)
+    {
+        auto l = ev->obtain_lock();
+        sd_event_source_unref(sourcep);
+    }
+}
+
+condition::condition(condition&& c)
+{
+    if (&c == this)
+    {
+        return;
+    }
+
+    condition_source = std::move(c.condition_source);
+    fd = std::exchange(c.fd, -1);
+}
+
+condition& condition::operator=(condition&& c)
+{
+    condition_source = std::move(c.condition_source);
+    if (fd >= 0)
+    {
+        close(fd);
+    }
+    fd = std::exchange(c.fd, -1);
+
+    return *this;
+}
+
+void condition::signal()
+{
+    uint64_t value = 1;
+    auto rc = write(fd, &value, sizeof(value));
+    if (rc < static_cast<decltype(rc)>(sizeof(value)))
+    {
+        throw exception::SdBusError(errno, __func__);
+    }
+}
+
+void condition::ack()
+{
+    uint64_t value = 0;
+    auto rc = read(fd, &value, sizeof(value));
+    if (rc < static_cast<decltype(rc)>(sizeof(value)))
+    {
+        throw exception::SdBusError(errno, __func__);
+    }
+}
+
+event::event()
+{
+    if (auto rc = sd_event_new(&eventp); rc < 0)
+    {
+        throw exception::SdBusError(-rc, __func__);
+    }
+    run_condition = add_condition(run_wakeup, this);
+}
+
+void event::run_one(std::chrono::microseconds timeout)
+{
+    auto l = obtain_lock<false>();
+
+    auto rc = sd_event_run(eventp, static_cast<uint64_t>(timeout.count()));
+    if (rc < 0)
+    {
+        throw exception::SdBusError(-rc, __func__);
+    }
+}
+
+void event::break_run()
+{
+    run_condition.signal();
+}
+
+source event::add_io(int fd, uint32_t events, sd_event_io_handler_t handler,
+                     void* data)
+{
+    auto l = obtain_lock();
+
+    source s{*this};
+
+    auto rc = sd_event_add_io(eventp, &s.sourcep, fd, events, handler, data);
+    if (rc < 0)
+    {
+        throw exception::SdBusError(-rc, __func__);
+    }
+
+    return s;
+}
+
+condition event::add_condition(sd_event_io_handler_t handler, void* data)
+{
+    // We don't need any locks here because we only touch the sd_event
+    // indirectly through `add_io` which handles its own locking.
+
+    auto fd = eventfd(0, 0);
+    if (fd < 0)
+    {
+        throw exception::SdBusError(errno, __func__);
+    }
+
+    try
+    {
+        auto io = add_io(fd, EPOLLIN, handler, data);
+        return {std::move(io), std::move(fd)};
+    }
+    catch (...)
+    {
+        close(fd);
+        throw;
+    }
+}
+
+int event::run_wakeup(sd_event_source*, int, uint32_t, void* data)
+{
+    auto self = static_cast<event*>(data);
+    self->run_condition.ack();
+
+    return 0;
+}
+
+template <bool Signal>
+std::unique_lock<std::recursive_mutex> event::obtain_lock()
+{
+    std::unique_lock<std::recursive_mutex> l{this->lock, std::defer_lock_t()};
+    if constexpr (Signal)
+    {
+        if (!l.try_lock())
+        {
+            run_condition.signal();
+            l.lock();
+        }
+    }
+    else
+    {
+        l.lock();
+    }
+
+    return l;
+}
+
+} // namespace sdbusplus::event