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