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