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 
17 #include "manager.hpp"
18 
19 #include "chassis.hpp"
20 #include "config_file_parser.hpp"
21 #include "exception_utils.hpp"
22 #include "rule.hpp"
23 #include "utility.hpp"
24 
25 #include <sdbusplus/bus.hpp>
26 
27 #include <chrono>
28 #include <exception>
29 #include <stdexcept>
30 #include <tuple>
31 #include <utility>
32 #include <variant>
33 
34 namespace phosphor::power::regulators
35 {
36 
37 namespace fs = std::filesystem;
38 
39 /**
40  * Standard configuration file directory.  This directory is part of the
41  * firmware install image.  It contains the standard version of the config file.
42  */
43 const fs::path standardConfigFileDir{"/usr/share/phosphor-regulators"};
44 
45 /**
46  * Test configuration file directory.  This directory can contain a test version
47  * of the config file.  The test version will override the standard version.
48  */
49 const fs::path testConfigFileDir{"/etc/phosphor-regulators"};
50 
51 Manager::Manager(sdbusplus::bus::bus& bus, const sdeventplus::Event& event) :
52     ManagerObject{bus, objPath, true}, bus{bus}, eventLoop{event}, services{bus}
53 {
54     /* Temporarily comment out until D-Bus interface is defined and available.
55         // Subscribe to interfacesAdded signal for filename property
56         std::unique_ptr<sdbusplus::server::match::match> matchPtr =
57             std::make_unique<sdbusplus::server::match::match>(
58                 bus,
59                 sdbusplus::bus::match::rules::interfacesAdded(sysDbusObj).c_str(),
60                 std::bind(std::mem_fn(&Manager::signalHandler), this,
61                           std::placeholders::_1));
62         signals.emplace_back(std::move(matchPtr));
63 
64         // Attempt to get the filename property from dbus
65         setFileName(getFileNameDbus());
66     */
67 
68     // Temporarily hard-code JSON config file name to first system that will use
69     // this application.  Remove this when D-Bus interface is available.
70     fileName = "ibm_rainier.json";
71 
72     if (!fileName.empty())
73     {
74         loadConfigFile();
75     }
76 
77     // Obtain dbus service name
78     bus.request_name(busName);
79 }
80 
81 void Manager::configure()
82 {
83     // Verify System object exists; this means config file has been loaded
84     if (system)
85     {
86         // Configure the regulator devices in the system
87         system->configure(services);
88     }
89     else
90     {
91         services.getJournal().logError("Unable to configure regulator devices: "
92                                        "Configuration file not loaded");
93         // TODO: Log error
94     }
95 
96     // TODO Configuration errors that should halt poweron,
97     // throw InternalFailure exception (or similar) to
98     // fail the call(busctl) to this method
99 }
100 
101 void Manager::monitor(bool enable)
102 {
103     if (enable)
104     {
105         /* Temporarily comment out until monitoring is supported.
106             Timer timer(eventLoop, std::bind(&Manager::timerExpired, this));
107             // Set timer as a repeating 1sec timer
108             timer.restart(std::chrono::milliseconds(1000));
109             timers.emplace_back(std::move(timer));
110         */
111     }
112     else
113     {
114         /* Temporarily comment out until monitoring is supported.
115             // Delete all timers to disable monitoring
116             timers.clear();
117         */
118 
119         // Verify System object exists; this means config file has been loaded
120         if (system)
121         {
122             // Close the regulator devices in the system.  Monitoring is
123             // normally disabled because the system is being powered off.  The
124             // devices should be closed in case hardware is removed or replaced
125             // while the system is at standby.
126             system->closeDevices();
127         }
128     }
129 }
130 
131 void Manager::timerExpired()
132 {
133     // TODO Analyze, refresh sensor status, and
134     // collect/update telemetry for each regulator
135 }
136 
137 void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/,
138                             const struct signalfd_siginfo* /*sigInfo*/)
139 {
140     if (!fileName.empty())
141     {
142         loadConfigFile();
143     }
144 }
145 
146 void Manager::signalHandler(sdbusplus::message::message& msg)
147 {
148     if (msg)
149     {
150         sdbusplus::message::object_path op;
151         msg.read(op);
152         if (static_cast<const std::string&>(op) != sysDbusPath)
153         {
154             // Object path does not match the path
155             return;
156         }
157 
158         // An interfacesAdded signal returns a dictionary of interface
159         // names to a dictionary of properties and their values
160         // https://dbus.freedesktop.org/doc/dbus-specification.html
161         std::map<std::string, std::map<std::string, std::variant<std::string>>>
162             intfProp;
163         msg.read(intfProp);
164         auto itIntf = intfProp.find(sysDbusIntf);
165         if (itIntf == intfProp.cend())
166         {
167             // Interface not found on the path
168             return;
169         }
170         auto itProp = itIntf->second.find(sysDbusProp);
171         if (itProp == itIntf->second.cend())
172         {
173             // Property not found on the interface
174             return;
175         }
176         // Set fileName and call parse json function
177         setFileName(std::get<std::string>(itProp->second));
178         if (!fileName.empty())
179         {
180             loadConfigFile();
181         }
182     }
183 }
184 
185 const std::string Manager::getFileNameDbus()
186 {
187     std::string fileName = "";
188     using namespace phosphor::power::util;
189 
190     try
191     {
192         // Do not log an error when service or property are not found
193         auto service = getService(sysDbusPath, sysDbusIntf, bus, false);
194         if (!service.empty())
195         {
196             getProperty(sysDbusIntf, sysDbusProp, sysDbusPath, service, bus,
197                         fileName);
198         }
199     }
200     catch (const sdbusplus::exception::SdBusError&)
201     {
202         // File name property not available on dbus
203         fileName = "";
204     }
205 
206     return fileName;
207 }
208 
209 fs::path Manager::findConfigFile()
210 {
211     // First look in the test directory
212     fs::path pathName{testConfigFileDir / fileName};
213     if (!fs::exists(pathName))
214     {
215         // Look in the standard directory
216         pathName = standardConfigFileDir / fileName;
217         if (!fs::exists(pathName))
218         {
219             throw std::runtime_error{"Configuration file does not exist: " +
220                                      pathName.string()};
221         }
222     }
223 
224     return pathName;
225 }
226 
227 void Manager::loadConfigFile()
228 {
229     try
230     {
231         // Find the absolute path to the config file
232         fs::path pathName = findConfigFile();
233 
234         // Log info message in journal; config file path is important
235         services.getJournal().logInfo("Loading configuration file " +
236                                       pathName.string());
237 
238         // Parse the config file
239         std::vector<std::unique_ptr<Rule>> rules{};
240         std::vector<std::unique_ptr<Chassis>> chassis{};
241         std::tie(rules, chassis) = config_file_parser::parse(pathName);
242 
243         // Store config file information in a new System object.  The old System
244         // object, if any, is automatically deleted.
245         system = std::make_unique<System>(std::move(rules), std::move(chassis));
246     }
247     catch (const std::exception& e)
248     {
249         // Log error messages in journal
250         services.getJournal().logError(exception_utils::getMessages(e));
251         services.getJournal().logError("Unable to load configuration file");
252 
253         // TODO: Create error log entry
254     }
255 }
256 
257 } // namespace phosphor::power::regulators
258