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