1 /**
2  * Copyright © 2024 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_file_parser.hpp"
18 
19 #include "config_file_parser_error.hpp"
20 
21 #include <exception>
22 #include <fstream>
23 #include <optional>
24 
25 using json = nlohmann::json;
26 namespace fs = std::filesystem;
27 
28 namespace phosphor::power::sequencer::config_file_parser
29 {
30 
31 const std::filesystem::path standardConfigFileDirectory{
32     "/usr/share/phosphor-power-sequencer"};
33 
34 std::filesystem::path
find(const std::vector<std::string> & compatibleSystemTypes,const std::filesystem::path & configFileDir)35     find(const std::vector<std::string>& compatibleSystemTypes,
36          const std::filesystem::path& configFileDir)
37 {
38     fs::path pathName, possiblePath;
39     std::string fileName;
40 
41     for (const std::string& systemType : compatibleSystemTypes)
42     {
43         // Look for file name that is entire system type + ".json"
44         // Example: com.acme.Hardware.Chassis.Model.MegaServer.json
45         fileName = systemType + ".json";
46         possiblePath = configFileDir / fileName;
47         if (fs::is_regular_file(possiblePath))
48         {
49             pathName = possiblePath;
50             break;
51         }
52 
53         // Look for file name that is last node of system type + ".json"
54         // Example: MegaServer.json
55         std::string::size_type pos = systemType.rfind('.');
56         if ((pos != std::string::npos) && ((systemType.size() - pos) > 1))
57         {
58             fileName = systemType.substr(pos + 1) + ".json";
59             possiblePath = configFileDir / fileName;
60             if (fs::is_regular_file(possiblePath))
61             {
62                 pathName = possiblePath;
63                 break;
64             }
65         }
66     }
67 
68     return pathName;
69 }
70 
parse(const std::filesystem::path & pathName)71 std::vector<std::unique_ptr<Rail>> parse(const std::filesystem::path& pathName)
72 {
73     try
74     {
75         // Use standard JSON parser to create tree of JSON elements
76         std::ifstream file{pathName};
77         json rootElement = json::parse(file);
78 
79         // Parse tree of JSON elements and return corresponding C++ objects
80         return internal::parseRoot(rootElement);
81     }
82     catch (const std::exception& e)
83     {
84         throw ConfigFileParserError{pathName, e.what()};
85     }
86 }
87 
88 namespace internal
89 {
90 
parseGPIO(const json & element)91 GPIO parseGPIO(const json& element)
92 {
93     verifyIsObject(element);
94     unsigned int propertyCount{0};
95 
96     // Required line property
97     const json& lineElement = getRequiredProperty(element, "line");
98     unsigned int line = parseUnsignedInteger(lineElement);
99     ++propertyCount;
100 
101     // Optional active_low property
102     bool activeLow{false};
103     auto activeLowIt = element.find("active_low");
104     if (activeLowIt != element.end())
105     {
106         activeLow = parseBoolean(*activeLowIt);
107         ++propertyCount;
108     }
109 
110     // Verify no invalid properties exist
111     verifyPropertyCount(element, propertyCount);
112 
113     return GPIO(line, activeLow);
114 }
115 
parseRail(const json & element)116 std::unique_ptr<Rail> parseRail(const json& element)
117 {
118     verifyIsObject(element);
119     unsigned int propertyCount{0};
120 
121     // Required name property
122     const json& nameElement = getRequiredProperty(element, "name");
123     std::string name = parseString(nameElement);
124     ++propertyCount;
125 
126     // Optional presence property
127     std::optional<std::string> presence{};
128     auto presenceIt = element.find("presence");
129     if (presenceIt != element.end())
130     {
131         presence = parseString(*presenceIt);
132         ++propertyCount;
133     }
134 
135     // Optional page property
136     std::optional<uint8_t> page{};
137     auto pageIt = element.find("page");
138     if (pageIt != element.end())
139     {
140         page = parseUint8(*pageIt);
141         ++propertyCount;
142     }
143 
144     // Optional is_power_supply_rail property
145     bool isPowerSupplyRail{false};
146     auto isPowerSupplyRailIt = element.find("is_power_supply_rail");
147     if (isPowerSupplyRailIt != element.end())
148     {
149         isPowerSupplyRail = parseBoolean(*isPowerSupplyRailIt);
150         ++propertyCount;
151     }
152 
153     // Optional check_status_vout property
154     bool checkStatusVout{false};
155     auto checkStatusVoutIt = element.find("check_status_vout");
156     if (checkStatusVoutIt != element.end())
157     {
158         checkStatusVout = parseBoolean(*checkStatusVoutIt);
159         ++propertyCount;
160     }
161 
162     // Optional compare_voltage_to_limit property
163     bool compareVoltageToLimit{false};
164     auto compareVoltageToLimitIt = element.find("compare_voltage_to_limit");
165     if (compareVoltageToLimitIt != element.end())
166     {
167         compareVoltageToLimit = parseBoolean(*compareVoltageToLimitIt);
168         ++propertyCount;
169     }
170 
171     // Optional gpio property
172     std::optional<GPIO> gpio{};
173     auto gpioIt = element.find("gpio");
174     if (gpioIt != element.end())
175     {
176         gpio = parseGPIO(*gpioIt);
177         ++propertyCount;
178     }
179 
180     // If check_status_vout or compare_voltage_to_limit property is true, the
181     // page property is required; verify page was specified
182     if ((checkStatusVout || compareVoltageToLimit) && !page.has_value())
183     {
184         throw std::invalid_argument{"Required property missing: page"};
185     }
186 
187     // Verify no invalid properties exist
188     verifyPropertyCount(element, propertyCount);
189 
190     return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
191                                   checkStatusVout, compareVoltageToLimit, gpio);
192 }
193 
parseRailArray(const json & element)194 std::vector<std::unique_ptr<Rail>> parseRailArray(const json& element)
195 {
196     verifyIsArray(element);
197     std::vector<std::unique_ptr<Rail>> rails;
198     for (auto& railElement : element)
199     {
200         rails.emplace_back(parseRail(railElement));
201     }
202     return rails;
203 }
204 
parseRoot(const json & element)205 std::vector<std::unique_ptr<Rail>> parseRoot(const json& element)
206 {
207     verifyIsObject(element);
208     unsigned int propertyCount{0};
209 
210     // Required rails property
211     const json& railsElement = getRequiredProperty(element, "rails");
212     std::vector<std::unique_ptr<Rail>> rails = parseRailArray(railsElement);
213     ++propertyCount;
214 
215     // Verify no invalid properties exist
216     verifyPropertyCount(element, propertyCount);
217 
218     return rails;
219 }
220 
221 } // namespace internal
222 
223 } // namespace phosphor::power::sequencer::config_file_parser
224