Fix exception handling inside asio coroutine method_handler

By default sdbusplus catches exceptions from method handlers and
translates them to proper error message reply.
See asio/object_server.hpp:method_handler for reference.

This behavior is not kept when coroutine is used. If yield_context
was not yet used - exception is caught properly. In other cases
exception was 'leaking' and left unhandled, effectively crashing app.

Following change introduces exception handling similar to previously
mentioned implementation. In case of exception thrown from handler
in coroutine it is caught and translated to proper D-Bus error reply.

Signed-off-by: Adrian Ambrożewicz <adrian.ambrozewicz@linux.intel.com>
Change-Id: Id86b77577a2547f999fa1a10ca4316ec0a9ac2d1
diff --git a/sdbusplus/asio/object_server.hpp b/sdbusplus/asio/object_server.hpp
index 91b58ee..a9d5fbf 100644
--- a/sdbusplus/asio/object_server.hpp
+++ b/sdbusplus/asio/object_server.hpp
@@ -202,7 +202,26 @@
         boost::asio::spawn(
             io_, [this, b = std::move(b)](boost::asio::yield_context yield) {
                 message::message mcpy{std::move(b)};
-                expandCall(yield, mcpy);
+                std::optional<message::message> err{};
+
+                try
+                {
+                    expandCall(yield, mcpy);
+                }
+                catch (sdbusplus::exception::SdBusError& e)
+                {
+                    // Catch D-Bus error explicitly called by method handler
+                    err = mcpy.new_method_errno(e.get_errno(), e.get_error());
+                }
+                catch (...)
+                {
+                    err = mcpy.new_method_errno(-EIO);
+                }
+
+                if (err)
+                {
+                    err->method_return();
+                }
             });
         return 1;
     }
@@ -240,7 +259,7 @@
         }
         catch (const exception::SdBusError& e)
         {
-            auto ret = m.new_method_errno(-EINVAL);
+            auto ret = m.new_method_errno(e.get_errno(), e.get_error());
             ret.method_return();
             return;
         }
diff --git a/sdbusplus/exception.cpp b/sdbusplus/exception.cpp
index a0ecc8d..3727122 100644
--- a/sdbusplus/exception.cpp
+++ b/sdbusplus/exception.cpp
@@ -73,6 +73,11 @@
     return intf->sd_bus_error_get_errno(&this->error);
 }
 
+const sd_bus_error* SdBusError::get_error() const noexcept
+{
+    return &error;
+}
+
 void SdBusError::populateMessage(const char* prefix)
 {
     full_message = prefix;
diff --git a/sdbusplus/exception.hpp b/sdbusplus/exception.hpp
index 2eca532..5e49113 100644
--- a/sdbusplus/exception.hpp
+++ b/sdbusplus/exception.hpp
@@ -46,6 +46,7 @@
     const char* description() const noexcept override;
     const char* what() const noexcept override;
     int get_errno() const noexcept;
+    const sd_bus_error* get_error() const noexcept;
 
   private:
     sd_bus_error error;
diff --git a/sdbusplus/message.hpp b/sdbusplus/message.hpp
index b56921b..f7eecc8 100644
--- a/sdbusplus/message.hpp
+++ b/sdbusplus/message.hpp
@@ -315,13 +315,14 @@
     /** @brief Create a 'method_error' type message from an existing message.
      *
      *  @param[in] error - integer error number
+     *  @param[in] e - optional pointer to preformatted sd_bus_error
      *  @return method-error message.
      */
-    message new_method_errno(int error)
+    message new_method_errno(int error, const sd_bus_error* e = nullptr)
     {
         msgp_t reply = nullptr;
         int r = _intf->sd_bus_message_new_method_errno(this->get(), &reply,
-                                                       error, nullptr);
+                                                       error, e);
         if (r < 0)
         {
             throw exception::SdBusError(-r, "sd_bus_message_new_method_errno");