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