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