xref: /openbmc/ipmi-fru-parser/writefrudata.cpp (revision 9a528f244e068f5e5be157b0aa02452642425746)
1 #include "fru-area.hpp"
2 #include "frup.hpp"
3 #include "types.hpp"
4 
5 #include <dlfcn.h>
6 #include <host-ipmid/ipmid-api.h>
7 #include <mapper.h>
8 #include <systemd/sd-bus.h>
9 #include <unistd.h>
10 
11 #include <algorithm>
12 #include <cstdio>
13 #include <cstring>
14 #include <exception>
15 #include <fstream>
16 #include <iostream>
17 #include <memory>
18 #include <phosphor-logging/log.hpp>
19 #include <sdbusplus/server.hpp>
20 #include <sstream>
21 #include <vector>
22 
23 using namespace ipmi::vpd;
24 using namespace phosphor::logging;
25 
26 extern const FruMap frus;
27 extern const std::map<Path, InterfaceMap> extras;
28 
29 namespace
30 {
31 
32 //------------------------------------------------------------
33 // Cleanup routine
34 // Must always be called as last reference to fru_fp.
35 //------------------------------------------------------------
36 int cleanupError(FILE* fru_fp, fru_area_vec_t& fru_area_vec)
37 {
38     if (fru_fp != NULL)
39     {
40         std::fclose(fru_fp);
41     }
42 
43     if (!(fru_area_vec.empty()))
44     {
45         fru_area_vec.clear();
46     }
47 
48     return -1;
49 }
50 
51 //------------------------------------------------------------------------
52 // Gets the value of the key from the fru dictionary of the given section.
53 // FRU dictionary is parsed fru data for all the sections.
54 //------------------------------------------------------------------------
55 std::string getFRUValue(const std::string& section, const std::string& key,
56                         const std::string& delimiter, IPMIFruInfo& fruData)
57 {
58 
59     auto minIndexValue = 0;
60     auto maxIndexValue = 0;
61     std::string fruValue = "";
62 
63     if (section == "Board")
64     {
65         minIndexValue = OPENBMC_VPD_KEY_BOARD_MFG_DATE;
66         maxIndexValue = OPENBMC_VPD_KEY_BOARD_MAX;
67     }
68     else if (section == "Product")
69     {
70         minIndexValue = OPENBMC_VPD_KEY_PRODUCT_MFR;
71         maxIndexValue = OPENBMC_VPD_KEY_PRODUCT_MAX;
72     }
73     else if (section == "Chassis")
74     {
75         minIndexValue = OPENBMC_VPD_KEY_CHASSIS_TYPE;
76         maxIndexValue = OPENBMC_VPD_KEY_CHASSIS_MAX;
77     }
78 
79     auto first = fruData.cbegin() + minIndexValue;
80     auto last = first + (maxIndexValue - minIndexValue) + 1;
81 
82     auto itr =
83         std::find_if(first, last, [&key](auto& e) { return key == e.first; });
84 
85     if (itr != last)
86     {
87         fruValue = itr->second;
88     }
89 
90     // if the key is custom property then the value could be in two formats.
91     // 1) custom field 2 = "value".
92     // 2) custom field 2 =  "key:value".
93     // if delimiter length = 0 i.e custom field 2 = "value"
94 
95     constexpr auto customProp = "Custom Field";
96     if (key.find(customProp) != std::string::npos)
97     {
98         if (delimiter.length() > 0)
99         {
100             size_t delimiterpos = fruValue.find(delimiter);
101             if (delimiterpos != std::string::npos)
102                 fruValue = fruValue.substr(delimiterpos + 1);
103         }
104     }
105     return fruValue;
106 }
107 
108 // Get the inventory service from the mapper.
109 auto getService(sdbusplus::bus::bus& bus, const std::string& intf,
110                 const std::string& path)
111 {
112     auto mapperCall =
113         bus.new_method_call("xyz.openbmc_project.ObjectMapper",
114                             "/xyz/openbmc_project/object_mapper",
115                             "xyz.openbmc_project.ObjectMapper", "GetObject");
116 
117     mapperCall.append(path);
118     mapperCall.append(std::vector<std::string>({intf}));
119     std::map<std::string, std::vector<std::string>> mapperResponse;
120 
121     try
122     {
123         auto mapperResponseMsg = bus.call(mapperCall);
124         mapperResponseMsg.read(mapperResponse);
125     }
126     catch (const sdbusplus::exception::SdBusError& ex)
127     {
128         log<level::ERR>("Exception from sdbus call",
129                         entry("WHAT=%s", ex.what()));
130         throw;
131     }
132 
133     if (mapperResponse.begin() == mapperResponse.end())
134     {
135         throw std::runtime_error("ERROR in reading the mapper response");
136     }
137 
138     return mapperResponse.begin()->first;
139 }
140 
141 // Takes FRU data, invokes Parser for each fru record area and updates
142 // Inventory
143 //------------------------------------------------------------------------
144 int updateInventory(fru_area_vec_t& area_vec, sd_bus* bus_sd)
145 {
146     // Generic error reporter
147     int rc = 0;
148     uint8_t fruid = 0;
149     IPMIFruInfo fruData;
150 
151     // For each FRU area, extract the needed data , get it parsed and update
152     // the Inventory.
153     for (const auto& fruArea : area_vec)
154     {
155         fruid = fruArea->get_fruid();
156         // Fill the container with information
157         rc = parse_fru_area((fruArea)->get_type(), (void*)(fruArea)->get_data(),
158                             (fruArea)->get_len(), fruData);
159         if (rc < 0)
160         {
161             log<level::ERR>("Error parsing FRU records");
162             return rc;
163         }
164     } // END walking the vector of areas and updating
165 
166     // For each Fru we have the list of instances which needs to be updated.
167     // Each instance object implements certain interfaces.
168     // Each Interface is having Dbus properties.
169     // Each Dbus Property would be having metaData(eg section,VpdPropertyName).
170 
171     // Here we are just printing the object,interface and the properties.
172     // which needs to be called with the new inventory manager implementation.
173     sdbusplus::bus::bus bus{bus_sd};
174     using namespace std::string_literals;
175     static const auto intf = "xyz.openbmc_project.Inventory.Manager"s;
176     static const auto path = "/xyz/openbmc_project/inventory"s;
177     std::string service;
178     try
179     {
180         service = getService(bus, intf, path);
181     }
182     catch (const std::exception& e)
183     {
184         std::cerr << e.what() << "\n";
185         return -1;
186     }
187 
188     auto iter = frus.find(fruid);
189     if (iter == frus.end())
190     {
191         log<level::ERR>("Unable to get the fru info",
192                         entry("FRU=%d", static_cast<int>(fruid)));
193         return -1;
194     }
195 
196     auto& instanceList = iter->second;
197     if (instanceList.size() <= 0)
198     {
199         log<level::DEBUG>("Object list empty for this FRU",
200                           entry("FRU=%d", static_cast<int>(fruid)));
201     }
202 
203     ObjectMap objects;
204     for (auto& instance : instanceList)
205     {
206         InterfaceMap interfaces;
207         const auto& extrasIter = extras.find(instance.path);
208 
209         for (auto& interfaceList : instance.interfaces)
210         {
211             PropertyMap props; // store all the properties
212             for (auto& properties : interfaceList.second)
213             {
214                 std::string value;
215                 decltype(auto) pdata = properties.second;
216 
217                 if (!pdata.section.empty() && !pdata.property.empty())
218                 {
219                     value = getFRUValue(pdata.section, pdata.property,
220                                         pdata.delimiter, fruData);
221                 }
222                 props.emplace(std::move(properties.first), std::move(value));
223             }
224             // Check and update extra properties
225             if (extras.end() != extrasIter)
226             {
227                 const auto& propsIter =
228                     (extrasIter->second).find(interfaceList.first);
229                 if ((extrasIter->second).end() != propsIter)
230                 {
231                     for (const auto& map : propsIter->second)
232                     {
233                         props.emplace(map.first, map.second);
234                     }
235                 }
236             }
237             interfaces.emplace(std::move(interfaceList.first),
238                                std::move(props));
239         }
240 
241         // Call the inventory manager
242         sdbusplus::message::object_path objectPath = instance.path;
243         // Check and update extra properties
244         if (extras.end() != extrasIter)
245         {
246             for (const auto& entry : extrasIter->second)
247             {
248                 if (interfaces.end() == interfaces.find(entry.first))
249                 {
250                     interfaces.emplace(entry.first, entry.second);
251                 }
252             }
253         }
254         objects.emplace(objectPath, interfaces);
255     }
256 
257     auto pimMsg = bus.new_method_call(service.c_str(), path.c_str(),
258                                       intf.c_str(), "Notify");
259     pimMsg.append(std::move(objects));
260 
261     try
262     {
263         auto inventoryMgrResponseMsg = bus.call(pimMsg);
264     }
265     catch (const sdbusplus::exception::SdBusError& ex)
266     {
267         log<level::ERR>("Error in notify call", entry("WHAT=%s", ex.what()));
268         return -1;
269     }
270 
271     return rc;
272 }
273 
274 } // namespace
275 
276 //----------------------------------------------------------------
277 // Constructor
278 //----------------------------------------------------------------
279 ipmi_fru::ipmi_fru(const uint8_t fruid, const ipmi_fru_area_type type,
280                    sd_bus* bus_type, bool bmc_fru)
281 {
282     iv_fruid = fruid;
283     iv_type = type;
284     iv_bmc_fru = bmc_fru;
285     iv_bus_type = bus_type;
286     iv_valid = false;
287     iv_data = NULL;
288     iv_present = false;
289 
290     if (iv_type == IPMI_FRU_AREA_INTERNAL_USE)
291     {
292         iv_name = "INTERNAL_";
293     }
294     else if (iv_type == IPMI_FRU_AREA_CHASSIS_INFO)
295     {
296         iv_name = "CHASSIS_";
297     }
298     else if (iv_type == IPMI_FRU_AREA_BOARD_INFO)
299     {
300         iv_name = "BOARD_";
301     }
302     else if (iv_type == IPMI_FRU_AREA_PRODUCT_INFO)
303     {
304         iv_name = "PRODUCT_";
305     }
306     else if (iv_type == IPMI_FRU_AREA_MULTI_RECORD)
307     {
308         iv_name = "MULTI_";
309     }
310     else
311     {
312         iv_name = IPMI_FRU_AREA_TYPE_MAX;
313         log<level::ERR>("Invalid Area", entry("TYPE=%d", iv_type));
314     }
315 }
316 
317 //-----------------------------------------------------
318 // For a FRU area type, accepts the data and updates
319 // area specific data.
320 //-----------------------------------------------------
321 void ipmi_fru::set_data(const uint8_t* data, const size_t len)
322 {
323     iv_len = len;
324     iv_data = new uint8_t[len];
325     std::memcpy(iv_data, data, len);
326 }
327 
328 //-----------------------------------------------------
329 // Sets the dbus parameters
330 //-----------------------------------------------------
331 void ipmi_fru::update_dbus_paths(const char* bus_name, const char* obj_path,
332                                  const char* intf_name)
333 {
334     iv_bus_name = bus_name;
335     iv_obj_path = obj_path;
336     iv_intf_name = intf_name;
337 }
338 
339 //-------------------
340 // Destructor
341 //-------------------
342 ipmi_fru::~ipmi_fru()
343 {
344     if (iv_data != NULL)
345     {
346         delete[] iv_data;
347         iv_data = NULL;
348     }
349 }
350 
351 //------------------------------------------------
352 // Takes the pointer to stream of bytes and length
353 // and returns the 8 bit checksum
354 // This algo is per IPMI V2.0 spec
355 //-------------------------------------------------
356 unsigned char calculate_crc(const unsigned char* data, size_t len)
357 {
358     char crc = 0;
359     size_t byte = 0;
360 
361     for (byte = 0; byte < len; byte++)
362     {
363         crc += *data++;
364     }
365 
366     return (-crc);
367 }
368 
369 //---------------------------------------------------------------------
370 // Accepts a fru area offset in commom hdr and tells which area it is.
371 //---------------------------------------------------------------------
372 ipmi_fru_area_type get_fru_area_type(uint8_t area_offset)
373 {
374     ipmi_fru_area_type type = IPMI_FRU_AREA_TYPE_MAX;
375 
376     switch (area_offset)
377     {
378         case IPMI_FRU_INTERNAL_OFFSET:
379             type = IPMI_FRU_AREA_INTERNAL_USE;
380             break;
381 
382         case IPMI_FRU_CHASSIS_OFFSET:
383             type = IPMI_FRU_AREA_CHASSIS_INFO;
384             break;
385 
386         case IPMI_FRU_BOARD_OFFSET:
387             type = IPMI_FRU_AREA_BOARD_INFO;
388             break;
389 
390         case IPMI_FRU_PRODUCT_OFFSET:
391             type = IPMI_FRU_AREA_PRODUCT_INFO;
392             break;
393 
394         case IPMI_FRU_MULTI_OFFSET:
395             type = IPMI_FRU_AREA_MULTI_RECORD;
396             break;
397 
398         default:
399             type = IPMI_FRU_AREA_TYPE_MAX;
400     }
401 
402     return type;
403 }
404 
405 ///-----------------------------------------------
406 // Validates the data for crc and mandatory fields
407 ///-----------------------------------------------
408 int verify_fru_data(const uint8_t* data, const size_t len)
409 {
410     uint8_t checksum = 0;
411     int rc = -1;
412 
413     // Validate for first byte to always have a value of [1]
414     if (data[0] != IPMI_FRU_HDR_BYTE_ZERO)
415     {
416         log<level::ERR>("Invalid entry in byte-0",
417                         entry("ENTRY=0x%X", static_cast<uint32_t>(data[0])));
418         return rc;
419     }
420 #ifdef __IPMI_DEBUG__
421     else
422     {
423         log<level::DEBUG>("Validated in entry_1 of fru_data",
424                           entry("ENTRY=0x%X", static_cast<uint32_t>(data[0])));
425     }
426 #endif
427 
428     // See if the calculated CRC matches with the embedded one.
429     // CRC to be calculated on all except the last one that is CRC itself.
430     checksum = calculate_crc(data, len - 1);
431     if (checksum != data[len - 1])
432     {
433 #ifdef __IPMI_DEBUG__
434         log<level::ERR>(
435             "Checksum mismatch",
436             entry("Calculated=0x%X", static_cast<uint32_t>(checksum)),
437             entry("Embedded=0x%X", static_cast<uint32_t>(data[len])));
438 #endif
439         return rc;
440     }
441 #ifdef __IPMI_DEBUG__
442     else
443     {
444         log<level::DEBUG>("Checksum matches");
445     }
446 #endif
447 
448     return EXIT_SUCCESS;
449 }
450 
451 ///----------------------------------------------------
452 // Checks if a particular fru area is populated or not
453 ///----------------------------------------------------
454 bool remove_invalid_area(const std::unique_ptr<ipmi_fru>& fru_area)
455 {
456     // Filter the ones that are empty
457     if (!(fru_area->get_len()))
458     {
459         return true;
460     }
461     return false;
462 }
463 
464 ///----------------------------------------------------------------------------------
465 // Populates various FRU areas
466 // @prereq : This must be called only after validating common header.
467 ///----------------------------------------------------------------------------------
468 int ipmi_populate_fru_areas(uint8_t* fru_data, const size_t data_len,
469                             fru_area_vec_t& fru_area_vec)
470 {
471     int rc = -1;
472 
473     // Now walk the common header and see if the file size has atleast the last
474     // offset mentioned by the common_hdr. If the file size is less than the
475     // offset of any if the fru areas mentioned in the common header, then we do
476     // not have a complete file.
477     for (uint8_t fru_entry = IPMI_FRU_INTERNAL_OFFSET;
478          fru_entry < (sizeof(struct common_header) - 2); fru_entry++)
479     {
480         rc = -1;
481         // Actual offset in the payload is the offset mentioned in common header
482         // multiplied by 8. Common header is always the first 8 bytes.
483         size_t area_offset = fru_data[fru_entry] * IPMI_EIGHT_BYTES;
484         if (area_offset && (data_len < (area_offset + 2)))
485         {
486             // Our file size is less than what it needs to be. +2 because we are
487             // using area len that is at 2 byte off area_offset
488             log<level::ERR>("fru file is incomplete",
489                             entry("SIZE=%d", data_len));
490             return rc;
491         }
492         else if (area_offset)
493         {
494             // Read 2 bytes to know the actual size of area.
495             uint8_t area_hdr[2] = {0};
496             std::memcpy(area_hdr, &((uint8_t*)fru_data)[area_offset],
497                         sizeof(area_hdr));
498 
499             // Size of this area will be the 2nd byte in the fru area header.
500             size_t area_len = area_hdr[1] * IPMI_EIGHT_BYTES;
501             uint8_t area_data[area_len] = {0};
502 
503             log<level::DEBUG>("Fru Data", entry("SIZE=%d", data_len),
504                               entry("AREA OFFSET=%d", area_offset),
505                               entry("AREA_SIZE=%d", area_len));
506 
507             // See if we really have that much buffer. We have area offset amd
508             // from there, the actual len.
509             if (data_len < (area_len + area_offset))
510             {
511                 log<level::ERR>("Incomplete Fru file",
512                                 entry("SIZE=%d", data_len));
513                 return rc;
514             }
515 
516             // Save off the data.
517             std::memcpy(area_data, &((uint8_t*)fru_data)[area_offset],
518                         area_len);
519 
520             // Validate the crc
521             rc = verify_fru_data(area_data, area_len);
522             if (rc < 0)
523             {
524                 log<level::ERR>("Err validating fru area",
525                                 entry("OFFSET=%d", area_offset));
526                 return rc;
527             }
528             else
529             {
530                 log<level::DEBUG>("Successfully verified area checksum.",
531                                   entry("OFFSET=%d", area_offset));
532             }
533 
534             // We already have a vector that is passed to us containing all
535             // of the fields populated. Update the data portion now.
536             for (auto& iter : fru_area_vec)
537             {
538                 if ((iter)->get_type() == get_fru_area_type(fru_entry))
539                 {
540                     (iter)->set_data(area_data, area_len);
541                 }
542             }
543         } // If we have fru data present
544     }     // Walk common_hdr
545 
546     // Not all the fields will be populated in a fru data. Mostly all cases will
547     // not have more than 2 or 3.
548     fru_area_vec.erase(std::remove_if(fru_area_vec.begin(), fru_area_vec.end(),
549                                       remove_invalid_area),
550                        fru_area_vec.end());
551 
552     return EXIT_SUCCESS;
553 }
554 
555 ///---------------------------------------------------------
556 // Validates the fru data per ipmi common header constructs.
557 // Returns with updated common_hdr and also file_size
558 //----------------------------------------------------------
559 int ipmi_validate_common_hdr(const uint8_t* fru_data, const size_t data_len)
560 {
561     int rc = -1;
562 
563     uint8_t common_hdr[sizeof(struct common_header)] = {0};
564     if (data_len >= sizeof(common_hdr))
565     {
566         std::memcpy(common_hdr, fru_data, sizeof(common_hdr));
567     }
568     else
569     {
570         log<level::ERR>("Incomplete fru data file", entry("SIZE=%d", data_len));
571         return rc;
572     }
573 
574     // Verify the crc and size
575     rc = verify_fru_data(common_hdr, sizeof(common_hdr));
576     if (rc < 0)
577     {
578         log<level::ERR>("Failed to validate common header");
579         return rc;
580     }
581 
582     return EXIT_SUCCESS;
583 }
584 
585 ///-----------------------------------------------------
586 // Accepts the filename and validates per IPMI FRU spec
587 //----------------------------------------------------
588 int validateFRUArea(const uint8_t fruid, const char* fru_file_name,
589                     sd_bus* bus_type, const bool bmc_fru)
590 {
591     size_t data_len = 0;
592     size_t bytes_read = 0;
593     int rc = -1;
594 
595     // Vector that holds individual IPMI FRU AREAs. Although MULTI and INTERNAL
596     // are not used, keeping it here for completeness.
597     fru_area_vec_t fru_area_vec;
598 
599     for (uint8_t fru_entry = IPMI_FRU_INTERNAL_OFFSET;
600          fru_entry < (sizeof(struct common_header) - 2); fru_entry++)
601     {
602         // Create an object and push onto a vector.
603         std::unique_ptr<ipmi_fru> fru_area = std::make_unique<ipmi_fru>(
604             fruid, get_fru_area_type(fru_entry), bus_type, bmc_fru);
605 
606         // Physically being present
607         bool present = access(fru_file_name, F_OK) == 0;
608         fru_area->set_present(present);
609 
610         fru_area_vec.emplace_back(std::move(fru_area));
611     }
612 
613     FILE* fru_fp = std::fopen(fru_file_name, "rb");
614     if (fru_fp == NULL)
615     {
616         log<level::ERR>("Unable to open fru file",
617                         entry("FILE=%s", fru_file_name),
618                         entry("ERRNO=%s", std::strerror(errno)));
619         return cleanupError(fru_fp, fru_area_vec);
620     }
621 
622     // Get the size of the file to see if it meets minimum requirement
623     if (std::fseek(fru_fp, 0, SEEK_END))
624     {
625         log<level::ERR>("Unable to seek fru file",
626                         entry("FILE=%s", fru_file_name),
627                         entry("ERRNO=%s", std::strerror(errno)));
628         return cleanupError(fru_fp, fru_area_vec);
629     }
630 
631     // Allocate a buffer to hold entire file content
632     data_len = std::ftell(fru_fp);
633     uint8_t fru_data[data_len] = {0};
634 
635     std::rewind(fru_fp);
636     bytes_read = std::fread(fru_data, data_len, 1, fru_fp);
637     if (bytes_read != 1)
638     {
639         log<level::ERR>("Failed reading fru data.",
640                         entry("BYTESREAD=%d", bytes_read),
641                         entry("ERRNO=%s", std::strerror(errno)));
642         return cleanupError(fru_fp, fru_area_vec);
643     }
644 
645     // We are done reading.
646     std::fclose(fru_fp);
647     fru_fp = NULL;
648 
649     rc = ipmi_validate_common_hdr(fru_data, data_len);
650     if (rc < 0)
651     {
652         return cleanupError(fru_fp, fru_area_vec);
653     }
654 
655     // Now that we validated the common header, populate various fru sections if
656     // we have them here.
657     rc = ipmi_populate_fru_areas(fru_data, data_len, fru_area_vec);
658     if (rc < 0)
659     {
660         log<level::ERR>("Populating FRU areas failed", entry("FRU=%d", fruid));
661         return cleanupError(fru_fp, fru_area_vec);
662     }
663     else
664     {
665         log<level::DEBUG>("Populated FRU areas",
666                           entry("FILE=%s", fru_file_name));
667     }
668 
669 #ifdef __IPMI_DEBUG__
670     for (auto& iter : fru_area_vec)
671     {
672         std::printf("FRU ID : [%d]\n", (iter)->get_fruid());
673         std::printf("AREA NAME : [%s]\n", (iter)->get_name());
674         std::printf("TYPE : [%d]\n", (iter)->get_type());
675         std::printf("LEN : [%d]\n", (iter)->get_len());
676         std::printf("BUS NAME : [%s]\n", (iter)->get_bus_name());
677         std::printf("OBJ PATH : [%s]\n", (iter)->get_obj_path());
678         std::printf("INTF NAME :[%s]\n", (iter)->get_intf_name());
679     }
680 #endif
681 
682     // If the vector is populated with everything, then go ahead and update the
683     // inventory.
684     if (!(fru_area_vec.empty()))
685     {
686 
687 #ifdef __IPMI_DEBUG__
688         std::printf("\n SIZE of vector is : [%d] \n", fru_area_vec.size());
689 #endif
690         rc = updateInventory(fru_area_vec, bus_type);
691         if (rc < 0)
692         {
693             log<level::ERR>("Error updating inventory.");
694         }
695     }
696 
697     // we are done with all that we wanted to do. This will do the job of
698     // calling any destructors too.
699     fru_area_vec.clear();
700 
701     return rc;
702 }
703