1 #include "config.h" 2 3 #include "activation.hpp" 4 5 #include "utils.hpp" 6 7 #include <cassert> 8 #include <filesystem> 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/log.hpp> 11 12 namespace phosphor 13 { 14 namespace software 15 { 16 namespace updater 17 { 18 19 constexpr auto SYSTEMD_BUSNAME = "org.freedesktop.systemd1"; 20 constexpr auto SYSTEMD_PATH = "/org/freedesktop/systemd1"; 21 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 22 23 namespace fs = std::filesystem; 24 namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server; 25 26 using namespace phosphor::logging; 27 using SoftwareActivation = softwareServer::Activation; 28 29 auto Activation::activation(Activations value) -> Activations 30 { 31 if (value == Status::Activating) 32 { 33 value = startActivation(); 34 } 35 else 36 { 37 activationBlocksTransition.reset(); 38 activationProgress.reset(); 39 } 40 41 return SoftwareActivation::activation(value); 42 } 43 44 auto Activation::requestedActivation(RequestedActivations value) 45 -> RequestedActivations 46 { 47 if ((value == SoftwareActivation::RequestedActivations::Active) && 48 (SoftwareActivation::requestedActivation() != 49 SoftwareActivation::RequestedActivations::Active)) 50 { 51 // PSU image could be activated even when it's in active, 52 // e.g. in case a PSU is replaced and has a older image, it will be 53 // updated with the running PSU image that is stored in BMC. 54 if ((activation() == Status::Ready) || 55 (activation() == Status::Failed) || activation() == Status::Active) 56 { 57 activation(Status::Activating); 58 } 59 } 60 return SoftwareActivation::requestedActivation(value); 61 } 62 63 void Activation::unitStateChange(sdbusplus::message::message& msg) 64 { 65 uint32_t newStateID{}; 66 sdbusplus::message::object_path newStateObjPath; 67 std::string newStateUnit{}; 68 std::string newStateResult{}; 69 70 // Read the msg and populate each variable 71 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 72 73 if (newStateUnit == psuUpdateUnit) 74 { 75 if (newStateResult == "done") 76 { 77 onUpdateDone(); 78 } 79 if (newStateResult == "failed" || newStateResult == "dependency") 80 { 81 onUpdateFailed(); 82 } 83 } 84 } 85 86 bool Activation::doUpdate(const std::string& psuInventoryPath) 87 { 88 currentUpdatingPsu = psuInventoryPath; 89 psuUpdateUnit = getUpdateService(currentUpdatingPsu); 90 try 91 { 92 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 93 SYSTEMD_INTERFACE, "StartUnit"); 94 method.append(psuUpdateUnit, "replace"); 95 bus.call_noreply(method); 96 return true; 97 } 98 catch (const sdbusplus::exception::exception& e) 99 { 100 log<level::ERR>("Error staring service", entry("ERROR=%s", e.what())); 101 onUpdateFailed(); 102 return false; 103 } 104 } 105 106 bool Activation::doUpdate() 107 { 108 // When the queue is empty, all updates are done 109 if (psuQueue.empty()) 110 { 111 finishActivation(); 112 return true; 113 } 114 115 // Do the update on a PSU 116 const auto& psu = psuQueue.front(); 117 return doUpdate(psu); 118 } 119 120 void Activation::onUpdateDone() 121 { 122 auto progress = activationProgress->progress() + progressStep; 123 activationProgress->progress(progress); 124 125 // Update the activation association 126 auto assocs = associations(); 127 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, 128 currentUpdatingPsu); 129 associations(assocs); 130 131 activationListener->onUpdateDone(versionId, currentUpdatingPsu); 132 currentUpdatingPsu.clear(); 133 134 psuQueue.pop(); 135 doUpdate(); // Update the next psu 136 } 137 138 void Activation::onUpdateFailed() 139 { 140 // TODO: report an event 141 log<level::ERR>("Failed to udpate PSU", 142 entry("PSU=%s", psuQueue.front().c_str())); 143 std::queue<std::string>().swap(psuQueue); // Clear the queue 144 activation(Status::Failed); 145 } 146 147 Activation::Status Activation::startActivation() 148 { 149 // Check if the activation has file path 150 if (path().empty()) 151 { 152 log<level::WARNING>("No image for the activation, skipped", 153 entry("VERSION_ID=%s", versionId.c_str())); 154 return activation(); // Return the previous activation status 155 } 156 157 auto psuPaths = utils::getPSUInventoryPath(bus); 158 if (psuPaths.empty()) 159 { 160 log<level::WARNING>("No PSU inventory found"); 161 return Status::Failed; 162 } 163 164 for (const auto& p : psuPaths) 165 { 166 if (isCompatible(p)) 167 { 168 if (utils::isAssociated(p, associations())) 169 { 170 log<level::NOTICE>("PSU already running the image, skipping", 171 entry("PSU=%s", p.c_str())); 172 continue; 173 } 174 psuQueue.push(p); 175 } 176 else 177 { 178 log<level::NOTICE>("PSU not compatible", 179 entry("PSU=%s", p.c_str())); 180 } 181 } 182 183 if (psuQueue.empty()) 184 { 185 log<level::WARNING>("No PSU compatible with the software"); 186 return activation(); // Return the previous activation status 187 } 188 189 if (!activationProgress) 190 { 191 activationProgress = std::make_unique<ActivationProgress>(bus, objPath); 192 } 193 if (!activationBlocksTransition) 194 { 195 activationBlocksTransition = 196 std::make_unique<ActivationBlocksTransition>(bus, objPath); 197 } 198 199 // The progress to be increased for each successful update of PSU 200 // E.g. in case we have 4 PSUs: 201 // 1. Initial progrss is 10 202 // 2. Add 20 after each update is done, so we will see progress to be 30, 203 // 50, 70, 90 204 // 3. When all PSUs are updated, it will be 100 and the interface is 205 // removed. 206 progressStep = 80 / psuQueue.size(); 207 if (doUpdate()) 208 { 209 activationProgress->progress(10); 210 return Status::Activating; 211 } 212 else 213 { 214 return Status::Failed; 215 } 216 } 217 218 void Activation::finishActivation() 219 { 220 storeImage(); 221 activationProgress->progress(100); 222 223 deleteImageManagerObject(); 224 225 associationInterface->createActiveAssociation(objPath); 226 associationInterface->addFunctionalAssociation(objPath); 227 associationInterface->addUpdateableAssociation(objPath); 228 229 // Reset RequestedActivations to none so that it could be activated in 230 // future 231 requestedActivation(SoftwareActivation::RequestedActivations::None); 232 activation(Status::Active); 233 } 234 235 void Activation::deleteImageManagerObject() 236 { 237 // Get the Delete object for <versionID> inside image_manager 238 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version"; 239 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete"; 240 std::string versionService; 241 auto services = utils::getServices(bus, objPath.c_str(), deleteInterface); 242 243 // We need to find the phosphor-version-software-manager's version service 244 // to invoke the delete interface 245 for (const auto& service : services) 246 { 247 if (service.find(versionServiceStr) != std::string::npos) 248 { 249 versionService = service; 250 break; 251 } 252 } 253 if (versionService.empty()) 254 { 255 // When updating a stored image, there is no version object created by 256 // "xyz.openbmc_project.Software.Version" service, so skip it. 257 return; 258 } 259 260 // Call the Delete object for <versionID> inside image_manager 261 auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(), 262 deleteInterface, "Delete"); 263 try 264 { 265 bus.call(method); 266 } 267 catch (const sdbusplus::exception::exception& e) 268 { 269 log<level::ERR>("Error performing call to Delete object path", 270 entry("ERROR=%s", e.what()), 271 entry("PATH=%s", objPath.c_str())); 272 } 273 } 274 275 bool Activation::isCompatible(const std::string& psuInventoryPath) 276 { 277 auto service = 278 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE); 279 auto psuManufacturer = utils::getProperty<std::string>( 280 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, 281 MANUFACTURER); 282 auto psuModel = utils::getProperty<std::string>( 283 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL); 284 if (psuModel != model) 285 { 286 // The model shall match 287 return false; 288 } 289 if (!psuManufacturer.empty()) 290 { 291 // If PSU inventory has manufacturer property, it shall match 292 return psuManufacturer == manufacturer; 293 } 294 return true; 295 } 296 297 void Activation::storeImage() 298 { 299 // Store image in persistent dir separated by model 300 // and only store the latest one by removing old ones 301 auto src = path(); 302 auto dst = fs::path(IMG_DIR_PERSIST) / model; 303 if (src == dst) 304 { 305 // This happens when updating an stored image, no need to store it again 306 return; 307 } 308 try 309 { 310 fs::remove_all(dst); 311 fs::create_directories(dst); 312 fs::copy(src, dst); 313 path(dst.string()); // Update the FilePath interface 314 } 315 catch (const fs::filesystem_error& e) 316 { 317 log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()), 318 entry("SRC=%s", src.c_str()), 319 entry("DST=%s", dst.c_str())); 320 } 321 } 322 323 std::string Activation::getUpdateService(const std::string& psuInventoryPath) 324 { 325 fs::path imagePath(path()); 326 327 // The systemd unit shall be escaped 328 std::string args = psuInventoryPath; 329 args += "\\x20"; 330 args += imagePath; 331 std::replace(args.begin(), args.end(), '/', '-'); 332 333 std::string service = PSU_UPDATE_SERVICE; 334 auto p = service.find('@'); 335 assert(p != std::string::npos); 336 service.insert(p + 1, args); 337 return service; 338 } 339 340 void ActivationBlocksTransition::enableRebootGuard() 341 { 342 log<level::INFO>("PSU image activating - BMC reboots are disabled."); 343 344 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 345 SYSTEMD_INTERFACE, "StartUnit"); 346 method.append("reboot-guard-enable.service", "replace"); 347 bus.call_noreply_noerror(method); 348 } 349 350 void ActivationBlocksTransition::disableRebootGuard() 351 { 352 log<level::INFO>("PSU activation has ended - BMC reboots are re-enabled."); 353 354 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 355 SYSTEMD_INTERFACE, "StartUnit"); 356 method.append("reboot-guard-disable.service", "replace"); 357 bus.call_noreply_noerror(method); 358 } 359 360 } // namespace updater 361 } // namespace software 362 } // namespace phosphor 363