sched-host-tran: store/restore the value of scheduled time and
requested transition

Store/restore the value of scheduled time and requested transition, in case
BMC is rebooted. After BMC is back, go on the host transition process based
on the stored values.

Tested:
1. Get current time
 # date
   Tue Mar 10 08:16:40 UTC 2020
2. Set scheduled time 08:25:00
 # busctl set-property xyz.openbmc_project.State.ScheduledHostTransition \
   /xyz/openbmc_project/state/host0 \
   xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime t 1583828700
3. Reboot BMC
4. Check sheduled time
 # busctl get-property xyz.openbmc_project.State.ScheduledHostTransition \
   /xyz/openbmc_project/state/host0 \
   xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime
   t 1583828700
5. Do host transition after the scheduled time is reached.
6. Check the scheduled time again
  # busctl get-property xyz.openbmc_project.State.ScheduledHostTransition \
    /xyz/openbmc_project/state/host0 \
    xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime
    t 0

Change-Id: I3bbae19a49e2fe84bf4e297e6daaa0461cbf2cb8
Signed-off-by: Carol Wang <wangkair@cn.ibm.com>
diff --git a/meson.build b/meson.build
index 790677d..fb24d76 100644
--- a/meson.build
+++ b/meson.build
@@ -32,6 +32,8 @@
 conf.set_quoted(
     'CHASSIS_STATE_CHANGE_PERSIST_PATH', get_option('chassis-state-change-persist-path'))
 conf.set_quoted(
+    'SCHEDULED_HOST_TRANSITION_PERSIST_PATH', get_option('scheduled-host-transition-persist-path'))
+conf.set_quoted(
     'SCHEDULED_HOST_TRANSITION_BUSNAME', get_option('scheduled-host-transition-busname'))
 conf.set(
     'BOOT_COUNT_MAX_ALLOWED', get_option('boot-count-max-allowed'))
diff --git a/meson_options.txt b/meson_options.txt
index 443e85c..385323d 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -61,6 +61,12 @@
 )
 
 option(
+    'scheduled-host-transition-persist-path', type: 'string',
+    value: '/var/lib/phosphor-state-manager/scheduledHostTransition',
+    description: 'Path of file for storing the scheduled time and the requested transition.',
+)
+
+option(
     'boot-count-max-allowed', type: 'integer',
     value: 3,
     description: 'The maximum allowed reboot count.',
diff --git a/scheduled_host_transition.cpp b/scheduled_host_transition.cpp
index 9fdd32b..6ff28ac 100644
--- a/scheduled_host_transition.cpp
+++ b/scheduled_host_transition.cpp
@@ -4,7 +4,10 @@
 #include <phosphor-logging/elog.hpp>
 #include <phosphor-logging/log.hpp>
 #include <xyz/openbmc_project/ScheduledTime/error.hpp>
+#include <cereal/archives/json.hpp>
 #include <chrono>
+#include <filesystem>
+#include <fstream>
 #include <sys/timerfd.h>
 #include <unistd.h>
 
@@ -24,6 +27,8 @@
 namespace manager
 {
 
+namespace fs = std::filesystem;
+
 using namespace std::chrono;
 using namespace phosphor::logging;
 using namespace xyz::openbmc_project::ScheduledTime;
@@ -48,10 +53,9 @@
         if (timer.isEnabled())
         {
             timer.setEnabled(false);
+            log<level::INFO>("scheduledTime: The function Scheduled Host "
+                             "Transition is disabled.");
         }
-
-        log<level::INFO>("scheduledTime: The function Scheduled Host "
-                         "Transition is disabled.");
     }
     else
     {
@@ -71,8 +75,12 @@
         }
     }
 
-    // Set and return the scheduled time
-    return HostTransition::scheduledTime(value);
+    // Set scheduledTime
+    HostTransition::scheduledTime(value);
+    // Store scheduled values
+    serializeScheduledValues();
+
+    return value;
 }
 
 seconds ScheduledHostTransition::getTime()
@@ -149,8 +157,9 @@
     // Stop timer, since we need to do host transition once only
     timer.setEnabled(false);
     hostTransition();
-    // Set scheduledTime to 0 to disable host transition
-    HostTransition::scheduledTime(0);
+    // Set scheduledTime to 0 to disable host transition and update scheduled
+    // values
+    scheduledTime(0);
 }
 
 void ScheduledHostTransition::initialize()
@@ -209,12 +218,13 @@
 
 void ScheduledHostTransition::handleTimeUpdates()
 {
-    if (!timer.isEnabled())
+    // Stop the timer if it's running.
+    // Don't return directly when timer is stopped, because the timer is always
+    // disabled after the BMC is rebooted.
+    if (timer.isEnabled())
     {
-        return;
+        timer.setEnabled(false);
     }
-    // Stop the timer if it's running
-    timer.setEnabled(false);
 
     // Get scheduled time
     auto schedTime = HostTransition::scheduledTime();
@@ -229,11 +239,10 @@
     auto deltaTime = seconds(schedTime) - getTime();
     if (deltaTime <= seconds(0))
     {
-        // When BMC time is changed to be later than scheduled time, check the
-        // state of host transition to decide whether need to do host transition
         hostTransition();
-        // Set scheduledTime to 0 to disable host transition
-        HostTransition::scheduledTime(0);
+        // Set scheduledTime to 0 to disable host transition and update
+        // scheduled values
+        scheduledTime(0);
     }
     else
     {
@@ -261,6 +270,60 @@
     return 0;
 }
 
+void ScheduledHostTransition::serializeScheduledValues()
+{
+    fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
+    std::ofstream os(path.c_str(), std::ios::binary);
+    cereal::JSONOutputArchive oarchive(os);
+
+    oarchive(HostTransition::scheduledTime(),
+             HostTransition::scheduledTransition());
+}
+
+bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
+                                                         Transition& trans)
+{
+    fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
+
+    try
+    {
+        if (fs::exists(path))
+        {
+            std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
+            cereal::JSONInputArchive iarchive(is);
+            iarchive(time, trans);
+            return true;
+        }
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(e.what());
+        fs::remove(path);
+    }
+
+    return false;
+}
+
+void ScheduledHostTransition::restoreScheduledValues()
+{
+    uint64_t time;
+    Transition trans;
+    if (!deserializeScheduledValues(time, trans))
+    {
+        // set to default value
+        HostTransition::scheduledTime(0);
+        HostTransition::scheduledTransition(Transition::On);
+    }
+    else
+    {
+        HostTransition::scheduledTime(time);
+        HostTransition::scheduledTransition(trans);
+        // Rebooting BMC is something like the BMC time is changed,
+        // so go on with the same process as BMC time changed.
+        handleTimeUpdates();
+    }
+}
+
 } // namespace manager
 } // namespace state
 } // namespace phosphor
diff --git a/scheduled_host_transition.hpp b/scheduled_host_transition.hpp
index a17ca3b..b42c5ae 100644
--- a/scheduled_host_transition.hpp
+++ b/scheduled_host_transition.hpp
@@ -31,11 +31,16 @@
   public:
     ScheduledHostTransition(sdbusplus::bus::bus& bus, const char* objPath,
                             const sdeventplus::Event& event) :
-        ScheduledHostTransitionInherit(bus, objPath),
+        ScheduledHostTransitionInherit(bus, objPath, true),
         bus(bus), event(event),
         timer(event, std::bind(&ScheduledHostTransition::callback, this))
     {
         initialize();
+
+        restoreScheduledValues();
+
+        // We deferred this until we could get our property correct
+        this->emit_object_added();
     }
 
     ~ScheduledHostTransition();
@@ -111,6 +116,22 @@
 
     /** @brief Handle with the process when bmc time is changed*/
     void handleTimeUpdates();
+
+    /** @brief Serialize the scheduled values */
+    void serializeScheduledValues();
+
+    /** @brief Deserialize the scheduled values
+     *
+     *  @param[out] time - Deserialized scheduled time
+     *  @param[out] trans - Deserialized requested transition
+     *
+     *  @return bool - true if successful, false otherwise
+     */
+    bool deserializeScheduledValues(uint64_t& time, Transition& trans);
+
+    /** @brief Restore scheduled time and requested transition from persisted
+     * file */
+    void restoreScheduledValues();
 };
 } // namespace manager
 } // namespace state
diff --git a/scheduled_host_transition_main.cpp b/scheduled_host_transition_main.cpp
index 4cd2436..61ab47a 100644
--- a/scheduled_host_transition_main.cpp
+++ b/scheduled_host_transition_main.cpp
@@ -1,11 +1,14 @@
 #include <cstdlib>
 #include <exception>
 #include <sdbusplus/bus.hpp>
+#include <filesystem>
 #include "config.h"
 #include "scheduled_host_transition.hpp"
 
 int main()
 {
+    namespace fs = std::filesystem;
+
     // Get a default event loop
     auto event = sdeventplus::Event::get_default();
 
@@ -15,6 +18,13 @@
     // For now, we only have one instance of the host
     auto objPathInst = std::string{HOST_OBJPATH} + '0';
 
+    // Check SCHEDULED_HOST_TRANSITION_PERSIST_PATH
+    auto dir = fs::path(SCHEDULED_HOST_TRANSITION_PERSIST_PATH).parent_path();
+    if (!fs::exists(dir))
+    {
+        fs::create_directories(dir);
+    }
+
     // Add sdbusplus ObjectManager.
     sdbusplus::server::manager::manager objManager(bus, objPathInst.c_str());