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