1 #include "inband_code_update.hpp" 2 3 #include "libpldm/entity.h" 4 5 #include "libpldmresponder/pdr.hpp" 6 #include "oem_ibm_handler.hpp" 7 #include "xyz/openbmc_project/Common/error.hpp" 8 9 #include <arpa/inet.h> 10 11 #include <sdbusplus/server.hpp> 12 #include <xyz/openbmc_project/Dump/NewDump/server.hpp> 13 14 #include <exception> 15 #include <fstream> 16 namespace pldm 17 { 18 using namespace utils; 19 20 namespace responder 21 { 22 using namespace oem_ibm_platform; 23 24 /** @brief Directory where the lid files without a header are stored */ 25 auto lidDirPath = fs::path(LID_STAGING_DIR) / "lid"; 26 27 /** @brief Directory where the image files are stored as they are built */ 28 auto imageDirPath = fs::path(LID_STAGING_DIR) / "image"; 29 30 /** @brief Directory where the code update tarball files are stored */ 31 auto updateDirPath = fs::path(LID_STAGING_DIR) / "update"; 32 33 /** @brief The file name of the code update tarball */ 34 constexpr auto tarImageName = "image.tar"; 35 36 /** @brief The file name of the hostfw image */ 37 constexpr auto hostfwImageName = "image-hostfw"; 38 39 /** @brief The path to the code update tarball file */ 40 auto tarImagePath = fs::path(imageDirPath) / tarImageName; 41 42 /** @brief The path to the hostfw image */ 43 auto hostfwImagePath = fs::path(imageDirPath) / hostfwImageName; 44 45 /** @brief The path to the tarball file expected by the phosphor software 46 * manager */ 47 auto updateImagePath = fs::path("/tmp/images") / tarImageName; 48 49 std::string CodeUpdate::fetchCurrentBootSide() 50 { 51 return currBootSide; 52 } 53 54 std::string CodeUpdate::fetchNextBootSide() 55 { 56 return nextBootSide; 57 } 58 59 int CodeUpdate::setCurrentBootSide(const std::string& currSide) 60 { 61 currBootSide = currSide; 62 return PLDM_SUCCESS; 63 } 64 65 int CodeUpdate::setNextBootSide(const std::string& nextSide) 66 { 67 nextBootSide = nextSide; 68 std::string objPath{}; 69 if (nextBootSide == currBootSide) 70 { 71 objPath = runningVersion; 72 } 73 else 74 { 75 objPath = nonRunningVersion; 76 } 77 if (objPath.empty()) 78 { 79 std::cerr << "no nonRunningVersion present \n"; 80 return PLDM_PLATFORM_INVALID_STATE_VALUE; 81 } 82 83 pldm::utils::DBusMapping dbusMapping{objPath, redundancyIntf, "Priority", 84 "uint8_t"}; 85 uint8_t val = 0; 86 pldm::utils::PropertyValue value = static_cast<uint8_t>(val); 87 try 88 { 89 dBusIntf->setDbusProperty(dbusMapping, value); 90 } 91 catch (const std::exception& e) 92 { 93 std::cerr << "failed to set the next boot side to " << objPath.c_str() 94 << " ERROR=" << e.what() << "\n"; 95 return PLDM_ERROR; 96 } 97 return PLDM_SUCCESS; 98 } 99 100 int CodeUpdate::setRequestedApplyTime() 101 { 102 int rc = PLDM_SUCCESS; 103 pldm::utils::PropertyValue value = 104 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset"; 105 DBusMapping dbusMapping; 106 dbusMapping.objectPath = "/xyz/openbmc_project/software/apply_time"; 107 dbusMapping.interface = "xyz.openbmc_project.Software.ApplyTime"; 108 dbusMapping.propertyName = "RequestedApplyTime"; 109 dbusMapping.propertyType = "string"; 110 try 111 { 112 pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value); 113 } 114 catch (const std::exception& e) 115 { 116 std::cerr << "Failed To set RequestedApplyTime property " 117 << "ERROR=" << e.what() << std::endl; 118 rc = PLDM_ERROR; 119 } 120 return rc; 121 } 122 123 int CodeUpdate::setRequestedActivation() 124 { 125 int rc = PLDM_SUCCESS; 126 pldm::utils::PropertyValue value = 127 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active"; 128 DBusMapping dbusMapping; 129 dbusMapping.objectPath = newImageId; 130 dbusMapping.interface = "xyz.openbmc_project.Software.Activation"; 131 dbusMapping.propertyName = "RequestedActivation"; 132 dbusMapping.propertyType = "string"; 133 try 134 { 135 pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value); 136 } 137 catch (const std::exception& e) 138 { 139 std::cerr << "Failed To set RequestedActivation property" 140 << "ERROR=" << e.what() << std::endl; 141 rc = PLDM_ERROR; 142 } 143 return rc; 144 } 145 146 void CodeUpdate::setVersions() 147 { 148 static constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper"; 149 static constexpr auto functionalObjPath = 150 "/xyz/openbmc_project/software/functional"; 151 static constexpr auto activeObjPath = 152 "/xyz/openbmc_project/software/active"; 153 static constexpr auto propIntf = "org.freedesktop.DBus.Properties"; 154 155 auto& bus = dBusIntf->getBus(); 156 try 157 { 158 auto method = bus.new_method_call(mapperService, functionalObjPath, 159 propIntf, "Get"); 160 method.append("xyz.openbmc_project.Association", "endpoints"); 161 std::variant<std::vector<std::string>> paths; 162 163 auto reply = bus.call(method); 164 reply.read(paths); 165 166 runningVersion = std::get<std::vector<std::string>>(paths)[0]; 167 168 auto method1 = 169 bus.new_method_call(mapperService, activeObjPath, propIntf, "Get"); 170 method1.append("xyz.openbmc_project.Association", "endpoints"); 171 172 auto reply1 = bus.call(method1); 173 reply1.read(paths); 174 for (const auto& path : std::get<std::vector<std::string>>(paths)) 175 { 176 if (path != runningVersion) 177 { 178 nonRunningVersion = path; 179 break; 180 } 181 } 182 } 183 catch (const std::exception& e) 184 { 185 std::cerr << "failed to make a d-bus call to Object Mapper " 186 "Association, ERROR=" 187 << e.what() << "\n"; 188 return; 189 } 190 191 using namespace sdbusplus::bus::match::rules; 192 captureNextBootSideChange.push_back( 193 std::make_unique<sdbusplus::bus::match_t>( 194 pldm::utils::DBusHandler::getBus(), 195 propertiesChanged(runningVersion, redundancyIntf), 196 [this](sdbusplus::message_t& msg) { 197 DbusChangedProps props; 198 std::string iface; 199 msg.read(iface, props); 200 processPriorityChangeNotification(props); 201 })); 202 fwUpdateMatcher.push_back(std::make_unique<sdbusplus::bus::match_t>( 203 pldm::utils::DBusHandler::getBus(), 204 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 205 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 206 [this](sdbusplus::message_t& msg) { 207 DBusInterfaceAdded interfaces; 208 sdbusplus::message::object_path path; 209 msg.read(path, interfaces); 210 211 for (auto& interface : interfaces) 212 { 213 if (interface.first == 214 "xyz.openbmc_project.Software.Activation") 215 { 216 auto imageInterface = 217 "xyz.openbmc_project.Software.Activation"; 218 auto imageObjPath = path.str.c_str(); 219 220 try 221 { 222 auto propVal = dBusIntf->getDbusPropertyVariant( 223 imageObjPath, "Activation", imageInterface); 224 const auto& imageProp = std::get<std::string>(propVal); 225 if (imageProp == "xyz.openbmc_project.Software." 226 "Activation.Activations.Ready" && 227 isCodeUpdateInProgress()) 228 { 229 newImageId = path.str; 230 if (!imageActivationMatch) 231 { 232 imageActivationMatch = std::make_unique< 233 sdbusplus::bus::match_t>( 234 pldm::utils::DBusHandler::getBus(), 235 propertiesChanged(newImageId, 236 "xyz.openbmc_project." 237 "Software.Activation"), 238 [this](sdbusplus::message_t& msg) { 239 DbusChangedProps props; 240 std::string iface; 241 msg.read(iface, props); 242 const auto itr = 243 props.find("Activation"); 244 if (itr != props.end()) 245 { 246 PropertyValue value = itr->second; 247 auto propVal = 248 std::get<std::string>(value); 249 if (propVal == 250 "xyz.openbmc_project.Software." 251 "Activation.Activations.Active") 252 { 253 CodeUpdateState state = 254 CodeUpdateState::END; 255 setCodeUpdateProgress(false); 256 auto sensorId = 257 getFirmwareUpdateSensor(); 258 sendStateSensorEvent( 259 sensorId, 260 PLDM_STATE_SENSOR_STATE, 0, 261 uint8_t(state), 262 uint8_t(CodeUpdateState:: 263 START)); 264 newImageId.clear(); 265 } 266 else if (propVal == 267 "xyz.openbmc_project." 268 "Software.Activation." 269 "Activations.Failed" || 270 propVal == 271 "xyz.openbmc_" 272 "project.Software." 273 "Activation." 274 "Activations." 275 "Invalid") 276 { 277 CodeUpdateState state = 278 CodeUpdateState::FAIL; 279 setCodeUpdateProgress(false); 280 auto sensorId = 281 getFirmwareUpdateSensor(); 282 sendStateSensorEvent( 283 sensorId, 284 PLDM_STATE_SENSOR_STATE, 0, 285 uint8_t(state), 286 uint8_t(CodeUpdateState:: 287 START)); 288 newImageId.clear(); 289 } 290 } 291 }); 292 } 293 auto rc = setRequestedActivation(); 294 if (rc != PLDM_SUCCESS) 295 { 296 CodeUpdateState state = CodeUpdateState::FAIL; 297 setCodeUpdateProgress(false); 298 auto sensorId = getFirmwareUpdateSensor(); 299 sendStateSensorEvent( 300 sensorId, PLDM_STATE_SENSOR_STATE, 0, 301 uint8_t(state), 302 uint8_t(CodeUpdateState::START)); 303 std::cerr 304 << "could not set RequestedActivation \n"; 305 } 306 break; 307 } 308 } 309 catch (const sdbusplus::exception_t& e) 310 { 311 std::cerr << "Error in getting Activation status \n"; 312 } 313 } 314 } 315 })); 316 } 317 318 void CodeUpdate::processPriorityChangeNotification( 319 const DbusChangedProps& chProperties) 320 { 321 static constexpr auto propName = "Priority"; 322 const auto it = chProperties.find(propName); 323 if (it == chProperties.end()) 324 { 325 return; 326 } 327 uint8_t newVal = std::get<uint8_t>(it->second); 328 nextBootSide = (newVal == 0) ? currBootSide 329 : ((currBootSide == Tside) ? Pside : Tside); 330 } 331 332 void CodeUpdate::setOemPlatformHandler( 333 pldm::responder::oem_platform::Handler* handler) 334 { 335 oemPlatformHandler = handler; 336 } 337 338 void CodeUpdate::clearDirPath(const std::string& dirPath) 339 { 340 if (!fs::is_directory(dirPath)) 341 { 342 std::cerr << "The directory does not exist, dirPath = " << dirPath 343 << std::endl; 344 return; 345 } 346 for (const auto& iter : fs::directory_iterator(dirPath)) 347 { 348 fs::remove_all(iter); 349 } 350 } 351 352 void CodeUpdate::sendStateSensorEvent( 353 uint16_t sensorId, enum sensor_event_class_states sensorEventClass, 354 uint8_t sensorOffset, uint8_t eventState, uint8_t prevEventState) 355 { 356 pldm::responder::oem_ibm_platform::Handler* oemIbmPlatformHandler = 357 dynamic_cast<pldm::responder::oem_ibm_platform::Handler*>( 358 oemPlatformHandler); 359 oemIbmPlatformHandler->sendStateSensorEvent( 360 sensorId, sensorEventClass, sensorOffset, eventState, prevEventState); 361 } 362 363 void CodeUpdate::deleteImage() 364 { 365 static constexpr auto UPDATER_SERVICE = 366 "xyz.openbmc_project.Software.BMC.Updater"; 367 static constexpr auto SW_OBJ_PATH = "/xyz/openbmc_project/software"; 368 static constexpr auto DELETE_INTF = 369 "xyz.openbmc_project.Collection.DeleteAll"; 370 371 auto& bus = dBusIntf->getBus(); 372 try 373 { 374 auto method = bus.new_method_call(UPDATER_SERVICE, SW_OBJ_PATH, 375 DELETE_INTF, "DeleteAll"); 376 bus.call_noreply(method); 377 } 378 catch (const std::exception& e) 379 { 380 std::cerr << "Failed to delete image, ERROR=" << e.what() << "\n"; 381 return; 382 } 383 } 384 385 uint8_t fetchBootSide(uint16_t entityInstance, CodeUpdate* codeUpdate) 386 { 387 uint8_t sensorOpState = tSideNum; 388 if (entityInstance == 0) 389 { 390 auto currSide = codeUpdate->fetchCurrentBootSide(); 391 if (currSide == Pside) 392 { 393 sensorOpState = pSideNum; 394 } 395 } 396 else if (entityInstance == 1) 397 { 398 auto nextSide = codeUpdate->fetchNextBootSide(); 399 if (nextSide == Pside) 400 { 401 sensorOpState = pSideNum; 402 } 403 } 404 else 405 { 406 sensorOpState = PLDM_SENSOR_UNKNOWN; 407 } 408 409 return sensorOpState; 410 } 411 412 int setBootSide(uint16_t entityInstance, uint8_t currState, 413 const std::vector<set_effecter_state_field>& stateField, 414 CodeUpdate* codeUpdate) 415 { 416 int rc = PLDM_SUCCESS; 417 auto side = (stateField[currState].effecter_state == pSideNum) ? "P" : "T"; 418 419 if (entityInstance == 0) 420 { 421 rc = codeUpdate->setCurrentBootSide(side); 422 } 423 else if (entityInstance == 1) 424 { 425 rc = codeUpdate->setNextBootSide(side); 426 } 427 else 428 { 429 rc = PLDM_PLATFORM_INVALID_STATE_VALUE; 430 } 431 return rc; 432 } 433 434 template <typename... T> 435 int executeCmd(T const&... t) 436 { 437 std::stringstream cmd; 438 ((cmd << t << " "), ...) << std::endl; 439 FILE* pipe = popen(cmd.str().c_str(), "r"); 440 if (!pipe) 441 { 442 throw std::runtime_error("popen() failed!"); 443 } 444 int rc = pclose(pipe); 445 if (WEXITSTATUS(rc)) 446 { 447 std::cerr << "Error executing: "; 448 ((std::cerr << " " << t), ...); 449 std::cerr << "\n"; 450 return -1; 451 } 452 453 return 0; 454 } 455 456 int processCodeUpdateLid(const std::string& filePath) 457 { 458 struct LidHeader 459 { 460 uint16_t magicNumber; 461 uint16_t headerVersion; 462 uint32_t lidNumber; 463 uint32_t lidDate; 464 uint16_t lidTime; 465 uint16_t lidClass; 466 uint32_t lidCrc; 467 uint32_t lidSize; 468 uint32_t headerSize; 469 }; 470 LidHeader header; 471 472 std::ifstream ifs(filePath, std::ios::in | std::ios::binary); 473 if (!ifs) 474 { 475 std::cerr << "ifstream open error: " << filePath << "\n"; 476 return PLDM_ERROR; 477 } 478 ifs.seekg(0); 479 ifs.read(reinterpret_cast<char*>(&header), sizeof(header)); 480 481 // File size should be the value of lid size minus the header size 482 auto fileSize = fs::file_size(filePath); 483 fileSize -= htonl(header.headerSize); 484 if (fileSize < htonl(header.lidSize)) 485 { 486 // File is not completely written yet 487 ifs.close(); 488 return PLDM_SUCCESS; 489 } 490 491 constexpr auto magicNumber = 0x0222; 492 if (htons(header.magicNumber) != magicNumber) 493 { 494 std::cerr << "Invalid magic number: " << filePath << "\n"; 495 ifs.close(); 496 return PLDM_ERROR; 497 } 498 499 fs::create_directories(imageDirPath); 500 fs::create_directories(lidDirPath); 501 502 constexpr auto bmcClass = 0x2000; 503 if (htons(header.lidClass) == bmcClass) 504 { 505 // Skip the header and concatenate the BMC LIDs into a tar file 506 std::ofstream ofs(tarImagePath, 507 std::ios::out | std::ios::binary | std::ios::app); 508 ifs.seekg(htonl(header.headerSize)); 509 ofs << ifs.rdbuf(); 510 ofs.flush(); 511 ofs.close(); 512 } 513 else 514 { 515 std::stringstream lidFileName; 516 lidFileName << std::hex << htonl(header.lidNumber) << ".lid"; 517 auto lidNoHeaderPath = fs::path(lidDirPath) / lidFileName.str(); 518 std::ofstream ofs(lidNoHeaderPath, 519 std::ios::out | std::ios::binary | std::ios::trunc); 520 ifs.seekg(htonl(header.headerSize)); 521 ofs << ifs.rdbuf(); 522 ofs.flush(); 523 ofs.close(); 524 } 525 526 ifs.close(); 527 fs::remove(filePath); 528 return PLDM_SUCCESS; 529 } 530 531 int CodeUpdate::assembleCodeUpdateImage() 532 { 533 pid_t pid = fork(); 534 535 if (pid == 0) 536 { 537 pid_t nextPid = fork(); 538 if (nextPid == 0) 539 { 540 // Create the hostfw squashfs image from the LID files without 541 // header 542 auto rc = executeCmd("/usr/sbin/mksquashfs", lidDirPath.c_str(), 543 hostfwImagePath.c_str(), "-all-root", 544 "-no-recovery"); 545 if (rc < 0) 546 { 547 std::cerr << "Error occurred during the mksqusquashfs call" 548 << std::endl; 549 setCodeUpdateProgress(false); 550 auto sensorId = getFirmwareUpdateSensor(); 551 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0, 552 uint8_t(CodeUpdateState::FAIL), 553 uint8_t(CodeUpdateState::START)); 554 exit(EXIT_FAILURE); 555 } 556 557 fs::create_directories(updateDirPath); 558 559 // Extract the BMC tarball content 560 rc = executeCmd("/bin/tar", "-xf", tarImagePath.c_str(), "-C", 561 updateDirPath); 562 if (rc < 0) 563 { 564 setCodeUpdateProgress(false); 565 auto sensorId = getFirmwareUpdateSensor(); 566 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0, 567 uint8_t(CodeUpdateState::FAIL), 568 uint8_t(CodeUpdateState::START)); 569 exit(EXIT_FAILURE); 570 } 571 572 // Add the hostfw image to the directory where the contents were 573 // extracted 574 fs::copy_file(hostfwImagePath, 575 fs::path(updateDirPath) / hostfwImageName, 576 fs::copy_options::overwrite_existing); 577 578 // Remove the tarball file, then re-generate it with so that the 579 // hostfw image becomes part of the tarball 580 fs::remove(tarImagePath); 581 rc = executeCmd("/bin/tar", "-cf", tarImagePath, ".", "-C", 582 updateDirPath); 583 if (rc < 0) 584 { 585 std::cerr 586 << "Error occurred during the generation of the tarball" 587 << std::endl; 588 setCodeUpdateProgress(false); 589 auto sensorId = getFirmwareUpdateSensor(); 590 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0, 591 uint8_t(CodeUpdateState::FAIL), 592 uint8_t(CodeUpdateState::START)); 593 exit(EXIT_FAILURE); 594 } 595 596 // Copy the tarball to the update directory to trigger the phosphor 597 // software manager to create a version interface 598 fs::copy_file(tarImagePath, updateImagePath, 599 fs::copy_options::overwrite_existing); 600 601 // Cleanup 602 fs::remove_all(updateDirPath); 603 fs::remove_all(lidDirPath); 604 fs::remove_all(imageDirPath); 605 606 exit(EXIT_SUCCESS); 607 } 608 else if (nextPid < 0) 609 { 610 std::cerr << "Error occurred during fork. ERROR=" << errno 611 << std::endl; 612 exit(EXIT_FAILURE); 613 } 614 615 // Do nothing as parent. When parent exits, child will be reparented 616 // under init and be reaped properly. 617 exit(0); 618 } 619 else if (pid > 0) 620 { 621 int status; 622 if (waitpid(pid, &status, 0) < 0) 623 { 624 std::cerr << "Error occurred during waitpid. ERROR=" << errno 625 << std::endl; 626 return PLDM_ERROR; 627 } 628 else if (WEXITSTATUS(status) != 0) 629 { 630 std::cerr 631 << "Failed to execute the assembling of the image. STATUS=" 632 << status << std::endl; 633 return PLDM_ERROR; 634 } 635 } 636 else 637 { 638 std::cerr << "Error occurred during fork. ERROR=" << errno << std::endl; 639 return PLDM_ERROR; 640 } 641 642 return PLDM_SUCCESS; 643 } 644 645 } // namespace responder 646 } // namespace pldm 647