1 #include "config.h"
2 
3 #include "activation.hpp"
4 
5 #include "item_updater.hpp"
6 #include "serialize.hpp"
7 
8 #include <experimental/filesystem>
9 #include <phosphor-logging/log.hpp>
10 #include <sdbusplus/exception.hpp>
11 
12 #ifdef WANT_SIGNATURE_VERIFY
13 #include "image_verify.hpp"
14 
15 #include <phosphor-logging/elog-errors.hpp>
16 #include <phosphor-logging/elog.hpp>
17 #include <sdbusplus/server.hpp>
18 #include <xyz/openbmc_project/Common/error.hpp>
19 #endif
20 
21 namespace openpower
22 {
23 namespace software
24 {
25 namespace updater
26 {
27 
28 namespace fs = std::experimental::filesystem;
29 namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server;
30 
31 using namespace phosphor::logging;
32 using sdbusplus::exception::SdBusError;
33 
34 #ifdef WANT_SIGNATURE_VERIFY
35 using InternalFailure =
36     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
37 
38 // Field mode path and interface.
39 constexpr auto FIELDMODE_PATH("/xyz/openbmc_project/software");
40 constexpr auto FIELDMODE_INTERFACE("xyz.openbmc_project.Control.FieldMode");
41 #endif
42 
43 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
44 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
45 
46 void Activation::subscribeToSystemdSignals()
47 {
48     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
49                                             SYSTEMD_INTERFACE, "Subscribe");
50     try
51     {
52         this->bus.call_noreply(method);
53     }
54     catch (const SdBusError& e)
55     {
56         if (e.name() != nullptr &&
57             strcmp("org.freedesktop.systemd1.AlreadySubscribed", e.name()) == 0)
58         {
59             // If an Activation attempt fails, the Unsubscribe method is not
60             // called. This may lead to an AlreadySubscribed error if the
61             // Activation is re-attempted.
62         }
63         else
64         {
65             log<level::ERR>("Error subscribing to systemd",
66                             entry("ERROR=%s", e.what()));
67         }
68     }
69     return;
70 }
71 
72 void Activation::unsubscribeFromSystemdSignals()
73 {
74     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
75                                             SYSTEMD_INTERFACE, "Unsubscribe");
76     this->bus.call_noreply(method);
77 
78     return;
79 }
80 
81 void Activation::startActivation()
82 {
83     // Since the squashfs image has not yet been loaded to pnor and the
84     // RW volumes have not yet been created, we need to start the
85     // service files for each of those actions.
86 
87     if (!activationProgress)
88     {
89         activationProgress = std::make_unique<ActivationProgress>(bus, path);
90     }
91 
92     if (!activationBlocksTransition)
93     {
94         activationBlocksTransition =
95             std::make_unique<ActivationBlocksTransition>(bus, path);
96     }
97 
98     constexpr auto ubimountService = "obmc-flash-bios-ubimount@";
99     auto ubimountServiceFile =
100         std::string(ubimountService) + versionId + ".service";
101     auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
102                                       SYSTEMD_INTERFACE, "StartUnit");
103     method.append(ubimountServiceFile, "replace");
104     bus.call_noreply(method);
105 
106     activationProgress->progress(10);
107 }
108 
109 void Activation::finishActivation()
110 {
111     activationProgress->progress(90);
112 
113     // Set Redundancy Priority before setting to Active
114     if (!redundancyPriority)
115     {
116         redundancyPriority =
117             std::make_unique<RedundancyPriority>(bus, path, *this, 0);
118     }
119 
120     activationProgress->progress(100);
121 
122     activationBlocksTransition.reset(nullptr);
123     activationProgress.reset(nullptr);
124 
125     ubiVolumesCreated = false;
126     Activation::unsubscribeFromSystemdSignals();
127     // Remove version object from image manager
128     Activation::deleteImageManagerObject();
129     // Create active association
130     parent.createActiveAssociation(path);
131 }
132 
133 auto Activation::activation(Activations value) -> Activations
134 {
135 
136     if (value != softwareServer::Activation::Activations::Active)
137     {
138         redundancyPriority.reset(nullptr);
139     }
140 
141     if (value == softwareServer::Activation::Activations::Activating)
142     {
143         parent.freeSpace();
144         softwareServer::Activation::activation(value);
145 
146         if (ubiVolumesCreated == false)
147         {
148             // Enable systemd signals
149             Activation::subscribeToSystemdSignals();
150 
151 #ifdef WANT_SIGNATURE_VERIFY
152             // Validate the signed image.
153             if (!validateSignature())
154             {
155                 // Cleanup
156                 activationBlocksTransition.reset(nullptr);
157                 activationProgress.reset(nullptr);
158 
159                 return softwareServer::Activation::activation(
160                     softwareServer::Activation::Activations::Failed);
161             }
162 #endif
163             Activation::startActivation();
164             return softwareServer::Activation::activation(value);
165         }
166         else if (ubiVolumesCreated == true)
167         {
168             // Only when the squashfs image is finished loading AND the RW
169             // volumes have been created do we proceed with activation. To
170             // verify that this happened, we check for the mount dirs PNOR_PRSV
171             // and PNOR_RW_PREFIX_<versionid>, as well as the image dir R0.
172 
173             if ((fs::is_directory(PNOR_PRSV)) &&
174                 (fs::is_directory(PNOR_RW_PREFIX + versionId)) &&
175                 (fs::is_directory(PNOR_RO_PREFIX + versionId)))
176             {
177                 Activation::finishActivation();
178                 return softwareServer::Activation::activation(
179                     softwareServer::Activation::Activations::Active);
180             }
181             else
182             {
183                 activationBlocksTransition.reset(nullptr);
184                 activationProgress.reset(nullptr);
185                 return softwareServer::Activation::activation(
186                     softwareServer::Activation::Activations::Failed);
187             }
188         }
189     }
190     else
191     {
192         activationBlocksTransition.reset(nullptr);
193         activationProgress.reset(nullptr);
194     }
195 
196     return softwareServer::Activation::activation(value);
197 }
198 
199 auto Activation::requestedActivation(RequestedActivations value)
200     -> RequestedActivations
201 {
202     ubiVolumesCreated = false;
203 
204     if ((value == softwareServer::Activation::RequestedActivations::Active) &&
205         (softwareServer::Activation::requestedActivation() !=
206          softwareServer::Activation::RequestedActivations::Active))
207     {
208         if ((softwareServer::Activation::activation() ==
209              softwareServer::Activation::Activations::Ready) ||
210             (softwareServer::Activation::activation() ==
211              softwareServer::Activation::Activations::Failed))
212         {
213             Activation::activation(
214                 softwareServer::Activation::Activations::Activating);
215         }
216     }
217     return softwareServer::Activation::requestedActivation(value);
218 }
219 
220 void Activation::deleteImageManagerObject()
221 {
222     // Get the Delete object for <versionID> inside image_manager
223     constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
224     constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
225     std::string versionService;
226     auto method = this->bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
227                                             MAPPER_INTERFACE, "GetObject");
228 
229     method.append(path);
230     method.append(std::vector<std::string>({deleteInterface}));
231     auto mapperResponseMsg = bus.call(method);
232     if (mapperResponseMsg.is_method_error())
233     {
234         log<level::ERR>("Error in Get Delete Object",
235                         entry("VERSIONPATH=%s", path.c_str()));
236         return;
237     }
238     std::map<std::string, std::vector<std::string>> mapperResponse;
239     mapperResponseMsg.read(mapperResponse);
240     if (mapperResponse.begin() == mapperResponse.end())
241     {
242         log<level::ERR>("ERROR in reading the mapper response",
243                         entry("VERSIONPATH=%s", path.c_str()));
244         return;
245     }
246 
247     // We need to find the phosphor-software-manager's version service
248     // to invoke the delete interface
249     for (auto resp : mapperResponse)
250     {
251         if (resp.first.find(versionServiceStr) != std::string::npos)
252         {
253             versionService = resp.first;
254         }
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     method = this->bus.new_method_call(versionService.c_str(), path.c_str(),
265                                        deleteInterface, "Delete");
266     try
267     {
268         auto mapperResponseMsg = bus.call(method);
269 
270         // Check that the bus call didn't result in an error
271         if (mapperResponseMsg.is_method_error())
272         {
273             log<level::ERR>("Error in Deleting image from image manager",
274                             entry("VERSIONPATH=%s", path.c_str()));
275             return;
276         }
277     }
278     catch (const SdBusError& e)
279     {
280         if (e.name() != nullptr && strcmp("System.Error.ELOOP", e.name()) == 0)
281         {
282             // TODO: Error being tracked with openbmc/openbmc#3311
283         }
284         else
285         {
286             log<level::ERR>("Error performing call to Delete object path",
287                             entry("ERROR=%s", e.what()),
288                             entry("PATH=%s", path.c_str()));
289         }
290         return;
291     }
292 }
293 
294 uint8_t RedundancyPriority::priority(uint8_t value)
295 {
296     parent.parent.freePriority(value, parent.versionId);
297     storeToFile(parent.versionId, value);
298     return softwareServer::RedundancyPriority::priority(value);
299 }
300 
301 void Activation::unitStateChange(sdbusplus::message::message& msg)
302 {
303     uint32_t newStateID{};
304     sdbusplus::message::object_path newStateObjPath;
305     std::string newStateUnit{};
306     std::string newStateResult{};
307 
308     // Read the msg and populate each variable
309     msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
310 
311     auto ubimountServiceFile =
312         "obmc-flash-bios-ubimount@" + versionId + ".service";
313 
314     if (newStateUnit == ubimountServiceFile && newStateResult == "done")
315     {
316         ubiVolumesCreated = true;
317         activationProgress->progress(activationProgress->progress() + 50);
318     }
319 
320     if (ubiVolumesCreated)
321     {
322         Activation::activation(
323             softwareServer::Activation::Activations::Activating);
324     }
325 
326     if ((newStateUnit == ubimountServiceFile) &&
327         (newStateResult == "failed" || newStateResult == "dependency"))
328     {
329         Activation::activation(softwareServer::Activation::Activations::Failed);
330     }
331 
332     return;
333 }
334 
335 #ifdef WANT_SIGNATURE_VERIFY
336 inline bool Activation::validateSignature()
337 {
338     using Signature = openpower::software::image::Signature;
339     fs::path imageDir(IMG_DIR);
340 
341     Signature signature(imageDir / versionId, PNOR_SIGNED_IMAGE_CONF_PATH);
342 
343     // Validate the signed image.
344     if (signature.verify())
345     {
346         return true;
347     }
348     // Log error and continue activation process, if field mode disabled.
349     log<level::ERR>("Error occurred during image validation");
350     report<InternalFailure>();
351 
352     try
353     {
354         if (!fieldModeEnabled())
355         {
356             return true;
357         }
358     }
359     catch (const InternalFailure& e)
360     {
361         report<InternalFailure>();
362     }
363     return false;
364 }
365 
366 bool Activation::fieldModeEnabled()
367 {
368     auto fieldModeSvc = getService(bus, FIELDMODE_PATH, FIELDMODE_INTERFACE);
369 
370     auto method = bus.new_method_call(fieldModeSvc.c_str(), FIELDMODE_PATH,
371                                       "org.freedesktop.DBus.Properties", "Get");
372 
373     method.append(FIELDMODE_INTERFACE, "FieldModeEnabled");
374     auto reply = bus.call(method);
375     if (reply.is_method_error())
376     {
377         log<level::ERR>("Error in fieldModeEnabled getValue");
378         elog<InternalFailure>();
379     }
380     sdbusplus::message::variant<bool> fieldMode;
381     reply.read(fieldMode);
382 
383     return sdbusplus::message::variant_ns::get<bool>(fieldMode);
384 }
385 
386 std::string Activation::getService(sdbusplus::bus::bus& bus,
387                                    const std::string& path,
388                                    const std::string& intf)
389 {
390     auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
391                                           MAPPER_INTERFACE, "GetObject");
392 
393     mapperCall.append(path);
394     mapperCall.append(std::vector<std::string>({intf}));
395 
396     auto mapperResponseMsg = bus.call(mapperCall);
397 
398     if (mapperResponseMsg.is_method_error())
399     {
400         log<level::ERR>("ERROR in getting service",
401                         entry("PATH=%s", path.c_str()),
402                         entry("INTERFACE=%s", intf.c_str()));
403 
404         elog<InternalFailure>();
405     }
406 
407     std::map<std::string, std::vector<std::string>> mapperResponse;
408     mapperResponseMsg.read(mapperResponse);
409 
410     if (mapperResponse.begin() == mapperResponse.end())
411     {
412         log<level::ERR>("ERROR reading mapper response",
413                         entry("PATH=%s", path.c_str()),
414                         entry("INTERFACE=%s", intf.c_str()));
415 
416         elog<InternalFailure>();
417     }
418     return mapperResponse.begin()->first;
419 }
420 #endif
421 
422 } // namespace updater
423 } // namespace software
424 } // namespace openpower
425