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