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