1 #include "storagehandler.hpp"
2 
3 #include "fruread.hpp"
4 #include "read_fru_data.hpp"
5 #include "selutility.hpp"
6 #include "sensorhandler.hpp"
7 #include "storageaddsel.hpp"
8 
9 #include <arpa/inet.h>
10 #include <mapper.h>
11 #include <systemd/sd-bus.h>
12 
13 #include <algorithm>
14 #include <chrono>
15 #include <cstdio>
16 #include <cstring>
17 #include <filesystem>
18 #include <ipmid/api.hpp>
19 #include <ipmid/utils.hpp>
20 #include <phosphor-logging/elog-errors.hpp>
21 #include <phosphor-logging/log.hpp>
22 #include <sdbusplus/message/types.hpp>
23 #include <sdbusplus/server.hpp>
24 #include <string>
25 #include <xyz/openbmc_project/Common/error.hpp>
26 
27 void register_netfn_storage_functions() __attribute__((constructor));
28 
29 unsigned int g_sel_time = 0xFFFFFFFF;
30 extern const ipmi::sensor::IdInfoMap sensors;
31 extern const FruMap frus;
32 
33 namespace
34 {
35 constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime";
36 constexpr auto HOST_TIME_PATH = "/xyz/openbmc_project/time/host";
37 constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties";
38 constexpr auto PROPERTY_ELAPSED = "Elapsed";
39 
40 const char* getTimeString(const uint64_t& usecSinceEpoch)
41 {
42     using namespace std::chrono;
43     system_clock::time_point tp{microseconds(usecSinceEpoch)};
44     auto t = system_clock::to_time_t(tp);
45     return std::ctime(&t);
46 }
47 } // namespace
48 
49 namespace cache
50 {
51 /*
52  * This cache contains the object paths of the logging entries sorted in the
53  * order of the filename(numeric order). The cache is initialized by
54  * invoking readLoggingObjectPaths with the cache as the parameter. The
55  * cache is invoked in the execution of the Get SEL info and Delete SEL
56  * entry command. The Get SEL Info command is typically invoked before the
57  * Get SEL entry command, so the cache is utilized for responding to Get SEL
58  * entry command. The cache is invalidated by clearing after Delete SEL
59  * entry and Clear SEL command.
60  */
61 ipmi::sel::ObjectPaths paths;
62 
63 } // namespace cache
64 
65 using InternalFailure =
66     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
67 using namespace phosphor::logging;
68 using namespace ipmi::fru;
69 
70 /**
71  * @enum Device access mode
72  */
73 enum class AccessMode
74 {
75     bytes, ///< Device is accessed by bytes
76     words  ///< Device is accessed by words
77 };
78 
79 ipmi_ret_t ipmi_storage_wildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
80                                  ipmi_request_t request,
81                                  ipmi_response_t response,
82                                  ipmi_data_len_t data_len,
83                                  ipmi_context_t context)
84 {
85     // Status code.
86     ipmi_ret_t rc = IPMI_CC_INVALID;
87     *data_len = 0;
88     return rc;
89 }
90 
91 ipmi_ret_t getSELInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
92                       ipmi_request_t request, ipmi_response_t response,
93                       ipmi_data_len_t data_len, ipmi_context_t context)
94 {
95     if (*data_len != 0)
96     {
97         *data_len = 0;
98         return IPMI_CC_REQ_DATA_LEN_INVALID;
99     }
100 
101     std::vector<uint8_t> outPayload(sizeof(ipmi::sel::GetSELInfoResponse));
102     auto responseData =
103         reinterpret_cast<ipmi::sel::GetSELInfoResponse*>(outPayload.data());
104 
105     responseData->selVersion = ipmi::sel::selVersion;
106     // Last erase timestamp is not available from log manager.
107     responseData->eraseTimeStamp = ipmi::sel::invalidTimeStamp;
108     responseData->operationSupport = ipmi::sel::operationSupport;
109 
110     try
111     {
112         ipmi::sel::readLoggingObjectPaths(cache::paths);
113     }
114     catch (const sdbusplus::exception::SdBusError& e)
115     {
116         // No action if reading log objects have failed for this command.
117         // readLoggingObjectPaths will throw exception if there are no log
118         // entries. The command will be responded with number of SEL entries
119         // as 0.
120     }
121 
122     responseData->entries = 0;
123     responseData->addTimeStamp = ipmi::sel::invalidTimeStamp;
124 
125     if (!cache::paths.empty())
126     {
127         responseData->entries = static_cast<uint16_t>(cache::paths.size());
128 
129         try
130         {
131             responseData->addTimeStamp = static_cast<uint32_t>(
132                 (ipmi::sel::getEntryTimeStamp(cache::paths.back()).count()));
133         }
134         catch (InternalFailure& e)
135         {
136         }
137         catch (const std::runtime_error& e)
138         {
139             log<level::ERR>(e.what());
140         }
141     }
142 
143     std::memcpy(response, outPayload.data(), outPayload.size());
144     *data_len = outPayload.size();
145 
146     return IPMI_CC_OK;
147 }
148 
149 ipmi_ret_t getSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
150                        ipmi_request_t request, ipmi_response_t response,
151                        ipmi_data_len_t data_len, ipmi_context_t context)
152 {
153     if (*data_len != sizeof(ipmi::sel::GetSELEntryRequest))
154     {
155         *data_len = 0;
156         return IPMI_CC_REQ_DATA_LEN_INVALID;
157     }
158 
159     auto requestData =
160         reinterpret_cast<const ipmi::sel::GetSELEntryRequest*>(request);
161 
162     if (requestData->reservationID != 0)
163     {
164         if (!checkSELReservation(requestData->reservationID))
165         {
166             *data_len = 0;
167             return IPMI_CC_INVALID_RESERVATION_ID;
168         }
169     }
170 
171     if (cache::paths.empty())
172     {
173         *data_len = 0;
174         return IPMI_CC_SENSOR_INVALID;
175     }
176 
177     ipmi::sel::ObjectPaths::const_iterator iter;
178 
179     // Check for the requested SEL Entry.
180     if (requestData->selRecordID == ipmi::sel::firstEntry)
181     {
182         iter = cache::paths.begin();
183     }
184     else if (requestData->selRecordID == ipmi::sel::lastEntry)
185     {
186         iter = cache::paths.end();
187     }
188     else
189     {
190         std::string objPath = std::string(ipmi::sel::logBasePath) + "/" +
191                               std::to_string(requestData->selRecordID);
192 
193         iter = std::find(cache::paths.begin(), cache::paths.end(), objPath);
194         if (iter == cache::paths.end())
195         {
196             *data_len = 0;
197             return IPMI_CC_SENSOR_INVALID;
198         }
199     }
200 
201     ipmi::sel::GetSELEntryResponse record{};
202 
203     // Convert the log entry into SEL record.
204     try
205     {
206         record = ipmi::sel::convertLogEntrytoSEL(*iter);
207     }
208     catch (InternalFailure& e)
209     {
210         *data_len = 0;
211         return IPMI_CC_UNSPECIFIED_ERROR;
212     }
213     catch (const std::runtime_error& e)
214     {
215         log<level::ERR>(e.what());
216         *data_len = 0;
217         return IPMI_CC_UNSPECIFIED_ERROR;
218     }
219 
220     // Identify the next SEL record ID
221     if (iter != cache::paths.end())
222     {
223         ++iter;
224         if (iter == cache::paths.end())
225         {
226             record.nextRecordID = ipmi::sel::lastEntry;
227         }
228         else
229         {
230             namespace fs = std::filesystem;
231             fs::path path(*iter);
232             record.nextRecordID = static_cast<uint16_t>(
233                 std::stoul(std::string(path.filename().c_str())));
234         }
235     }
236     else
237     {
238         record.nextRecordID = ipmi::sel::lastEntry;
239     }
240 
241     if (requestData->readLength == ipmi::sel::entireRecord)
242     {
243         std::memcpy(response, &record, sizeof(record));
244         *data_len = sizeof(record);
245     }
246     else
247     {
248         if (requestData->offset >= ipmi::sel::selRecordSize ||
249             requestData->readLength > ipmi::sel::selRecordSize)
250         {
251             *data_len = 0;
252             return IPMI_CC_INVALID_FIELD_REQUEST;
253         }
254 
255         auto diff = ipmi::sel::selRecordSize - requestData->offset;
256         auto readLength =
257             std::min(diff, static_cast<int>(requestData->readLength));
258 
259         std::memcpy(response, &record.nextRecordID,
260                     sizeof(record.nextRecordID));
261         std::memcpy(static_cast<uint8_t*>(response) +
262                         sizeof(record.nextRecordID),
263                     &record.recordID + requestData->offset, readLength);
264         *data_len = sizeof(record.nextRecordID) + readLength;
265     }
266 
267     return IPMI_CC_OK;
268 }
269 
270 ipmi_ret_t deleteSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
271                           ipmi_request_t request, ipmi_response_t response,
272                           ipmi_data_len_t data_len, ipmi_context_t context)
273 {
274     if (*data_len != sizeof(ipmi::sel::DeleteSELEntryRequest))
275     {
276         *data_len = 0;
277         return IPMI_CC_REQ_DATA_LEN_INVALID;
278     }
279 
280     namespace fs = std::filesystem;
281     auto requestData =
282         reinterpret_cast<const ipmi::sel::DeleteSELEntryRequest*>(request);
283 
284     if (!checkSELReservation(requestData->reservationID))
285     {
286         *data_len = 0;
287         return IPMI_CC_INVALID_RESERVATION_ID;
288     }
289 
290     // Per the IPMI spec, need to cancel the reservation when a SEL entry is
291     // deleted
292     cancelSELReservation();
293 
294     try
295     {
296         ipmi::sel::readLoggingObjectPaths(cache::paths);
297     }
298     catch (const sdbusplus::exception::SdBusError& e)
299     {
300         // readLoggingObjectPaths will throw exception if there are no error
301         // log entries.
302         *data_len = 0;
303         return IPMI_CC_SENSOR_INVALID;
304     }
305 
306     if (cache::paths.empty())
307     {
308         *data_len = 0;
309         return IPMI_CC_SENSOR_INVALID;
310     }
311 
312     ipmi::sel::ObjectPaths::const_iterator iter;
313     uint16_t delRecordID = 0;
314 
315     if (requestData->selRecordID == ipmi::sel::firstEntry)
316     {
317         iter = cache::paths.begin();
318         fs::path path(*iter);
319         delRecordID = static_cast<uint16_t>(
320             std::stoul(std::string(path.filename().c_str())));
321     }
322     else if (requestData->selRecordID == ipmi::sel::lastEntry)
323     {
324         iter = cache::paths.end();
325         fs::path path(*iter);
326         delRecordID = static_cast<uint16_t>(
327             std::stoul(std::string(path.filename().c_str())));
328     }
329     else
330     {
331         std::string objPath = std::string(ipmi::sel::logBasePath) + "/" +
332                               std::to_string(requestData->selRecordID);
333 
334         iter = std::find(cache::paths.begin(), cache::paths.end(), objPath);
335         if (iter == cache::paths.end())
336         {
337             *data_len = 0;
338             return IPMI_CC_SENSOR_INVALID;
339         }
340         delRecordID = requestData->selRecordID;
341     }
342 
343     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
344     std::string service;
345 
346     try
347     {
348         service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, *iter);
349     }
350     catch (const std::runtime_error& e)
351     {
352         log<level::ERR>(e.what());
353         *data_len = 0;
354         return IPMI_CC_UNSPECIFIED_ERROR;
355     }
356 
357     auto methodCall = bus.new_method_call(service.c_str(), (*iter).c_str(),
358                                           ipmi::sel::logDeleteIntf, "Delete");
359     auto reply = bus.call(methodCall);
360     if (reply.is_method_error())
361     {
362         *data_len = 0;
363         return IPMI_CC_UNSPECIFIED_ERROR;
364     }
365 
366     // Invalidate the cache of dbus entry objects.
367     cache::paths.clear();
368     std::memcpy(response, &delRecordID, sizeof(delRecordID));
369     *data_len = sizeof(delRecordID);
370 
371     return IPMI_CC_OK;
372 }
373 
374 ipmi_ret_t clearSEL(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
375                     ipmi_response_t response, ipmi_data_len_t data_len,
376                     ipmi_context_t context)
377 {
378     if (*data_len != sizeof(ipmi::sel::ClearSELRequest))
379     {
380         *data_len = 0;
381         return IPMI_CC_REQ_DATA_LEN_INVALID;
382     }
383 
384     auto requestData =
385         reinterpret_cast<const ipmi::sel::ClearSELRequest*>(request);
386 
387     if (!checkSELReservation(requestData->reservationID))
388     {
389         *data_len = 0;
390         return IPMI_CC_INVALID_RESERVATION_ID;
391     }
392 
393     if (requestData->charC != 'C' || requestData->charL != 'L' ||
394         requestData->charR != 'R')
395     {
396         *data_len = 0;
397         return IPMI_CC_INVALID_FIELD_REQUEST;
398     }
399 
400     uint8_t eraseProgress = ipmi::sel::eraseComplete;
401 
402     /*
403      * Erasure status cannot be fetched from DBUS, so always return erasure
404      * status as `erase completed`.
405      */
406     if (requestData->eraseOperation == ipmi::sel::getEraseStatus)
407     {
408         std::memcpy(response, &eraseProgress, sizeof(eraseProgress));
409         *data_len = sizeof(eraseProgress);
410         return IPMI_CC_OK;
411     }
412 
413     // Per the IPMI spec, need to cancel any reservation when the SEL is cleared
414     cancelSELReservation();
415 
416     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
417     ipmi::sel::ObjectPaths objectPaths;
418     auto depth = 0;
419 
420     auto mapperCall =
421         bus.new_method_call(ipmi::sel::mapperBusName, ipmi::sel::mapperObjPath,
422                             ipmi::sel::mapperIntf, "GetSubTreePaths");
423     mapperCall.append(ipmi::sel::logBasePath);
424     mapperCall.append(depth);
425     mapperCall.append(ipmi::sel::ObjectPaths({ipmi::sel::logEntryIntf}));
426 
427     try
428     {
429         auto reply = bus.call(mapperCall);
430         if (reply.is_method_error())
431         {
432             std::memcpy(response, &eraseProgress, sizeof(eraseProgress));
433             *data_len = sizeof(eraseProgress);
434             return IPMI_CC_OK;
435         }
436 
437         reply.read(objectPaths);
438         if (objectPaths.empty())
439         {
440             std::memcpy(response, &eraseProgress, sizeof(eraseProgress));
441             *data_len = sizeof(eraseProgress);
442             return IPMI_CC_OK;
443         }
444     }
445     catch (const sdbusplus::exception::SdBusError& e)
446     {
447         std::memcpy(response, &eraseProgress, sizeof(eraseProgress));
448         *data_len = sizeof(eraseProgress);
449         return IPMI_CC_OK;
450     }
451 
452     std::string service;
453 
454     try
455     {
456         service = ipmi::getService(bus, ipmi::sel::logDeleteIntf,
457                                    objectPaths.front());
458     }
459     catch (const std::runtime_error& e)
460     {
461         log<level::ERR>(e.what());
462         *data_len = 0;
463         return IPMI_CC_UNSPECIFIED_ERROR;
464     }
465 
466     for (const auto& iter : objectPaths)
467     {
468         auto methodCall = bus.new_method_call(
469             service.c_str(), iter.c_str(), ipmi::sel::logDeleteIntf, "Delete");
470 
471         auto reply = bus.call(methodCall);
472         if (reply.is_method_error())
473         {
474             *data_len = 0;
475             return IPMI_CC_UNSPECIFIED_ERROR;
476         }
477     }
478 
479     // Invalidate the cache of dbus entry objects.
480     cache::paths.clear();
481     std::memcpy(response, &eraseProgress, sizeof(eraseProgress));
482     *data_len = sizeof(eraseProgress);
483     return IPMI_CC_OK;
484 }
485 
486 ipmi_ret_t ipmi_storage_get_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
487                                      ipmi_request_t request,
488                                      ipmi_response_t response,
489                                      ipmi_data_len_t data_len,
490                                      ipmi_context_t context)
491 {
492     if (*data_len != 0)
493     {
494         *data_len = 0;
495         return IPMI_CC_REQ_DATA_LEN_INVALID;
496     }
497 
498     using namespace std::chrono;
499     uint64_t host_time_usec = 0;
500     uint32_t resp = 0;
501     std::stringstream hostTime;
502 
503     try
504     {
505         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
506         auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH);
507         sdbusplus::message::variant<uint64_t> value;
508 
509         // Get host time
510         auto method = bus.new_method_call(service.c_str(), HOST_TIME_PATH,
511                                           DBUS_PROPERTIES, "Get");
512 
513         method.append(TIME_INTERFACE, PROPERTY_ELAPSED);
514         auto reply = bus.call(method);
515         if (reply.is_method_error())
516         {
517             log<level::ERR>("Error getting time",
518                             entry("SERVICE=%s", service.c_str()),
519                             entry("PATH=%s", HOST_TIME_PATH));
520             return IPMI_CC_UNSPECIFIED_ERROR;
521         }
522         reply.read(value);
523         host_time_usec = std::get<uint64_t>(value);
524     }
525     catch (InternalFailure& e)
526     {
527         log<level::ERR>(e.what());
528         return IPMI_CC_UNSPECIFIED_ERROR;
529     }
530     catch (const std::runtime_error& e)
531     {
532         log<level::ERR>(e.what());
533         return IPMI_CC_UNSPECIFIED_ERROR;
534     }
535 
536     hostTime << "Host time:" << getTimeString(host_time_usec);
537     log<level::DEBUG>(hostTime.str().c_str());
538 
539     // Time is really long int but IPMI wants just uint32. This works okay until
540     // the number of seconds since 1970 overflows uint32 size.. Still a whole
541     // lot of time here to even think about that.
542     resp = duration_cast<seconds>(microseconds(host_time_usec)).count();
543     resp = htole32(resp);
544 
545     // From the IPMI Spec 2.0, response should be a 32-bit value
546     *data_len = sizeof(resp);
547 
548     // Pack the actual response
549     std::memcpy(response, &resp, *data_len);
550 
551     return IPMI_CC_OK;
552 }
553 
554 ipmi_ret_t ipmi_storage_set_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
555                                      ipmi_request_t request,
556                                      ipmi_response_t response,
557                                      ipmi_data_len_t data_len,
558                                      ipmi_context_t context)
559 {
560     if (*data_len != sizeof(uint32_t))
561     {
562         *data_len = 0;
563         return IPMI_CC_REQ_DATA_LEN_INVALID;
564     }
565     using namespace std::chrono;
566     ipmi_ret_t rc = IPMI_CC_OK;
567     uint32_t secs = *static_cast<uint32_t*>(request);
568     *data_len = 0;
569 
570     secs = le32toh(secs);
571     microseconds usec{seconds(secs)};
572 
573     try
574     {
575         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
576         auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH);
577         sdbusplus::message::variant<uint64_t> value{usec.count()};
578 
579         // Set host time
580         auto method = bus.new_method_call(service.c_str(), HOST_TIME_PATH,
581                                           DBUS_PROPERTIES, "Set");
582 
583         method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value);
584         auto reply = bus.call(method);
585         if (reply.is_method_error())
586         {
587             log<level::ERR>("Error setting time",
588                             entry("SERVICE=%s", service.c_str()),
589                             entry("PATH=%s", HOST_TIME_PATH));
590             rc = IPMI_CC_UNSPECIFIED_ERROR;
591         }
592     }
593     catch (InternalFailure& e)
594     {
595         log<level::ERR>(e.what());
596         rc = IPMI_CC_UNSPECIFIED_ERROR;
597     }
598     catch (const std::runtime_error& e)
599     {
600         log<level::ERR>(e.what());
601         rc = IPMI_CC_UNSPECIFIED_ERROR;
602     }
603 
604     return rc;
605 }
606 
607 ipmi_ret_t ipmi_storage_reserve_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
608                                     ipmi_request_t request,
609                                     ipmi_response_t response,
610                                     ipmi_data_len_t data_len,
611                                     ipmi_context_t context)
612 {
613     if (*data_len != 0)
614     {
615         *data_len = 0;
616         return IPMI_CC_REQ_DATA_LEN_INVALID;
617     }
618 
619     ipmi_ret_t rc = IPMI_CC_OK;
620     unsigned short selResID = reserveSel();
621 
622     *data_len = sizeof(selResID);
623 
624     // Pack the actual response
625     std::memcpy(response, &selResID, *data_len);
626 
627     return rc;
628 }
629 
630 ipmi_ret_t ipmi_storage_add_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
631                                 ipmi_request_t request,
632                                 ipmi_response_t response,
633                                 ipmi_data_len_t data_len,
634                                 ipmi_context_t context)
635 {
636     if (*data_len != sizeof(ipmi_add_sel_request_t))
637     {
638         *data_len = 0;
639         return IPMI_CC_REQ_DATA_LEN_INVALID;
640     }
641 
642     ipmi_ret_t rc = IPMI_CC_OK;
643     ipmi_add_sel_request_t* p = (ipmi_add_sel_request_t*)request;
644     uint16_t recordid;
645 
646     // Per the IPMI spec, need to cancel the reservation when a SEL entry is
647     // added
648     cancelSELReservation();
649 
650     recordid = ((uint16_t)p->eventdata[1] << 8) | p->eventdata[2];
651 
652     *data_len = sizeof(recordid);
653 
654     // Pack the actual response
655     std::memcpy(response, &p->eventdata[1], 2);
656 
657     // Hostboot sends SEL with OEM record type 0xDE to indicate that there is
658     // a maintenance procedure associated with eSEL record.
659     static constexpr auto procedureType = 0xDE;
660     if (p->recordtype == procedureType)
661     {
662         // In the OEM record type 0xDE, byte 11 in the SEL record indicate the
663         // procedure number.
664         createProcedureLogEntry(p->sensortype);
665     }
666 
667     return rc;
668 }
669 
670 // Read FRU info area
671 ipmi_ret_t ipmi_storage_get_fru_inv_area_info(
672     ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
673     ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
674 {
675     ipmi_ret_t rc = IPMI_CC_OK;
676     const FruInvenAreaInfoRequest* reqptr =
677         reinterpret_cast<const FruInvenAreaInfoRequest*>(request);
678 
679     auto iter = frus.find(reqptr->fruID);
680     if (iter == frus.end())
681     {
682         *data_len = 0;
683         return IPMI_CC_SENSOR_INVALID;
684     }
685 
686     try
687     {
688         const auto& fruArea = getFruAreaData(reqptr->fruID);
689         auto size = static_cast<uint16_t>(fruArea.size());
690         FruInvenAreaInfoResponse resp;
691         resp.sizems = size >> 8;
692         resp.sizels = size;
693         resp.access = static_cast<uint8_t>(AccessMode::bytes);
694 
695         *data_len = sizeof(resp);
696 
697         // Pack the actual response
698         std::memcpy(response, &resp, *data_len);
699     }
700     catch (const InternalFailure& e)
701     {
702         rc = IPMI_CC_UNSPECIFIED_ERROR;
703         *data_len = 0;
704         log<level::ERR>(e.what());
705     }
706     return rc;
707 }
708 
709 // Read FRU data
710 ipmi_ret_t ipmi_storage_read_fru_data(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
711                                       ipmi_request_t request,
712                                       ipmi_response_t response,
713                                       ipmi_data_len_t data_len,
714                                       ipmi_context_t context)
715 {
716     ipmi_ret_t rc = IPMI_CC_OK;
717     const ReadFruDataRequest* reqptr =
718         reinterpret_cast<const ReadFruDataRequest*>(request);
719     auto resptr = reinterpret_cast<ReadFruDataResponse*>(response);
720 
721     auto iter = frus.find(reqptr->fruID);
722     if (iter == frus.end())
723     {
724         *data_len = 0;
725         return IPMI_CC_SENSOR_INVALID;
726     }
727 
728     auto offset =
729         static_cast<uint16_t>(reqptr->offsetMS << 8 | reqptr->offsetLS);
730     try
731     {
732         const auto& fruArea = getFruAreaData(reqptr->fruID);
733         auto size = fruArea.size();
734 
735         if (offset >= size)
736         {
737             return IPMI_CC_PARM_OUT_OF_RANGE;
738         }
739 
740         // Write the count of response data.
741         if ((offset + reqptr->count) <= size)
742         {
743             resptr->count = reqptr->count;
744         }
745         else
746         {
747             resptr->count = size - offset;
748         }
749 
750         std::copy((fruArea.begin() + offset),
751                   (fruArea.begin() + offset + resptr->count), resptr->data);
752 
753         *data_len = resptr->count + 1; // additional one byte for count
754     }
755     catch (const InternalFailure& e)
756     {
757         rc = IPMI_CC_UNSPECIFIED_ERROR;
758         *data_len = 0;
759         log<level::ERR>(e.what());
760     }
761     return rc;
762 }
763 
764 ipmi_ret_t ipmi_get_repository_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
765                                     ipmi_request_t request,
766                                     ipmi_response_t response,
767                                     ipmi_data_len_t data_len,
768                                     ipmi_context_t context)
769 {
770     constexpr auto sdrVersion = 0x51;
771     auto responseData = reinterpret_cast<GetRepositoryInfoResponse*>(response);
772 
773     std::memset(responseData, 0, sizeof(GetRepositoryInfoResponse));
774 
775     responseData->sdrVersion = sdrVersion;
776 
777     uint16_t records = frus.size() + sensors.size();
778     responseData->recordCountMs = records >> 8;
779     responseData->recordCountLs = records;
780 
781     responseData->freeSpace[0] = 0xFF;
782     responseData->freeSpace[1] = 0xFF;
783 
784     *data_len = sizeof(GetRepositoryInfoResponse);
785 
786     return IPMI_CC_OK;
787 }
788 
789 void register_netfn_storage_functions()
790 {
791     // <Wildcard Command>
792     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_WILDCARD, NULL,
793                            ipmi_storage_wildcard, PRIVILEGE_USER);
794 
795     // <Get SEL Info>
796     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_INFO, NULL,
797                            getSELInfo, PRIVILEGE_USER);
798 
799     // <Get SEL Time>
800     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_TIME, NULL,
801                            ipmi_storage_get_sel_time, PRIVILEGE_USER);
802 
803     // <Set SEL Time>
804     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_SET_SEL_TIME, NULL,
805                            ipmi_storage_set_sel_time, PRIVILEGE_OPERATOR);
806 
807     // <Reserve SEL>
808     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SEL, NULL,
809                            ipmi_storage_reserve_sel, PRIVILEGE_USER);
810 
811     // <Get SEL Entry>
812     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL,
813                            getSELEntry, PRIVILEGE_USER);
814 
815     // <Delete SEL Entry>
816     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_DELETE_SEL, NULL,
817                            deleteSELEntry, PRIVILEGE_OPERATOR);
818 
819     // <Add SEL Entry>
820     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_ADD_SEL, NULL,
821                            ipmi_storage_add_sel, PRIVILEGE_OPERATOR);
822     // <Clear SEL>
823     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_CLEAR_SEL, NULL, clearSEL,
824                            PRIVILEGE_OPERATOR);
825     // <Get FRU Inventory Area Info>
826     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_FRU_INV_AREA_INFO, NULL,
827                            ipmi_storage_get_fru_inv_area_info,
828                            PRIVILEGE_OPERATOR);
829 
830     // <Add READ FRU Data
831     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_READ_FRU_DATA, NULL,
832                            ipmi_storage_read_fru_data, PRIVILEGE_OPERATOR);
833 
834     // <Get Repository Info>
835     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_REPOSITORY_INFO,
836                            nullptr, ipmi_get_repository_info, PRIVILEGE_USER);
837 
838     // <Reserve SDR Repository>
839     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SDR, nullptr,
840                            ipmi_sen_reserve_sdr, PRIVILEGE_USER);
841 
842     // <Get SDR>
843     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, nullptr,
844                            ipmi_sen_get_sdr, PRIVILEGE_USER);
845 
846     ipmi::fru::registerCallbackHandler();
847     return;
848 }
849