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