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