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 delimeter 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.first);
357 
358         for (auto& interfaceList : instance.second)
359         {
360             PropertyMap props;//store all the properties
361             for (auto& properties : interfaceList.second)
362             {
363                 std::string section, property, delimiter, value;
364                 for (auto& info : properties.second)
365                 {
366                     if (info.first == "IPMIFruSection")
367                     {
368                         section = std::move(info.second);
369                     }
370                     if (info.first == "IPMIFruProperty")
371                     {
372                         property = std::move(info.second);
373                     }
374                     if (info.first == "IPMIFruValueDelimiter")
375                     {
376                         //Read the delimeter as ascii value
377                         //convert it into char
378                         if( info.second.length() > 0 )
379                         {
380                             char dlm = ' ';
381                             rc = sscanf(info.second.c_str(),"%hhd",&dlm);
382                             if (rc > 0)
383                             {
384                                 delimiter = std::string(1,dlm);
385                             }
386                         }
387                     }
388 
389                 }
390 
391                 if (!section.empty() && !property.empty())
392                 {
393                     value = getFRUValue(section, property, delimiter, fruData);
394                 }
395                 props.emplace(std::move(properties.first), std::move(value));
396             }
397             // Check and update extra properties
398             if(extras.end() != extrasIter)
399             {
400                 const auto& propsIter =
401                     (extrasIter->second).find(interfaceList.first);
402                 if((extrasIter->second).end() != propsIter)
403                 {
404                     for(const auto& map : propsIter->second)
405                     {
406                         props.emplace(map.first, map.second);
407                     }
408                 }
409             }
410             interfaces.emplace(std::move(interfaceList.first),
411                                std::move(props));
412         }
413 
414         // Call the inventory manager
415         sdbusplus::message::object_path path = instance.first;
416         // Check and update extra properties
417         if(extras.end() != extrasIter)
418         {
419             for(const auto& entry : extrasIter->second)
420             {
421                 if(interfaces.end() == interfaces.find(entry.first))
422                 {
423                     interfaces.emplace(entry.first, entry.second);
424                 }
425             }
426         }
427         objects.emplace(path,interfaces);
428     }
429 
430     auto pimMsg = bus.new_method_call(
431                           service.c_str(),
432                           path.c_str(),
433                           intf.c_str(),
434                           "Notify");
435     pimMsg.append(std::move(objects));
436     auto inventoryMgrResponseMsg = bus.call(pimMsg);
437     if (inventoryMgrResponseMsg.is_method_error())
438     {
439         std::cerr << "Error in notify call\n";
440         return -1;
441     }
442     return rc;
443 }
444 
445 ///----------------------------------------------------
446 // Checks if a particular fru area is populated or not
447 ///----------------------------------------------------
448 bool remove_invalid_area(const std::unique_ptr<ipmi_fru> &fru_area)
449 {
450     // Filter the ones that are empty
451     if(!(fru_area->get_len()))
452     {
453         return true;
454     }
455     return false;
456 }
457 
458 ///----------------------------------------------------------------------------------
459 // Populates various FRU areas
460 // @prereq : This must be called only after validating common header.
461 ///----------------------------------------------------------------------------------
462 int ipmi_populate_fru_areas(uint8_t *fru_data, const size_t data_len,
463                             fru_area_vec_t & fru_area_vec)
464 {
465     size_t area_offset = 0;
466     int rc = -1;
467 
468     // Now walk the common header and see if the file size has atleast the last
469     // offset mentioned by the common_hdr. If the file size is less than the
470     // offset of any if the fru areas mentioned in the common header, then we do
471     // not have a complete file.
472     for(uint8_t fru_entry = IPMI_FRU_INTERNAL_OFFSET;
473             fru_entry < (sizeof(struct common_header) -2); fru_entry++)
474     {
475         rc = -1;
476         // Actual offset in the payload is the offset mentioned in common header
477         // multipled by 8. Common header is always the first 8 bytes.
478         area_offset = fru_data[fru_entry] * IPMI_EIGHT_BYTES;
479         if(area_offset && (data_len < (area_offset + 2)))
480         {
481             // Our file size is less than what it needs to be. +2 because we are
482             // using area len that is at 2 byte off area_offset
483             fprintf(stderr, "fru file is incomplete. Size:[%zd]\n",data_len);
484             return rc;
485         }
486         else if(area_offset)
487         {
488             // Read 2 bytes to know the actual size of area.
489             uint8_t area_hdr[2] = {0};
490             memcpy(area_hdr, &((uint8_t *)fru_data)[area_offset], sizeof(area_hdr));
491 
492             // Size of this area will be the 2nd byte in the fru area header.
493             size_t  area_len = area_hdr[1] * IPMI_EIGHT_BYTES;
494             uint8_t area_data[area_len] = {0};
495 
496             printf("fru data size:[%zd], area offset:[%zd], area_size:[%zd]\n",
497                     data_len, area_offset, area_len);
498 
499             // See if we really have that much buffer. We have area offset amd
500             // from there, the actual len.
501             if(data_len < (area_len + area_offset))
502             {
503                 fprintf(stderr, "Incomplete Fru file.. Size:[%zd]\n",data_len);
504                 return rc;
505             }
506 
507             // Save off the data.
508             memcpy(area_data, &((uint8_t *)fru_data)[area_offset], area_len);
509 
510             // Validate the crc
511             rc = verify_fru_data(area_data, area_len);
512             if(rc < 0)
513             {
514                 fprintf(stderr, "Error validating fru area. offset:[%zd]\n",area_offset);
515                 return rc;
516             }
517             else
518             {
519                 printf("Successfully verified area checksum. offset:[%zd]\n",area_offset);
520             }
521 
522             // We already have a vector that is passed to us containing all
523             // of the fields populated. Update the data portion now.
524             for(auto& iter : fru_area_vec)
525             {
526                 if((iter)->get_type() == get_fru_area_type(fru_entry))
527                 {
528                     (iter)->set_data(area_data, area_len);
529                 }
530             }
531         } // If we have fru data present
532     } // Walk common_hdr
533 
534     // Not all the fields will be populated in a fru data. Mostly all cases will
535     // not have more than 2 or 3.
536     fru_area_vec.erase(std::remove_if(fru_area_vec.begin(), fru_area_vec.end(),
537                        remove_invalid_area), fru_area_vec.end());
538 
539     return EXIT_SUCCESS;
540 }
541 
542 ///---------------------------------------------------------
543 // Validates the fru data per ipmi common header constructs.
544 // Returns with updated common_hdr and also file_size
545 //----------------------------------------------------------
546 int ipmi_validate_common_hdr(const uint8_t *fru_data, const size_t data_len)
547 {
548     int rc = -1;
549 
550     uint8_t common_hdr[sizeof(struct common_header)] = {0};
551     if(data_len >= sizeof(common_hdr))
552     {
553         memcpy(common_hdr, fru_data, sizeof(common_hdr));
554     }
555     else
556     {
557         fprintf(stderr, "Incomplete fru data file. Size:[%zd]\n", data_len);
558         return rc;
559     }
560 
561     // Verify the crc and size
562     rc = verify_fru_data(common_hdr, sizeof(common_hdr));
563     if(rc < 0)
564     {
565         fprintf(stderr, "Failed to validate common header\n");
566         return rc;
567     }
568 
569     return EXIT_SUCCESS;
570 }
571 
572 //------------------------------------------------------------
573 // Cleanup routine
574 //------------------------------------------------------------
575 int cleanup_error(FILE *fru_fp, fru_area_vec_t & fru_area_vec)
576 {
577     if(fru_fp != NULL)
578     {
579         fclose(fru_fp);
580         fru_fp = NULL;
581     }
582 
583     if(!(fru_area_vec.empty()))
584     {
585         fru_area_vec.clear();
586     }
587 
588     return  -1;
589 }
590 
591 ///-----------------------------------------------------
592 // Accepts the filename and validates per IPMI FRU spec
593 //----------------------------------------------------
594 int ipmi_validate_fru_area(const uint8_t fruid, const char *fru_file_name,
595                            sd_bus *bus_type, const bool bmc_fru)
596 {
597     size_t data_len = 0;
598     size_t bytes_read = 0;
599     int rc = -1;
600 
601     // Vector that holds individual IPMI FRU AREAs. Although MULTI and INTERNAL
602     // are not used, keeping it here for completeness.
603     fru_area_vec_t fru_area_vec;
604     std::vector<std::string> defined_fru_area;
605 
606     for(uint8_t fru_entry = IPMI_FRU_INTERNAL_OFFSET;
607         fru_entry < (sizeof(struct common_header) -2); fru_entry++)
608     {
609         // Create an object and push onto a vector.
610         std::unique_ptr<ipmi_fru> fru_area = std::make_unique<ipmi_fru>
611                          (fruid, get_fru_area_type(fru_entry), bus_type, bmc_fru);
612 
613         // Physically being present
614         bool present = access(fru_file_name, F_OK) == 0;
615         fru_area->set_present(present);
616 
617         fru_area_vec.emplace_back(std::move(fru_area));
618     }
619 
620     FILE *fru_fp = fopen(fru_file_name,"rb");
621     if(fru_fp == NULL)
622     {
623         fprintf(stderr, "ERROR: opening:[%s]\n",fru_file_name);
624         perror("Error:");
625         return cleanup_error(fru_fp, fru_area_vec);
626     }
627 
628     // Get the size of the file to see if it meets minimum requirement
629     if(fseek(fru_fp, 0, SEEK_END))
630     {
631         perror("Error:");
632         return cleanup_error(fru_fp, fru_area_vec);
633     }
634 
635     // Allocate a buffer to hold entire file content
636     data_len = ftell(fru_fp);
637     uint8_t fru_data[data_len] = {0};
638 
639     rewind(fru_fp);
640     bytes_read = fread(fru_data, data_len, 1, fru_fp);
641     if(bytes_read != 1)
642     {
643         fprintf(stderr, "Failed reading fru data. Bytes_read=[%zd]\n",bytes_read);
644         perror("Error:");
645         return cleanup_error(fru_fp, fru_area_vec);
646     }
647 
648     // We are done reading.
649     fclose(fru_fp);
650     fru_fp = NULL;
651 
652     rc = ipmi_validate_common_hdr(fru_data, data_len);
653     if(rc < 0)
654     {
655         return cleanup_error(fru_fp, fru_area_vec);
656     }
657 
658     // Now that we validated the common header, populate various fru sections if we have them here.
659     rc = ipmi_populate_fru_areas(fru_data, data_len, fru_area_vec);
660     if(rc < 0)
661     {
662         fprintf(stderr,"Populating FRU areas failed for:[%d]\n",fruid);
663         return cleanup_error(fru_fp, fru_area_vec);
664     }
665     else
666     {
667         printf("SUCCESS: Populated FRU areas for:[%s]\n",fru_file_name);
668     }
669 
670 #ifdef __IPMI_DEBUG__
671     for(auto& iter : fru_area_vec)
672     {
673         printf("FRU ID : [%d]\n",(iter)->get_fruid());
674         printf("AREA NAME : [%s]\n",(iter)->get_name());
675         printf("TYPE : [%d]\n",(iter)->get_type());
676         printf("LEN : [%d]\n",(iter)->get_len());
677         printf("BUS NAME : [%s]\n", (iter)->get_bus_name());
678         printf("OBJ PATH : [%s]\n", (iter)->get_obj_path());
679         printf("INTF NAME :[%s]\n", (iter)->get_intf_name());
680     }
681 #endif
682 
683     // If the vector is populated with everything, then go ahead and update the
684     // inventory.
685     if(!(fru_area_vec.empty()))
686     {
687 
688 #ifdef __IPMI_DEBUG__
689         printf("\n SIZE of vector is : [%d] \n",fru_area_vec.size());
690 #endif
691         rc = ipmi_update_inventory(fru_area_vec, bus_type);
692         if(rc <0)
693         {
694             fprintf(stderr, "Error updating inventory\n");
695         }
696     }
697 
698     // we are done with all that we wanted to do. This will do the job of
699     // calling any destructors too.
700     fru_area_vec.clear();
701 
702     return rc;
703 }
704