xref: /openbmc/entity-manager/src/entity_manager/utils.cpp (revision 5df916f17c178e9633a9df999a6adae5449fc524)
1 #include "utils.hpp"
2 
3 #include "../utils.hpp"
4 #include "../variant_visitors.hpp"
5 #include "expression.hpp"
6 #include "phosphor-logging/lg2.hpp"
7 
8 #include <boost/algorithm/string/case_conv.hpp>
9 #include <boost/algorithm/string/classification.hpp>
10 #include <boost/algorithm/string/find.hpp>
11 #include <boost/algorithm/string/replace.hpp>
12 #include <boost/algorithm/string/split.hpp>
13 #include <phosphor-logging/lg2.hpp>
14 #include <sdbusplus/bus/match.hpp>
15 
16 #include <fstream>
17 #include <iostream>
18 #include <regex>
19 
20 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
21 
22 namespace em_utils
23 {
24 
25 constexpr const char* templateChar = "$";
26 
fwVersionIsSame()27 bool fwVersionIsSame()
28 {
29     std::ifstream version(versionFile);
30     if (!version.good())
31     {
32         lg2::error("Can't read {PATH}", "PATH", versionFile);
33         return false;
34     }
35 
36     std::string versionData;
37     std::string line;
38     while (std::getline(version, line))
39     {
40         versionData += line;
41     }
42 
43     std::string expectedHash =
44         std::to_string(std::hash<std::string>{}(versionData));
45 
46     std::error_code ec;
47     std::filesystem::create_directory(configurationOutDir, ec);
48 
49     if (ec)
50     {
51         lg2::error("could not create directory {DIR}", "DIR",
52                    configurationOutDir);
53         return false;
54     }
55 
56     std::ifstream hashFile(versionHashFile);
57     if (hashFile.good())
58     {
59         std::string hashString;
60         hashFile >> hashString;
61 
62         if (expectedHash == hashString)
63         {
64             return true;
65         }
66         hashFile.close();
67     }
68 
69     std::ofstream output(versionHashFile);
70     output << expectedHash;
71     return false;
72 }
73 
handleLeftOverTemplateVars(nlohmann::json::iterator & keyPair)74 void handleLeftOverTemplateVars(nlohmann::json::iterator& keyPair)
75 {
76     if (keyPair.value().type() == nlohmann::json::value_t::object ||
77         keyPair.value().type() == nlohmann::json::value_t::array)
78     {
79         for (auto nextLayer = keyPair.value().begin();
80              nextLayer != keyPair.value().end(); nextLayer++)
81         {
82             handleLeftOverTemplateVars(nextLayer);
83         }
84         return;
85     }
86 
87     std::string* strPtr = keyPair.value().get_ptr<std::string*>();
88     if (strPtr == nullptr)
89     {
90         return;
91     }
92 
93     // Walking through the string to find $<templateVar>
94     while (true)
95     {
96         boost::iterator_range<std::string::const_iterator> findStart =
97             boost::ifind_first(*strPtr, std::string_view(templateChar));
98 
99         if (!findStart)
100         {
101             break;
102         }
103 
104         boost::iterator_range<std::string::iterator> searchRange(
105             strPtr->begin() + (findStart.end() - strPtr->begin()),
106             strPtr->end());
107         boost::iterator_range<std::string::const_iterator> findSpace =
108             boost::ifind_first(searchRange, " ");
109 
110         std::string::const_iterator templateVarEnd;
111 
112         if (!findSpace)
113         {
114             // No space means the template var spans to the end of
115             // of the keyPair value
116             templateVarEnd = strPtr->end();
117         }
118         else
119         {
120             // A space marks the end of a template var
121             templateVarEnd = findSpace.begin();
122         }
123 
124         lg2::error(
125             "There's still template variable {VAR} un-replaced. Removing it from the string.\n",
126             "VAR", std::string(findStart.begin(), templateVarEnd));
127         strPtr->erase(findStart.begin(), templateVarEnd);
128     }
129 }
130 
131 // Replaces the template character like the other version of this function,
132 // but checks all properties on all interfaces provided to do the substitution
133 // with.
templateCharReplace(nlohmann::json::iterator & keyPair,const DBusObject & object,const size_t index,const std::optional<std::string> & replaceStr)134 std::optional<std::string> templateCharReplace(
135     nlohmann::json::iterator& keyPair, const DBusObject& object,
136     const size_t index, const std::optional<std::string>& replaceStr)
137 {
138     for (const auto& [_, interface] : object)
139     {
140         auto ret = templateCharReplace(keyPair, interface, index, replaceStr);
141         if (ret)
142         {
143             handleLeftOverTemplateVars(keyPair);
144             return ret;
145         }
146     }
147     handleLeftOverTemplateVars(keyPair);
148     return std::nullopt;
149 }
150 
151 // finds the template character (currently set to $) and replaces the value with
152 // the field found in a dbus object i.e. $ADDRESS would get populated with the
153 // ADDRESS field from a object on dbus
templateCharReplace(nlohmann::json::iterator & keyPair,const DBusInterface & interface,const size_t index,const std::optional<std::string> & replaceStr)154 std::optional<std::string> templateCharReplace(
155     nlohmann::json::iterator& keyPair, const DBusInterface& interface,
156     const size_t index, const std::optional<std::string>& replaceStr)
157 {
158     std::optional<std::string> ret = std::nullopt;
159 
160     if (keyPair.value().type() == nlohmann::json::value_t::object ||
161         keyPair.value().type() == nlohmann::json::value_t::array)
162     {
163         for (auto nextLayer = keyPair.value().begin();
164              nextLayer != keyPair.value().end(); nextLayer++)
165         {
166             templateCharReplace(nextLayer, interface, index, replaceStr);
167         }
168         return ret;
169     }
170 
171     std::string* strPtr = keyPair.value().get_ptr<std::string*>();
172     if (strPtr == nullptr)
173     {
174         return ret;
175     }
176 
177     boost::replace_all(*strPtr, std::string(templateChar) + "index",
178                        std::to_string(index));
179     if (replaceStr)
180     {
181         boost::replace_all(*strPtr, *replaceStr, std::to_string(index));
182     }
183 
184     for (const auto& [propName, propValue] : interface)
185     {
186         std::string templateName = templateChar + propName;
187         boost::iterator_range<std::string::const_iterator> find =
188             boost::ifind_first(*strPtr, templateName);
189         if (!find)
190         {
191             continue;
192         }
193 
194         size_t start = find.begin() - strPtr->begin();
195 
196         // check for additional operations
197         if ((start == 0U) && find.end() == strPtr->end())
198         {
199             std::visit([&](auto&& val) { keyPair.value() = val; }, propValue);
200             return ret;
201         }
202 
203         constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
204                                                          '/'};
205         size_t nextItemIdx = start + templateName.size() + 1;
206 
207         if (nextItemIdx > strPtr->size() ||
208             std::find(mathChars.begin(), mathChars.end(),
209                       strPtr->at(nextItemIdx)) == mathChars.end())
210         {
211             std::string val = std::visit(VariantToStringVisitor(), propValue);
212             boost::ireplace_all(*strPtr, templateName, val);
213             continue;
214         }
215 
216         // save the prefix
217         std::string prefix = strPtr->substr(0, start);
218 
219         // operate on the rest
220         std::string end = strPtr->substr(nextItemIdx);
221 
222         std::vector<std::string> split;
223         boost::split(split, end, boost::is_any_of(" "));
224 
225         // need at least 1 operation and number
226         if (split.size() < 2)
227         {
228             std::cerr << "Syntax error on template replacement of " << *strPtr
229                       << "\n";
230             for (const std::string& data : split)
231             {
232                 std::cerr << data << " ";
233             }
234             std::cerr << "\n";
235             continue;
236         }
237 
238         // we assume that the replacement is a number, because we can
239         // only do math on numbers.. we might concatenate strings in the
240         // future, but thats later
241         int number = std::visit(VariantToIntVisitor(), propValue);
242         auto exprBegin = split.begin();
243         auto exprEnd = split.end();
244 
245         number = expression::evaluate(number, exprBegin, exprEnd);
246 
247         std::string replaced(find.begin(), find.end());
248         while (exprBegin != exprEnd)
249         {
250             replaced.append(" ").append(*exprBegin++);
251         }
252         ret = replaced;
253 
254         std::string result = prefix + std::to_string(number);
255         while (exprEnd != split.end())
256         {
257             result.append(" ").append(*exprEnd++);
258         }
259         keyPair.value() = result;
260 
261         // We probably just invalidated the pointer abovei,
262         // reset and continue to handle multiple templates
263         strPtr = keyPair.value().get_ptr<std::string*>();
264         if (strPtr == nullptr)
265         {
266             break;
267         }
268     }
269 
270     strPtr = keyPair.value().get_ptr<std::string*>();
271     if (strPtr == nullptr)
272     {
273         return ret;
274     }
275 
276     std::string_view strView = *strPtr;
277     int base = 10;
278     if (strView.starts_with("0x"))
279     {
280         strView.remove_prefix(2);
281         base = 16;
282     }
283 
284     uint64_t temp = 0;
285     bool fullMatch = false;
286     const std::from_chars_result res =
287         fromCharsWrapper(strView, temp, fullMatch, base);
288     if (res.ec == std::errc{} && fullMatch)
289     {
290         keyPair.value() = temp;
291     }
292 
293     return ret;
294 }
295 
buildInventorySystemPath(std::string & boardName,const std::string & boardType)296 std::string buildInventorySystemPath(std::string& boardName,
297                                      const std::string& boardType)
298 {
299     std::string path = "/xyz/openbmc_project/inventory/system/";
300     std::string boardTypeLower = boost::algorithm::to_lower_copy(boardType);
301 
302     std::regex_replace(boardName.begin(), boardName.begin(), boardName.end(),
303                        illegalDbusMemberRegex, "_");
304 
305     return std::format("{}{}/{}", path, boardTypeLower, boardName);
306 }
307 } // namespace em_utils
308