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