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