add async fd sender receiver for coroutines

Add async sender receiver for file descriptor based events. The user of
the async file descriptor needs to initialize a fdio instance and then
call next() to get each new event for the fd.

Tested:
```
> meson test -C builddir test_async_fdio
ninja: Entering directory `/host/repos/sdbusplus/builddir'
ninja: no work to do.
1/1 test_async_fdio        OK              6.01s

Ok:                 1
Expected Fail:      0
Fail:               0
Unexpected Pass:    0
Skipped:            0
Timeout:            0

Full log written to /host/repos/sdbusplus/builddir/meson-logs/testlog.txt
```

Change-Id: I1b4f16963e6096f30484c4a6df471e64ed24448b
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/src/async/fdio.cpp b/src/async/fdio.cpp
new file mode 100644
index 0000000..641d6a6
--- /dev/null
+++ b/src/async/fdio.cpp
@@ -0,0 +1,72 @@
+#include <sdbusplus/async/fdio.hpp>
+
+namespace sdbusplus::async
+{
+fdio::fdio(context& ctx, int fd) : context_ref(ctx), fd(fd)
+{
+    static auto eventHandler =
+        [](sd_event_source*, int, uint32_t, void* data) noexcept {
+            static_cast<fdio*>(data)->handleEvent();
+            return 0;
+        };
+
+    try
+    {
+        source = event_loop().add_io(fd, EPOLLIN, eventHandler, this);
+    }
+    catch (...)
+    {
+        throw std::runtime_error("Failed to add fd to event loop");
+    }
+}
+
+void fdio::handleEvent() noexcept
+{
+    std::unique_lock l{lock};
+    if (complete == nullptr)
+    {
+        return;
+    }
+    auto c = std::exchange(complete, nullptr);
+    l.unlock();
+    c->complete();
+}
+
+namespace fdio_ns
+{
+
+fdio_completion::~fdio_completion()
+{
+    std::unique_lock l{fdioInstance.lock};
+
+    if (fdioInstance.complete == this)
+    {
+        std::exchange(fdioInstance.complete, nullptr);
+    }
+}
+
+void fdio_completion::arm() noexcept
+{
+    // Set ourselves as the awaiting Receiver
+    std::unique_lock l{fdioInstance.lock};
+
+    if (std::exchange(fdioInstance.complete, this) != nullptr)
+    {
+        // We do not support two awaiters; throw exception. Since we are in
+        // a noexcept context this will std::terminate anyhow, which is
+        // approximately the same as 'assert' but with better information.
+        try
+        {
+            throw std::logic_error(
+                "fdio_completion started with another await already pending!");
+        }
+        catch (...)
+        {
+            std::terminate();
+        }
+    }
+}
+
+} // namespace fdio_ns
+
+} // namespace sdbusplus::async