monitor: Support for running with power off

Put in the remaining changes necessary so that fan monitor doesn't need
to be killed when power turns off.

This includes things like:
* Support for starting before the Present property is on D-Bus.
* Support for starting before the config file name is available.
* Stopping any running timers when power is turned off.
* Checking the power off rules when power turns on.

Most, but not all, of the changes are common between the JSON and YAML
modes, but this only truly supported when compiled for JSON.

This also removes the init vs monitor modes of operation, if compiled
for JSON.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ic2c6848f24511c9dc763227e05bbebb4c8c80cd1
diff --git a/monitor/fan.cpp b/monitor/fan.cpp
index 18e9d05..5919d92 100644
--- a/monitor/fan.cpp
+++ b/monitor/fan.cpp
@@ -55,6 +55,12 @@
                                             util::INV_ITEM_IFACE),
                    std::bind(std::mem_fn(&Fan::presenceChanged), this,
                              std::placeholders::_1)),
+    _presenceIfaceAddedMatch(
+        bus,
+        rules::interfacesAdded() +
+            rules::argNpath(0, util::INVENTORY_PATH + _name),
+        std::bind(std::mem_fn(&Fan::presenceIfaceAdded), this,
+                  std::placeholders::_1)),
     _fanMissingErrorDelay(std::get<fanMissingErrDelayField>(def))
 {
     // Start from a known state of functional (even if
@@ -102,41 +108,77 @@
         tachChanged();
     }
 #else
-    // If it used the JSON config, then it also will do all the work
-    // out of fan-monitor-init, after _monitorDelay.
-    _monitorTimer.restartOnce(std::chrono::seconds(_monitorDelay));
+    if (_system.isPowerOn())
+    {
+        _monitorTimer.restartOnce(std::chrono::seconds(_monitorDelay));
+    }
 #endif
 
-    // Get the initial presence state
-    bool available = true;
-
-    try
-    {
-        _present = util::SDBusPlus::getProperty<bool>(
-            util::INVENTORY_PATH + _name, util::INV_ITEM_IFACE, "Present");
-    }
-    catch (const util::DBusServiceError& e)
-    {
-        // This could be the initial boot and phosphor-fan-presence hasn't
-        // written to the inventory yet.
-        available = false;
-    }
-
     if (_fanMissingErrorDelay)
     {
         _fanMissingErrorTimer = std::make_unique<
             sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
             event, std::bind(&System::fanMissingErrorTimerExpired, &system,
                              std::ref(*this)));
+    }
 
-        if (!_present && available)
+    try
+    {
+        _present = util::SDBusPlus::getProperty<bool>(
+            util::INVENTORY_PATH + _name, util::INV_ITEM_IFACE, "Present");
+
+        if (!_present)
         {
             getLogger().log(
                 fmt::format("On startup, fan {} is missing", _name));
+            if (_system.isPowerOn() && _fanMissingErrorTimer)
+            {
+                _fanMissingErrorTimer->restartOnce(
+                    std::chrono::seconds{*_fanMissingErrorDelay});
+            }
+        }
+    }
+    catch (const util::DBusServiceError& e)
+    {
+        // This could happen on the first BMC boot if the presence
+        // detect app hasn't started yet and there isn't an inventory
+        // cache yet.
+    }
+}
+
+void Fan::presenceIfaceAdded(sdbusplus::message::message& msg)
+{
+    sdbusplus::message::object_path path;
+    std::map<std::string, std::map<std::string, std::variant<bool>>> interfaces;
+
+    msg.read(path, interfaces);
+
+    auto properties = interfaces.find(util::INV_ITEM_IFACE);
+    if (properties == interfaces.end())
+    {
+        return;
+    }
+
+    auto property = properties->second.find("Present");
+    if (property == properties->second.end())
+    {
+        return;
+    }
+
+    _present = std::get<bool>(property->second);
+
+    if (!_present)
+    {
+        getLogger().log(fmt::format(
+            "New fan {} interface added and fan is not present", _name));
+        if (_system.isPowerOn() && _fanMissingErrorTimer)
+        {
             _fanMissingErrorTimer->restartOnce(
                 std::chrono::seconds{*_fanMissingErrorDelay});
         }
     }
+
+    _system.fanStatusChange(*this);
 }
 
 void Fan::startMonitor()
@@ -159,6 +201,11 @@
 
 void Fan::tachChanged(TachSensor& sensor)
 {
+    if (!_system.isPowerOn() || !_monitorReady)
+    {
+        return;
+    }
+
     if (_trustManager->active())
     {
         if (!_trustManager->checkTrust(sensor))
@@ -265,6 +312,12 @@
 void Fan::updateState(TachSensor& sensor)
 {
     auto range = sensor.getRange(_deviation);
+
+    if (!_system.isPowerOn())
+    {
+        return;
+    }
+
     sensor.setFunctional(!sensor.functional());
     getLogger().log(
         fmt::format("Setting tach sensor {} functional state to {}. "
@@ -338,12 +391,12 @@
 
         if (_fanMissingErrorDelay)
         {
-            if (!_present)
+            if (!_present && _system.isPowerOn())
             {
                 _fanMissingErrorTimer->restartOnce(
                     std::chrono::seconds{*_fanMissingErrorDelay});
             }
-            else if (_fanMissingErrorTimer->isEnabled())
+            else if (_present && _fanMissingErrorTimer->isEnabled())
             {
                 _fanMissingErrorTimer->setEnabled(false);
             }
@@ -353,12 +406,59 @@
 
 void Fan::sensorErrorTimerExpired(const TachSensor& sensor)
 {
-    if (_present)
+    if (_present && _system.isPowerOn())
     {
         _system.sensorErrorTimerExpired(*this, sensor);
     }
 }
 
+void Fan::powerStateChanged(bool powerStateOn)
+{
+#ifdef MONITOR_USE_JSON
+    if (powerStateOn)
+    {
+        // set all fans back to functional to start with
+        std::for_each(_sensors.begin(), _sensors.end(),
+                      [](auto& sensor) { sensor->setFunctional(true); });
+
+        _monitorTimer.restartOnce(std::chrono::seconds(_monitorDelay));
+
+        if (!_present)
+        {
+            getLogger().log(
+                fmt::format("At power on, fan {} is missing", _name));
+
+            if (_fanMissingErrorTimer)
+            {
+                _fanMissingErrorTimer->restartOnce(
+                    std::chrono::seconds{*_fanMissingErrorDelay});
+            }
+        }
+    }
+    else
+    {
+        _monitorReady = false;
+
+        if (_monitorTimer.isEnabled())
+        {
+            _monitorTimer.setEnabled(false);
+        }
+
+        if (_fanMissingErrorTimer && _fanMissingErrorTimer->isEnabled())
+        {
+            _fanMissingErrorTimer->setEnabled(false);
+        }
+
+        std::for_each(_sensors.begin(), _sensors.end(), [](auto& sensor) {
+            if (sensor->timerRunning())
+            {
+                sensor->stopTimer();
+            }
+        });
+    }
+#endif
+}
+
 } // namespace monitor
 } // namespace fan
 } // namespace phosphor
diff --git a/monitor/fan.hpp b/monitor/fan.hpp
index 5e3802e..45d1586 100644
--- a/monitor/fan.hpp
+++ b/monitor/fan.hpp
@@ -178,6 +178,13 @@
      */
     void process(TachSensor& sensor);
 
+    /**
+     * @brief The function that runs when the power state changes
+     *
+     * @param[in] powerStateOn - If power is now on or not
+     */
+    void powerStateChanged(bool powerStateOn);
+
   private:
     /**
      * @brief Returns true if the sensor input is not within
@@ -215,6 +222,15 @@
     void presenceChanged(sdbusplus::message::message& msg);
 
     /**
+     * @brief Called when there is an interfacesAdded signal on the
+     *        fan D-Bus path so the code can look for the 'Present'
+     *        property value.
+     *
+     * @param[in] msg - The message from the interfacesAdded signal
+     */
+    void presenceIfaceAdded(sdbusplus::message::message& msg);
+
+    /**
      * @brief the dbus object
      */
     sdbusplus::bus::bus& _bus;
@@ -289,6 +305,12 @@
     sdbusplus::bus::match::match _presenceMatch;
 
     /**
+     * @brief The match object for the interfacesAdded signal
+     *        for the interface that has the Present property.
+     */
+    sdbusplus::bus::match::match _presenceIfaceAddedMatch;
+
+    /**
      * @brief The current presence state
      */
     bool _present = false;
diff --git a/monitor/main.cpp b/monitor/main.cpp
index ae6d034..fc19827 100644
--- a/monitor/main.cpp
+++ b/monitor/main.cpp
@@ -15,8 +15,14 @@
  */
 #include "config.h"
 
+#ifndef MONITOR_USE_JSON
 #include "argument.hpp"
+#endif
 #include "fan.hpp"
+#ifdef MONITOR_USE_JSON
+#include "json_config.hpp"
+#include "json_parser.hpp"
+#endif
 #include "system.hpp"
 #include "trust_manager.hpp"
 
@@ -31,6 +37,9 @@
 {
     auto event = sdeventplus::Event::get_default();
     auto bus = sdbusplus::bus::new_default();
+    Mode mode = Mode::init;
+
+#ifndef MONITOR_USE_JSON
     phosphor::fan::util::ArgumentParser args(argc, argv);
 
     if (argc != 2)
@@ -39,7 +48,6 @@
         return 1;
     }
 
-    Mode mode;
     if (args["init"] == "true")
     {
         mode = Mode::init;
@@ -53,15 +61,6 @@
         args.usage(argv);
         return 1;
     }
-
-    // If using JSON, then everything is handled in a single
-    // step - the init step.  Hopefully these can eventually be
-    // reduced into a single invocation.
-#ifdef MONITOR_USE_JSON
-    if (mode == Mode::monitor)
-    {
-        return 0;
-    }
 #endif
 
     // Attach the event object to the bus object so we can
@@ -71,17 +70,21 @@
     System system(mode, bus, event);
 
 #ifdef MONITOR_USE_JSON
+
+    phosphor::fan::JsonConfig config(
+        bus, confAppName, confFileName,
+        std::bind(&System::start, &system, std::placeholders::_1));
+
     // Enable SIGHUP handling to reload JSON config
     stdplus::signal::block(SIGHUP);
     sdeventplus::source::Signal signal(event, SIGHUP,
                                        std::bind(&System::sighupHandler,
                                                  &system, std::placeholders::_1,
                                                  std::placeholders::_2));
-
     bus.request_name(THERMAL_ALERT_BUSNAME);
-#endif
+#else
+    system.start();
 
-#ifndef MONITOR_USE_JSON
     if (mode == Mode::init)
     {
         // Fans were initialized to be functional, exit
diff --git a/monitor/power_off_rule.hpp b/monitor/power_off_rule.hpp
index 72d0052..3172bd5 100644
--- a/monitor/power_off_rule.hpp
+++ b/monitor/power_off_rule.hpp
@@ -76,35 +76,33 @@
      */
     void check(PowerRuleState state, const FanHealth& fanHealth)
     {
-        if (state == _validState)
+        auto satisfied = _cause->satisfied(fanHealth);
+
+        // Only start an action if it matches on the current state,
+        // but be able to stop it no matter what the state is.
+        if (!_active && satisfied && (state == _validState))
         {
-            auto satisfied = _cause->satisfied(fanHealth);
+            // Start the action
+            getLogger().log(
+                fmt::format("Starting shutdown action '{}' due to cause '{}'",
+                            _action->name(), _cause->name()));
 
-            if (!_active && satisfied)
+            _active = true;
+            _action->start();
+        }
+        else if (_active && !satisfied)
+        {
+            // Attempt to cancel the action, but don't force it
+            if (_action->cancel(false))
             {
-                // Start the action
-                getLogger().log(fmt::format(
-                    "Starting shutdown action '{}' due to cause '{}'",
-                    _action->name(), _cause->name()));
-
-                _active = true;
-                _action->start();
+                getLogger().log(fmt::format("Stopped shutdown action '{}'",
+                                            _action->name()));
+                _active = false;
             }
-            else if (_active && !satisfied)
+            else
             {
-                // Attempt to cancel the action, but don't force it
-                if (_action->cancel(false))
-                {
-                    getLogger().log(fmt::format("Stopped shutdown action '{}'",
-                                                _action->name()));
-                    _active = false;
-                }
-                else
-                {
-                    getLogger().log(
-                        fmt::format("Could not stop shutdown action '{}'",
-                                    _action->name()));
-                }
+                getLogger().log(fmt::format(
+                    "Could not stop shutdown action '{}'", _action->name()));
             }
         }
     }
diff --git a/monitor/system.cpp b/monitor/system.cpp
index 4c30f9f..44da0ba 100644
--- a/monitor/system.cpp
+++ b/monitor/system.cpp
@@ -48,11 +48,18 @@
         bus, std::bind(std::mem_fn(&System::powerStateChanged), this,
                        std::placeholders::_1))),
     _thermalAlert(bus, THERMAL_ALERT_OBJPATH)
-{
+{}
 
+void System::start(
+#ifdef MONITOR_USE_JSON
+    const std::string& confFile
+#endif
+)
+{
+    _started = true;
     json jsonObj = json::object();
 #ifdef MONITOR_USE_JSON
-    jsonObj = getJsonObj(bus);
+    jsonObj = fan::JsonConfig::load(confFile);
 #endif
     // Retrieve and set trust groups within the trust manager
     setTrustMgr(getTrustGroups(jsonObj));
@@ -61,20 +68,10 @@
     setFaultConfig(jsonObj);
     log<level::INFO>("Configuration loaded");
 
-    // Since this doesn't run at standby yet, powerStateChanged
-    // will never be called so for now treat start up as the
-    // pgood.  When this does run at standby, the 'atPgood'
-    // rules won't need to be checked here.
     if (_powerState->isPowerOn())
     {
         std::for_each(_powerOffRules.begin(), _powerOffRules.end(),
                       [this](auto& rule) {
-                          rule->check(PowerRuleState::atPgood, _fanHealth);
-                      });
-        // Runtime rules still need to be checked since fans may already
-        // be missing that could trigger a runtime rule.
-        std::for_each(_powerOffRules.begin(), _powerOffRules.end(),
-                      [this](auto& rule) {
                           rule->check(PowerRuleState::runtime, _fanHealth);
                       });
     }
@@ -202,8 +199,18 @@
 
 void System::powerStateChanged(bool powerStateOn)
 {
+    std::for_each(_fans.begin(), _fans.end(), [powerStateOn](auto& fan) {
+        fan->powerStateChanged(powerStateOn);
+    });
+
     if (powerStateOn)
     {
+        if (!_started)
+        {
+            log<level::ERR>("No conf file found at power on");
+            throw std::runtime_error("No conf file fount at power on");
+        }
+
         std::for_each(_powerOffRules.begin(), _powerOffRules.end(),
                       [this](auto& rule) {
                           rule->check(PowerRuleState::atPgood, _fanHealth);
diff --git a/monitor/system.hpp b/monitor/system.hpp
index a0030da..e01d81a 100644
--- a/monitor/system.hpp
+++ b/monitor/system.hpp
@@ -49,7 +49,6 @@
 
     /**
      * Constructor
-     * Parses and populates the fan monitor trust groups and list of fans
      *
      * @param[in] mode - mode of fan monitor
      * @param[in] bus - sdbusplus bus object
@@ -100,6 +99,26 @@
      */
     void logShutdownError();
 
+    /**
+     * @brief Returns true if power is on
+     */
+    bool isPowerOn() const
+    {
+        return _powerState->isPowerOn();
+    }
+
+    /**
+     * @brief Parses and populates the fan monitor
+     *        trust groups and list of fans
+     *
+     * @param[in] confFile - The config file path
+     */
+    void start(
+#ifdef MONITOR_USE_JSON
+        const std::string& confFile
+#endif
+    );
+
   private:
     /* The mode of fan monitor */
     Mode _mode;
@@ -151,6 +170,11 @@
     ThermalAlertObject _thermalAlert;
 
     /**
+     * @brief If start() has been called
+     */
+    bool _started = false;
+
+    /**
      * @brief Captures tach sensor data as JSON for use in
      *        fan fault and fan missing event logs.
      *