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