exception: Add an exception for holding sd_bus_errors

This enables us to return more error information about the underlying
sd_bus api call that failed. This will make error output more verbose
and enable us to apply more granular filters to specific dbus related
errors.

Change-Id: I3811139011753d7bd596b46cb69da68f7826ed7b
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/sdbusplus/exception.cpp b/sdbusplus/exception.cpp
index e3a5ac9..5e99abb 100644
--- a/sdbusplus/exception.cpp
+++ b/sdbusplus/exception.cpp
@@ -5,6 +5,83 @@
 namespace exception
 {
 
+SdBusError::SdBusError(int error, const char* prefix) :
+    std::system_error(error, std::generic_category()), error(SD_BUS_ERROR_NULL)
+{
+    if (error == ENOMEM ||
+        sd_bus_error_set_errno(&this->error, error) == -ENOMEM)
+    {
+        throw std::bad_alloc();
+    }
+
+    populateMessage(prefix);
+}
+
+SdBusError::SdBusError(sd_bus_error error, const char* prefix) :
+    std::system_error(sd_bus_error_get_errno(&error), std::generic_category()),
+    error(error)
+{
+    populateMessage(prefix);
+}
+
+SdBusError::SdBusError(SdBusError&& other)
+{
+    move(std::move(other));
+}
+
+SdBusError& SdBusError::operator=(SdBusError&& other)
+{
+    if (this != &other)
+    {
+        move(std::move(other));
+    }
+    return *this;
+}
+
+SdBusError::~SdBusError()
+{
+    sd_bus_error_free(&error);
+}
+
+const char* SdBusError::name() const noexcept
+{
+    return error.name;
+}
+
+const char* SdBusError::description() const noexcept
+{
+    return error.message;
+}
+
+const char* SdBusError::what() const noexcept
+{
+    return full_message.c_str();
+}
+
+void SdBusError::populateMessage(const char* prefix)
+{
+    full_message = prefix;
+    if (error.name)
+    {
+        full_message += ": ";
+        full_message += error.name;
+    }
+    if (error.message)
+    {
+        full_message += ": ";
+        full_message += error.message;
+    }
+}
+
+void SdBusError::move(SdBusError&& other)
+{
+    sd_bus_error_free(&error);
+    error = other.error;
+    other.error = SD_BUS_ERROR_NULL;
+
+    full_message = std::move(other.full_message);
+}
+
 const char* InvalidEnumString::name() const noexcept
 {
     return errName;
diff --git a/sdbusplus/exception.hpp b/sdbusplus/exception.hpp
index 37dbac9..06f488e 100644
--- a/sdbusplus/exception.hpp
+++ b/sdbusplus/exception.hpp
@@ -1,6 +1,9 @@
 #pragma once
 
 #include <exception>
+#include <string>
+#include <system_error>
+#include <systemd/sd-bus.h>
 
 namespace sdbusplus
 {
@@ -21,6 +24,37 @@
 {
 };
 
+/** Exception for when an underlying sd_bus method call fails. */
+class SdBusError final : public internal_exception, public std::system_error
+{
+  public:
+    /** Errno must be positive */
+    SdBusError(int error, const char* prefix);
+    /** Becomes the owner of the error */
+    SdBusError(sd_bus_error error, const char* prefix);
+
+    SdBusError(const SdBusError&) = delete;
+    SdBusError& operator=(const SdBusError&) = delete;
+    SdBusError(SdBusError&& other);
+    SdBusError& operator=(SdBusError&& other);
+    virtual ~SdBusError();
+
+    const char* name() const noexcept override;
+    const char* description() const noexcept override;
+    const char* what() const noexcept override;
+
+  private:
+    sd_bus_error error;
+    std::string full_message;
+
+    /** Populates the full_message from the stored
+     *  error and the passed in prefix. */
+    void populateMessage(const char* prefix);
+
+    /** Helper to reduce duplicate move logic */
+    void move(SdBusError&& other);
+};
+
 /** Exception for when an invalid conversion from string to enum is
  *  attempted. */
 struct InvalidEnumString final : public internal_exception