xref: /openbmc/phosphor-fan-presence/json_config.hpp (revision 9e9f599cece7fe6cdf99b1927a515fc8a552b7e9)
1 /**
2  * Copyright © 2020 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 #pragma once
17 
18 #include "sdbusplus.hpp"
19 
20 #include <fmt/format.h>
21 
22 #include <nlohmann/json.hpp>
23 #include <phosphor-logging/log.hpp>
24 #include <sdbusplus/bus.hpp>
25 #include <sdeventplus/source/signal.hpp>
26 
27 #include <filesystem>
28 #include <fstream>
29 
30 namespace phosphor::fan
31 {
32 
33 namespace fs = std::filesystem;
34 using json = nlohmann::json;
35 using namespace phosphor::logging;
36 
37 constexpr auto confOverridePath = "/etc/phosphor-fan-presence";
38 constexpr auto confBasePath = "/usr/share/phosphor-fan-presence";
39 constexpr auto confDbusPath = "/xyz/openbmc_project/inventory/system/chassis";
40 constexpr auto confDbusIntf =
41     "xyz.openbmc_project.Inventory.Decorator.Compatible";
42 constexpr auto confDbusProp = "Names";
43 
44 class JsonConfig
45 {
46   public:
47     /**
48      * Get the json configuration file. The first location found to contain
49      * the json config file for the given fan application is used from the
50      * following locations in order.
51      * 1.) From the confOverridePath location
52      * 2.) From config file found using a property value as a relative
53      * path extension on the base path from the dbus object where:
54      *     path = Path set in confDbusPath
55      *     interface = Interface set in confDbusIntf
56      *     property = Property set in confDbusProp
57      * 3.) *DEFAULT* - From the confBasePath location
58      *
59      * @brief Get the configuration file to be used
60      *
61      * @param[in] bus - The dbus bus object
62      * @param[in] appName - The phosphor-fan-presence application name
63      * @param[in] fileName - Application's configuration file's name
64      * @param[in] isOptional - Config file is optional, default to 'false'
65      *
66      * @return filesystem path
67      *     The filesystem path to the configuration file to use
68      */
69     static const fs::path getConfFile(sdbusplus::bus::bus& bus,
70                                       const std::string& appName,
71                                       const std::string& fileName,
72                                       bool isOptional = false)
73     {
74         // Check override location
75         fs::path confFile = fs::path{confOverridePath} / appName / fileName;
76         if (fs::exists(confFile))
77         {
78             return confFile;
79         }
80 
81         try
82         {
83             // Retrieve json config relative path location from dbus
84             auto confDbusValue =
85                 util::SDBusPlus::getProperty<std::vector<std::string>>(
86                     bus, confDbusPath, confDbusIntf, confDbusProp);
87             // Look for a config file at each entry relative to the base
88             // path and use the first one found
89             auto it = std::find_if(
90                 confDbusValue.begin(), confDbusValue.end(),
91                 [&confFile, &appName, &fileName](auto const& entry) {
92                     confFile =
93                         fs::path{confBasePath} / appName / entry / fileName;
94                     return fs::exists(confFile);
95                 });
96             if (it == confDbusValue.end())
97             {
98                 // Property exists, but no config file found. Use default base
99                 // path
100                 confFile = fs::path{confBasePath} / appName / fileName;
101             }
102         }
103         catch (const util::DBusError&)
104         {
105             // Property unavailable, attempt default base path
106             confFile = fs::path{confBasePath} / appName / fileName;
107         }
108 
109         if (!fs::exists(confFile))
110         {
111             if (!isOptional)
112             {
113                 log<level::ERR>(
114                     fmt::format("No JSON config file found. Default file: {}",
115                                 confFile.string())
116                         .c_str());
117                 throw std::runtime_error(
118                     fmt::format("No JSON config file found. Default file: {}",
119                                 confFile.string())
120                         .c_str());
121             }
122             else
123             {
124                 confFile.clear();
125             }
126         }
127 
128         return confFile;
129     }
130 
131     /**
132      * @brief Load the JSON config file
133      *
134      * @param[in] confFile - File system path of the configuration file to load
135      *
136      * @return Parsed JSON object
137      *     The parsed JSON configuration file object
138      */
139     static const json load(const fs::path& confFile)
140     {
141         std::ifstream file;
142         json jsonConf;
143 
144         if (!confFile.empty() && fs::exists(confFile))
145         {
146             log<level::INFO>(
147                 fmt::format("Loading configuration from {}", confFile.string())
148                     .c_str());
149             file.open(confFile);
150             try
151             {
152                 jsonConf = json::parse(file);
153             }
154             catch (std::exception& e)
155             {
156                 log<level::ERR>(
157                     fmt::format(
158                         "Failed to parse JSON config file: {}, error: {}",
159                         confFile.string(), e.what())
160                         .c_str());
161                 throw std::runtime_error(
162                     fmt::format(
163                         "Failed to parse JSON config file: {}, error: {}",
164                         confFile.string(), e.what())
165                         .c_str());
166             }
167         }
168         else
169         {
170             log<level::ERR>(fmt::format("Unable to open JSON config file: {}",
171                                         confFile.string())
172                                 .c_str());
173             throw std::runtime_error(
174                 fmt::format("Unable to open JSON config file: {}",
175                             confFile.string())
176                     .c_str());
177         }
178 
179         return jsonConf;
180     }
181 };
182 
183 } // namespace phosphor::fan
184