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