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