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