1 #include "config.h"
2 
3 #include "ibm_vpd_utils.hpp"
4 
5 #include "common_utility.hpp"
6 #include "defines.hpp"
7 #include "vpd_exceptions.hpp"
8 
9 #include <filesystem>
10 #include <fstream>
11 #include <iomanip>
12 #include <nlohmann/json.hpp>
13 #include <phosphor-logging/elog-errors.hpp>
14 #include <phosphor-logging/log.hpp>
15 #include <regex>
16 #include <sdbusplus/server.hpp>
17 #include <sstream>
18 #include <vector>
19 #include <xyz/openbmc_project/Common/error.hpp>
20 
21 using json = nlohmann::json;
22 
23 namespace openpower
24 {
25 namespace vpd
26 {
27 using namespace openpower::vpd::constants;
28 using namespace inventory;
29 using namespace phosphor::logging;
30 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
31 using namespace record;
32 using namespace openpower::vpd::exceptions;
33 using namespace common::utility;
34 using Severity = openpower::vpd::constants::PelSeverity;
35 namespace fs = std::filesystem;
36 
37 // mapping of severity enum to severity interface
38 static std::unordered_map<Severity, std::string> sevMap = {
39     {Severity::INFORMATIONAL,
40      "xyz.openbmc_project.Logging.Entry.Level.Informational"},
41     {Severity::DEBUG, "xyz.openbmc_project.Logging.Entry.Level.Debug"},
42     {Severity::NOTICE, "xyz.openbmc_project.Logging.Entry.Level.Notice"},
43     {Severity::WARNING, "xyz.openbmc_project.Logging.Entry.Level.Warning"},
44     {Severity::CRITICAL, "xyz.openbmc_project.Logging.Entry.Level.Critical"},
45     {Severity::EMERGENCY, "xyz.openbmc_project.Logging.Entry.Level.Emergency"},
46     {Severity::ERROR, "xyz.openbmc_project.Logging.Entry.Level.Error"},
47     {Severity::ALERT, "xyz.openbmc_project.Logging.Entry.Level.Alert"}};
48 
49 namespace inventory
50 {
51 
52 MapperResponse
53     getObjectSubtreeForInterfaces(const std::string& root, const int32_t depth,
54                                   const std::vector<std::string>& interfaces)
55 {
56     auto bus = sdbusplus::bus::new_default();
57     auto mapperCall = bus.new_method_call(mapperDestination, mapperObjectPath,
58                                           mapperInterface, "GetSubTree");
59     mapperCall.append(root);
60     mapperCall.append(depth);
61     mapperCall.append(interfaces);
62 
63     MapperResponse result = {};
64 
65     try
66     {
67         auto response = bus.call(mapperCall);
68 
69         response.read(result);
70     }
71     catch (const sdbusplus::exception::exception& e)
72     {
73         log<level::ERR>("Error in mapper GetSubTree",
74                         entry("ERROR=%s", e.what()));
75     }
76 
77     return result;
78 }
79 
80 } // namespace inventory
81 
82 LE2ByteData readUInt16LE(Binary::const_iterator iterator)
83 {
84     LE2ByteData lowByte = *iterator;
85     LE2ByteData highByte = *(iterator + 1);
86     lowByte |= (highByte << 8);
87     return lowByte;
88 }
89 
90 /** @brief Encodes a keyword for D-Bus.
91  */
92 string encodeKeyword(const string& kw, const string& encoding)
93 {
94     if (encoding == "MAC")
95     {
96         string res{};
97         size_t first = kw[0];
98         res += toHex(first >> 4);
99         res += toHex(first & 0x0f);
100         for (size_t i = 1; i < kw.size(); ++i)
101         {
102             res += ":";
103             res += toHex(kw[i] >> 4);
104             res += toHex(kw[i] & 0x0f);
105         }
106         return res;
107     }
108     else if (encoding == "DATE")
109     {
110         // Date, represent as
111         // <year>-<month>-<day> <hour>:<min>
112         string res{};
113         static constexpr uint8_t skipPrefix = 3;
114 
115         auto strItr = kw.begin();
116         advance(strItr, skipPrefix);
117         for_each(strItr, kw.end(), [&res](size_t c) { res += c; });
118 
119         res.insert(BD_YEAR_END, 1, '-');
120         res.insert(BD_MONTH_END, 1, '-');
121         res.insert(BD_DAY_END, 1, ' ');
122         res.insert(BD_HOUR_END, 1, ':');
123 
124         return res;
125     }
126     else // default to string encoding
127     {
128         return string(kw.begin(), kw.end());
129     }
130 }
131 
132 string readBusProperty(const string& obj, const string& inf, const string& prop)
133 {
134     std::string propVal{};
135     std::string object = INVENTORY_PATH + obj;
136     auto bus = sdbusplus::bus::new_default();
137     auto properties = bus.new_method_call(
138         "xyz.openbmc_project.Inventory.Manager", object.c_str(),
139         "org.freedesktop.DBus.Properties", "Get");
140     properties.append(inf);
141     properties.append(prop);
142     auto result = bus.call(properties);
143     if (!result.is_method_error())
144     {
145         variant<Binary, string> val;
146         result.read(val);
147         if (auto pVal = get_if<Binary>(&val))
148         {
149             propVal.assign(reinterpret_cast<const char*>(pVal->data()),
150                            pVal->size());
151         }
152         else if (auto pVal = get_if<string>(&val))
153         {
154             propVal.assign(pVal->data(), pVal->size());
155         }
156     }
157     return propVal;
158 }
159 
160 void createPEL(const std::map<std::string, std::string>& additionalData,
161                const Severity& sev, const std::string& errIntf)
162 {
163     try
164     {
165         std::string pelSeverity =
166             "xyz.openbmc_project.Logging.Entry.Level.Error";
167         auto bus = sdbusplus::bus::new_default();
168         auto service = getService(bus, loggerObjectPath, loggerCreateInterface);
169         auto method = bus.new_method_call(service.c_str(), loggerObjectPath,
170                                           loggerCreateInterface, "Create");
171 
172         auto itr = sevMap.find(sev);
173         if (itr != sevMap.end())
174         {
175             pelSeverity = itr->second;
176         }
177 
178         method.append(errIntf, pelSeverity, additionalData);
179         auto resp = bus.call(method);
180     }
181     catch (const sdbusplus::exception::exception& e)
182     {
183         throw std::runtime_error(
184             "Error in invoking D-Bus logging create interface to register PEL");
185     }
186 }
187 
188 inventory::VPDfilepath getVpdFilePath(const string& jsonFile,
189                                       const std::string& ObjPath)
190 {
191     ifstream inventoryJson(jsonFile);
192     const auto& jsonObject = json::parse(inventoryJson);
193     inventory::VPDfilepath filePath{};
194 
195     if (jsonObject.find("frus") == jsonObject.end())
196     {
197         throw(VpdJsonException(
198             "Invalid JSON structure - frus{} object not found in ", jsonFile));
199     }
200 
201     const nlohmann::json& groupFRUS =
202         jsonObject["frus"].get_ref<const nlohmann::json::object_t&>();
203     for (const auto& itemFRUS : groupFRUS.items())
204     {
205         const std::vector<nlohmann::json>& groupEEPROM =
206             itemFRUS.value().get_ref<const nlohmann::json::array_t&>();
207         for (const auto& itemEEPROM : groupEEPROM)
208         {
209             if (itemEEPROM["inventoryPath"]
210                     .get_ref<const nlohmann::json::string_t&>() == ObjPath)
211             {
212                 filePath = itemFRUS.key();
213                 return filePath;
214             }
215         }
216     }
217 
218     return filePath;
219 }
220 
221 bool isPathInJson(const std::string& eepromPath)
222 {
223     bool present = false;
224     ifstream inventoryJson(INVENTORY_JSON_SYM_LINK);
225 
226     try
227     {
228         auto js = json::parse(inventoryJson);
229         if (js.find("frus") == js.end())
230         {
231             throw(VpdJsonException(
232                 "Invalid JSON structure - frus{} object not found in ",
233                 INVENTORY_JSON_SYM_LINK));
234         }
235         json fruJson = js["frus"];
236 
237         if (fruJson.find(eepromPath) != fruJson.end())
238         {
239             present = true;
240         }
241     }
242     catch (const json::parse_error& ex)
243     {
244         throw(VpdJsonException("Json Parsing failed", INVENTORY_JSON_SYM_LINK));
245     }
246     return present;
247 }
248 
249 bool isRecKwInDbusJson(const std::string& recordName,
250                        const std::string& keyword)
251 {
252     ifstream propertyJson(DBUS_PROP_JSON);
253     json dbusProperty;
254     bool present = false;
255 
256     if (propertyJson.is_open())
257     {
258         try
259         {
260             auto dbusPropertyJson = json::parse(propertyJson);
261             if (dbusPropertyJson.find("dbusProperties") ==
262                 dbusPropertyJson.end())
263             {
264                 throw(VpdJsonException("dbusProperties{} object not found in "
265                                        "DbusProperties json : ",
266                                        DBUS_PROP_JSON));
267             }
268 
269             dbusProperty = dbusPropertyJson["dbusProperties"];
270             if (dbusProperty.contains(recordName))
271             {
272                 const vector<string>& kwdsToPublish = dbusProperty[recordName];
273                 if (find(kwdsToPublish.begin(), kwdsToPublish.end(), keyword) !=
274                     kwdsToPublish.end()) // present
275                 {
276                     present = true;
277                 }
278             }
279         }
280         catch (const json::parse_error& ex)
281         {
282             throw(VpdJsonException("Json Parsing failed", DBUS_PROP_JSON));
283         }
284     }
285     else
286     {
287         // If dbus properties json is not available, we assume the given
288         // record-keyword is part of dbus-properties json. So setting the bool
289         // variable to true.
290         present = true;
291     }
292     return present;
293 }
294 
295 vpdType vpdTypeCheck(const Binary& vpdVector)
296 {
297     // Read first 3 Bytes to check the 11S bar code format
298     std::string is11SFormat = "";
299     for (uint8_t i = 0; i < FORMAT_11S_LEN; i++)
300     {
301         is11SFormat += vpdVector[MEMORY_VPD_DATA_START + i];
302     }
303 
304     if (vpdVector[IPZ_DATA_START] == KW_VAL_PAIR_START_TAG)
305     {
306         // IPZ VPD FORMAT
307         return vpdType::IPZ_VPD;
308     }
309     else if (vpdVector[KW_VPD_DATA_START] == KW_VPD_START_TAG)
310     {
311         // KEYWORD VPD FORMAT
312         return vpdType::KEYWORD_VPD;
313     }
314     else if (is11SFormat.compare(MEMORY_VPD_START_TAG) == 0)
315     {
316         // Memory VPD format
317         return vpdType::MEMORY_VPD;
318     }
319 
320     // INVALID VPD FORMAT
321     return vpdType::INVALID_VPD_FORMAT;
322 }
323 
324 const string getIM(const Parsed& vpdMap)
325 {
326     Binary imVal;
327     auto property = vpdMap.find("VSBP");
328     if (property != vpdMap.end())
329     {
330         auto kw = (property->second).find("IM");
331         if (kw != (property->second).end())
332         {
333             copy(kw->second.begin(), kw->second.end(), back_inserter(imVal));
334         }
335     }
336 
337     ostringstream oss;
338     for (auto& i : imVal)
339     {
340         oss << setw(2) << setfill('0') << hex << static_cast<int>(i);
341     }
342 
343     return oss.str();
344 }
345 
346 const string getHW(const Parsed& vpdMap)
347 {
348     Binary hwVal;
349     auto prop = vpdMap.find("VINI");
350     if (prop != vpdMap.end())
351     {
352         auto kw = (prop->second).find("HW");
353         if (kw != (prop->second).end())
354         {
355             copy(kw->second.begin(), kw->second.end(), back_inserter(hwVal));
356         }
357     }
358 
359     // The planar pass only comes from the LSB of the HW keyword,
360     // where as the MSB is used for other purposes such as signifying clock
361     // termination.
362     hwVal[0] = 0x00;
363 
364     ostringstream hwString;
365     for (auto& i : hwVal)
366     {
367         hwString << setw(2) << setfill('0') << hex << static_cast<int>(i);
368     }
369 
370     return hwString.str();
371 }
372 
373 string getSystemsJson(const Parsed& vpdMap)
374 {
375     string jsonPath = "/usr/share/vpd/";
376     string jsonName{};
377 
378     ifstream systemJson(SYSTEM_JSON);
379     if (!systemJson)
380     {
381         throw((VpdJsonException("Failed to access Json path", SYSTEM_JSON)));
382     }
383 
384     try
385     {
386         auto js = json::parse(systemJson);
387 
388         const string hwKeyword = getHW(vpdMap);
389         const string imKeyword = getIM(vpdMap);
390 
391         if (js.find("system") == js.end())
392         {
393             throw runtime_error("Invalid systems Json");
394         }
395 
396         if (js["system"].find(imKeyword) == js["system"].end())
397         {
398             throw runtime_error(
399                 "Invalid system. This system type is not present "
400                 "in the systemsJson. IM: " +
401                 imKeyword);
402         }
403 
404         if ((js["system"][imKeyword].find("constraint") !=
405              js["system"][imKeyword].end()) &&
406             (hwKeyword == js["system"][imKeyword]["constraint"]["HW"]))
407         {
408             jsonName = js["system"][imKeyword]["constraint"]["json"];
409         }
410         else if (js["system"][imKeyword].find("default") !=
411                  js["system"][imKeyword].end())
412         {
413             jsonName = js["system"][imKeyword]["default"];
414         }
415         else
416         {
417             throw runtime_error(
418                 "Bad System json. Neither constraint nor default found");
419         }
420 
421         jsonPath += jsonName;
422     }
423 
424     catch (const json::parse_error& ex)
425     {
426         throw(VpdJsonException("Json Parsing failed", SYSTEM_JSON));
427     }
428     return jsonPath;
429 }
430 
431 void udevToGenericPath(string& file)
432 {
433     // Sample udevEvent i2c path :
434     // "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a480.i2c-bus/i2c-8/8-0051/8-00510/nvmem"
435     // find if the path contains the word i2c in it.
436     if (file.find("i2c") != string::npos)
437     {
438         string i2cBusAddr{};
439 
440         // Every udev i2c path should have the common pattern
441         // "i2c-bus_number/bus_number-vpd_address". Search for
442         // "bus_number-vpd_address".
443         regex i2cPattern("((i2c)-[0-9]+\\/)([0-9]+-[0-9]{4})");
444         smatch match;
445         if (regex_search(file, match, i2cPattern))
446         {
447             i2cBusAddr = match.str(3);
448         }
449         else
450         {
451             cerr << "The given udev path < " << file
452                  << " > doesn't match the required pattern. Skipping VPD "
453                     "collection."
454                  << endl;
455             exit(EXIT_SUCCESS);
456         }
457         // Forming the generic file path
458         file = i2cPathPrefix + i2cBusAddr + "/eeprom";
459     }
460     // Sample udevEvent spi path :
461     // "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/fsi0/slave@00:00/00:00:00:04/spi_master/spi2/spi2.0/spi2.00/nvmem"
462     // find if the path contains the word spi in it.
463     else if (file.find("spi") != string::npos)
464     {
465         // Every udev spi path will have common pattern "spi<Digit>/", which
466         // describes the spi bus number at which the fru is connected; Followed
467         // by a slash following the vpd address of the fru. Taking the above
468         // input as a common key, we try to search for the pattern "spi<Digit>/"
469         // using regular expression.
470         regex spiPattern("((spi)[0-9]+)(\\/)");
471         string spiBus{};
472         smatch match;
473         if (regex_search(file, match, spiPattern))
474         {
475             spiBus = match.str(1);
476         }
477         else
478         {
479             cerr << "The given udev path < " << file
480                  << " > doesn't match the required pattern. Skipping VPD "
481                     "collection."
482                  << endl;
483             exit(EXIT_SUCCESS);
484         }
485         // Forming the generic path
486         file = spiPathPrefix + spiBus + ".0/eeprom";
487     }
488     else
489     {
490         cerr << "\n The given EEPROM path < " << file
491              << " > is not valid. It's neither I2C nor "
492                 "SPI path. Skipping VPD collection.."
493              << endl;
494         exit(EXIT_SUCCESS);
495     }
496 }
497 string getBadVpdName(const string& file)
498 {
499     string badVpd = BAD_VPD_DIR;
500     if (file.find("i2c") != string::npos)
501     {
502         badVpd += "i2c-";
503         regex i2cPattern("(at24/)([0-9]+-[0-9]+)\\/");
504         smatch match;
505         if (regex_search(file, match, i2cPattern))
506         {
507             badVpd += match.str(2);
508         }
509     }
510     else if (file.find("spi") != string::npos)
511     {
512         regex spiPattern("((spi)[0-9]+)(.0)");
513         smatch match;
514         if (regex_search(file, match, spiPattern))
515         {
516             badVpd += match.str(1);
517         }
518     }
519     return badVpd;
520 }
521 
522 void dumpBadVpd(const string& file, const Binary& vpdVector)
523 {
524     fs::path badVpdDir = BAD_VPD_DIR;
525     fs::create_directory(badVpdDir);
526     string badVpdPath = getBadVpdName(file);
527     if (fs::exists(badVpdPath))
528     {
529         std::error_code ec;
530         fs::remove(badVpdPath, ec);
531         if (ec) // error code
532         {
533             string error = "Error removing the existing broken vpd in ";
534             error += badVpdPath;
535             error += ". Error code : ";
536             error += ec.value();
537             error += ". Error message : ";
538             error += ec.message();
539             throw runtime_error(error);
540         }
541     }
542     ofstream badVpdFileStream(badVpdPath, ofstream::binary);
543     if (!badVpdFileStream)
544     {
545         throw runtime_error("Failed to open bad vpd file path in /tmp/bad-vpd. "
546                             "Unable to dump the broken/bad vpd file.");
547     }
548     badVpdFileStream.write(reinterpret_cast<const char*>(vpdVector.data()),
549                            vpdVector.size());
550 }
551 
552 const string getKwVal(const Parsed& vpdMap, const string& rec,
553                       const string& kwd)
554 {
555     string kwVal{};
556 
557     auto findRec = vpdMap.find(rec);
558 
559     // check if record is found in map we got by parser
560     if (findRec != vpdMap.end())
561     {
562         auto findKwd = findRec->second.find(kwd);
563 
564         if (findKwd != findRec->second.end())
565         {
566             kwVal = findKwd->second;
567         }
568     }
569 
570     return kwVal;
571 }
572 
573 string byteArrayToHexString(const Binary& vec)
574 {
575     stringstream ss;
576     string hexRep = "0x";
577     ss << hexRep;
578     string str = ss.str();
579 
580     // convert Decimal to Hex string
581     for (auto& v : vec)
582     {
583         ss << setfill('0') << setw(2) << hex << (int)v;
584         str = ss.str();
585     }
586     return str;
587 }
588 
589 string getPrintableValue(const Binary& vec)
590 {
591     string str{};
592 
593     // find for a non printable value in the vector
594     const auto it = std::find_if(vec.begin(), vec.end(),
595                                  [](const auto& ele) { return !isprint(ele); });
596 
597     if (it != vec.end()) // if the given vector has any non printable value
598     {
599         for (auto itr = it; itr != vec.end(); itr++)
600         {
601             if (*itr != 0x00)
602             {
603                 str = byteArrayToHexString(vec);
604                 return str;
605             }
606         }
607         str = string(vec.begin(), it);
608     }
609     else
610     {
611         str = string(vec.begin(), vec.end());
612     }
613     return str;
614 }
615 
616 } // namespace vpd
617 } // namespace openpower