1 /**
2  * Copyright © 2022 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "config.h"
18 
19 #include "sdbusplus.hpp"
20 
21 #include <CLI/CLI.hpp>
22 #include <nlohmann/json.hpp>
23 #include <sdbusplus/bus.hpp>
24 
25 #include <chrono>
26 #include <filesystem>
27 #include <iomanip>
28 #include <iostream>
29 
30 using SDBusPlus = phosphor::fan::util::SDBusPlus;
31 
32 constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
33 constexpr auto systemdPath = "/org/freedesktop/systemd1";
34 constexpr auto systemdService = "org.freedesktop.systemd1";
35 constexpr auto phosphorServiceName = "phosphor-fan-control@0.service";
36 constexpr auto dumpFile = "/tmp/fan_control_dump.json";
37 
38 enum
39 {
40     FAN_NAMES = 0,
41     PATH_MAP = 1,
42     IFACES = 2,
43     METHOD = 3
44 };
45 
46 struct DumpQuery
47 {
48     std::string section;
49     std::string name;
50     std::vector<std::string> properties;
51     bool dump{false};
52 };
53 
54 /**
55  * @function extracts fan name from dbus path string (last token where
56  * delimiter is the / character), with proper bounds checking.
57  * @param[in] path - D-Bus path
58  * @return just the fan name.
59  */
60 
justFanName(const std::string & path)61 std::string justFanName(const std::string& path)
62 {
63     std::string fanName;
64 
65     auto itr = path.rfind("/");
66     if (itr != std::string::npos && itr < path.size())
67     {
68         fanName = path.substr(1 + itr);
69     }
70 
71     return fanName;
72 }
73 
74 /**
75  * @function produces subtree paths whose names match fan token names.
76  * @param[in] path - D-Bus path to obtain subtree from
77  * @param[in] iface - interface to obtain subTreePaths from
78  * @param[in] fans - label matching tokens to filter by
79  * @param[in] shortPath - flag to shorten fan token
80  * @return map of paths by fan name
81  */
82 
getPathsFromIface(const std::string & path,const std::string & iface,const std::vector<std::string> & fans,bool shortPath=false)83 std::map<std::string, std::vector<std::string>> getPathsFromIface(
84     const std::string& path, const std::string& iface,
85     const std::vector<std::string>& fans, bool shortPath = false)
86 {
87     std::map<std::string, std::vector<std::string>> dest;
88 
89     for (auto& path :
90          SDBusPlus::getSubTreePathsRaw(SDBusPlus::getBus(), path, iface, 0))
91     {
92         for (auto& fan : fans)
93         {
94             if (shortPath)
95             {
96                 if (fan == justFanName(path))
97                 {
98                     dest[fan].push_back(path);
99                 }
100             }
101             else if (std::string::npos != path.find(fan + "_"))
102             {
103                 dest[fan].push_back(path);
104             }
105         }
106     }
107 
108     return dest;
109 }
110 
111 /**
112  * @function consolidated function to load dbus paths and fan names
113  */
loadDBusData()114 auto loadDBusData()
115 {
116     auto& bus{SDBusPlus::getBus()};
117 
118     std::vector<std::string> fanNames;
119 
120     // paths by D-bus interface,fan name
121     std::map<std::string, std::map<std::string, std::vector<std::string>>>
122         pathMap;
123 
124     std::string method("RPM");
125 
126     std::map<const std::string, const std::string> interfaces{
127         {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"},
128         {"FanPwm", "xyz.openbmc_project.Control.FanPwm"},
129         {"SensorValue", "xyz.openbmc_project.Sensor.Value"},
130         {"Item", "xyz.openbmc_project.Inventory.Item"},
131         {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}};
132 
133     std::map<const std::string, const std::string> paths{
134         {"motherboard",
135          "/xyz/openbmc_project/inventory/system/chassis/motherboard"},
136         {"tach", "/xyz/openbmc_project/sensors/fan_tach"}};
137 
138     // build a list of all fans
139     for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"],
140                                                     interfaces["FanSpeed"], 0))
141     {
142         // special case where we build the list of fans
143         auto fan = justFanName(path);
144         fan = fan.substr(0, fan.rfind("_"));
145         fanNames.push_back(fan);
146     }
147 
148     // retry with PWM mode if none found
149     if (0 == fanNames.size())
150     {
151         method = "PWM";
152 
153         for (auto& path : SDBusPlus::getSubTreePathsRaw(
154                  bus, paths["tach"], interfaces["FanPwm"], 0))
155         {
156             // special case where we build the list of fans
157             auto fan = justFanName(path);
158             fan = fan.substr(0, fan.rfind("_"));
159             fanNames.push_back(fan);
160         }
161     }
162 
163     // load tach sensor paths for each fan
164     pathMap["tach"] =
165         getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames);
166 
167     // load inventory Item data for each fan
168     pathMap["inventory"] = getPathsFromIface(
169         paths["motherboard"], interfaces["Item"], fanNames, true);
170 
171     // load operational status data for each fan
172     pathMap["opstatus"] = getPathsFromIface(
173         paths["motherboard"], interfaces["OpStatus"], fanNames, true);
174 
175     return std::make_tuple(fanNames, pathMap, interfaces, method);
176 }
177 
178 /**
179  * @function gets the states of phosphor-fanctl. equivalent to
180  * "systemctl status phosphor-fan-control@0"
181  * @return a list of several (sub)states of fanctl (loaded,
182  * active, running) as well as D-Bus properties representing
183  * BMC states (bmc state,chassis power state, host state)
184  */
185 
getStates()186 std::array<std::string, 6> getStates()
187 {
188     using DBusTuple =
189         std::tuple<std::string, std::string, std::string, std::string,
190                    std::string, std::string, sdbusplus::message::object_path,
191                    uint32_t, std::string, sdbusplus::message::object_path>;
192 
193     std::array<std::string, 6> ret;
194 
195     std::vector<std::string> services{phosphorServiceName};
196 
197     try
198     {
199         auto fields{SDBusPlus::callMethodAndRead<std::vector<DBusTuple>>(
200             systemdService, systemdPath, systemdMgrIface, "ListUnitsByNames",
201             services)};
202 
203         if (fields.size() > 0)
204         {
205             ret[0] = std::get<2>(fields[0]);
206             ret[1] = std::get<3>(fields[0]);
207             ret[2] = std::get<4>(fields[0]);
208         }
209         else
210         {
211             std::cout << "No units found for systemd service: " << services[0]
212                       << std::endl;
213         }
214     }
215     catch (const std::exception& e)
216     {
217         std::cerr << "Failure retrieving phosphor-fan-control states: "
218                   << e.what() << std::endl;
219     }
220 
221     std::string path("/xyz/openbmc_project/state/bmc0");
222     std::string iface("xyz.openbmc_project.State.BMC");
223     ret[3] =
224         SDBusPlus::getProperty<std::string>(path, iface, "CurrentBMCState");
225 
226     path = "/xyz/openbmc_project/state/chassis0";
227     iface = "xyz.openbmc_project.State.Chassis";
228     ret[4] =
229         SDBusPlus::getProperty<std::string>(path, iface, "CurrentPowerState");
230 
231     path = "/xyz/openbmc_project/state/host0";
232     iface = "xyz.openbmc_project.State.Host";
233     ret[5] =
234         SDBusPlus::getProperty<std::string>(path, iface, "CurrentHostState");
235 
236     return ret;
237 }
238 
239 /**
240  * @function helper to determine interface type from a given control method
241  */
ifaceTypeFromMethod(const std::string & method)242 std::string ifaceTypeFromMethod(const std::string& method)
243 {
244     return (method == "RPM" ? "FanSpeed" : "FanPwm");
245 }
246 
247 /**
248  * @function performs the "status" command from the cmdline.
249  * get states and sensor data and output to the console
250  */
status()251 void status()
252 {
253     using std::cout;
254     using std::endl;
255     using std::setw;
256 
257     auto busData = loadDBusData();
258     auto& method = std::get<METHOD>(busData);
259 
260     std::string property;
261 
262     // get the state,substate of fan-control and obmc
263     auto states = getStates();
264 
265     // print the header
266     cout << "Fan Control Service State   : " << states[0] << ", " << states[1]
267          << "(" << states[2] << ")" << endl;
268     cout << endl;
269     cout << "CurrentBMCState     : " << states[3] << endl;
270     cout << "CurrentPowerState   : " << states[4] << endl;
271     cout << "CurrentHostState    : " << states[5] << endl;
272     cout << endl;
273     cout << "FAN       "
274          << "TARGET(" << method << ")     FEEDBACKS(RPM)   PRESENT"
275          << "   FUNCTIONAL" << endl;
276     cout << "==============================================================="
277          << endl;
278 
279     auto& fanNames{std::get<FAN_NAMES>(busData)};
280     auto& pathMap{std::get<PATH_MAP>(busData)};
281     auto& interfaces{std::get<IFACES>(busData)};
282 
283     for (auto& fan : fanNames)
284     {
285         cout << setw(8) << std::left << fan << std::right << setw(13);
286 
287         // get the target RPM
288         property = "Target";
289         cout << SDBusPlus::getProperty<uint64_t>(
290                     pathMap["tach"][fan][0],
291                     interfaces[ifaceTypeFromMethod(method)], property)
292              << setw(19);
293 
294         // get the sensor RPM
295         property = "Value";
296 
297         std::ostringstream output;
298         int numRotors = pathMap["tach"][fan].size();
299         // print tach readings for each rotor
300         for (auto& path : pathMap["tach"][fan])
301         {
302             output << SDBusPlus::getProperty<double>(
303                 path, interfaces["SensorValue"], property);
304 
305             // dont print slash on last rotor
306             if (--numRotors)
307                 output << "/";
308         }
309         cout << output.str() << setw(10);
310 
311         // print the Present property
312         property = "Present";
313         auto itFan = pathMap["inventory"].find(fan);
314         if (itFan != pathMap["inventory"].end())
315         {
316             for (auto& path : itFan->second)
317             {
318                 try
319                 {
320                     cout << std::boolalpha
321                          << SDBusPlus::getProperty<bool>(
322                                 path, interfaces["Item"], property);
323                 }
324                 catch (const phosphor::fan::util::DBusError&)
325                 {
326                     cout << "Unknown";
327                 }
328             }
329         }
330         else
331         {
332             cout << "Unknown";
333         }
334 
335         cout << setw(13);
336 
337         // and the functional property
338         property = "Functional";
339         itFan = pathMap["opstatus"].find(fan);
340         if (itFan != pathMap["opstatus"].end())
341         {
342             for (auto& path : itFan->second)
343             {
344                 try
345                 {
346                     cout << std::boolalpha
347                          << SDBusPlus::getProperty<bool>(
348                                 path, interfaces["OpStatus"], property);
349                 }
350                 catch (const phosphor::fan::util::DBusError&)
351                 {
352                     cout << "Unknown";
353                 }
354             }
355         }
356         else
357         {
358             cout << "Unknown";
359         }
360 
361         cout << endl;
362     }
363 }
364 
365 /**
366  * @function print target RPM/PWM and tach readings from each fan
367  */
get()368 void get()
369 {
370     using std::cout;
371     using std::endl;
372     using std::setw;
373 
374     auto busData = loadDBusData();
375 
376     auto& fanNames{std::get<FAN_NAMES>(busData)};
377     auto& pathMap{std::get<PATH_MAP>(busData)};
378     auto& interfaces{std::get<IFACES>(busData)};
379     auto& method = std::get<METHOD>(busData);
380 
381     std::string property;
382 
383     // print the header
384     cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method
385          << ")   FEEDBACK SENSOR    FEEDBACK(RPM)" << endl;
386     cout << "==============================================================="
387          << endl;
388 
389     for (auto& fan : fanNames)
390     {
391         if (pathMap["tach"][fan].size() == 0)
392             continue;
393         // print just the sensor name
394         auto shortPath = pathMap["tach"][fan][0];
395         shortPath = justFanName(shortPath);
396         cout << setw(13) << std::left << shortPath << std::right << setw(15);
397 
398         // print its target RPM/PWM
399         property = "Target";
400         cout << SDBusPlus::getProperty<uint64_t>(
401             pathMap["tach"][fan][0], interfaces[ifaceTypeFromMethod(method)],
402             property);
403 
404         // print readings for each rotor
405         property = "Value";
406 
407         auto indent = 0;
408         for (auto& path : pathMap["tach"][fan])
409         {
410             cout << setw(18 + indent) << justFanName(path) << setw(17)
411                  << SDBusPlus::getProperty<double>(
412                         path, interfaces["SensorValue"], property)
413                  << endl;
414 
415             if (0 == indent)
416                 indent = 28;
417         }
418     }
419 }
420 
421 /**
422  * @function set fan[s] to a target RPM
423  */
set(uint64_t target,std::vector<std::string> & fanList)424 void set(uint64_t target, std::vector<std::string>& fanList)
425 {
426     auto busData = loadDBusData();
427     auto& bus{SDBusPlus::getBus()};
428     auto& pathMap{std::get<PATH_MAP>(busData)};
429     auto& interfaces{std::get<IFACES>(busData)};
430     auto& method = std::get<METHOD>(busData);
431 
432     std::string ifaceType(method == "RPM" ? "FanSpeed" : "FanPwm");
433 
434     // stop the fan-control service
435     SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
436         systemdService, systemdPath, systemdMgrIface, "StopUnit",
437         phosphorServiceName, "replace");
438 
439     if (fanList.size() == 0)
440     {
441         fanList = std::get<FAN_NAMES>(busData);
442     }
443 
444     for (auto& fan : fanList)
445     {
446         try
447         {
448             auto paths(pathMap["tach"].find(fan));
449 
450             if (pathMap["tach"].end() == paths)
451             {
452                 // try again, maybe it was a sensor name instead of a fan name
453                 for (const auto& [fanName, sensors] : pathMap["tach"])
454                 {
455                     for (const auto& path : sensors)
456                     {
457                         std::string sensor(path.substr(path.rfind("/")));
458 
459                         if (sensor.size() > 0)
460                         {
461                             sensor = sensor.substr(1);
462 
463                             if (sensor == fan)
464                             {
465                                 paths = pathMap["tach"].find(fanName);
466 
467                                 break;
468                             }
469                         }
470                     }
471                 }
472             }
473 
474             if (pathMap["tach"].end() == paths)
475             {
476                 std::cout << "Could not find tach path for fan: " << fan
477                           << std::endl;
478                 continue;
479             }
480 
481             // set the target RPM
482             SDBusPlus::setProperty<uint64_t>(bus, paths->second[0],
483                                              interfaces[ifaceType], "Target",
484                                              std::move(target));
485         }
486         catch (const phosphor::fan::util::DBusPropertyError& e)
487         {
488             std::cerr << "Cannot set target rpm for " << fan
489                       << " caught D-Bus exception: " << e.what() << std::endl;
490         }
491     }
492 }
493 
494 /**
495  * @function restart fan-control to allow it to manage fan speeds
496  */
resume()497 void resume()
498 {
499     try
500     {
501         auto retval =
502             SDBusPlus::callMethodAndRead<sdbusplus::message::object_path>(
503                 systemdService, systemdPath, systemdMgrIface, "StartUnit",
504                 phosphorServiceName, "replace");
505     }
506     catch (const phosphor::fan::util::DBusMethodError& e)
507     {
508         std::cerr << "Unable to start fan control: " << e.what() << std::endl;
509     }
510 }
511 
512 /**
513  * @function force reload of control files by sending HUP signal
514  */
reload()515 void reload()
516 {
517     try
518     {
519         SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
520                               "KillUnit", phosphorServiceName, "main", SIGHUP);
521     }
522     catch (const phosphor::fan::util::DBusPropertyError& e)
523     {
524         std::cerr << "Unable to reload configuration files: " << e.what()
525                   << std::endl;
526     }
527 }
528 
529 /**
530  * @function dump debug data
531  */
dumpFanControl()532 void dumpFanControl()
533 {
534     namespace fs = std::filesystem;
535 
536     try
537     {
538         // delete existing file
539         if (fs::exists(dumpFile))
540         {
541             std::filesystem::remove(dumpFile);
542         }
543 
544         SDBusPlus::callMethod(systemdService, systemdPath, systemdMgrIface,
545                               "KillUnit", phosphorServiceName, "main", SIGUSR1);
546 
547         bool done = false;
548         size_t tries = 0;
549         const size_t maxTries = 30;
550 
551         do
552         {
553             // wait for file to be detected
554             sleep(1);
555 
556             if (fs::exists(dumpFile))
557             {
558                 try
559                 {
560                     auto unused{nlohmann::json::parse(std::ifstream{dumpFile})};
561                     done = true;
562                 }
563                 catch (...)
564                 {}
565             }
566 
567             if (++tries > maxTries)
568             {
569                 std::cerr << "Timed out waiting for fan control dump.\n";
570                 return;
571             }
572         } while (!done);
573 
574         std::cout << "Fan control dump written to: " << dumpFile << std::endl;
575     }
576     catch (const phosphor::fan::util::DBusPropertyError& e)
577     {
578         std::cerr << "Unable to dump fan control: " << e.what() << std::endl;
579     }
580 }
581 
582 /**
583  * @function Query items in the dump file
584  */
queryDumpFile(const DumpQuery & dq)585 void queryDumpFile(const DumpQuery& dq)
586 {
587     nlohmann::json output;
588     std::ifstream file{dumpFile};
589 
590     if (!file.good())
591     {
592         std::cerr << "Unable to open dump file, please run 'fanctl dump'.\n";
593         return;
594     }
595 
596     auto dumpData = nlohmann::json::parse(file);
597 
598     if (!dumpData.contains(dq.section))
599     {
600         std::cerr << "Error: Dump file does not contain " << dq.section
601                   << " section"
602                   << "\n";
603         return;
604     }
605 
606     const auto& section = dumpData.at(dq.section);
607 
608     if (section.is_array())
609     {
610         for (const auto& entry : section)
611         {
612             if (!entry.is_string() || dq.name.empty() ||
613                 (entry.get<std::string>().find(dq.name) != std::string::npos))
614             {
615                 output[dq.section].push_back(entry);
616             }
617         }
618         std::cout << std::setw(4) << output << "\n";
619         return;
620     }
621 
622     for (const auto& [key1, values1] : section.items())
623     {
624         if (dq.name.empty() || (key1.find(dq.name) != std::string::npos))
625         {
626             // If no properties specified, print the whole JSON value
627             if (dq.properties.empty())
628             {
629                 output[key1] = values1;
630                 continue;
631             }
632 
633             // Look for properties both one and two levels down.
634             // Future improvement: Use recursion.
635             for (const auto& [key2, values2] : values1.items())
636             {
637                 for (const auto& prop : dq.properties)
638                 {
639                     if (prop == key2)
640                     {
641                         output[key1][prop] = values2;
642                     }
643                 }
644 
645                 for (const auto& [key3, values3] : values2.items())
646                 {
647                     for (const auto& prop : dq.properties)
648                     {
649                         if (prop == key3)
650                         {
651                             output[key1][prop] = values3;
652                         }
653                     }
654                 }
655             }
656         }
657     }
658 
659     if (!output.empty())
660     {
661         std::cout << std::setw(4) << output << "\n";
662     }
663 }
664 
665 /**
666  * @function setup the CLI object to accept all options
667  */
initCLI(CLI::App & app,uint64_t & target,std::vector<std::string> & fanList,DumpQuery & dq)668 void initCLI(CLI::App& app, uint64_t& target, std::vector<std::string>& fanList,
669              [[maybe_unused]] DumpQuery& dq)
670 {
671     app.set_help_flag("-h,--help", "Print this help page and exit.");
672 
673     // App requires only 1 subcommand to be given
674     app.require_subcommand(1);
675 
676     // This represents the command given
677     auto commands = app.add_option_group("Commands");
678 
679     // status method
680     std::string strHelp("Prints fan target/tach readings, present/functional "
681                         "states, and fan-monitor/BMC/Power service status");
682 
683     auto cmdStatus = commands->add_subcommand("status", strHelp);
684     cmdStatus->set_help_flag("-h, --help", strHelp);
685     cmdStatus->require_option(0);
686 
687     // get method
688     strHelp = "Get the current fan target and feedback speeds for all rotors";
689     auto cmdGet = commands->add_subcommand("get", strHelp);
690     cmdGet->set_help_flag("-h, --help", strHelp);
691     cmdGet->require_option(0);
692 
693     // set method
694     strHelp = "Set target (all rotors) for one-or-more fans";
695     auto cmdSet = commands->add_subcommand("set", strHelp);
696     strHelp = R"(set <TARGET> [TARGET SENSOR(S)]
697   <TARGET>
698       - RPM/PWM target to set the fans
699 [TARGET SENSOR LIST]
700 - list of target sensors to set)";
701     cmdSet->set_help_flag("-h, --help", strHelp);
702     cmdSet->add_option("target", target, "RPM/PWM target to set the fans");
703     cmdSet->add_option(
704         "fan list", fanList,
705         "[optional] list of 1+ fans to set target RPM/PWM (default: all)");
706     cmdSet->require_option();
707 
708 #ifdef CONTROL_USE_JSON
709     strHelp = "Reload phosphor-fan configuration files";
710     auto cmdReload = commands->add_subcommand("reload", strHelp);
711     cmdReload->set_help_flag("-h, --help", strHelp);
712     cmdReload->require_option(0);
713 #endif
714 
715     strHelp = "Resume running phosphor-fan-control";
716     auto cmdResume = commands->add_subcommand("resume", strHelp);
717     cmdResume->set_help_flag("-h, --help", strHelp);
718     cmdResume->require_option(0);
719 
720     // Dump method
721     auto cmdDump = commands->add_subcommand("dump", "Dump debug data");
722     cmdDump->set_help_flag("-h, --help", "Dump debug data");
723     cmdDump->require_option(0);
724 
725 #ifdef CONTROL_USE_JSON
726     // Query dump
727     auto cmdDumpQuery =
728         commands->add_subcommand("query_dump", "Query the dump file");
729 
730     cmdDumpQuery->set_help_flag("-h, --help", "Query the dump file");
731     cmdDumpQuery
732         ->add_option("-s, --section", dq.section, "Dump file section name")
733         ->required();
734     cmdDumpQuery->add_option("-n, --name", dq.name,
735                              "Optional dump file entry name (or substring)");
736     cmdDumpQuery->add_option("-p, --properties", dq.properties,
737                              "Optional list of dump file property names");
738     cmdDumpQuery->add_flag("-d, --dump", dq.dump,
739                            "Force a dump before the query");
740 #endif
741 }
742 
743 /**
744  * @function main entry point for the application
745  */
main(int argc,char * argv[])746 int main(int argc, char* argv[])
747 {
748     auto rc = 0;
749     uint64_t target{0U};
750     std::vector<std::string> fanList;
751     DumpQuery dq;
752 
753     try
754     {
755         CLI::App app{"Manually control, get fan tachs, view status, and resume "
756                      "automatic control of all fans within a chassis. Full "
757                      "documentation can be found at the readme:\n"
758                      "https://github.com/openbmc/phosphor-fan-presence/tree/"
759                      "master/docs/control/fanctl"};
760 
761         initCLI(app, target, fanList, dq);
762 
763         CLI11_PARSE(app, argc, argv);
764 
765         if (app.got_subcommand("get"))
766         {
767             get();
768         }
769         else if (app.got_subcommand("set"))
770         {
771             set(target, fanList);
772         }
773 #ifdef CONTROL_USE_JSON
774         else if (app.got_subcommand("reload"))
775         {
776             reload();
777         }
778 #endif
779         else if (app.got_subcommand("resume"))
780         {
781             resume();
782         }
783         else if (app.got_subcommand("status"))
784         {
785             status();
786         }
787         else if (app.got_subcommand("dump"))
788         {
789 #ifdef CONTROL_USE_JSON
790             dumpFanControl();
791 #else
792             std::ofstream(dumpFile)
793                 << "{\n\"msg\":   \"Unable to create dump on "
794                    "non-JSON config based system\"\n}";
795 #endif
796         }
797 #ifdef CONTROL_USE_JSON
798         else if (app.got_subcommand("query_dump"))
799         {
800             if (dq.dump)
801             {
802                 dumpFanControl();
803             }
804             queryDumpFile(dq);
805         }
806 #endif
807     }
808     catch (const std::exception& e)
809     {
810         rc = -1;
811         std::cerr << argv[0] << " failed: " << e.what() << std::endl;
812     }
813 
814     return rc;
815 }
816