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