1 /*
2 // Copyright (c) 2017-2019 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "storagecommands.hpp"
18 
19 #include "commandutils.hpp"
20 #include "ipmi_to_redfish_hooks.hpp"
21 #include "sdrutils.hpp"
22 
23 #include <boost/algorithm/string.hpp>
24 #include <boost/container/flat_map.hpp>
25 #include <boost/process.hpp>
26 #include <ipmid/api.hpp>
27 #include <ipmid/message.hpp>
28 #include <phosphor-ipmi-host/selutility.hpp>
29 #include <phosphor-logging/log.hpp>
30 #include <sdbusplus/message/types.hpp>
31 #include <sdbusplus/timer.hpp>
32 
33 #include <filesystem>
34 #include <functional>
35 #include <iostream>
36 #include <stdexcept>
37 #include <string_view>
38 
39 static constexpr bool DEBUG = false;
40 
41 namespace intel_oem::ipmi::sel
42 {
43 static const std::filesystem::path selLogDir = "/var/log";
44 static const std::string selLogFilename = "ipmi_sel";
45 
46 static int getFileTimestamp(const std::filesystem::path& file)
47 {
48     struct stat st;
49 
50     if (stat(file.c_str(), &st) >= 0)
51     {
52         return st.st_mtime;
53     }
54     return ::ipmi::sel::invalidTimeStamp;
55 }
56 
57 namespace erase_time
58 {
59 static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time";
60 
61 void save()
62 {
63     // open the file, creating it if necessary
64     int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
65     if (fd < 0)
66     {
67         std::cerr << "Failed to open file\n";
68         return;
69     }
70 
71     // update the file timestamp to the current time
72     if (futimens(fd, NULL) < 0)
73     {
74         std::cerr << "Failed to update timestamp: "
75                   << std::string(strerror(errno));
76     }
77     close(fd);
78 }
79 
80 int get()
81 {
82     return getFileTimestamp(selEraseTimestamp);
83 }
84 } // namespace erase_time
85 } // namespace intel_oem::ipmi::sel
86 
87 namespace ipmi
88 {
89 
90 namespace storage
91 {
92 
93 constexpr static const size_t maxMessageSize = 64;
94 constexpr static const size_t maxFruSdrNameSize = 16;
95 using ManagedObjectType = boost::container::flat_map<
96     sdbusplus::message::object_path,
97     boost::container::flat_map<
98         std::string, boost::container::flat_map<std::string, DbusVariant>>>;
99 using ManagedEntry = std::pair<
100     sdbusplus::message::object_path,
101     boost::container::flat_map<
102         std::string, boost::container::flat_map<std::string, DbusVariant>>>;
103 
104 constexpr static const char* fruDeviceServiceName =
105     "xyz.openbmc_project.FruDevice";
106 constexpr static const char* entityManagerServiceName =
107     "xyz.openbmc_project.EntityManager";
108 constexpr static const size_t cacheTimeoutSeconds = 30;
109 constexpr static const size_t writeTimeoutSeconds = 10;
110 constexpr static const char* chassisTypeRackMount = "23";
111 
112 // event direction is bit[7] of eventType where 1b = Deassertion event
113 constexpr static const uint8_t deassertionEvent = 0x80;
114 
115 static std::vector<uint8_t> fruCache;
116 static uint8_t cacheBus = 0xFF;
117 static uint8_t cacheAddr = 0XFF;
118 
119 static uint8_t writeBus = 0xFF;
120 static uint8_t writeAddr = 0XFF;
121 
122 std::unique_ptr<phosphor::Timer> writeTimer = nullptr;
123 std::unique_ptr<phosphor::Timer> cacheTimer = nullptr;
124 
125 ManagedObjectType frus;
126 
127 // we unfortunately have to build a map of hashes in case there is a
128 // collision to verify our dev-id
129 boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
130 
131 void registerStorageFunctions() __attribute__((constructor));
132 
133 bool writeFru()
134 {
135     if (writeBus == 0xFF && writeAddr == 0xFF)
136     {
137         return true;
138     }
139     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
140     sdbusplus::message::message writeFru = dbus->new_method_call(
141         fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
142         "xyz.openbmc_project.FruDeviceManager", "WriteFru");
143     writeFru.append(writeBus, writeAddr, fruCache);
144     try
145     {
146         sdbusplus::message::message writeFruResp = dbus->call(writeFru);
147     }
148     catch (sdbusplus::exception_t&)
149     {
150         // todo: log sel?
151         phosphor::logging::log<phosphor::logging::level::ERR>(
152             "error writing fru");
153         return false;
154     }
155     writeBus = 0xFF;
156     writeAddr = 0xFF;
157     return true;
158 }
159 
160 void createTimers()
161 {
162 
163     writeTimer = std::make_unique<phosphor::Timer>(writeFru);
164     cacheTimer = std::make_unique<phosphor::Timer>([]() { return; });
165 }
166 
167 ipmi::Cc replaceCacheFru(ipmi::Context::ptr ctx, uint8_t devId)
168 {
169     static uint8_t lastDevId = 0xFF;
170 
171     bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired();
172     if (lastDevId == devId && timerRunning)
173     {
174         return IPMI_CC_OK; // cache already up to date
175     }
176     // if timer is running, stop it and writeFru manually
177     else if (timerRunning)
178     {
179         cacheTimer->stop();
180     }
181 
182     cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
183         std::chrono::seconds(cacheTimeoutSeconds)));
184 
185     boost::system::error_code ec;
186 
187     frus = ctx->bus->yield_method_call<ManagedObjectType>(
188         ctx->yield, ec, fruDeviceServiceName, "/",
189         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
190     if (ec)
191     {
192         phosphor::logging::log<phosphor::logging::level::ERR>(
193             "GetMangagedObjects for getSensorMap failed",
194             phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
195 
196         return ipmi::ccResponseError;
197     }
198 
199     deviceHashes.clear();
200 
201     // hash the object paths to create unique device id's. increment on
202     // collision
203     std::hash<std::string> hasher;
204     for (const auto& fru : frus)
205     {
206         auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
207         if (fruIface == fru.second.end())
208         {
209             continue;
210         }
211 
212         auto busFind = fruIface->second.find("BUS");
213         auto addrFind = fruIface->second.find("ADDRESS");
214         if (busFind == fruIface->second.end() ||
215             addrFind == fruIface->second.end())
216         {
217             phosphor::logging::log<phosphor::logging::level::INFO>(
218                 "fru device missing Bus or Address",
219                 phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
220             continue;
221         }
222 
223         uint8_t fruBus = std::get<uint32_t>(busFind->second);
224         uint8_t fruAddr = std::get<uint32_t>(addrFind->second);
225         auto chassisFind = fruIface->second.find("CHASSIS_TYPE");
226         std::string chassisType;
227         if (chassisFind != fruIface->second.end())
228         {
229             chassisType = std::get<std::string>(chassisFind->second);
230         }
231 
232         uint8_t fruHash = 0;
233         if (chassisType.compare(chassisTypeRackMount) != 0)
234         {
235             fruHash = hasher(fru.first.str);
236             // can't be 0xFF based on spec, and 0 is reserved for baseboard
237             if (fruHash == 0 || fruHash == 0xFF)
238             {
239                 fruHash = 1;
240             }
241         }
242         std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
243 
244         bool emplacePassed = false;
245         while (!emplacePassed)
246         {
247             auto resp = deviceHashes.emplace(fruHash, newDev);
248             emplacePassed = resp.second;
249             if (!emplacePassed)
250             {
251                 fruHash++;
252                 // can't be 0xFF based on spec, and 0 is reserved for
253                 // baseboard
254                 if (fruHash == 0XFF)
255                 {
256                     fruHash = 0x1;
257                 }
258             }
259         }
260     }
261     auto deviceFind = deviceHashes.find(devId);
262     if (deviceFind == deviceHashes.end())
263     {
264         return IPMI_CC_SENSOR_INVALID;
265     }
266 
267     fruCache.clear();
268     ec.clear();
269 
270     cacheBus = deviceFind->second.first;
271     cacheAddr = deviceFind->second.second;
272 
273     fruCache = ctx->bus->yield_method_call<std::vector<uint8_t>>(
274         ctx->yield, ec, fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
275         "xyz.openbmc_project.FruDeviceManager", "GetRawFru", cacheBus,
276         cacheAddr);
277     if (ec)
278     {
279         phosphor::logging::log<phosphor::logging::level::ERR>(
280             "Couldn't get raw fru",
281             phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
282 
283         lastDevId = 0xFF;
284         cacheBus = 0xFF;
285         cacheAddr = 0xFF;
286         return ipmi::ccResponseError;
287     }
288 
289     lastDevId = devId;
290     return ipmi::ccSuccess;
291 }
292 
293 /** @brief implements the read FRU data command
294  *  @param fruDeviceId        - FRU Device ID
295  *  @param fruInventoryOffset - FRU Inventory Offset to write
296  *  @param countToRead        - Count to read
297  *
298  *  @returns ipmi completion code plus response data
299  *   - countWritten  - Count written
300  */
301 ipmi::RspType<uint8_t,             // Count
302               std::vector<uint8_t> // Requested data
303               >
304     ipmiStorageReadFruData(ipmi::Context::ptr ctx, uint8_t fruDeviceId,
305                            uint16_t fruInventoryOffset, uint8_t countToRead)
306 {
307     if (fruDeviceId == 0xFF)
308     {
309         return ipmi::responseInvalidFieldRequest();
310     }
311 
312     ipmi::Cc status = replaceCacheFru(ctx, fruDeviceId);
313 
314     if (status != ipmi::ccSuccess)
315     {
316         return ipmi::response(status);
317     }
318 
319     size_t fromFruByteLen = 0;
320     if (countToRead + fruInventoryOffset < fruCache.size())
321     {
322         fromFruByteLen = countToRead;
323     }
324     else if (fruCache.size() > fruInventoryOffset)
325     {
326         fromFruByteLen = fruCache.size() - fruInventoryOffset;
327     }
328     else
329     {
330         return ipmi::responseReqDataLenExceeded();
331     }
332 
333     std::vector<uint8_t> requestedData;
334 
335     requestedData.insert(
336         requestedData.begin(), fruCache.begin() + fruInventoryOffset,
337         fruCache.begin() + fruInventoryOffset + fromFruByteLen);
338 
339     return ipmi::responseSuccess(static_cast<uint8_t>(requestedData.size()),
340                                  requestedData);
341 }
342 
343 /** @brief implements the write FRU data command
344  *  @param fruDeviceId        - FRU Device ID
345  *  @param fruInventoryOffset - FRU Inventory Offset to write
346  *  @param dataToWrite        - Data to write
347  *
348  *  @returns ipmi completion code plus response data
349  *   - countWritten  - Count written
350  */
351 ipmi::RspType<uint8_t>
352     ipmiStorageWriteFruData(ipmi::Context::ptr ctx, uint8_t fruDeviceId,
353                             uint16_t fruInventoryOffset,
354                             std::vector<uint8_t>& dataToWrite)
355 {
356     if (fruDeviceId == 0xFF)
357     {
358         return ipmi::responseInvalidFieldRequest();
359     }
360 
361     size_t writeLen = dataToWrite.size();
362 
363     ipmi::Cc status = replaceCacheFru(ctx, fruDeviceId);
364     if (status != ipmi::ccSuccess)
365     {
366         return ipmi::response(status);
367     }
368     int lastWriteAddr = fruInventoryOffset + writeLen;
369     if (fruCache.size() < lastWriteAddr)
370     {
371         fruCache.resize(fruInventoryOffset + writeLen);
372     }
373 
374     std::copy(dataToWrite.begin(), dataToWrite.begin() + writeLen,
375               fruCache.begin() + fruInventoryOffset);
376 
377     bool atEnd = false;
378 
379     if (fruCache.size() >= sizeof(FRUHeader))
380     {
381         FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
382 
383         int areaLength = 0;
384         int lastRecordStart = std::max(
385             {header->internalOffset, header->chassisOffset, header->boardOffset,
386              header->productOffset, header->multiRecordOffset});
387         lastRecordStart *= 8; // header starts in are multiples of 8 bytes
388 
389         if (header->multiRecordOffset)
390         {
391             // This FRU has a MultiRecord Area
392             uint8_t endOfList = 0;
393             // Walk the MultiRecord headers until the last record
394             while (!endOfList)
395             {
396                 // The MSB in the second byte of the MultiRecord header signals
397                 // "End of list"
398                 endOfList = fruCache[lastRecordStart + 1] & 0x80;
399                 // Third byte in the MultiRecord header is the length
400                 areaLength = fruCache[lastRecordStart + 2];
401                 // This length is in bytes (not 8 bytes like other headers)
402                 areaLength += 5; // The length omits the 5 byte header
403                 if (!endOfList)
404                 {
405                     // Next MultiRecord header
406                     lastRecordStart += areaLength;
407                 }
408             }
409         }
410         else
411         {
412             // This FRU does not have a MultiRecord Area
413             // Get the length of the area in multiples of 8 bytes
414             if (lastWriteAddr > (lastRecordStart + 1))
415             {
416                 // second byte in record area is the length
417                 areaLength = fruCache[lastRecordStart + 1];
418                 areaLength *= 8; // it is in multiples of 8 bytes
419             }
420         }
421         if (lastWriteAddr >= (areaLength + lastRecordStart))
422         {
423             atEnd = true;
424         }
425     }
426     uint8_t countWritten = 0;
427 
428     writeBus = cacheBus;
429     writeAddr = cacheAddr;
430     if (atEnd)
431     {
432         // cancel timer, we're at the end so might as well send it
433         writeTimer->stop();
434         if (!writeFru())
435         {
436             return ipmi::responseInvalidFieldRequest();
437         }
438         countWritten = std::min(fruCache.size(), static_cast<size_t>(0xFF));
439     }
440     else
441     {
442         // start a timer, if no further data is sent  to check to see if it is
443         // valid
444         writeTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
445             std::chrono::seconds(writeTimeoutSeconds)));
446         countWritten = 0;
447     }
448 
449     return ipmi::responseSuccess(countWritten);
450 }
451 
452 /** @brief implements the get FRU inventory area info command
453  *  @param fruDeviceId  - FRU Device ID
454  *
455  *  @returns IPMI completion code plus response data
456  *   - inventorySize - Number of possible allocation units
457  *   - accessType    - Allocation unit size in bytes.
458  */
459 ipmi::RspType<uint16_t, // inventorySize
460               uint8_t>  // accessType
461     ipmiStorageGetFruInvAreaInfo(ipmi::Context::ptr ctx, uint8_t fruDeviceId)
462 {
463     if (fruDeviceId == 0xFF)
464     {
465         return ipmi::responseInvalidFieldRequest();
466     }
467 
468     ipmi::Cc status = replaceCacheFru(ctx, fruDeviceId);
469 
470     if (status != IPMI_CC_OK)
471     {
472         return ipmi::response(status);
473     }
474 
475     constexpr uint8_t accessType =
476         static_cast<uint8_t>(GetFRUAreaAccessType::byte);
477 
478     return ipmi::responseSuccess(fruCache.size(), accessType);
479 }
480 
481 ipmi_ret_t getFruSdrCount(ipmi::Context::ptr ctx, size_t& count)
482 {
483     ipmi_ret_t ret = replaceCacheFru(ctx, 0);
484     if (ret != IPMI_CC_OK)
485     {
486         return ret;
487     }
488     count = deviceHashes.size();
489     return IPMI_CC_OK;
490 }
491 
492 ipmi_ret_t getFruSdrs(ipmi::Context::ptr ctx, size_t index,
493                       get_sdr::SensorDataFruRecord& resp)
494 {
495     ipmi_ret_t ret = replaceCacheFru(ctx, 0); // this will update the hash list
496     if (ret != IPMI_CC_OK)
497     {
498         return ret;
499     }
500     if (deviceHashes.size() < index)
501     {
502         return IPMI_CC_INVALID_FIELD_REQUEST;
503     }
504     auto device = deviceHashes.begin() + index;
505     uint8_t& bus = device->second.first;
506     uint8_t& address = device->second.second;
507 
508     boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
509     auto fru =
510         std::find_if(frus.begin(), frus.end(),
511                      [bus, address, &fruData](ManagedEntry& entry) {
512                          auto findFruDevice =
513                              entry.second.find("xyz.openbmc_project.FruDevice");
514                          if (findFruDevice == entry.second.end())
515                          {
516                              return false;
517                          }
518                          fruData = &(findFruDevice->second);
519                          auto findBus = findFruDevice->second.find("BUS");
520                          auto findAddress =
521                              findFruDevice->second.find("ADDRESS");
522                          if (findBus == findFruDevice->second.end() ||
523                              findAddress == findFruDevice->second.end())
524                          {
525                              return false;
526                          }
527                          if (std::get<uint32_t>(findBus->second) != bus)
528                          {
529                              return false;
530                          }
531                          if (std::get<uint32_t>(findAddress->second) != address)
532                          {
533                              return false;
534                          }
535                          return true;
536                      });
537     if (fru == frus.end())
538     {
539         return IPMI_CC_RESPONSE_ERROR;
540     }
541 
542 #ifdef USING_ENTITY_MANAGER_DECORATORS
543 
544     boost::container::flat_map<std::string, DbusVariant>* entityData = nullptr;
545 
546     // todo: this should really use caching, this is a very inefficient lookup
547     boost::system::error_code ec;
548     ManagedObjectType entities = ctx->bus->yield_method_call<ManagedObjectType>(
549         ctx->yield, ec, entityManagerServiceName, "/",
550         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
551 
552     if (ec)
553     {
554         phosphor::logging::log<phosphor::logging::level::ERR>(
555             "GetMangagedObjects for getSensorMap failed",
556             phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
557 
558         return ipmi::ccResponseError;
559     }
560 
561     auto entity = std::find_if(
562         entities.begin(), entities.end(),
563         [bus, address, &entityData](ManagedEntry& entry) {
564             auto findFruDevice = entry.second.find(
565                 "xyz.openbmc_project.Inventory.Decorator.FruDevice");
566             if (findFruDevice == entry.second.end())
567             {
568                 return false;
569             }
570 
571             // Integer fields added via Entity-Manager json are uint64_ts by
572             // default.
573             auto findBus = findFruDevice->second.find("Bus");
574             auto findAddress = findFruDevice->second.find("Address");
575 
576             if (findBus == findFruDevice->second.end() ||
577                 findAddress == findFruDevice->second.end())
578             {
579                 return false;
580             }
581             if ((std::get<uint64_t>(findBus->second) != bus) ||
582                 (std::get<uint64_t>(findAddress->second) != address))
583             {
584                 return false;
585             }
586 
587             // At this point we found the device entry and should return
588             // true.
589             auto findIpmiDevice = entry.second.find(
590                 "xyz.openbmc_project.Inventory.Decorator.Ipmi");
591             if (findIpmiDevice != entry.second.end())
592             {
593                 entityData = &(findIpmiDevice->second);
594             }
595 
596             return true;
597         });
598 
599     if (entity == entities.end())
600     {
601         if constexpr (DEBUG)
602         {
603             std::fprintf(stderr, "Ipmi or FruDevice Decorator interface "
604                                  "not found for Fru\n");
605         }
606     }
607 
608 #endif
609 
610     std::string name;
611     auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
612     auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
613     if (findProductName != fruData->end())
614     {
615         name = std::get<std::string>(findProductName->second);
616     }
617     else if (findBoardName != fruData->end())
618     {
619         name = std::get<std::string>(findBoardName->second);
620     }
621     else
622     {
623         name = "UNKNOWN";
624     }
625     if (name.size() > maxFruSdrNameSize)
626     {
627         name = name.substr(0, maxFruSdrNameSize);
628     }
629     size_t sizeDiff = maxFruSdrNameSize - name.size();
630 
631     resp.header.record_id_lsb = 0x0; // calling code is to implement these
632     resp.header.record_id_msb = 0x0;
633     resp.header.sdr_version = ipmiSdrVersion;
634     resp.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD;
635     resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
636     resp.key.deviceAddress = 0x20;
637     resp.key.fruID = device->first;
638     resp.key.accessLun = 0x80; // logical / physical fru device
639     resp.key.channelNumber = 0x0;
640     resp.body.reserved = 0x0;
641     resp.body.deviceType = 0x10;
642     resp.body.deviceTypeModifier = 0x0;
643 
644     uint8_t entityID = 0;
645     uint8_t entityInstance = 0x1;
646 
647 #ifdef USING_ENTITY_MANAGER_DECORATORS
648     if (entityData)
649     {
650         auto entityIdProperty = entityData->find("EntityId");
651         auto entityInstanceProperty = entityData->find("EntityInstance");
652 
653         if (entityIdProperty != entityData->end())
654         {
655             entityID = static_cast<uint8_t>(
656                 std::get<uint64_t>(entityIdProperty->second));
657         }
658         if (entityInstanceProperty != entityData->end())
659         {
660             entityInstance = static_cast<uint8_t>(
661                 std::get<uint64_t>(entityInstanceProperty->second));
662         }
663     }
664 #endif
665 
666     resp.body.entityID = entityID;
667     resp.body.entityInstance = entityInstance;
668 
669     resp.body.oem = 0x0;
670     resp.body.deviceIDLen = name.size();
671     name.copy(resp.body.deviceID, name.size());
672 
673     return IPMI_CC_OK;
674 }
675 
676 static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles)
677 {
678     // Loop through the directory looking for ipmi_sel log files
679     for (const std::filesystem::directory_entry& dirEnt :
680          std::filesystem::directory_iterator(intel_oem::ipmi::sel::selLogDir))
681     {
682         std::string filename = dirEnt.path().filename();
683         if (boost::starts_with(filename, intel_oem::ipmi::sel::selLogFilename))
684         {
685             // If we find an ipmi_sel log file, save the path
686             selLogFiles.emplace_back(intel_oem::ipmi::sel::selLogDir /
687                                      filename);
688         }
689     }
690     // As the log files rotate, they are appended with a ".#" that is higher for
691     // the older logs. Since we don't expect more than 10 log files, we
692     // can just sort the list to get them in order from newest to oldest
693     std::sort(selLogFiles.begin(), selLogFiles.end());
694 
695     return !selLogFiles.empty();
696 }
697 
698 static int countSELEntries()
699 {
700     // Get the list of ipmi_sel log files
701     std::vector<std::filesystem::path> selLogFiles;
702     if (!getSELLogFiles(selLogFiles))
703     {
704         return 0;
705     }
706     int numSELEntries = 0;
707     // Loop through each log file and count the number of logs
708     for (const std::filesystem::path& file : selLogFiles)
709     {
710         std::ifstream logStream(file);
711         if (!logStream.is_open())
712         {
713             continue;
714         }
715 
716         std::string line;
717         while (std::getline(logStream, line))
718         {
719             numSELEntries++;
720         }
721     }
722     return numSELEntries;
723 }
724 
725 static bool findSELEntry(const int recordID,
726                          const std::vector<std::filesystem::path>& selLogFiles,
727                          std::string& entry)
728 {
729     // Record ID is the first entry field following the timestamp. It is
730     // preceded by a space and followed by a comma
731     std::string search = " " + std::to_string(recordID) + ",";
732 
733     // Loop through the ipmi_sel log entries
734     for (const std::filesystem::path& file : selLogFiles)
735     {
736         std::ifstream logStream(file);
737         if (!logStream.is_open())
738         {
739             continue;
740         }
741 
742         while (std::getline(logStream, entry))
743         {
744             // Check if the record ID matches
745             if (entry.find(search) != std::string::npos)
746             {
747                 return true;
748             }
749         }
750     }
751     return false;
752 }
753 
754 static uint16_t
755     getNextRecordID(const uint16_t recordID,
756                     const std::vector<std::filesystem::path>& selLogFiles)
757 {
758     uint16_t nextRecordID = recordID + 1;
759     std::string entry;
760     if (findSELEntry(nextRecordID, selLogFiles, entry))
761     {
762         return nextRecordID;
763     }
764     else
765     {
766         return ipmi::sel::lastEntry;
767     }
768 }
769 
770 static int fromHexStr(const std::string& hexStr, std::vector<uint8_t>& data)
771 {
772     for (unsigned int i = 0; i < hexStr.size(); i += 2)
773     {
774         try
775         {
776             data.push_back(static_cast<uint8_t>(
777                 std::stoul(hexStr.substr(i, 2), nullptr, 16)));
778         }
779         catch (std::invalid_argument& e)
780         {
781             phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
782             return -1;
783         }
784         catch (std::out_of_range& e)
785         {
786             phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
787             return -1;
788         }
789     }
790     return 0;
791 }
792 
793 ipmi::RspType<uint8_t,  // SEL version
794               uint16_t, // SEL entry count
795               uint16_t, // free space
796               uint32_t, // last add timestamp
797               uint32_t, // last erase timestamp
798               uint8_t>  // operation support
799     ipmiStorageGetSELInfo()
800 {
801     constexpr uint8_t selVersion = ipmi::sel::selVersion;
802     uint16_t entries = countSELEntries();
803     uint32_t addTimeStamp = intel_oem::ipmi::sel::getFileTimestamp(
804         intel_oem::ipmi::sel::selLogDir / intel_oem::ipmi::sel::selLogFilename);
805     uint32_t eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get();
806     constexpr uint8_t operationSupport =
807         intel_oem::ipmi::sel::selOperationSupport;
808     constexpr uint16_t freeSpace =
809         0xffff; // Spec indicates that more than 64kB is free
810 
811     return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp,
812                                  eraseTimeStamp, operationSupport);
813 }
814 
815 using systemEventType = std::tuple<
816     uint32_t, // Timestamp
817     uint16_t, // Generator ID
818     uint8_t,  // EvM Rev
819     uint8_t,  // Sensor Type
820     uint8_t,  // Sensor Number
821     uint7_t,  // Event Type
822     bool,     // Event Direction
823     std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize>>; // Event Data
824 using oemTsEventType = std::tuple<
825     uint32_t,                                                   // Timestamp
826     std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize>>; // Event Data
827 using oemEventType =
828     std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize>; // Event Data
829 
830 ipmi::RspType<uint16_t, // Next Record ID
831               uint16_t, // Record ID
832               uint8_t,  // Record Type
833               std::variant<systemEventType, oemTsEventType,
834                            oemEventType>> // Record Content
835     ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID,
836                            uint8_t offset, uint8_t size)
837 {
838     // Only support getting the entire SEL record. If a partial size or non-zero
839     // offset is requested, return an error
840     if (offset != 0 || size != ipmi::sel::entireRecord)
841     {
842         return ipmi::responseRetBytesUnavailable();
843     }
844 
845     // Check the reservation ID if one is provided or required (only if the
846     // offset is non-zero)
847     if (reservationID != 0 || offset != 0)
848     {
849         if (!checkSELReservation(reservationID))
850         {
851             return ipmi::responseInvalidReservationId();
852         }
853     }
854 
855     // Get the ipmi_sel log files
856     std::vector<std::filesystem::path> selLogFiles;
857     if (!getSELLogFiles(selLogFiles))
858     {
859         return ipmi::responseSensorInvalid();
860     }
861 
862     std::string targetEntry;
863 
864     if (targetID == ipmi::sel::firstEntry)
865     {
866         // The first entry will be at the top of the oldest log file
867         std::ifstream logStream(selLogFiles.back());
868         if (!logStream.is_open())
869         {
870             return ipmi::responseUnspecifiedError();
871         }
872 
873         if (!std::getline(logStream, targetEntry))
874         {
875             return ipmi::responseUnspecifiedError();
876         }
877     }
878     else if (targetID == ipmi::sel::lastEntry)
879     {
880         // The last entry will be at the bottom of the newest log file
881         std::ifstream logStream(selLogFiles.front());
882         if (!logStream.is_open())
883         {
884             return ipmi::responseUnspecifiedError();
885         }
886 
887         std::string line;
888         while (std::getline(logStream, line))
889         {
890             targetEntry = line;
891         }
892     }
893     else
894     {
895         if (!findSELEntry(targetID, selLogFiles, targetEntry))
896         {
897             return ipmi::responseSensorInvalid();
898         }
899     }
900 
901     // The format of the ipmi_sel message is "<Timestamp>
902     // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]".
903     // First get the Timestamp
904     size_t space = targetEntry.find_first_of(" ");
905     if (space == std::string::npos)
906     {
907         return ipmi::responseUnspecifiedError();
908     }
909     std::string entryTimestamp = targetEntry.substr(0, space);
910     // Then get the log contents
911     size_t entryStart = targetEntry.find_first_not_of(" ", space);
912     if (entryStart == std::string::npos)
913     {
914         return ipmi::responseUnspecifiedError();
915     }
916     std::string_view entry(targetEntry);
917     entry.remove_prefix(entryStart);
918     // Use split to separate the entry into its fields
919     std::vector<std::string> targetEntryFields;
920     boost::split(targetEntryFields, entry, boost::is_any_of(","),
921                  boost::token_compress_on);
922     if (targetEntryFields.size() < 3)
923     {
924         return ipmi::responseUnspecifiedError();
925     }
926     std::string& recordIDStr = targetEntryFields[0];
927     std::string& recordTypeStr = targetEntryFields[1];
928     std::string& eventDataStr = targetEntryFields[2];
929 
930     uint16_t recordID;
931     uint8_t recordType;
932     try
933     {
934         recordID = std::stoul(recordIDStr);
935         recordType = std::stoul(recordTypeStr, nullptr, 16);
936     }
937     catch (const std::invalid_argument&)
938     {
939         return ipmi::responseUnspecifiedError();
940     }
941     uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles);
942     std::vector<uint8_t> eventDataBytes;
943     if (fromHexStr(eventDataStr, eventDataBytes) < 0)
944     {
945         return ipmi::responseUnspecifiedError();
946     }
947 
948     if (recordType == intel_oem::ipmi::sel::systemEvent)
949     {
950         // Get the timestamp
951         std::tm timeStruct = {};
952         std::istringstream entryStream(entryTimestamp);
953 
954         uint32_t timestamp = ipmi::sel::invalidTimeStamp;
955         if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
956         {
957             timestamp = std::mktime(&timeStruct);
958         }
959 
960         // Set the event message revision
961         uint8_t evmRev = intel_oem::ipmi::sel::eventMsgRev;
962 
963         uint16_t generatorID = 0;
964         uint8_t sensorType = 0;
965         uint8_t sensorNum = 0xFF;
966         uint7_t eventType = 0;
967         bool eventDir = 0;
968         // System type events should have six fields
969         if (targetEntryFields.size() >= 6)
970         {
971             std::string& generatorIDStr = targetEntryFields[3];
972             std::string& sensorPath = targetEntryFields[4];
973             std::string& eventDirStr = targetEntryFields[5];
974 
975             // Get the generator ID
976             try
977             {
978                 generatorID = std::stoul(generatorIDStr, nullptr, 16);
979             }
980             catch (const std::invalid_argument&)
981             {
982                 std::cerr << "Invalid Generator ID\n";
983             }
984 
985             // Get the sensor type, sensor number, and event type for the sensor
986             sensorType = getSensorTypeFromPath(sensorPath);
987             sensorNum = getSensorNumberFromPath(sensorPath);
988             eventType = getSensorEventTypeFromPath(sensorPath);
989 
990             // Get the event direction
991             try
992             {
993                 eventDir = std::stoul(eventDirStr) ? 0 : 1;
994             }
995             catch (const std::invalid_argument&)
996             {
997                 std::cerr << "Invalid Event Direction\n";
998             }
999         }
1000 
1001         // Only keep the eventData bytes that fit in the record
1002         std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize> eventData{};
1003         std::copy_n(eventDataBytes.begin(),
1004                     std::min(eventDataBytes.size(), eventData.size()),
1005                     eventData.begin());
1006 
1007         return ipmi::responseSuccess(
1008             nextRecordID, recordID, recordType,
1009             systemEventType{timestamp, generatorID, evmRev, sensorType,
1010                             sensorNum, eventType, eventDir, eventData});
1011     }
1012     else if (recordType >= intel_oem::ipmi::sel::oemTsEventFirst &&
1013              recordType <= intel_oem::ipmi::sel::oemTsEventLast)
1014     {
1015         // Get the timestamp
1016         std::tm timeStruct = {};
1017         std::istringstream entryStream(entryTimestamp);
1018 
1019         uint32_t timestamp = ipmi::sel::invalidTimeStamp;
1020         if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
1021         {
1022             timestamp = std::mktime(&timeStruct);
1023         }
1024 
1025         // Only keep the bytes that fit in the record
1026         std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize> eventData{};
1027         std::copy_n(eventDataBytes.begin(),
1028                     std::min(eventDataBytes.size(), eventData.size()),
1029                     eventData.begin());
1030 
1031         return ipmi::responseSuccess(nextRecordID, recordID, recordType,
1032                                      oemTsEventType{timestamp, eventData});
1033     }
1034     else if (recordType >= intel_oem::ipmi::sel::oemEventFirst)
1035     {
1036         // Only keep the bytes that fit in the record
1037         std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize> eventData{};
1038         std::copy_n(eventDataBytes.begin(),
1039                     std::min(eventDataBytes.size(), eventData.size()),
1040                     eventData.begin());
1041 
1042         return ipmi::responseSuccess(nextRecordID, recordID, recordType,
1043                                      eventData);
1044     }
1045 
1046     return ipmi::responseUnspecifiedError();
1047 }
1048 
1049 ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(
1050     uint16_t recordID, uint8_t recordType, uint32_t timestamp,
1051     uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum,
1052     uint8_t eventType, uint8_t eventData1, uint8_t eventData2,
1053     uint8_t eventData3)
1054 {
1055     // Per the IPMI spec, need to cancel any reservation when a SEL entry is
1056     // added
1057     cancelSELReservation();
1058 
1059     // Send this request to the Redfish hooks to log it as a Redfish message
1060     // instead.  There is no need to add it to the SEL, so just return success.
1061     intel_oem::ipmi::sel::checkRedfishHooks(
1062         recordID, recordType, timestamp, generatorID, evmRev, sensorType,
1063         sensorNum, eventType, eventData1, eventData2, eventData3);
1064 
1065     uint16_t responseID = 0xFFFF;
1066     return ipmi::responseSuccess(responseID);
1067 }
1068 
1069 ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr ctx,
1070                                            uint16_t reservationID,
1071                                            const std::array<uint8_t, 3>& clr,
1072                                            uint8_t eraseOperation)
1073 {
1074     if (!checkSELReservation(reservationID))
1075     {
1076         return ipmi::responseInvalidReservationId();
1077     }
1078 
1079     static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
1080     if (clr != clrExpected)
1081     {
1082         return ipmi::responseInvalidFieldRequest();
1083     }
1084 
1085     // Erasure status cannot be fetched, so always return erasure status as
1086     // `erase completed`.
1087     if (eraseOperation == ipmi::sel::getEraseStatus)
1088     {
1089         return ipmi::responseSuccess(ipmi::sel::eraseComplete);
1090     }
1091 
1092     // Check that initiate erase is correct
1093     if (eraseOperation != ipmi::sel::initiateErase)
1094     {
1095         return ipmi::responseInvalidFieldRequest();
1096     }
1097 
1098     // Per the IPMI spec, need to cancel any reservation when the SEL is
1099     // cleared
1100     cancelSELReservation();
1101 
1102     // Save the erase time
1103     intel_oem::ipmi::sel::erase_time::save();
1104 
1105     // Clear the SEL by deleting the log files
1106     std::vector<std::filesystem::path> selLogFiles;
1107     if (getSELLogFiles(selLogFiles))
1108     {
1109         for (const std::filesystem::path& file : selLogFiles)
1110         {
1111             std::error_code ec;
1112             std::filesystem::remove(file, ec);
1113         }
1114     }
1115 
1116     // Reload rsyslog so it knows to start new log files
1117     std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
1118     sdbusplus::message::message rsyslogReload = dbus->new_method_call(
1119         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1120         "org.freedesktop.systemd1.Manager", "ReloadUnit");
1121     rsyslogReload.append("rsyslog.service", "replace");
1122     try
1123     {
1124         sdbusplus::message::message reloadResponse = dbus->call(rsyslogReload);
1125     }
1126     catch (sdbusplus::exception_t& e)
1127     {
1128         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
1129     }
1130 
1131     return ipmi::responseSuccess(ipmi::sel::eraseComplete);
1132 }
1133 
1134 ipmi::RspType<uint32_t> ipmiStorageGetSELTime()
1135 {
1136     struct timespec selTime = {};
1137 
1138     if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
1139     {
1140         return ipmi::responseUnspecifiedError();
1141     }
1142 
1143     return ipmi::responseSuccess(selTime.tv_sec);
1144 }
1145 
1146 ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime)
1147 {
1148     // Set SEL Time is not supported
1149     return ipmi::responseInvalidCommand();
1150 }
1151 
1152 std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId)
1153 {
1154     std::vector<uint8_t> resp;
1155     if (index == 0)
1156     {
1157         Type12Record bmc = {};
1158         bmc.header.record_id_lsb = recordId;
1159         bmc.header.record_id_msb = recordId >> 8;
1160         bmc.header.sdr_version = ipmiSdrVersion;
1161         bmc.header.record_type = 0x12;
1162         bmc.header.record_length = 0x1b;
1163         bmc.slaveAddress = 0x20;
1164         bmc.channelNumber = 0;
1165         bmc.powerStateNotification = 0;
1166         bmc.deviceCapabilities = 0xBF;
1167         bmc.reserved = 0;
1168         bmc.entityID = 0x2E;
1169         bmc.entityInstance = 1;
1170         bmc.oem = 0;
1171         bmc.typeLengthCode = 0xD0;
1172         std::string bmcName = "Basbrd Mgmt Ctlr";
1173         std::copy(bmcName.begin(), bmcName.end(), bmc.name);
1174         uint8_t* bmcPtr = reinterpret_cast<uint8_t*>(&bmc);
1175         resp.insert(resp.end(), bmcPtr, bmcPtr + sizeof(Type12Record));
1176     }
1177     else if (index == 1)
1178     {
1179         Type12Record me = {};
1180         me.header.record_id_lsb = recordId;
1181         me.header.record_id_msb = recordId >> 8;
1182         me.header.sdr_version = ipmiSdrVersion;
1183         me.header.record_type = 0x12;
1184         me.header.record_length = 0x16;
1185         me.slaveAddress = 0x2C;
1186         me.channelNumber = 6;
1187         me.powerStateNotification = 0x24;
1188         me.deviceCapabilities = 0x21;
1189         me.reserved = 0;
1190         me.entityID = 0x2E;
1191         me.entityInstance = 2;
1192         me.oem = 0;
1193         me.typeLengthCode = 0xCB;
1194         std::string meName = "Mgmt Engine";
1195         std::copy(meName.begin(), meName.end(), me.name);
1196         uint8_t* mePtr = reinterpret_cast<uint8_t*>(&me);
1197         resp.insert(resp.end(), mePtr, mePtr + sizeof(Type12Record));
1198     }
1199     else
1200     {
1201         throw std::runtime_error("getType12SDRs:: Illegal index " +
1202                                  std::to_string(index));
1203     }
1204 
1205     return resp;
1206 }
1207 
1208 std::vector<uint8_t> getNMDiscoverySDR(uint16_t index, uint16_t recordId)
1209 {
1210     std::vector<uint8_t> resp;
1211     if (index == 0)
1212     {
1213         NMDiscoveryRecord nm = {};
1214         nm.header.record_id_lsb = recordId;
1215         nm.header.record_id_msb = recordId >> 8;
1216         nm.header.sdr_version = ipmiSdrVersion;
1217         nm.header.record_type = 0xC0;
1218         nm.header.record_length = 0xB;
1219         nm.oemID0 = 0x57;
1220         nm.oemID1 = 0x1;
1221         nm.oemID2 = 0x0;
1222         nm.subType = 0x0D;
1223         nm.version = 0x1;
1224         nm.slaveAddress = 0x2C;
1225         nm.channelNumber = 0x60;
1226         nm.healthEventSensor = 0x19;
1227         nm.exceptionEventSensor = 0x18;
1228         nm.operationalCapSensor = 0x1A;
1229         nm.thresholdExceededSensor = 0x1B;
1230 
1231         uint8_t* nmPtr = reinterpret_cast<uint8_t*>(&nm);
1232         resp.insert(resp.end(), nmPtr, nmPtr + sizeof(NMDiscoveryRecord));
1233     }
1234     else
1235     {
1236         throw std::runtime_error("getNMDiscoverySDR:: Illegal index " +
1237                                  std::to_string(index));
1238     }
1239 
1240     return resp;
1241 }
1242 
1243 void registerStorageFunctions()
1244 {
1245     createTimers();
1246     // <Get FRU Inventory Area Info>
1247     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage,
1248                           ipmi::storage::cmdGetFruInventoryAreaInfo,
1249                           ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo);
1250     // <READ FRU Data>
1251     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1252                           ipmi::storage::cmdReadFruData, ipmi::Privilege::User,
1253                           ipmiStorageReadFruData);
1254 
1255     // <WRITE FRU Data>
1256     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1257                           ipmi::storage::cmdWriteFruData,
1258                           ipmi::Privilege::Operator, ipmiStorageWriteFruData);
1259 
1260     // <Get SEL Info>
1261     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1262                           ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
1263                           ipmiStorageGetSELInfo);
1264 
1265     // <Get SEL Entry>
1266     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1267                           ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
1268                           ipmiStorageGetSELEntry);
1269 
1270     // <Add SEL Entry>
1271     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1272                           ipmi::storage::cmdAddSelEntry,
1273                           ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
1274 
1275     // <Clear SEL>
1276     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1277                           ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
1278                           ipmiStorageClearSEL);
1279 
1280     // <Get SEL Time>
1281     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1282                           ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
1283                           ipmiStorageGetSELTime);
1284 
1285     // <Set SEL Time>
1286     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1287                           ipmi::storage::cmdSetSelTime,
1288                           ipmi::Privilege::Operator, ipmiStorageSetSELTime);
1289 }
1290 } // namespace storage
1291 } // namespace ipmi
1292