xref: /openbmc/phosphor-host-ipmid/storagehandler.cpp (revision bf47a916bce97ad927d3f6104d070e7eaae2ae8d)
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 <optional>
21 #include <phosphor-logging/elog-errors.hpp>
22 #include <phosphor-logging/log.hpp>
23 #include <sdbusplus/server.hpp>
24 #include <string>
25 #include <variant>
26 #include <xyz/openbmc_project/Common/error.hpp>
27 
28 void register_netfn_storage_functions() __attribute__((constructor));
29 
30 unsigned int g_sel_time = 0xFFFFFFFF;
31 namespace ipmi
32 {
33 namespace sensor
34 {
35 extern const IdInfoMap sensors;
36 } // namespace sensor
37 } // namespace ipmi
38 
39 extern const FruMap frus;
40 constexpr uint8_t eventDataSize = 3;
41 namespace
42 {
43 constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime";
44 constexpr auto BMC_TIME_PATH = "/xyz/openbmc_project/time/bmc";
45 constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties";
46 constexpr auto PROPERTY_ELAPSED = "Elapsed";
47 
48 constexpr auto logWatchPath = "/xyz/openbmc_project/logging";
49 constexpr auto logBasePath = "/xyz/openbmc_project/logging/entry";
50 constexpr auto logEntryIntf = "xyz.openbmc_project.Logging.Entry";
51 constexpr auto logDeleteIntf = "xyz.openbmc_project.Object.Delete";
52 } // namespace
53 
54 using InternalFailure =
55     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
56 using namespace phosphor::logging;
57 using namespace ipmi::fru;
58 
59 using SELRecordID = uint16_t;
60 using SELEntry = ipmi::sel::SELEventRecordFormat;
61 using SELCacheMap = std::map<SELRecordID, SELEntry>;
62 
63 SELCacheMap selCacheMap __attribute__((init_priority(101)));
64 bool selCacheMapInitialized;
65 std::unique_ptr<sdbusplus::bus::match::match> selAddedMatch
66     __attribute__((init_priority(101)));
67 std::unique_ptr<sdbusplus::bus::match::match> selRemovedMatch
68     __attribute__((init_priority(101)));
69 std::unique_ptr<sdbusplus::bus::match::match> selUpdatedMatch
70     __attribute__((init_priority(101)));
71 
72 static inline uint16_t getLoggingId(const std::string& p)
73 {
74     namespace fs = std::filesystem;
75     fs::path entryPath(p);
76     return std::stoul(entryPath.filename().string());
77 }
78 
79 static inline std::string getLoggingObjPath(uint16_t id)
80 {
81     return std::string(ipmi::sel::logBasePath) + "/" + std::to_string(id);
82 }
83 
84 std::optional<std::pair<uint16_t, SELEntry>>
85     parseLoggingEntry(const std::string& p)
86 {
87     try
88     {
89         auto id = getLoggingId(p);
90         ipmi::sel::GetSELEntryResponse record{};
91         record = ipmi::sel::convertLogEntrytoSEL(p);
92         return std::pair<uint16_t, SELEntry>({id, std::move(record.event)});
93     }
94     catch (const std::exception& e)
95     {
96         fprintf(stderr, "Failed to convert %s to SEL: %s\n", p.c_str(),
97                 e.what());
98     }
99     return std::nullopt;
100 }
101 
102 static void selAddedCallback(sdbusplus::message::message& m)
103 {
104     sdbusplus::message::object_path objPath;
105     try
106     {
107         m.read(objPath);
108     }
109     catch (const sdbusplus::exception::exception& e)
110     {
111         log<level::ERR>("Failed to read object path");
112         return;
113     }
114     std::string p = objPath;
115     auto entry = parseLoggingEntry(p);
116     if (entry)
117     {
118         selCacheMap.insert(std::move(*entry));
119     }
120 }
121 
122 static void selRemovedCallback(sdbusplus::message::message& m)
123 {
124     sdbusplus::message::object_path objPath;
125     try
126     {
127         m.read(objPath);
128     }
129     catch (const sdbusplus::exception::exception& e)
130     {
131         log<level::ERR>("Failed to read object path");
132     }
133     try
134     {
135         std::string p = objPath;
136         selCacheMap.erase(getLoggingId(p));
137     }
138     catch (const std::invalid_argument& e)
139     {
140         log<level::ERR>("Invalid logging entry ID");
141     }
142 }
143 
144 static void selUpdatedCallback(sdbusplus::message::message& m)
145 {
146     std::string p = m.get_path();
147     auto entry = parseLoggingEntry(p);
148     if (entry)
149     {
150         selCacheMap.insert_or_assign(entry->first, std::move(entry->second));
151     }
152 }
153 
154 void registerSelCallbackHandler()
155 {
156     using namespace sdbusplus::bus::match::rules;
157     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
158     if (!selAddedMatch)
159     {
160         selAddedMatch = std::make_unique<sdbusplus::bus::match::match>(
161             bus, interfacesAdded(logWatchPath),
162             std::bind(selAddedCallback, std::placeholders::_1));
163     }
164     if (!selRemovedMatch)
165     {
166         selRemovedMatch = std::make_unique<sdbusplus::bus::match::match>(
167             bus, interfacesRemoved(logWatchPath),
168             std::bind(selRemovedCallback, std::placeholders::_1));
169     }
170     if (!selUpdatedMatch)
171     {
172         selUpdatedMatch = std::make_unique<sdbusplus::bus::match::match>(
173             bus,
174             type::signal() + member("PropertiesChanged"s) +
175                 interface("org.freedesktop.DBus.Properties"s) +
176                 argN(0, logEntryIntf),
177             std::bind(selUpdatedCallback, std::placeholders::_1));
178     }
179 }
180 
181 void initSELCache()
182 {
183     ipmi::sel::ObjectPaths paths;
184     try
185     {
186         ipmi::sel::readLoggingObjectPaths(paths);
187     }
188     catch (const sdbusplus::exception::exception& e)
189     {
190         log<level::ERR>("Failed to get logging object paths");
191         return;
192     }
193     for (const auto& p : paths)
194     {
195         auto entry = parseLoggingEntry(p);
196         if (entry)
197         {
198             selCacheMap.insert(std::move(*entry));
199         }
200     }
201     registerSelCallbackHandler();
202     selCacheMapInitialized = true;
203 }
204 
205 /**
206  * @enum Device access mode
207  */
208 enum class AccessMode
209 {
210     bytes, ///< Device is accessed by bytes
211     words  ///< Device is accessed by words
212 };
213 
214 /** @brief implements the get SEL Info command
215  *  @returns IPMI completion code plus response data
216  *   - selVersion - SEL revision
217  *   - entries    - Number of log entries in SEL.
218  *   - freeSpace  - Free Space in bytes.
219  *   - addTimeStamp - Most recent addition timestamp
220  *   - eraseTimeStamp - Most recent erase timestamp
221  *   - operationSupport - Reserve & Delete SEL operations supported
222  */
223 
224 ipmi::RspType<uint8_t,  // SEL revision.
225               uint16_t, // number of log entries in SEL.
226               uint16_t, // free Space in bytes.
227               uint32_t, // most recent addition timestamp
228               uint32_t, // most recent erase timestamp.
229 
230               bool,    // SEL allocation info supported
231               bool,    // reserve SEL supported
232               bool,    // partial Add SEL Entry supported
233               bool,    // delete SEL supported
234               uint3_t, // reserved
235               bool     // overflow flag
236               >
237     ipmiStorageGetSelInfo()
238 {
239     uint16_t entries = 0;
240     // Most recent addition timestamp.
241     uint32_t addTimeStamp = ipmi::sel::invalidTimeStamp;
242 
243     if (!selCacheMapInitialized)
244     {
245         // In case the initSELCache() fails, try it again
246         initSELCache();
247     }
248     if (!selCacheMap.empty())
249     {
250         entries = static_cast<uint16_t>(selCacheMap.size());
251 
252         try
253         {
254             auto objPath = getLoggingObjPath(selCacheMap.rbegin()->first);
255             addTimeStamp = static_cast<uint32_t>(
256                 (ipmi::sel::getEntryTimeStamp(objPath).count()));
257         }
258         catch (const InternalFailure& e)
259         {
260         }
261         catch (const std::runtime_error& e)
262         {
263             log<level::ERR>(e.what());
264         }
265     }
266 
267     constexpr uint8_t selVersion = ipmi::sel::selVersion;
268     constexpr uint16_t freeSpace = 0xFFFF;
269     constexpr uint32_t eraseTimeStamp = ipmi::sel::invalidTimeStamp;
270     constexpr uint3_t reserved{0};
271 
272     return ipmi::responseSuccess(
273         selVersion, entries, freeSpace, addTimeStamp, eraseTimeStamp,
274         ipmi::sel::operationSupport::getSelAllocationInfo,
275         ipmi::sel::operationSupport::reserveSel,
276         ipmi::sel::operationSupport::partialAddSelEntry,
277         ipmi::sel::operationSupport::deleteSel, reserved,
278         ipmi::sel::operationSupport::overflow);
279 }
280 
281 ipmi_ret_t getSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
282                        ipmi_request_t request, ipmi_response_t response,
283                        ipmi_data_len_t data_len, ipmi_context_t context)
284 {
285     if (*data_len != sizeof(ipmi::sel::GetSELEntryRequest))
286     {
287         *data_len = 0;
288         return IPMI_CC_REQ_DATA_LEN_INVALID;
289     }
290 
291     auto requestData =
292         reinterpret_cast<const ipmi::sel::GetSELEntryRequest*>(request);
293 
294     if (requestData->reservationID != 0)
295     {
296         if (!checkSELReservation(requestData->reservationID))
297         {
298             *data_len = 0;
299             return IPMI_CC_INVALID_RESERVATION_ID;
300         }
301     }
302 
303     if (!selCacheMapInitialized)
304     {
305         // In case the initSELCache() fails, try it again
306         initSELCache();
307     }
308 
309     if (selCacheMap.empty())
310     {
311         *data_len = 0;
312         return IPMI_CC_SENSOR_INVALID;
313     }
314 
315     SELCacheMap::const_iterator iter;
316 
317     // Check for the requested SEL Entry.
318     if (requestData->selRecordID == ipmi::sel::firstEntry)
319     {
320         iter = selCacheMap.begin();
321     }
322     else if (requestData->selRecordID == ipmi::sel::lastEntry)
323     {
324         if (selCacheMap.size() > 1)
325         {
326             iter = selCacheMap.end();
327             --iter;
328         }
329         else
330         {
331             // Only one entry exists, return the first
332             iter = selCacheMap.begin();
333         }
334     }
335     else
336     {
337         iter = selCacheMap.find(requestData->selRecordID);
338         if (iter == selCacheMap.end())
339         {
340             *data_len = 0;
341             return IPMI_CC_SENSOR_INVALID;
342         }
343     }
344 
345     ipmi::sel::GetSELEntryResponse record{0, iter->second};
346     // Identify the next SEL record ID
347     ++iter;
348     if (iter == selCacheMap.end())
349     {
350         record.nextRecordID = ipmi::sel::lastEntry;
351     }
352     else
353     {
354         record.nextRecordID = iter->first;
355     }
356 
357     if (requestData->readLength == ipmi::sel::entireRecord)
358     {
359         std::memcpy(response, &record, sizeof(record));
360         *data_len = sizeof(record);
361     }
362     else
363     {
364         if (requestData->offset >= ipmi::sel::selRecordSize ||
365             requestData->readLength > ipmi::sel::selRecordSize)
366         {
367             *data_len = 0;
368             return IPMI_CC_INVALID_FIELD_REQUEST;
369         }
370 
371         auto diff = ipmi::sel::selRecordSize - requestData->offset;
372         auto readLength =
373             std::min(diff, static_cast<int>(requestData->readLength));
374 
375         std::memcpy(response, &record.nextRecordID,
376                     sizeof(record.nextRecordID));
377         std::memcpy(static_cast<uint8_t*>(response) +
378                         sizeof(record.nextRecordID),
379                     &record.event.eventRecord.recordID + requestData->offset,
380                     readLength);
381         *data_len = sizeof(record.nextRecordID) + readLength;
382     }
383 
384     return IPMI_CC_OK;
385 }
386 
387 /** @brief implements the delete SEL entry command
388  * @request
389  *   - reservationID; // reservation ID.
390  *   - selRecordID;   // SEL record ID.
391  *
392  *  @returns ipmi completion code plus response data
393  *   - Record ID of the deleted record
394  */
395 ipmi::RspType<uint16_t // deleted record ID
396               >
397     deleteSELEntry(uint16_t reservationID, uint16_t selRecordID)
398 {
399 
400     namespace fs = std::filesystem;
401 
402     if (!checkSELReservation(reservationID))
403     {
404         return ipmi::responseInvalidReservationId();
405     }
406 
407     // Per the IPMI spec, need to cancel the reservation when a SEL entry is
408     // deleted
409     cancelSELReservation();
410 
411     if (!selCacheMapInitialized)
412     {
413         // In case the initSELCache() fails, try it again
414         initSELCache();
415     }
416 
417     if (selCacheMap.empty())
418     {
419         return ipmi::responseSensorInvalid();
420     }
421 
422     SELCacheMap::const_iterator iter;
423     uint16_t delRecordID = 0;
424 
425     if (selRecordID == ipmi::sel::firstEntry)
426     {
427         delRecordID = selCacheMap.begin()->first;
428     }
429     else if (selRecordID == ipmi::sel::lastEntry)
430     {
431         delRecordID = selCacheMap.rbegin()->first;
432     }
433     else
434     {
435         iter = selCacheMap.find(selRecordID);
436         if (iter == selCacheMap.end())
437         {
438             return ipmi::responseSensorInvalid();
439         }
440         delRecordID = selRecordID;
441     }
442 
443     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
444     std::string service;
445 
446     auto objPath = getLoggingObjPath(iter->first);
447     try
448     {
449         service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, objPath);
450     }
451     catch (const std::runtime_error& e)
452     {
453         log<level::ERR>(e.what());
454         return ipmi::responseUnspecifiedError();
455     }
456 
457     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
458                                           ipmi::sel::logDeleteIntf, "Delete");
459     auto reply = bus.call(methodCall);
460     if (reply.is_method_error())
461     {
462         return ipmi::responseUnspecifiedError();
463     }
464 
465     return ipmi::responseSuccess(delRecordID);
466 }
467 
468 /** @brief implements the Clear SEL command
469  * @request
470  *   - reservationID   // Reservation ID.
471  *   - clr             // char array { 'C'(0x43h), 'L'(0x4Ch), 'R'(0x52h) }
472  *   - eraseOperation; // requested operation.
473  *
474  *  @returns ipmi completion code plus response data
475  *   - erase status
476  */
477 
478 ipmi::RspType<uint8_t // erase status
479               >
480     clearSEL(uint16_t reservationID, const std::array<char, 3>& clr,
481              uint8_t eraseOperation)
482 {
483     static constexpr std::array<char, 3> clrOk = {'C', 'L', 'R'};
484     if (clr != clrOk)
485     {
486         return ipmi::responseInvalidFieldRequest();
487     }
488 
489     if (!checkSELReservation(reservationID))
490     {
491         return ipmi::responseInvalidReservationId();
492     }
493 
494     /*
495      * Erasure status cannot be fetched from DBUS, so always return erasure
496      * status as `erase completed`.
497      */
498     if (eraseOperation == ipmi::sel::getEraseStatus)
499     {
500         return ipmi::responseSuccess(
501             static_cast<uint8_t>(ipmi::sel::eraseComplete));
502     }
503 
504     // Per the IPMI spec, need to cancel any reservation when the SEL is cleared
505     cancelSELReservation();
506 
507     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
508     ipmi::sel::ObjectPaths objectPaths;
509     auto depth = 0;
510 
511     auto mapperCall =
512         bus.new_method_call(ipmi::sel::mapperBusName, ipmi::sel::mapperObjPath,
513                             ipmi::sel::mapperIntf, "GetSubTreePaths");
514     mapperCall.append(ipmi::sel::logBasePath);
515     mapperCall.append(depth);
516     mapperCall.append(ipmi::sel::ObjectPaths({ipmi::sel::logEntryIntf}));
517 
518     try
519     {
520         auto reply = bus.call(mapperCall);
521         if (reply.is_method_error())
522         {
523             return ipmi::responseSuccess(
524                 static_cast<uint8_t>(ipmi::sel::eraseComplete));
525         }
526 
527         reply.read(objectPaths);
528         if (objectPaths.empty())
529         {
530             return ipmi::responseSuccess(
531                 static_cast<uint8_t>(ipmi::sel::eraseComplete));
532         }
533     }
534     catch (const sdbusplus::exception::exception& e)
535     {
536         return ipmi::responseSuccess(
537             static_cast<uint8_t>(ipmi::sel::eraseComplete));
538     }
539 
540     std::string service;
541 
542     try
543     {
544         service = ipmi::getService(bus, ipmi::sel::logDeleteIntf,
545                                    objectPaths.front());
546     }
547     catch (const std::runtime_error& e)
548     {
549         log<level::ERR>(e.what());
550         return ipmi::responseUnspecifiedError();
551     }
552 
553     for (const auto& iter : objectPaths)
554     {
555         auto methodCall = bus.new_method_call(
556             service.c_str(), iter.c_str(), ipmi::sel::logDeleteIntf, "Delete");
557 
558         auto reply = bus.call(methodCall);
559         if (reply.is_method_error())
560         {
561             return ipmi::responseUnspecifiedError();
562         }
563     }
564 
565     return ipmi::responseSuccess(
566         static_cast<uint8_t>(ipmi::sel::eraseComplete));
567 }
568 
569 /** @brief implements the get SEL time command
570  *  @returns IPMI completion code plus response data
571  *   -current time
572  */
573 ipmi::RspType<uint32_t> // current time
574     ipmiStorageGetSelTime()
575 {
576     using namespace std::chrono;
577     uint64_t bmc_time_usec = 0;
578     std::stringstream bmcTime;
579 
580     try
581     {
582         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
583         auto service = ipmi::getService(bus, TIME_INTERFACE, BMC_TIME_PATH);
584         std::variant<uint64_t> value;
585 
586         // Get bmc time
587         auto method = bus.new_method_call(service.c_str(), BMC_TIME_PATH,
588                                           DBUS_PROPERTIES, "Get");
589 
590         method.append(TIME_INTERFACE, PROPERTY_ELAPSED);
591         auto reply = bus.call(method);
592         if (reply.is_method_error())
593         {
594             log<level::ERR>("Error getting time",
595                             entry("SERVICE=%s", service.c_str()),
596                             entry("PATH=%s", BMC_TIME_PATH));
597             return ipmi::responseUnspecifiedError();
598         }
599         reply.read(value);
600         bmc_time_usec = std::get<uint64_t>(value);
601     }
602     catch (const InternalFailure& e)
603     {
604         log<level::ERR>(e.what());
605         return ipmi::responseUnspecifiedError();
606     }
607     catch (const std::exception& e)
608     {
609         log<level::ERR>(e.what());
610         return ipmi::responseUnspecifiedError();
611     }
612 
613     bmcTime << "BMC time:"
614             << duration_cast<seconds>(microseconds(bmc_time_usec)).count();
615     log<level::DEBUG>(bmcTime.str().c_str());
616 
617     // Time is really long int but IPMI wants just uint32. This works okay until
618     // the number of seconds since 1970 overflows uint32 size.. Still a whole
619     // lot of time here to even think about that.
620     return ipmi::responseSuccess(
621         duration_cast<seconds>(microseconds(bmc_time_usec)).count());
622 }
623 
624 /** @brief implements the set SEL time command
625  *  @param selDeviceTime - epoch time
626  *        -local time as the number of seconds from 00:00:00, January 1, 1970
627  *  @returns IPMI completion code
628  */
629 ipmi::RspType<> ipmiStorageSetSelTime(uint32_t selDeviceTime)
630 {
631     using namespace std::chrono;
632     microseconds usec{seconds(selDeviceTime)};
633 
634     try
635     {
636         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
637         auto service = ipmi::getService(bus, TIME_INTERFACE, BMC_TIME_PATH);
638         std::variant<uint64_t> value{(uint64_t)usec.count()};
639 
640         // Set bmc time
641         auto method = bus.new_method_call(service.c_str(), BMC_TIME_PATH,
642                                           DBUS_PROPERTIES, "Set");
643 
644         method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value);
645         auto reply = bus.call(method);
646         if (reply.is_method_error())
647         {
648             log<level::ERR>("Error setting time",
649                             entry("SERVICE=%s", service.c_str()),
650                             entry("PATH=%s", BMC_TIME_PATH));
651             return ipmi::responseUnspecifiedError();
652         }
653     }
654     catch (const InternalFailure& e)
655     {
656         log<level::ERR>(e.what());
657         return ipmi::responseUnspecifiedError();
658     }
659     catch (const std::exception& e)
660     {
661         log<level::ERR>(e.what());
662         return ipmi::responseUnspecifiedError();
663     }
664 
665     return ipmi::responseSuccess();
666 }
667 
668 /** @brief implements the reserve SEL command
669  *  @returns IPMI completion code plus response data
670  *   - SEL reservation ID.
671  */
672 ipmi::RspType<uint16_t> ipmiStorageReserveSel()
673 {
674     return ipmi::responseSuccess(reserveSel());
675 }
676 
677 /** @brief implements the Add SEL entry command
678  * @request
679  *
680  *   - recordID      ID used for SEL Record access
681  *   - recordType    Record Type
682  *   - timeStamp     Time when event was logged. LS byte first
683  *   - generatorID   software ID if event was generated from
684  *                   system software
685  *   - evmRev        event message format version
686  *   - sensorType    sensor type code for service that generated
687  *                   the event
688  *   - sensorNumber  number of sensors that generated the event
689  *   - eventDir     event dir
690  *   - eventData    event data field contents
691  *
692  *  @returns ipmi completion code plus response data
693  *   - RecordID of the Added SEL entry
694  */
695 ipmi::RspType<uint16_t // recordID of the Added SEL entry
696               >
697     ipmiStorageAddSEL(uint16_t recordID, uint8_t recordType, uint32_t timeStamp,
698                       uint16_t generatorID, uint8_t evmRev, uint8_t sensorType,
699                       uint8_t sensorNumber, uint8_t eventDir,
700                       std::array<uint8_t, eventDataSize> eventData)
701 {
702     // Per the IPMI spec, need to cancel the reservation when a SEL entry is
703     // added
704     cancelSELReservation();
705     // Hostboot sends SEL with OEM record type 0xDE to indicate that there is
706     // a maintenance procedure associated with eSEL record.
707     static constexpr auto procedureType = 0xDE;
708     if (recordType == procedureType)
709     {
710         // In the OEM record type 0xDE, byte 11 in the SEL record indicate the
711         // procedure number.
712         createProcedureLogEntry(sensorType);
713     }
714 
715     return ipmi::responseSuccess(recordID);
716 }
717 
718 bool isFruPresent(const std::string& fruPath)
719 {
720     using namespace ipmi::fru;
721 
722     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
723 
724     auto propValue =
725         ipmi::getDbusProperty(bus, invMgrInterface, invObjPath + fruPath,
726                               invItemInterface, itemPresentProp);
727 
728     return std::get<bool>(propValue);
729 }
730 
731 /** @brief implements the get FRU Inventory Area Info command
732  *
733  *  @returns IPMI completion code plus response data
734  *   - FRU Inventory area size in bytes,
735  *   - access bit
736  **/
737 ipmi::RspType<uint16_t, // FRU Inventory area size in bytes,
738               uint8_t   // access size (bytes / words)
739               >
740     ipmiStorageGetFruInvAreaInfo(uint8_t fruID)
741 {
742 
743     auto iter = frus.find(fruID);
744     if (iter == frus.end())
745     {
746         return ipmi::responseSensorInvalid();
747     }
748 
749     auto path = iter->second[0].path;
750     if (!isFruPresent(path))
751     {
752         return ipmi::responseSensorInvalid();
753     }
754 
755     try
756     {
757         return ipmi::responseSuccess(
758             static_cast<uint16_t>(getFruAreaData(fruID).size()),
759             static_cast<uint8_t>(AccessMode::bytes));
760     }
761     catch (const InternalFailure& e)
762     {
763         log<level::ERR>(e.what());
764         return ipmi::responseUnspecifiedError();
765     }
766 }
767 
768 /**@brief implements the Read FRU Data command
769  * @param fruDeviceId - FRU device ID. FFh = reserved
770  * @param offset      - FRU inventory offset to read
771  * @param readCount   - count to read
772  *
773  * @return IPMI completion code plus response data
774  * - returnCount - response data count.
775  * - data        -  response data
776  */
777 ipmi::RspType<uint8_t,              // count returned
778               std::vector<uint8_t>> // FRU data
779     ipmiStorageReadFruData(uint8_t fruDeviceId, uint16_t offset,
780                            uint8_t readCount)
781 {
782     if (fruDeviceId == 0xFF)
783     {
784         return ipmi::responseInvalidFieldRequest();
785     }
786 
787     auto iter = frus.find(fruDeviceId);
788     if (iter == frus.end())
789     {
790         return ipmi::responseSensorInvalid();
791     }
792 
793     try
794     {
795         const auto& fruArea = getFruAreaData(fruDeviceId);
796         auto size = fruArea.size();
797 
798         if (offset >= size)
799         {
800             return ipmi::responseParmOutOfRange();
801         }
802 
803         // Write the count of response data.
804         uint8_t returnCount;
805         if ((offset + readCount) <= size)
806         {
807             returnCount = readCount;
808         }
809         else
810         {
811             returnCount = size - offset;
812         }
813 
814         std::vector<uint8_t> fruData((fruArea.begin() + offset),
815                                      (fruArea.begin() + offset + returnCount));
816 
817         return ipmi::responseSuccess(returnCount, fruData);
818     }
819     catch (const InternalFailure& e)
820     {
821         log<level::ERR>(e.what());
822         return ipmi::responseUnspecifiedError();
823     }
824 }
825 
826 ipmi::RspType<uint8_t,  // SDR version
827               uint16_t, // record count LS first
828               uint16_t, // free space in bytes, LS first
829               uint32_t, // addition timestamp LS first
830               uint32_t, // deletion timestamp LS first
831               uint8_t>  // operation Support
832     ipmiGetRepositoryInfo()
833 {
834 
835     constexpr uint8_t sdrVersion = 0x51;
836     constexpr uint16_t freeSpace = 0xFFFF;
837     constexpr uint32_t additionTimestamp = 0x0;
838     constexpr uint32_t deletionTimestamp = 0x0;
839     constexpr uint8_t operationSupport = 0;
840 
841     uint16_t records = frus.size() + ipmi::sensor::sensors.size();
842 
843     return ipmi::responseSuccess(sdrVersion, records, freeSpace,
844                                  additionTimestamp, deletionTimestamp,
845                                  operationSupport);
846 }
847 
848 void register_netfn_storage_functions()
849 {
850     selCacheMapInitialized = false;
851     initSELCache();
852     // <Get SEL Info>
853     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
854                           ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
855                           ipmiStorageGetSelInfo);
856 
857     // <Get SEL Time>
858     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
859                           ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
860                           ipmiStorageGetSelTime);
861 
862     // <Set SEL Time>
863     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
864                           ipmi::storage::cmdSetSelTime,
865                           ipmi::Privilege::Operator, ipmiStorageSetSelTime);
866 
867     // <Reserve SEL>
868     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
869                           ipmi::storage::cmdReserveSel, ipmi::Privilege::User,
870                           ipmiStorageReserveSel);
871     // <Get SEL Entry>
872     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL,
873                            getSELEntry, PRIVILEGE_USER);
874 
875     // <Delete SEL Entry>
876     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
877                           ipmi::storage::cmdDeleteSelEntry,
878                           ipmi::Privilege::Operator, deleteSELEntry);
879 
880     // <Add SEL Entry>
881     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
882                           ipmi::storage::cmdAddSelEntry,
883                           ipmi::Privilege::Operator, ipmiStorageAddSEL);
884 
885     // <Clear SEL>
886     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
887                           ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
888                           clearSEL);
889 
890     // <Get FRU Inventory Area Info>
891     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
892                           ipmi::storage::cmdGetFruInventoryAreaInfo,
893                           ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo);
894 
895     // <READ FRU Data>
896     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
897                           ipmi::storage::cmdReadFruData,
898                           ipmi::Privilege::Operator, ipmiStorageReadFruData);
899 
900     // <Get Repository Info>
901     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
902                           ipmi::storage::cmdGetSdrRepositoryInfo,
903                           ipmi::Privilege::User, ipmiGetRepositoryInfo);
904 
905     // <Reserve SDR Repository>
906     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
907                           ipmi::storage::cmdReserveSdrRepository,
908                           ipmi::Privilege::User, ipmiSensorReserveSdr);
909 
910     // <Get SDR>
911     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, nullptr,
912                            ipmi_sen_get_sdr, PRIVILEGE_USER);
913 
914     ipmi::fru::registerCallbackHandler();
915     return;
916 }
917