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