|  | /* | 
|  | // Copyright (c) 2017 2018 Intel Corporation | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  | */ | 
|  |  | 
|  | #include <boost/algorithm/string.hpp> | 
|  | #include <boost/container/flat_map.hpp> | 
|  | #include <boost/process.hpp> | 
|  | #include <commandutils.hpp> | 
|  | #include <filesystem> | 
|  | #include <iostream> | 
|  | #include <ipmid/api.hpp> | 
|  | #include <phosphor-ipmi-host/selutility.hpp> | 
|  | #include <phosphor-logging/log.hpp> | 
|  | #include <sdbusplus/message/types.hpp> | 
|  | #include <sdbusplus/timer.hpp> | 
|  | #include <sdrutils.hpp> | 
|  | #include <stdexcept> | 
|  | #include <storagecommands.hpp> | 
|  |  | 
|  | namespace intel_oem::ipmi::sel | 
|  | { | 
|  | static const std::filesystem::path selLogDir = "/var/log"; | 
|  | static const std::string selLogFilename = "ipmi_sel"; | 
|  |  | 
|  | static int getFileTimestamp(const std::filesystem::path& file) | 
|  | { | 
|  | struct stat st; | 
|  |  | 
|  | if (stat(file.c_str(), &st) >= 0) | 
|  | { | 
|  | return st.st_mtime; | 
|  | } | 
|  | return ::ipmi::sel::invalidTimeStamp; | 
|  | } | 
|  |  | 
|  | namespace erase_time | 
|  | { | 
|  | static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time"; | 
|  |  | 
|  | void save() | 
|  | { | 
|  | // open the file, creating it if necessary | 
|  | int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); | 
|  | if (fd < 0) | 
|  | { | 
|  | std::cerr << "Failed to open file\n"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // update the file timestamp to the current time | 
|  | if (futimens(fd, NULL) < 0) | 
|  | { | 
|  | std::cerr << "Failed to update timestamp: " | 
|  | << std::string(strerror(errno)); | 
|  | } | 
|  | close(fd); | 
|  | } | 
|  |  | 
|  | int get() | 
|  | { | 
|  | return getFileTimestamp(selEraseTimestamp); | 
|  | } | 
|  | } // namespace erase_time | 
|  | } // namespace intel_oem::ipmi::sel | 
|  |  | 
|  | namespace ipmi | 
|  | { | 
|  |  | 
|  | namespace storage | 
|  | { | 
|  |  | 
|  | constexpr static const size_t maxMessageSize = 64; | 
|  | constexpr static const size_t maxFruSdrNameSize = 16; | 
|  | using ManagedObjectType = boost::container::flat_map< | 
|  | sdbusplus::message::object_path, | 
|  | boost::container::flat_map< | 
|  | std::string, boost::container::flat_map<std::string, DbusVariant>>>; | 
|  | using ManagedEntry = std::pair< | 
|  | sdbusplus::message::object_path, | 
|  | boost::container::flat_map< | 
|  | std::string, boost::container::flat_map<std::string, DbusVariant>>>; | 
|  |  | 
|  | constexpr static const char* fruDeviceServiceName = | 
|  | "xyz.openbmc_project.FruDevice"; | 
|  | constexpr static const size_t cacheTimeoutSeconds = 10; | 
|  |  | 
|  | // event direction is bit[7] of eventType where 1b = Deassertion event | 
|  | constexpr static const uint8_t deassertionEvent = 0x80; | 
|  |  | 
|  | static std::vector<uint8_t> fruCache; | 
|  | static uint8_t cacheBus = 0xFF; | 
|  | static uint8_t cacheAddr = 0XFF; | 
|  |  | 
|  | std::unique_ptr<phosphor::Timer> cacheTimer = nullptr; | 
|  |  | 
|  | // we unfortunately have to build a map of hashes in case there is a | 
|  | // collision to verify our dev-id | 
|  | boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes; | 
|  |  | 
|  | void registerStorageFunctions() __attribute__((constructor)); | 
|  | static sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection()); | 
|  |  | 
|  | bool writeFru() | 
|  | { | 
|  | sdbusplus::message::message writeFru = dbus.new_method_call( | 
|  | fruDeviceServiceName, "/xyz/openbmc_project/FruDevice", | 
|  | "xyz.openbmc_project.FruDeviceManager", "WriteFru"); | 
|  | writeFru.append(cacheBus, cacheAddr, fruCache); | 
|  | try | 
|  | { | 
|  | sdbusplus::message::message writeFruResp = dbus.call(writeFru); | 
|  | } | 
|  | catch (sdbusplus::exception_t&) | 
|  | { | 
|  | // todo: log sel? | 
|  | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | "error writing fru"); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void createTimer() | 
|  | { | 
|  | if (cacheTimer == nullptr) | 
|  | { | 
|  | cacheTimer = std::make_unique<phosphor::Timer>(writeFru); | 
|  | } | 
|  | } | 
|  |  | 
|  | ipmi_ret_t replaceCacheFru(uint8_t devId) | 
|  | { | 
|  | static uint8_t lastDevId = 0xFF; | 
|  |  | 
|  | bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired(); | 
|  | if (lastDevId == devId && timerRunning) | 
|  | { | 
|  | return IPMI_CC_OK; // cache already up to date | 
|  | } | 
|  | // if timer is running, stop it and writeFru manually | 
|  | else if (timerRunning) | 
|  | { | 
|  | cacheTimer->stop(); | 
|  | writeFru(); | 
|  | } | 
|  |  | 
|  | sdbusplus::message::message getObjects = dbus.new_method_call( | 
|  | fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager", | 
|  | "GetManagedObjects"); | 
|  | ManagedObjectType frus; | 
|  | try | 
|  | { | 
|  | sdbusplus::message::message resp = dbus.call(getObjects); | 
|  | resp.read(frus); | 
|  | } | 
|  | catch (sdbusplus::exception_t&) | 
|  | { | 
|  | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | "replaceCacheFru: error getting managed objects"); | 
|  | return IPMI_CC_RESPONSE_ERROR; | 
|  | } | 
|  |  | 
|  | deviceHashes.clear(); | 
|  |  | 
|  | // hash the object paths to create unique device id's. increment on | 
|  | // collision | 
|  | std::hash<std::string> hasher; | 
|  | for (const auto& fru : frus) | 
|  | { | 
|  | auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice"); | 
|  | if (fruIface == fru.second.end()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | auto busFind = fruIface->second.find("BUS"); | 
|  | auto addrFind = fruIface->second.find("ADDRESS"); | 
|  | if (busFind == fruIface->second.end() || | 
|  | addrFind == fruIface->second.end()) | 
|  | { | 
|  | phosphor::logging::log<phosphor::logging::level::INFO>( | 
|  | "fru device missing Bus or Address", | 
|  | phosphor::logging::entry("FRU=%s", fru.first.str.c_str())); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | uint8_t fruBus = | 
|  | sdbusplus::message::variant_ns::get<uint32_t>(busFind->second); | 
|  | uint8_t fruAddr = | 
|  | sdbusplus::message::variant_ns::get<uint32_t>(addrFind->second); | 
|  |  | 
|  | uint8_t fruHash = 0; | 
|  | if (fruBus != 0 || fruAddr != 0) | 
|  | { | 
|  | fruHash = hasher(fru.first.str); | 
|  | // can't be 0xFF based on spec, and 0 is reserved for baseboard | 
|  | if (fruHash == 0 || fruHash == 0xFF) | 
|  | { | 
|  | fruHash = 1; | 
|  | } | 
|  | } | 
|  | std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr); | 
|  |  | 
|  | bool emplacePassed = false; | 
|  | while (!emplacePassed) | 
|  | { | 
|  | auto resp = deviceHashes.emplace(fruHash, newDev); | 
|  | emplacePassed = resp.second; | 
|  | if (!emplacePassed) | 
|  | { | 
|  | fruHash++; | 
|  | // can't be 0xFF based on spec, and 0 is reserved for | 
|  | // baseboard | 
|  | if (fruHash == 0XFF) | 
|  | { | 
|  | fruHash = 0x1; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | auto deviceFind = deviceHashes.find(devId); | 
|  | if (deviceFind == deviceHashes.end()) | 
|  | { | 
|  | return IPMI_CC_SENSOR_INVALID; | 
|  | } | 
|  |  | 
|  | fruCache.clear(); | 
|  | sdbusplus::message::message getRawFru = dbus.new_method_call( | 
|  | fruDeviceServiceName, "/xyz/openbmc_project/FruDevice", | 
|  | "xyz.openbmc_project.FruDeviceManager", "GetRawFru"); | 
|  | cacheBus = deviceFind->second.first; | 
|  | cacheAddr = deviceFind->second.second; | 
|  | getRawFru.append(cacheBus, cacheAddr); | 
|  | try | 
|  | { | 
|  | sdbusplus::message::message getRawResp = dbus.call(getRawFru); | 
|  | getRawResp.read(fruCache); | 
|  | } | 
|  | catch (sdbusplus::exception_t&) | 
|  | { | 
|  | lastDevId = 0xFF; | 
|  | cacheBus = 0xFF; | 
|  | cacheAddr = 0xFF; | 
|  | return IPMI_CC_RESPONSE_ERROR; | 
|  | } | 
|  |  | 
|  | lastDevId = devId; | 
|  | return IPMI_CC_OK; | 
|  | } | 
|  |  | 
|  | ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd, | 
|  | ipmi_request_t request, | 
|  | ipmi_response_t response, | 
|  | ipmi_data_len_t dataLen, | 
|  | ipmi_context_t context) | 
|  | { | 
|  | if (*dataLen != 4) | 
|  | { | 
|  | *dataLen = 0; | 
|  | return IPMI_CC_REQ_DATA_LEN_INVALID; | 
|  | } | 
|  | *dataLen = 0; // default to 0 in case of an error | 
|  |  | 
|  | auto req = static_cast<GetFRUAreaReq*>(request); | 
|  |  | 
|  | if (req->countToRead > maxMessageSize - 1) | 
|  | { | 
|  | return IPMI_CC_INVALID_FIELD_REQUEST; | 
|  | } | 
|  | ipmi_ret_t status = replaceCacheFru(req->fruDeviceID); | 
|  |  | 
|  | if (status != IPMI_CC_OK) | 
|  | { | 
|  | return status; | 
|  | } | 
|  |  | 
|  | size_t fromFRUByteLen = 0; | 
|  | if (req->countToRead + req->fruInventoryOffset < fruCache.size()) | 
|  | { | 
|  | fromFRUByteLen = req->countToRead; | 
|  | } | 
|  | else if (fruCache.size() > req->fruInventoryOffset) | 
|  | { | 
|  | fromFRUByteLen = fruCache.size() - req->fruInventoryOffset; | 
|  | } | 
|  | size_t padByteLen = req->countToRead - fromFRUByteLen; | 
|  | uint8_t* respPtr = static_cast<uint8_t*>(response); | 
|  | *respPtr = req->countToRead; | 
|  | std::copy(fruCache.begin() + req->fruInventoryOffset, | 
|  | fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen, | 
|  | ++respPtr); | 
|  | // if longer than the fru is requested, fill with 0xFF | 
|  | if (padByteLen) | 
|  | { | 
|  | respPtr += fromFRUByteLen; | 
|  | std::fill(respPtr, respPtr + padByteLen, 0xFF); | 
|  | } | 
|  | *dataLen = fromFRUByteLen + 1; | 
|  |  | 
|  | return IPMI_CC_OK; | 
|  | } | 
|  |  | 
|  | ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t netfn, ipmi_cmd_t cmd, | 
|  | ipmi_request_t request, | 
|  | ipmi_response_t response, | 
|  | ipmi_data_len_t dataLen, | 
|  | ipmi_context_t context) | 
|  | { | 
|  | if (*dataLen < 4 || | 
|  | *dataLen >= | 
|  | 0xFF + 3) // count written return is one byte, so limit to one byte | 
|  | // of data after the three request data bytes | 
|  | { | 
|  | *dataLen = 0; | 
|  | return IPMI_CC_REQ_DATA_LEN_INVALID; | 
|  | } | 
|  |  | 
|  | auto req = static_cast<WriteFRUDataReq*>(request); | 
|  | size_t writeLen = *dataLen - 3; | 
|  | *dataLen = 0; // default to 0 in case of an error | 
|  |  | 
|  | ipmi_ret_t status = replaceCacheFru(req->fruDeviceID); | 
|  | if (status != IPMI_CC_OK) | 
|  | { | 
|  | return status; | 
|  | } | 
|  | int lastWriteAddr = req->fruInventoryOffset + writeLen; | 
|  | if (fruCache.size() < lastWriteAddr) | 
|  | { | 
|  | fruCache.resize(req->fruInventoryOffset + writeLen); | 
|  | } | 
|  |  | 
|  | std::copy(req->data, req->data + writeLen, | 
|  | fruCache.begin() + req->fruInventoryOffset); | 
|  |  | 
|  | bool atEnd = false; | 
|  |  | 
|  | if (fruCache.size() >= sizeof(FRUHeader)) | 
|  | { | 
|  |  | 
|  | FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data()); | 
|  |  | 
|  | int lastRecordStart = std::max( | 
|  | header->internalOffset, | 
|  | std::max(header->chassisOffset, | 
|  | std::max(header->boardOffset, header->productOffset))); | 
|  | // TODO: Handle Multi-Record FRUs? | 
|  |  | 
|  | lastRecordStart *= 8; // header starts in are multiples of 8 bytes | 
|  |  | 
|  | // get the length of the area in multiples of 8 bytes | 
|  | if (lastWriteAddr > (lastRecordStart + 1)) | 
|  | { | 
|  | // second byte in record area is the length | 
|  | int areaLength(fruCache[lastRecordStart + 1]); | 
|  | areaLength *= 8; // it is in multiples of 8 bytes | 
|  |  | 
|  | if (lastWriteAddr >= (areaLength + lastRecordStart)) | 
|  | { | 
|  | atEnd = true; | 
|  | } | 
|  | } | 
|  | } | 
|  | uint8_t* respPtr = static_cast<uint8_t*>(response); | 
|  | if (atEnd) | 
|  | { | 
|  | // cancel timer, we're at the end so might as well send it | 
|  | cacheTimer->stop(); | 
|  | if (!writeFru()) | 
|  | { | 
|  | return IPMI_CC_INVALID_FIELD_REQUEST; | 
|  | } | 
|  | *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF)); | 
|  | } | 
|  | else | 
|  | { | 
|  | // start a timer, if no further data is sent in cacheTimeoutSeconds | 
|  | // seconds, check to see if it is valid | 
|  | createTimer(); | 
|  | cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>( | 
|  | std::chrono::seconds(cacheTimeoutSeconds))); | 
|  | *respPtr = 0; | 
|  | } | 
|  |  | 
|  | *dataLen = 1; | 
|  |  | 
|  | return IPMI_CC_OK; | 
|  | } | 
|  |  | 
|  | ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd, | 
|  | ipmi_request_t request, | 
|  | ipmi_response_t response, | 
|  | ipmi_data_len_t dataLen, | 
|  | ipmi_context_t context) | 
|  | { | 
|  | if (*dataLen != 1) | 
|  | { | 
|  | *dataLen = 0; | 
|  | return IPMI_CC_REQ_DATA_LEN_INVALID; | 
|  | } | 
|  | *dataLen = 0; // default to 0 in case of an error | 
|  |  | 
|  | uint8_t reqDev = *(static_cast<uint8_t*>(request)); | 
|  | if (reqDev == 0xFF) | 
|  | { | 
|  | return IPMI_CC_INVALID_FIELD_REQUEST; | 
|  | } | 
|  | ipmi_ret_t status = replaceCacheFru(reqDev); | 
|  |  | 
|  | if (status != IPMI_CC_OK) | 
|  | { | 
|  | return status; | 
|  | } | 
|  |  | 
|  | GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response); | 
|  | respPtr->inventorySizeLSB = fruCache.size() & 0xFF; | 
|  | respPtr->inventorySizeMSB = fruCache.size() >> 8; | 
|  | respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte); | 
|  |  | 
|  | *dataLen = sizeof(GetFRUAreaResp); | 
|  | return IPMI_CC_OK; | 
|  | } | 
|  |  | 
|  | ipmi_ret_t getFruSdrCount(size_t& count) | 
|  | { | 
|  | ipmi_ret_t ret = replaceCacheFru(0); | 
|  | if (ret != IPMI_CC_OK) | 
|  | { | 
|  | return ret; | 
|  | } | 
|  | count = deviceHashes.size(); | 
|  | return IPMI_CC_OK; | 
|  | } | 
|  |  | 
|  | ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp) | 
|  | { | 
|  | ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list | 
|  | if (ret != IPMI_CC_OK) | 
|  | { | 
|  | return ret; | 
|  | } | 
|  | if (deviceHashes.size() < index) | 
|  | { | 
|  | return IPMI_CC_INVALID_FIELD_REQUEST; | 
|  | } | 
|  | auto device = deviceHashes.begin() + index; | 
|  | uint8_t& bus = device->second.first; | 
|  | uint8_t& address = device->second.second; | 
|  |  | 
|  | ManagedObjectType frus; | 
|  |  | 
|  | sdbusplus::message::message getObjects = dbus.new_method_call( | 
|  | fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager", | 
|  | "GetManagedObjects"); | 
|  | try | 
|  | { | 
|  | sdbusplus::message::message resp = dbus.call(getObjects); | 
|  | resp.read(frus); | 
|  | } | 
|  | catch (sdbusplus::exception_t&) | 
|  | { | 
|  | return IPMI_CC_RESPONSE_ERROR; | 
|  | } | 
|  | boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr; | 
|  | auto fru = | 
|  | std::find_if(frus.begin(), frus.end(), | 
|  | [bus, address, &fruData](ManagedEntry& entry) { | 
|  | auto findFruDevice = | 
|  | entry.second.find("xyz.openbmc_project.FruDevice"); | 
|  | if (findFruDevice == entry.second.end()) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | fruData = &(findFruDevice->second); | 
|  | auto findBus = findFruDevice->second.find("BUS"); | 
|  | auto findAddress = | 
|  | findFruDevice->second.find("ADDRESS"); | 
|  | if (findBus == findFruDevice->second.end() || | 
|  | findAddress == findFruDevice->second.end()) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | if (sdbusplus::message::variant_ns::get<uint32_t>( | 
|  | findBus->second) != bus) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | if (sdbusplus::message::variant_ns::get<uint32_t>( | 
|  | findAddress->second) != address) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | }); | 
|  | if (fru == frus.end()) | 
|  | { | 
|  | return IPMI_CC_RESPONSE_ERROR; | 
|  | } | 
|  | std::string name; | 
|  | auto findProductName = fruData->find("BOARD_PRODUCT_NAME"); | 
|  | auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME"); | 
|  | if (findProductName != fruData->end()) | 
|  | { | 
|  | name = sdbusplus::message::variant_ns::get<std::string>( | 
|  | findProductName->second); | 
|  | } | 
|  | else if (findBoardName != fruData->end()) | 
|  | { | 
|  | name = sdbusplus::message::variant_ns::get<std::string>( | 
|  | findBoardName->second); | 
|  | } | 
|  | else | 
|  | { | 
|  | name = "UNKNOWN"; | 
|  | } | 
|  | if (name.size() > maxFruSdrNameSize) | 
|  | { | 
|  | name = name.substr(0, maxFruSdrNameSize); | 
|  | } | 
|  | size_t sizeDiff = maxFruSdrNameSize - name.size(); | 
|  |  | 
|  | resp.header.record_id_lsb = 0x0; // calling code is to implement these | 
|  | resp.header.record_id_msb = 0x0; | 
|  | resp.header.sdr_version = ipmiSdrVersion; | 
|  | resp.header.record_type = 0x11; // FRU Device Locator | 
|  | resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff; | 
|  | resp.key.deviceAddress = 0x20; | 
|  | resp.key.fruID = device->first; | 
|  | resp.key.accessLun = 0x80; // logical / physical fru device | 
|  | resp.key.channelNumber = 0x0; | 
|  | resp.body.reserved = 0x0; | 
|  | resp.body.deviceType = 0x10; | 
|  | resp.body.deviceTypeModifier = 0x0; | 
|  | resp.body.entityID = 0x0; | 
|  | resp.body.entityInstance = 0x1; | 
|  | resp.body.oem = 0x0; | 
|  | resp.body.deviceIDLen = name.size(); | 
|  | name.copy(resp.body.deviceID, name.size()); | 
|  |  | 
|  | return IPMI_CC_OK; | 
|  | } | 
|  |  | 
|  | static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles) | 
|  | { | 
|  | // Loop through the directory looking for ipmi_sel log files | 
|  | for (const std::filesystem::directory_entry& dirEnt : | 
|  | std::filesystem::directory_iterator(intel_oem::ipmi::sel::selLogDir)) | 
|  | { | 
|  | std::string filename = dirEnt.path().filename(); | 
|  | if (boost::starts_with(filename, intel_oem::ipmi::sel::selLogFilename)) | 
|  | { | 
|  | // If we find an ipmi_sel log file, save the path | 
|  | selLogFiles.emplace_back(intel_oem::ipmi::sel::selLogDir / | 
|  | filename); | 
|  | } | 
|  | } | 
|  | // As the log files rotate, they are appended with a ".#" that is higher for | 
|  | // the older logs. Since we don't expect more than 10 log files, we | 
|  | // can just sort the list to get them in order from newest to oldest | 
|  | std::sort(selLogFiles.begin(), selLogFiles.end()); | 
|  |  | 
|  | return !selLogFiles.empty(); | 
|  | } | 
|  |  | 
|  | static int countSELEntries() | 
|  | { | 
|  | // Get the list of ipmi_sel log files | 
|  | std::vector<std::filesystem::path> selLogFiles; | 
|  | if (!getSELLogFiles(selLogFiles)) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  | int numSELEntries = 0; | 
|  | // Loop through each log file and count the number of logs | 
|  | for (const std::filesystem::path& file : selLogFiles) | 
|  | { | 
|  | std::ifstream logStream(file); | 
|  | if (!logStream.is_open()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::string line; | 
|  | while (std::getline(logStream, line)) | 
|  | { | 
|  | numSELEntries++; | 
|  | } | 
|  | } | 
|  | return numSELEntries; | 
|  | } | 
|  |  | 
|  | static bool findSELEntry(const int recordID, | 
|  | const std::vector<std::filesystem::path> selLogFiles, | 
|  | std::string& entry) | 
|  | { | 
|  | // Record ID is the first entry field following the timestamp. It is | 
|  | // preceded by a space and followed by a comma | 
|  | std::string search = " " + std::to_string(recordID) + ","; | 
|  |  | 
|  | // Loop through the ipmi_sel log entries | 
|  | for (const std::filesystem::path& file : selLogFiles) | 
|  | { | 
|  | std::ifstream logStream(file); | 
|  | if (!logStream.is_open()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | while (std::getline(logStream, entry)) | 
|  | { | 
|  | // Check if the record ID matches | 
|  | if (entry.find(search) != std::string::npos) | 
|  | { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static uint16_t | 
|  | getNextRecordID(const uint16_t recordID, | 
|  | const std::vector<std::filesystem::path> selLogFiles) | 
|  | { | 
|  | uint16_t nextRecordID = recordID + 1; | 
|  | std::string entry; | 
|  | if (findSELEntry(nextRecordID, selLogFiles, entry)) | 
|  | { | 
|  | return nextRecordID; | 
|  | } | 
|  | else | 
|  | { | 
|  | return ipmi::sel::lastEntry; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data) | 
|  | { | 
|  | for (unsigned int i = 0; i < hexStr.size(); i += 2) | 
|  | { | 
|  | try | 
|  | { | 
|  | data.push_back(static_cast<uint8_t>( | 
|  | std::stoul(hexStr.substr(i, 2), nullptr, 16))); | 
|  | } | 
|  | catch (std::invalid_argument& e) | 
|  | { | 
|  | phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); | 
|  | return -1; | 
|  | } | 
|  | catch (std::out_of_range& e) | 
|  | { | 
|  | phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); | 
|  | return -1; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | ipmi::RspType<uint8_t,  // SEL version | 
|  | uint16_t, // SEL entry count | 
|  | uint16_t, // free space | 
|  | uint32_t, // last add timestamp | 
|  | uint32_t, // last erase timestamp | 
|  | uint8_t>  // operation support | 
|  | ipmiStorageGetSELInfo() | 
|  | { | 
|  | constexpr uint8_t selVersion = ipmi::sel::selVersion; | 
|  | uint16_t entries = countSELEntries(); | 
|  | uint32_t addTimeStamp = intel_oem::ipmi::sel::getFileTimestamp( | 
|  | intel_oem::ipmi::sel::selLogDir / intel_oem::ipmi::sel::selLogFilename); | 
|  | uint32_t eraseTimeStamp = intel_oem::ipmi::sel::erase_time::get(); | 
|  | constexpr uint8_t operationSupport = | 
|  | intel_oem::ipmi::sel::selOperationSupport; | 
|  | constexpr uint16_t freeSpace = | 
|  | 0xffff; // Spec indicates that more than 64kB is free | 
|  |  | 
|  | return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp, | 
|  | eraseTimeStamp, operationSupport); | 
|  | } | 
|  |  | 
|  | using systemEventType = std::tuple< | 
|  | uint32_t, // Timestamp | 
|  | uint16_t, // Generator ID | 
|  | uint8_t,  // EvM Rev | 
|  | uint8_t,  // Sensor Type | 
|  | uint8_t,  // Sensor Number | 
|  | uint7_t,  // Event Type | 
|  | bool,     // Event Direction | 
|  | std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize>>; // Event Data | 
|  | using oemTsEventType = std::tuple< | 
|  | uint32_t,                                                   // Timestamp | 
|  | std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize>>; // Event Data | 
|  | using oemEventType = | 
|  | std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize>; // Event Data | 
|  |  | 
|  | ipmi::RspType<uint16_t, // Next Record ID | 
|  | uint16_t, // Record ID | 
|  | uint8_t,  // Record Type | 
|  | std::variant<systemEventType, oemTsEventType, | 
|  | oemEventType>> // Record Content | 
|  | ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID, | 
|  | uint8_t offset, uint8_t size) | 
|  | { | 
|  | // Only support getting the entire SEL record. If a partial size or non-zero | 
|  | // offset is requested, return an error | 
|  | if (offset != 0 || size != ipmi::sel::entireRecord) | 
|  | { | 
|  | return ipmi::responseRetBytesUnavailable(); | 
|  | } | 
|  |  | 
|  | // Check the reservation ID if one is provided or required (only if the | 
|  | // offset is non-zero) | 
|  | if (reservationID != 0 || offset != 0) | 
|  | { | 
|  | if (!checkSELReservation(reservationID)) | 
|  | { | 
|  | return ipmi::responseInvalidReservationId(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Get the ipmi_sel log files | 
|  | std::vector<std::filesystem::path> selLogFiles; | 
|  | if (!getSELLogFiles(selLogFiles)) | 
|  | { | 
|  | return ipmi::responseSensorInvalid(); | 
|  | } | 
|  |  | 
|  | std::string targetEntry; | 
|  |  | 
|  | if (targetID == ipmi::sel::firstEntry) | 
|  | { | 
|  | // The first entry will be at the top of the oldest log file | 
|  | std::ifstream logStream(selLogFiles.back()); | 
|  | if (!logStream.is_open()) | 
|  | { | 
|  | return ipmi::responseUnspecifiedError(); | 
|  | } | 
|  |  | 
|  | if (!std::getline(logStream, targetEntry)) | 
|  | { | 
|  | return ipmi::responseUnspecifiedError(); | 
|  | } | 
|  | } | 
|  | else if (targetID == ipmi::sel::lastEntry) | 
|  | { | 
|  | // The last entry will be at the bottom of the newest log file | 
|  | std::ifstream logStream(selLogFiles.front()); | 
|  | if (!logStream.is_open()) | 
|  | { | 
|  | return ipmi::responseUnspecifiedError(); | 
|  | } | 
|  |  | 
|  | std::string line; | 
|  | while (std::getline(logStream, line)) | 
|  | { | 
|  | targetEntry = line; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (!findSELEntry(targetID, selLogFiles, targetEntry)) | 
|  | { | 
|  | return ipmi::responseSensorInvalid(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // The format of the ipmi_sel message is "<timestamp> | 
|  | // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]". Split it | 
|  | // into the individual fields. | 
|  | std::vector<std::string> targetEntryFields; | 
|  | boost::split(targetEntryFields, targetEntry, boost::is_any_of(" ,"), | 
|  | boost::token_compress_on); | 
|  | if (targetEntryFields.size() < 4) | 
|  | { | 
|  | return ipmi::responseUnspecifiedError(); | 
|  | } | 
|  |  | 
|  | uint16_t recordID = std::stoul(targetEntryFields[1]); | 
|  | uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles); | 
|  | uint8_t recordType = std::stoul(targetEntryFields[2], nullptr, 16); | 
|  | std::vector<uint8_t> eventDataBytes; | 
|  | if (fromHexStr(targetEntryFields[3], eventDataBytes) < 0) | 
|  | { | 
|  | return ipmi::responseUnspecifiedError(); | 
|  | } | 
|  |  | 
|  | if (recordType == intel_oem::ipmi::sel::systemEvent) | 
|  | { | 
|  | // System type events have three additional fields | 
|  | if (targetEntryFields.size() < 7) | 
|  | { | 
|  | return ipmi::responseUnspecifiedError(); | 
|  | } | 
|  |  | 
|  | // Get the timestamp | 
|  | std::tm timeStruct = {}; | 
|  | std::istringstream entryStream(targetEntryFields[0]); | 
|  |  | 
|  | uint32_t timestamp = ipmi::sel::invalidTimeStamp; | 
|  | if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) | 
|  | { | 
|  | timestamp = std::mktime(&timeStruct); | 
|  | } | 
|  |  | 
|  | // Get the generator ID | 
|  | uint16_t generatorID = std::stoul(targetEntryFields[4], nullptr, 16); | 
|  |  | 
|  | // Set the event message revision | 
|  | uint8_t evmRev = intel_oem::ipmi::sel::eventMsgRev; | 
|  |  | 
|  | // Get the sensor type, sensor number, and event type for the sensor | 
|  | uint8_t sensorType = getSensorTypeFromPath(targetEntryFields[5]); | 
|  | uint8_t sensorNum = getSensorNumberFromPath(targetEntryFields[5]); | 
|  | uint7_t eventType = getSensorEventTypeFromPath(targetEntryFields[5]); | 
|  |  | 
|  | // Get the event direction | 
|  | bool eventDir = std::stoul(targetEntryFields[6]) ? 0 : 1; | 
|  |  | 
|  | // Only keep the eventData bytes that fit in the record | 
|  | std::array<uint8_t, intel_oem::ipmi::sel::systemEventSize> eventData{}; | 
|  | std::copy_n(eventDataBytes.begin(), | 
|  | std::min(eventDataBytes.size(), eventData.size()), | 
|  | eventData.begin()); | 
|  |  | 
|  | return ipmi::responseSuccess( | 
|  | nextRecordID, recordID, recordType, | 
|  | systemEventType{timestamp, generatorID, evmRev, sensorType, | 
|  | sensorNum, eventType, eventDir, eventData}); | 
|  | } | 
|  | else if (recordType >= intel_oem::ipmi::sel::oemTsEventFirst && | 
|  | recordType <= intel_oem::ipmi::sel::oemTsEventLast) | 
|  | { | 
|  | // Get the timestamp | 
|  | std::tm timeStruct = {}; | 
|  | std::istringstream entryStream(targetEntryFields[0]); | 
|  |  | 
|  | uint32_t timestamp = ipmi::sel::invalidTimeStamp; | 
|  | if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) | 
|  | { | 
|  | timestamp = std::mktime(&timeStruct); | 
|  | } | 
|  |  | 
|  | // Only keep the bytes that fit in the record | 
|  | std::array<uint8_t, intel_oem::ipmi::sel::oemTsEventSize> eventData{}; | 
|  | std::copy_n(eventDataBytes.begin(), | 
|  | std::min(eventDataBytes.size(), eventData.size()), | 
|  | eventData.begin()); | 
|  |  | 
|  | return ipmi::responseSuccess(nextRecordID, recordID, recordType, | 
|  | oemTsEventType{timestamp, eventData}); | 
|  | } | 
|  | else if (recordType >= intel_oem::ipmi::sel::oemEventFirst && | 
|  | recordType <= intel_oem::ipmi::sel::oemEventLast) | 
|  | { | 
|  | // Only keep the bytes that fit in the record | 
|  | std::array<uint8_t, intel_oem::ipmi::sel::oemEventSize> eventData{}; | 
|  | std::copy_n(eventDataBytes.begin(), | 
|  | std::min(eventDataBytes.size(), eventData.size()), | 
|  | eventData.begin()); | 
|  |  | 
|  | return ipmi::responseSuccess(nextRecordID, recordID, recordType, | 
|  | eventData); | 
|  | } | 
|  |  | 
|  | return ipmi::responseUnspecifiedError(); | 
|  | } | 
|  |  | 
|  | ipmi_ret_t ipmiStorageAddSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, | 
|  | ipmi_request_t request, | 
|  | ipmi_response_t response, | 
|  | ipmi_data_len_t data_len, | 
|  | ipmi_context_t context) | 
|  | { | 
|  | static constexpr char const* ipmiSELObject = | 
|  | "xyz.openbmc_project.Logging.IPMI"; | 
|  | static constexpr char const* ipmiSELPath = | 
|  | "/xyz/openbmc_project/Logging/IPMI"; | 
|  | static constexpr char const* ipmiSELAddInterface = | 
|  | "xyz.openbmc_project.Logging.IPMI"; | 
|  | static const std::string ipmiSELAddMessage = | 
|  | "IPMI SEL entry logged using IPMI Add SEL Entry command."; | 
|  | uint16_t recordID = 0; | 
|  | sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; | 
|  |  | 
|  | if (*data_len != sizeof(AddSELRequest)) | 
|  | { | 
|  | *data_len = 0; | 
|  | return IPMI_CC_REQ_DATA_LEN_INVALID; | 
|  | } | 
|  | AddSELRequest* req = static_cast<AddSELRequest*>(request); | 
|  |  | 
|  | // Per the IPMI spec, need to cancel any reservation when a SEL entry is | 
|  | // added | 
|  | cancelSELReservation(); | 
|  |  | 
|  | if (req->recordType == intel_oem::ipmi::sel::systemEvent) | 
|  | { | 
|  | std::string sensorPath = | 
|  | getPathFromSensorNumber(req->record.system.sensorNum); | 
|  | std::vector<uint8_t> eventData( | 
|  | req->record.system.eventData, | 
|  | req->record.system.eventData + | 
|  | intel_oem::ipmi::sel::systemEventSize); | 
|  | bool assert = !(req->record.system.eventType & deassertionEvent); | 
|  | uint16_t genId = req->record.system.generatorID; | 
|  | sdbusplus::message::message writeSEL = bus.new_method_call( | 
|  | ipmiSELObject, ipmiSELPath, ipmiSELAddInterface, "IpmiSelAdd"); | 
|  | writeSEL.append(ipmiSELAddMessage, sensorPath, eventData, assert, | 
|  | genId); | 
|  | try | 
|  | { | 
|  | sdbusplus::message::message writeSELResp = bus.call(writeSEL); | 
|  | writeSELResp.read(recordID); | 
|  | } | 
|  | catch (sdbusplus::exception_t& e) | 
|  | { | 
|  | phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); | 
|  | *data_len = 0; | 
|  | return IPMI_CC_UNSPECIFIED_ERROR; | 
|  | } | 
|  | } | 
|  | else if (req->recordType >= intel_oem::ipmi::sel::oemTsEventFirst && | 
|  | req->recordType <= intel_oem::ipmi::sel::oemEventLast) | 
|  | { | 
|  | std::vector<uint8_t> eventData; | 
|  | if (req->recordType <= intel_oem::ipmi::sel::oemTsEventLast) | 
|  | { | 
|  | eventData = | 
|  | std::vector<uint8_t>(req->record.oemTs.eventData, | 
|  | req->record.oemTs.eventData + | 
|  | intel_oem::ipmi::sel::oemTsEventSize); | 
|  | } | 
|  | else | 
|  | { | 
|  | eventData = std::vector<uint8_t>( | 
|  | req->record.oem.eventData, | 
|  | req->record.oem.eventData + intel_oem::ipmi::sel::oemEventSize); | 
|  | } | 
|  | sdbusplus::message::message writeSEL = bus.new_method_call( | 
|  | ipmiSELObject, ipmiSELPath, ipmiSELAddInterface, "IpmiSelAddOem"); | 
|  | writeSEL.append(ipmiSELAddMessage, eventData, req->recordType); | 
|  | try | 
|  | { | 
|  | sdbusplus::message::message writeSELResp = bus.call(writeSEL); | 
|  | writeSELResp.read(recordID); | 
|  | } | 
|  | catch (sdbusplus::exception_t& e) | 
|  | { | 
|  | phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); | 
|  | *data_len = 0; | 
|  | return IPMI_CC_UNSPECIFIED_ERROR; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | *data_len = 0; | 
|  | return IPMI_CC_PARM_OUT_OF_RANGE; | 
|  | } | 
|  |  | 
|  | *static_cast<uint16_t*>(response) = recordID; | 
|  | *data_len = sizeof(recordID); | 
|  | return IPMI_CC_OK; | 
|  | } | 
|  |  | 
|  | ipmi::RspType<uint8_t> ipmiStorageClearSEL(ipmi::Context::ptr ctx, | 
|  | uint16_t reservationID, | 
|  | const std::array<uint8_t, 3>& clr, | 
|  | uint8_t eraseOperation) | 
|  | { | 
|  | if (!checkSELReservation(reservationID)) | 
|  | { | 
|  | return ipmi::responseInvalidReservationId(); | 
|  | } | 
|  |  | 
|  | static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'}; | 
|  | if (clr != clrExpected) | 
|  | { | 
|  | return ipmi::responseInvalidFieldRequest(); | 
|  | } | 
|  |  | 
|  | // Erasure status cannot be fetched, so always return erasure status as | 
|  | // `erase completed`. | 
|  | if (eraseOperation == ipmi::sel::getEraseStatus) | 
|  | { | 
|  | return ipmi::responseSuccess(ipmi::sel::eraseComplete); | 
|  | } | 
|  |  | 
|  | // Check that initiate erase is correct | 
|  | if (eraseOperation != ipmi::sel::initiateErase) | 
|  | { | 
|  | return ipmi::responseInvalidFieldRequest(); | 
|  | } | 
|  |  | 
|  | // Per the IPMI spec, need to cancel any reservation when the SEL is | 
|  | // cleared | 
|  | cancelSELReservation(); | 
|  |  | 
|  | // Save the erase time | 
|  | intel_oem::ipmi::sel::erase_time::save(); | 
|  |  | 
|  | // Clear the SEL by deleting the log files | 
|  | std::vector<std::filesystem::path> selLogFiles; | 
|  | if (getSELLogFiles(selLogFiles)) | 
|  | { | 
|  | for (const std::filesystem::path& file : selLogFiles) | 
|  | { | 
|  | std::error_code ec; | 
|  | std::filesystem::remove(file, ec); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Reload rsyslog so it knows to start new log files | 
|  | sdbusplus::message::message rsyslogReload = dbus.new_method_call( | 
|  | "org.freedesktop.systemd1", "/org/freedesktop/systemd1", | 
|  | "org.freedesktop.systemd1.Manager", "ReloadUnit"); | 
|  | rsyslogReload.append("rsyslog.service", "replace"); | 
|  | try | 
|  | { | 
|  | sdbusplus::message::message reloadResponse = dbus.call(rsyslogReload); | 
|  | } | 
|  | catch (sdbusplus::exception_t& e) | 
|  | { | 
|  | phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); | 
|  | } | 
|  |  | 
|  | return ipmi::responseSuccess(ipmi::sel::eraseComplete); | 
|  | } | 
|  |  | 
|  | ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime) | 
|  | { | 
|  | // Set SEL Time is not supported | 
|  | return ipmi::responseInvalidCommand(); | 
|  | } | 
|  |  | 
|  | void registerStorageFunctions() | 
|  | { | 
|  | // <Get FRU Inventory Area Info> | 
|  | ipmiPrintAndRegister( | 
|  | NETFUN_STORAGE, | 
|  | static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo), | 
|  | NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR); | 
|  |  | 
|  | // <READ FRU Data> | 
|  | ipmiPrintAndRegister( | 
|  | NETFUN_STORAGE, | 
|  | static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL, | 
|  | ipmiStorageReadFRUData, PRIVILEGE_OPERATOR); | 
|  |  | 
|  | // <WRITE FRU Data> | 
|  | ipmiPrintAndRegister( | 
|  | NETFUN_STORAGE, | 
|  | static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData), | 
|  | NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR); | 
|  |  | 
|  | // <Get SEL Info> | 
|  | ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, | 
|  | ipmi::storage::cmdGetSelInfo, | 
|  | ipmi::Privilege::Operator, ipmiStorageGetSELInfo); | 
|  |  | 
|  | // <Get SEL Entry> | 
|  | ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, | 
|  | ipmi::storage::cmdGetSelEntry, | 
|  | ipmi::Privilege::Operator, ipmiStorageGetSELEntry); | 
|  |  | 
|  | // <Add SEL Entry> | 
|  | ipmiPrintAndRegister( | 
|  | NETFUN_STORAGE, | 
|  | static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdAddSEL), NULL, | 
|  | ipmiStorageAddSELEntry, PRIVILEGE_OPERATOR); | 
|  |  | 
|  | // <Clear SEL> | 
|  | ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, | 
|  | ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, | 
|  | ipmiStorageClearSEL); | 
|  |  | 
|  | // <Set SEL Time> | 
|  | ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, | 
|  | ipmi::storage::cmdSetSelTime, | 
|  | ipmi::Privilege::Operator, ipmiStorageSetSELTime); | 
|  | } | 
|  | } // namespace storage | 
|  | } // namespace ipmi |