control: DBusZone object for dbus objects

Create a DBusZone object to handle zones configured to provide the
ThermalMode dbus interface on dbus. Each zone will have an instance of
this object when configured to provide the ThermalMode interface instead
of the zone object doing this directly. This was done to handle SIGHUP
signals that would reload the zone configuration which may or may not
affect what interfaces are put on dbus for each zone object.

Change-Id: I3e78c6d53a9690b9297c71ea3e0852d7d7b01746
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/json/dbus_zone.cpp b/control/json/dbus_zone.cpp
new file mode 100644
index 0000000..03cb0da
--- /dev/null
+++ b/control/json/dbus_zone.cpp
@@ -0,0 +1,107 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "dbus_zone.hpp"
+
+#include "sdbusplus.hpp"
+#include "zone.hpp"
+
+#include <cereal/archives/json.hpp>
+#include <cereal/cereal.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <algorithm>
+#include <filesystem>
+#include <fstream>
+
+namespace phosphor::fan::control::json
+{
+
+using namespace phosphor::logging;
+namespace fs = std::filesystem;
+
+DBusZone::DBusZone(const Zone& zone) :
+    ThermalModeIntf(util::SDBusPlus::getBus(),
+                    (fs::path{CONTROL_OBJPATH} /= zone.getName()).c_str(),
+                    true),
+    _zone(zone)
+{}
+
+std::string DBusZone::current(std::string value)
+{
+    auto current = ThermalModeIntf::current();
+    std::transform(value.begin(), value.end(), value.begin(), toupper);
+
+    auto supported = ThermalModeIntf::supported();
+    auto isSupported =
+        std::any_of(supported.begin(), supported.end(), [&value](auto& s) {
+            std::transform(s.begin(), s.end(), s.begin(), toupper);
+            return value == s;
+        });
+
+    if (isSupported && value != current)
+    {
+        current = ThermalModeIntf::current(value);
+        if (_zone.isPersisted(thermalModeIntf, currentProp))
+        {
+            saveCurrentMode();
+        }
+    }
+
+    return current;
+}
+
+void DBusZone::restoreCurrentMode()
+{
+    auto current = ThermalModeIntf::current();
+    fs::path path{CONTROL_PERSIST_ROOT_PATH};
+    // Append this object's name and property description
+    path /= _zone.getName();
+    path /= "CurrentMode";
+    fs::create_directories(path.parent_path());
+
+    try
+    {
+        if (fs::exists(path))
+        {
+            std::ifstream ifs(path.c_str(), std::ios::in | std::ios::binary);
+            cereal::JSONInputArchive iArch(ifs);
+            iArch(current);
+        }
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(e.what());
+        fs::remove(path);
+        current = ThermalModeIntf::current();
+    }
+
+    this->current(current);
+}
+
+void DBusZone::saveCurrentMode()
+{
+    fs::path path{CONTROL_PERSIST_ROOT_PATH};
+    // Append this object's name and property description
+    path /= _zone.getName();
+    path /= "CurrentMode";
+    std::ofstream ofs(path.c_str(), std::ios::binary);
+    cereal::JSONOutputArchive oArch(ofs);
+    oArch(ThermalModeIntf::current());
+}
+
+} // namespace phosphor::fan::control::json
diff --git a/control/json/dbus_zone.hpp b/control/json/dbus_zone.hpp
new file mode 100644
index 0000000..8dfce1a
--- /dev/null
+++ b/control/json/dbus_zone.hpp
@@ -0,0 +1,77 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "xyz/openbmc_project/Control/ThermalMode/server.hpp"
+
+/* Extend the Control::ThermalMode interface */
+using ThermalModeIntf = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Control::server::ThermalMode>;
+
+namespace phosphor::fan::control::json
+{
+
+class Zone;
+
+class DBusZone : public ThermalModeIntf
+{
+  public:
+    static constexpr auto thermalModeIntf =
+        "xyz.openbmc_project.Control.ThermalMode";
+    static constexpr auto supportedProp = "Supported";
+    static constexpr auto currentProp = "Current";
+
+    DBusZone() = delete;
+    DBusZone(const DBusZone&) = delete;
+    DBusZone(DBusZone&&) = delete;
+    DBusZone& operator=(const DBusZone&) = delete;
+    DBusZone& operator=(DBusZone&&) = delete;
+    ~DBusZone() = default;
+
+    /**
+     * Constructor
+     * Creates a thermal control dbus object associated with the given zone
+     *
+     * @param[in] zone - Zone object
+     */
+    DBusZone(const Zone& zone);
+
+    /**
+     * @brief Overridden thermalmode interface's set 'Current' property function
+     *
+     * @param[in] value - Value to set 'Current' to
+     *
+     * @return - The updated value of the 'Current' property
+     */
+    std::string current(std::string value) override;
+
+    /**
+     * @brief Restore persisted thermalmode `Current` mode property value,
+     * setting the mode to "Default" otherwise
+     */
+    void restoreCurrentMode();
+
+  private:
+    /* Zone object associated with this thermal control dbus object */
+    const Zone& _zone;
+
+    /**
+     * @brief Save the thermalmode `Current` mode property to persisted storage
+     */
+    void saveCurrentMode();
+};
+
+} // namespace phosphor::fan::control::json
diff --git a/control/json/zone.cpp b/control/json/zone.cpp
index d7d1a90..8053a16 100644
--- a/control/json/zone.cpp
+++ b/control/json/zone.cpp
@@ -215,6 +215,18 @@
     }
 }
 
+bool Zone::isPersisted(const std::string& intf, const std::string& prop) const
+{
+    auto it = _propsPersisted.find(intf);
+    if (it == _propsPersisted.end())
+    {
+        return false;
+    }
+
+    return std::any_of(it->second.begin(), it->second.end(),
+                       [&prop](const auto& p) { return prop == p; });
+}
+
 std::string Zone::current(std::string value)
 {
     auto current = ThermalObject::current();
@@ -359,18 +371,6 @@
     }
 }
 
-bool Zone::isPersisted(const std::string& intf, const std::string& prop)
-{
-    auto it = _propsPersisted.find(intf);
-    if (it == _propsPersisted.end())
-    {
-        return false;
-    }
-
-    return std::any_of(it->second.begin(), it->second.end(),
-                       [&prop](const auto& p) { return prop == p; });
-}
-
 void Zone::saveCurrentMode()
 {
     fs::path path{CONTROL_PERSIST_ROOT_PATH};
diff --git a/control/json/zone.hpp b/control/json/zone.hpp
index 6ed90f8..5176c4b 100644
--- a/control/json/zone.hpp
+++ b/control/json/zone.hpp
@@ -296,6 +296,16 @@
     void setPersisted(const std::string& intf, const std::string& prop);
 
     /**
+     * @brief Is the property persisted
+     *
+     * @param[in] intf - Interface containing property
+     * @param[in] prop - Property to check if persisted
+     *
+     * @return - True if property is to be persisted, false otherwise
+     */
+    bool isPersisted(const std::string& intf, const std::string& prop) const;
+
+    /**
      * @brief Overridden thermal object's set 'Current' property function
      *
      * @param[in] value - Value to set 'Current' to
@@ -465,16 +475,6 @@
     void setInterfaces(const json& jsonObj);
 
     /**
-     * @brief Is the property persisted
-     *
-     * @param[in] intf - Interface containing property
-     * @param[in] prop - Property to check if persisted
-     *
-     * @return - True if property is to be persisted, false otherwise
-     */
-    bool isPersisted(const std::string& intf, const std::string& prop);
-
-    /**
      * @brief Save the thermal control current mode property to persisted
      * storage
      */