Persist user requested host state

Resolves openbmc/openbmc#1785

Change-Id: I5f23ce50dc357489c7b7eece8bab3bfd6a61ffae
Signed-off-by: Dhruvaraj Subhashchandran <dhruvaraj@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index f15d970..0cea407 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,7 +9,8 @@
 
 phosphor_host_state_manager_SOURCES = \
 	host_state_manager.cpp \
-	host_state_manager_main.cpp
+	host_state_manager_main.cpp \
+        host_state_serialize.cpp
 
 phosphor_chassis_state_manager_SOURCES = \
 	chassis_state_manager.cpp \
@@ -29,7 +30,7 @@
 generic_ldflags = $(SYSTEMD_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) $(SDBUSPLUS_LIBS)
 
 phosphor_host_state_manager_CXXFLAGS = $(generic_cxxflags)
-phosphor_host_state_manager_LDFLAGS = $(generic_ldflags)
+phosphor_host_state_manager_LDFLAGS = $(generic_ldflags) -lstdc++fs
 
 phosphor_chassis_state_manager_CXXFLAGS = $(generic_cxxflags)
 phosphor_chassis_state_manager_LDFLAGS = $(generic_ldflags)
diff --git a/configure.ac b/configure.ac
index b6ee1da..eb1ac58 100644
--- a/configure.ac
+++ b/configure.ac
@@ -52,6 +52,12 @@
 AS_IF([test "x$HOST_RUNNING_FILE" == "x"], [HOST_RUNNING_FILE="/run/openbmc/host@%u-on"])
 AC_DEFINE_UNQUOTED([HOST_RUNNING_FILE], ["$HOST_RUNNING_FILE"], [File to create if host is running])
 
+AC_ARG_VAR(HOST_STATE_PERSIST_PATH, [Path of file for storing requested host state.])
+AS_IF([test "x$HOST_STATE_PERSIST_PATH" == "x"], \
+    [HOST_STATE_PERSIST_PATH="/var/lib/phosphor-state-manager/requestedHostTransition"])
+AC_DEFINE_UNQUOTED([HOST_STATE_PERSIST_PATH], ["$HOST_STATE_PERSIST_PATH"], \
+    [Path of file for storing requested host state.])
+
 # Check for header files.
 AC_CHECK_HEADER(systemd/sd-bus.h, ,[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd developement package required])])
 AC_CHECK_HEADER(sdbusplus/server.hpp, ,[AC_MSG_ERROR([Could not find sdbusplus/server.hpp...openbmc/sdbusplus package required])])
diff --git a/host_state_manager.cpp b/host_state_manager.cpp
index fd1d056..da42918 100644
--- a/host_state_manager.cpp
+++ b/host_state_manager.cpp
@@ -4,7 +4,12 @@
 #include <systemd/sd-bus.h>
 #include <sdbusplus/server.hpp>
 #include <phosphor-logging/log.hpp>
+#include <experimental/filesystem>
+#include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp>
 #include "host_state_manager.hpp"
+#include "host_state_serialize.hpp"
+#include "config.h"
+
 
 namespace phosphor
 {
@@ -17,6 +22,7 @@
 namespace server = sdbusplus::xyz::openbmc_project::State::server;
 
 using namespace phosphor::logging;
+namespace fs = std::experimental::filesystem;
 
 // host-shutdown notifies host of shutdown and that leads to host-stop being
 // called so initiate a host shutdown with the -shutdown target and consider the
@@ -51,6 +57,11 @@
 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit";
 
+constexpr auto SETTINGS_INTERFACE =
+               "xyz.openbmc_project.Control.Power.RestorePolicy";
+constexpr auto SETTINGS_SERVICE_ROOT = "/";
+constexpr auto SETTINGS_HOST_STATE_RESTORE = "PowerRestorePolicy";
+
 // TODO openbmc/openbmc#1646 - boot count needs to be defined in 1 place
 constexpr auto DEFAULT_BOOTCOUNT = 3;
 
@@ -92,13 +103,77 @@
         server::Host::requestedHostTransition(Transition::Off);
     }
 
-    // Set transition initially to Off
-    // TODO - Eventually need to restore this from persistent storage
-    server::Host::requestedHostTransition(Transition::Off);
+    auto restore = getStateRestoreSetting();
+
+    if ((!restore) || (!deserialize(HOST_STATE_PERSIST_PATH,*this)))
+    {
+        //set to default value.
+        server::Host::requestedHostTransition(Transition::Off);
+    }
 
     return;
 }
 
+bool Host::getStateRestoreSetting() const
+{
+    using namespace phosphor::logging;
+    auto depth = 0;
+    auto mapperCall = bus.new_method_call(MAPPER_BUSNAME,
+                                          MAPPER_PATH,
+                                          MAPPER_INTERFACE,
+                                          "GetSubTree");
+    mapperCall.append(SETTINGS_SERVICE_ROOT);
+    mapperCall.append(depth);
+    mapperCall.append(std::vector<std::string>({SETTINGS_INTERFACE}));
+
+    auto mapperResponseMsg = bus.call(mapperCall);
+    if (mapperResponseMsg.is_method_error())
+    {
+        log<level::ERR>("Error in mapper call");
+        return false;
+    }
+
+    using MapperResponseType = std::map<std::string,
+                               std::map<std::string, std::vector<std::string>>>;
+    MapperResponseType mapperResponse;
+    mapperResponseMsg.read(mapperResponse);
+    if (mapperResponse.empty())
+    {
+        log<level::ERR>("Invalid response from mapper");
+        return false;
+    }
+
+    auto& settingsPath = mapperResponse.begin()->first;
+    auto& service = mapperResponse.begin()->second.begin()->first;
+
+    auto cmdMsg  =  bus.new_method_call(service.c_str(),
+                                        settingsPath.c_str(),
+                                        SYSTEMD_PROPERTY_IFACE,
+                                        "Get");
+    cmdMsg.append(SETTINGS_INTERFACE);
+    cmdMsg.append(SETTINGS_HOST_STATE_RESTORE);
+
+    auto response = bus.call(cmdMsg);
+    if (response.is_method_error())
+    {
+        log<level::ERR>("Error in fetching host state restore settings");
+        return false;
+    }
+
+    sdbusplus::message::variant<std::string> result;
+    response.read(result);
+
+    using RestorePolicy = sdbusplus::xyz::openbmc_project::Control::
+         Power::server::RestorePolicy;
+
+    if (RestorePolicy::convertPolicyFromString(result.get<std::string>()) ==
+        RestorePolicy::Policy::Restore)
+    {
+        return true;
+    }
+    return false;
+}
+
 void Host::executeTransition(Transition tranReq)
 {
     auto sysdUnit = SYSTEMD_TARGET_TABLE.find(tranReq)->second;
@@ -130,7 +205,7 @@
     auto result = this->bus.call(method);
 
     //Check that the bus call didn't result in an error
-    if(result.is_method_error())
+    if (result.is_method_error())
     {
         log<level::ERR>("Error in bus call - could not resolve GetUnit for:",
                         entry(" %s", SYSTEMD_INTERFACE));
@@ -149,7 +224,7 @@
     result = this->bus.call(method);
 
     //Check that the bus call didn't result in an error
-    if(result.is_method_error())
+    if (result.is_method_error())
     {
         log<level::ERR>("Error in bus call - could not resolve Get for:",
                         entry(" %s", SYSTEMD_PROPERTY_IFACE));
@@ -158,7 +233,7 @@
 
     result.read(currentState);
 
-    if(currentState != ACTIVE_STATE && currentState != ACTIVATING_STATE)
+    if (currentState != ACTIVE_STATE && currentState != ACTIVATING_STATE)
     {
         //False - not active
         return false;
@@ -249,7 +324,7 @@
 
     if (strParam == "yes")
     {
-        if( rebootCounterParam > 0)
+        if ( rebootCounterParam > 0)
         {
             // Reduce BOOTCOUNT by 1
             log<level::INFO>("Auto reboot enabled. "
@@ -355,7 +430,9 @@
     }
 
     executeTransition(tranReq);
-    return server::Host::requestedHostTransition(value);
+    auto retVal =  server::Host::requestedHostTransition(value);
+    serialize(*this);
+    return retVal;
 }
 
 Host::HostState Host::currentHostState(HostState value)
diff --git a/host_state_manager.hpp b/host_state_manager.hpp
index de9d5fb..af4fa15 100644
--- a/host_state_manager.hpp
+++ b/host_state_manager.hpp
@@ -127,6 +127,12 @@
          */
         void sysStateChange(sdbusplus::message::message& msg);
 
+        /** @brief Determine whether restoring of host requested state is enabled
+         *
+         * @return boolean corresponding to restore setting
+         */
+        bool getStateRestoreSetting() const;
+
         /** @brief Persistent sdbusplus DBus bus connection. */
         sdbusplus::bus::bus& bus;
 
diff --git a/host_state_manager_main.cpp b/host_state_manager_main.cpp
index 2084f36..12d5e9a 100644
--- a/host_state_manager_main.cpp
+++ b/host_state_manager_main.cpp
@@ -2,11 +2,14 @@
 #include <iostream>
 #include <exception>
 #include <sdbusplus/bus.hpp>
+#include <experimental/filesystem>
 #include "config.h"
 #include "host_state_manager.hpp"
 
 int main(int argc, char *argv[])
 {
+    namespace fs = std::experimental::filesystem;
+
     auto bus = sdbusplus::bus::new_default();
 
     // For now, we only have one instance of the host
@@ -19,6 +22,9 @@
                                            HOST_BUSNAME,
                                            objPathInst.c_str());
 
+    auto dir = fs::path(HOST_STATE_PERSIST_PATH).parent_path();
+    fs::create_directories(dir);
+
     bus.request_name(HOST_BUSNAME);
 
     while(true)
diff --git a/host_state_serialize.cpp b/host_state_serialize.cpp
new file mode 100644
index 0000000..d5a3a98
--- /dev/null
+++ b/host_state_serialize.cpp
@@ -0,0 +1,68 @@
+#include <cereal/types/string.hpp>
+#include <cereal/types/vector.hpp>
+#include <cereal/types/tuple.hpp>
+#include <cereal/archives/json.hpp>
+#include <fstream>
+#include "host_state_serialize.hpp"
+#include "host_state_manager.hpp"
+
+namespace phosphor
+{
+namespace state
+{
+namespace manager
+{
+/** @brief Function required by Cereal to perform serialization.
+ *  @tparam Archive - Cereal archive type (binary in our case).
+ *  @param[in] archive - reference to Cereal archive.
+ *  @param[in] host - const reference to host.
+ */
+template<class Archive>
+void save(Archive& archive, const Host& host)
+{
+    archive(convertForMessage(host.sdbusplus::xyz::openbmc_project::
+           State::server::Host::requestedHostTransition()));
+}
+
+/** @brief Function required by Cereal to perform deserialization.
+ *  @tparam Archive - Cereal archive type (binary in our case).
+ *  @param[in] archive - reference to Cereal archive.
+ *  @param[in] host - reference to host.
+ */
+template<class Archive>
+void load(Archive& archive, Host& host)
+{
+    using namespace
+        sdbusplus::xyz::openbmc_project::State::server;
+
+    Host::Transition requestedHostTransition{};
+
+    std::string str;
+    archive(str);
+    requestedHostTransition = Host::convertTransitionFromString(
+                 str);
+    host.requestedHostTransition(requestedHostTransition);
+}
+
+fs::path serialize(const Host& host, const fs::path& dir)
+{
+    std::ofstream os(dir.c_str(), std::ios::binary);
+    cereal::JSONOutputArchive oarchive(os);
+    oarchive(host);
+    return dir;
+}
+
+bool deserialize(const fs::path& path, Host& host)
+{
+    if (fs::exists(path))
+    {
+        std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
+        cereal::JSONInputArchive iarchive(is);
+        iarchive(host);
+        return true;
+    }
+    return false;
+}
+} //namespace manager
+} // namespace state
+} // namespace phosphor
diff --git a/host_state_serialize.hpp b/host_state_serialize.hpp
new file mode 100644
index 0000000..0bc7684
--- /dev/null
+++ b/host_state_serialize.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <experimental/filesystem>
+#include "host_state_manager.hpp"
+#include "config.h"
+
+namespace phosphor
+{
+namespace state
+{
+namespace manager
+{
+
+namespace fs = std::experimental::filesystem;
+
+/** @brief Serialize and persist requested host state
+ *  @param[in] host - const reference to host state.
+ *  @param[in] dir - pathname of file where the serialized host state will
+ *                   be placed.
+ *  @return fs::path - pathname of persisted requested host state.
+ */
+fs::path serialize(const Host& host,
+                   const fs::path& dir = fs::path(HOST_STATE_PERSIST_PATH));
+
+/** @brief Deserialze a persisted requested host state.
+ *  @param[in] path - pathname of persisted host state file
+ *  @param[in] host - reference to host state object which is the target of
+ *             deserialization.
+ *  @return bool - true if the deserialization was successful, false otherwise.
+ */
+bool deserialize(const fs::path& path, Host& host);
+
+} // namespace manager
+} // namespace state
+} // namespace phosphor