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         // 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 
64 void Activation::unitStateChange(sdbusplus::message::message& 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 
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 SdBusError& e)
100     {
101         log<level::ERR>("Error staring service", entry("ERROR=%s", e.what()));
102         onUpdateFailed();
103         return false;
104     }
105 }
106 
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 
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     currentUpdatingPsu.clear();
131     associations(assocs);
132 
133     psuQueue.pop();
134     doUpdate(); // Update the next psu
135 }
136 
137 void Activation::onUpdateFailed()
138 {
139     // TODO: report an event
140     log<level::ERR>("Failed to udpate PSU",
141                     entry("PSU=%s", psuQueue.front().c_str()));
142     std::queue<std::string>().swap(psuQueue); // Clear the queue
143     activation(Status::Failed);
144 }
145 
146 Activation::Status Activation::startActivation()
147 {
148     // Check if the activation has file path
149     if (path().empty())
150     {
151         log<level::WARNING>("No image for the activation, skipped",
152                             entry("VERSION_ID=%s", versionId.c_str()));
153         return activation(); // Return the previous activation status
154     }
155     if (!activationProgress)
156     {
157         activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
158     }
159     if (!activationBlocksTransition)
160     {
161         activationBlocksTransition =
162             std::make_unique<ActivationBlocksTransition>(bus, objPath);
163     }
164 
165     auto psuPaths = utils::getPSUInventoryPath(bus);
166     if (psuPaths.empty())
167     {
168         log<level::WARNING>("No PSU inventory found");
169         return Status::Failed;
170     }
171 
172     for (const auto& p : psuPaths)
173     {
174         if (isCompatible(p))
175         {
176             if (utils::isAssociated(p, associations()))
177             {
178                 log<level::NOTICE>("PSU already running the image, skipping",
179                                    entry("PSU=%s", p.c_str()));
180                 continue;
181             }
182             psuQueue.push(p);
183         }
184         else
185         {
186             log<level::NOTICE>("PSU not compatible",
187                                entry("PSU=%s", p.c_str()));
188         }
189     }
190 
191     if (psuQueue.empty())
192     {
193         log<level::WARNING>("No PSU compatible with the software");
194         return activation(); // Return the previous activation status
195     }
196 
197     // The progress to be increased for each successful update of PSU
198     // E.g. in case we have 4 PSUs:
199     //   1. Initial progrss is 10
200     //   2. Add 20 after each update is done, so we will see progress to be 30,
201     //      50, 70, 90
202     //   3. When all PSUs are updated, it will be 100 and the interface is
203     //   removed.
204     progressStep = 80 / psuQueue.size();
205     if (doUpdate())
206     {
207         activationProgress->progress(10);
208         return Status::Activating;
209     }
210     else
211     {
212         return Status::Failed;
213     }
214 }
215 
216 void Activation::finishActivation()
217 {
218     storeImage();
219     activationProgress->progress(100);
220 
221     // TODO: delete the old software object
222     deleteImageManagerObject();
223 
224     associationInterface->createActiveAssociation(objPath);
225     associationInterface->addFunctionalAssociation(objPath);
226 
227     activation(Status::Active);
228 }
229 
230 void Activation::deleteImageManagerObject()
231 {
232     // Get the Delete object for <versionID> inside image_manager
233     constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
234     constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
235     std::string versionService;
236     auto services = utils::getServices(bus, objPath.c_str(), deleteInterface);
237 
238     // We need to find the phosphor-version-software-manager's version service
239     // to invoke the delete interface
240     for (const auto& service : services)
241     {
242         if (service.find(versionServiceStr) != std::string::npos)
243         {
244             versionService = service;
245             break;
246         }
247     }
248     if (versionService.empty())
249     {
250         // When updating a stored image, there is no version object created by
251         // "xyz.openbmc_project.Software.Version" service, so skip it.
252         return;
253     }
254 
255     // Call the Delete object for <versionID> inside image_manager
256     auto method = bus.new_method_call(versionService.c_str(), objPath.c_str(),
257                                       deleteInterface, "Delete");
258     try
259     {
260         bus.call(method);
261     }
262     catch (const SdBusError& e)
263     {
264         log<level::ERR>("Error performing call to Delete object path",
265                         entry("ERROR=%s", e.what()),
266                         entry("PATH=%s", objPath.c_str()));
267     }
268 }
269 
270 bool Activation::isCompatible(const std::string& psuInventoryPath)
271 {
272     auto service =
273         utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
274     auto psuManufacturer = utils::getProperty<std::string>(
275         bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
276         MANUFACTURER);
277     auto psuModel = utils::getProperty<std::string>(
278         bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE, MODEL);
279     if (psuModel != model)
280     {
281         // The model shall match
282         return false;
283     }
284     if (!psuManufacturer.empty())
285     {
286         // If PSU inventory has manufacturer property, it shall match
287         return psuManufacturer == manufacturer;
288     }
289     return true;
290 }
291 
292 void Activation::storeImage()
293 {
294     // Store image in persistent dir separated by model
295     // and only store the latest one by removing old ones
296     auto src = path();
297     auto dst = fs::path(IMG_DIR_PERSIST) / model;
298     if (src == dst)
299     {
300         // This happens when updating an stored image, no need to store it again
301         return;
302     }
303     try
304     {
305         fs::remove_all(dst);
306         fs::create_directories(dst);
307         fs::copy(src, dst);
308         path(dst.string()); // Update the FilePath interface
309     }
310     catch (const fs::filesystem_error& e)
311     {
312         log<level::ERR>("Error storing PSU image", entry("ERROR=%s", e.what()),
313                         entry("SRC=%s", src.c_str()),
314                         entry("DST=%s", dst.c_str()));
315     }
316 }
317 
318 std::string Activation::getUpdateService(const std::string& psuInventoryPath)
319 {
320     fs::path imagePath(path());
321 
322     // The systemd unit shall be escaped
323     std::string args = psuInventoryPath;
324     args += "\\x20";
325     args += imagePath;
326     std::replace(args.begin(), args.end(), '/', '-');
327 
328     std::string service = PSU_UPDATE_SERVICE;
329     auto p = service.find('@');
330     assert(p != std::string::npos);
331     service.insert(p + 1, args);
332     return service;
333 }
334 
335 } // namespace updater
336 } // namespace software
337 } // namespace phosphor
338