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