control: command line tool to retrieve fan status (reload function)

This creates a fan control command line utility to report service status,
show target/actual RPM/PWM info, report present/functional status as
retrieved from D-Bus (or "Unknown" if not supported), and set target
RPM/PWM speeds for one or more fans. A "reload" command restarts the fan
control service to allow reload of configuration files. Note: reload
is only available for JSON-enabled configuration, YAML-based systems
emit an error when reload is run.

sample output:

root@p10bmc:~# fanctl status
Fan Control Service State   : loaded, inactive(dead)
CurrentBMCState     : xyz.openbmc_project.State.BMC.BMCState.Ready
CurrentPowerState   : xyz.openbmc_project.State.Chassis.PowerState.Off
CurrentHostState    : xyz.openbmc_project.State.Host.HostState.Off

 FAN        TARGET(RPM)  FEEDBACKS(RPMS)   PRESENT     FUNCTIONAL
===============================================================
 fan0              9777       9777/13267      true         true
 fan1              9777       9777/13267      true         true
 fan2              9777       9777/13267      true         true
 fan3              9777       9777/13267      true         true
 fan4              9777       9777/13267      true         true
 fan5              9777       9777/13267      true         true
root@p10bmc:~# fanctl set 8000 fan0 fan1
root@p10bmc:~# fanctl get
TARGET SENSOR    TARGET(RPM)   FEEDBACK SENSOR   FEEDBACK(RPM)
===============================================================
fan0_0                  8000            fan0_0             8000
                                        fan0_1            10691
fan1_0                  8000            fan1_0             8000
                                        fan1_1            10691
fan2_0                  9777            fan2_0             9777
                                        fan2_1            13267
fan3_0                  9777            fan3_0             9777
                                        fan3_1            13267
fan4_0                  9777            fan4_0             9777
                                        fan4_1            13267
fan5_0                  9777            fan5_0             9777
                                        fan5_1            13267
root@p10bmc:~# fanctl reload
root@p10bmc:~#

Signed-off-by: Mike Capps <mikepcapps@gmail.com>
Change-Id: I19f25a2c47ffb84b1771fda4ea6ad17a9c78b8ec
diff --git a/control/fanctl.cpp b/control/fanctl.cpp
index c701817..b9085e1 100644
--- a/control/fanctl.cpp
+++ b/control/fanctl.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "config.h"
+
 #include "sdbusplus.hpp"
 
 #include <CLI/CLI.hpp>
@@ -24,10 +26,10 @@
 
 using SDBusPlus = phosphor::fan::util::SDBusPlus;
 
-constexpr auto phosphorServiceName = "phosphor-fan-control@0.service";
 constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
 constexpr auto systemdPath = "/org/freedesktop/systemd1";
 constexpr auto systemdService = "org.freedesktop.systemd1";
+constexpr auto phosphorServiceName = "phosphor-fan-control@0.service";
 
 enum
 {
@@ -258,8 +260,8 @@
     cout << "CurrentHostState    : " << states[5] << endl;
     cout << endl;
     cout << " FAN        "
-         << "TARGET(" << method << ")  FEEDBACKS(RPMS)   PRESENT"
-         << "   FUNCTIONAL" << endl;
+         << "TARGET(" << method << ")  FEEDBACK(RPM)   PRESENT"
+         << "     FUNCTIONAL" << endl;
     cout << "==============================================================="
          << endl;
 
@@ -269,30 +271,31 @@
 
     for (auto& fan : fanNames)
     {
-        cout << " " << fan << setw(18);
+        cout << " " << fan << setw(14);
 
         // get the target RPM
         property = "Target";
         cout << SDBusPlus::getProperty<uint64_t>(
                     pathMap["tach"][fan][0],
                     interfaces[ifaceTypeFromMethod(method)], property)
-             << setw(11);
+             << setw(12);
 
         // get the sensor RPM
         property = "Value";
 
+        std::ostringstream output;
         int numRotors = pathMap["tach"][fan].size();
         // print tach readings for each rotor
         for (auto& path : pathMap["tach"][fan])
         {
-            cout << SDBusPlus::getProperty<double>(
+            output << SDBusPlus::getProperty<double>(
                 path, interfaces["SensorValue"], property);
 
             // dont print slash on last rotor
             if (--numRotors)
-                cout << "/";
+                output << "/";
         }
-        cout << setw(10);
+        cout << setw(18) << output.str() << setw(10);
 
         // print the Present property
         property = "Present";
@@ -367,8 +370,7 @@
 
     // print the header
     cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method
-         << ")   FEEDBACK SENSOR   ";
-    cout << "FEEDBACK(" << method << ")" << endl;
+         << ")   FEEDBACK SENSOR    FEEDBACK(RPM)" << endl;
     cout << "==============================================================="
          << endl;
 
@@ -379,7 +381,7 @@
         // print just the sensor name
         auto shortPath = pathMap["tach"][fan][0];
         shortPath = justFanName(shortPath);
-        cout << shortPath << setw(22);
+        cout << shortPath << setw(18);
 
         // print its target RPM/PWM
         property = "Target";
@@ -395,13 +397,13 @@
         for (auto& path : pathMap["tach"][fan])
         {
             cout << setw(indent);
-            cout << justFanName(path) << setw(17)
+            cout << justFanName(path) << setw(16)
                  << SDBusPlus::getProperty<double>(
                         path, interfaces["SensorValue"], property)
                  << endl;
 
             if (0 == indent)
-                indent = 46;
+                indent = 42;
         }
     }
 }
@@ -409,10 +411,9 @@
 /**
  * @function set fan[s] to a target RPM
  */
-void set(uint64_t target, std::vector<std::string> fanList)
+void set(uint64_t target, std::vector<std::string>& fanList)
 {
     auto busData = loadDBusData();
-
     auto& bus{SDBusPlus::getBus()};
     auto& pathMap{std::get<PATH_MAP>(busData)};
     auto& interfaces{std::get<IFACES>(busData)};
@@ -421,7 +422,7 @@
     std::string ifaceType(method == "RPM" ? "FanSpeed" : "FanPwm");
 
     // stop the fan-control service
-    auto retval = SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
+    SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
         systemdService, systemdPath, systemdMgrIface, "StopUnit",
         phosphorServiceName, "replace");
 
@@ -499,6 +500,81 @@
 }
 
 /**
+ * @function force reload of control files by sending HUP signal
+ */
+void reload()
+{
+#ifdef CONTROL_USE_JSON
+    try
+    {
+        SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
+                              "KillUnit", phosphorServiceName, "main", SIGHUP);
+    }
+    catch (const phosphor::fan::util::DBusPropertyError& e)
+    {
+        std::cerr << "Unable to reload configuration files: " << e.what()
+                  << std::endl;
+    }
+#else
+    // YAML config doesn't support SIGHUP-based reloads
+    std::cerr << "Error: reload function unavailable for YAML-configuration"
+              << std::endl;
+#endif
+}
+
+/**
+ * @function setup the CLI object to accept all options
+ */
+void initCLI(CLI::App& app, uint64_t& target, std::vector<std::string>& fanList)
+{
+    app.set_help_flag("-h,--help", "Print this help page and exit.");
+
+    // App requires only 1 subcommand to be given
+    app.require_subcommand(1);
+
+    // This represents the command given
+    auto commands = app.add_option_group("Commands");
+
+    // status method
+    std::string strHelp("Prints fan target/tach readings, present/functional "
+                        "states, and fan-monitor/BMC/Power service status");
+    auto cmdStatus = commands->add_subcommand("status", strHelp);
+    cmdStatus->set_help_flag("-h, --help", strHelp);
+    cmdStatus->require_option(0);
+
+    // get method
+    strHelp = "Get the current fan target and feedback speeds for all rotors";
+    auto cmdGet = commands->add_subcommand("get", strHelp);
+    cmdGet->set_help_flag("-h, --help", strHelp);
+    cmdGet->require_option(0);
+
+    // set method
+    strHelp = "Set target (all rotors) for one-or-more fans";
+    auto cmdSet = commands->add_subcommand("set", strHelp);
+    strHelp = R"(set <TARGET> [TARGET SENSOR(S)]
+  <TARGET>
+      - RPM/PWM target to set the fans
+[TARGET SENSOR LIST]
+- list of target sensors to set)";
+    cmdSet->set_help_flag("-h, --help", strHelp);
+    cmdSet->add_option("target", target, "RPM/PWM target to set the fans");
+    cmdSet->add_option(
+        "fan list", fanList,
+        "[optional] list of 1+ fans to set target RPM/PWM (default: all)");
+    cmdSet->require_option();
+
+    strHelp = "Reload phosphor-fan configuration files";
+    auto cmdReload = commands->add_subcommand("reload", strHelp);
+    cmdReload->set_help_flag("-h, --help", strHelp);
+    cmdReload->require_option(0);
+
+    strHelp = "Resume running phosphor-fan-control";
+    auto cmdResume = commands->add_subcommand("resume", strHelp);
+    cmdResume->set_help_flag("-h, --help", strHelp);
+    cmdResume->require_option(0);
+}
+
+/**
  * @function main entry point for the application
  */
 int main(int argc, char* argv[])
@@ -512,70 +588,30 @@
         CLI::App app{R"(Manually control, get fan tachs, view status, and resume
              automatic control of all fans within a chassis.)"};
 
-        app.set_help_flag("-h,--help", "Print this help page and exit.");
-
-        // App requires only 1 subcommand to be given
-        app.require_subcommand(1);
-
-        // This represents the command given
-        auto commands = app.add_option_group("Commands");
-
-        // status method
-        auto cmdStatus = commands->add_subcommand(
-            "status",
-            "Get the fan tach targets/values and fan-control service status");
-        cmdStatus->set_help_flag(
-            "-h, --help", "Prints fan target/tach readings, present/functional "
-                          "states, and fan-monitor/BMC/Power service status");
-        cmdStatus->require_option(0);
-
-        // get method
-        auto cmdGet = commands->add_subcommand(
-            "get",
-            "Get the current fan target and feedback speeds for all rotors");
-        cmdGet->set_help_flag(
-            "-h, --help",
-            "Get the current fan target and feedback speeds for all rotors");
-        cmdGet->require_option(0);
-
-        // set method
-        auto cmdSet = commands->add_subcommand(
-            "set", "Set fan(s) target speed for all rotors");
-        cmdSet->add_option("target", target, "RPM/PWM target to set the fans");
-        cmdSet->add_option("fan list", fanList,
-                           "[optional] list of fans to set target RPM");
-
-        std::string strHelp = "Resume running phosphor-fan-control";
-        auto cmdResume = commands->add_subcommand("resume", strHelp);
-        cmdResume->set_help_flag("-h, --help", strHelp);
-        cmdResume->require_option(0);
-
-        auto setHelp(R"(set <TARGET> [\"TARGET SENSOR LIST\"]
-      <TARGET>
-          - RPM/PWM target to set the fans
-[TARGET SENSOR LIST]
-  - list of target sensors to set)");
-        cmdSet->set_help_flag("-h, --help", setHelp);
-        cmdSet->require_option();
+        initCLI(app, target, fanList);
 
         CLI11_PARSE(app, argc, argv);
 
-        if (app.got_subcommand("status"))
+        if (app.got_subcommand("get"))
         {
-            status();
+            get();
         }
         else if (app.got_subcommand("set"))
         {
             set(target, fanList);
         }
-        else if (app.got_subcommand("get"))
+        else if (app.got_subcommand("reload"))
         {
-            get();
+            reload();
         }
         else if (app.got_subcommand("resume"))
         {
             resume();
         }
+        else if (app.got_subcommand("status"))
+        {
+            status();
+        }
     }
     catch (const std::exception& e)
     {