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