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