xref: /openbmc/openpower-pnor-code-mgmt/static/item_updater_static.cpp (revision 716de5b8e3867fb829a2c1e39ade2b8f6aa1fa39)
1 #include "config.h"
2 
3 #include "item_updater_static.hpp"
4 
5 #include "activation_static.hpp"
6 #include "version.hpp"
7 
8 #include <array>
9 #include <cstring>
10 #include <filesystem>
11 #include <fstream>
12 #include <phosphor-logging/elog-errors.hpp>
13 #include <phosphor-logging/log.hpp>
14 #include <sstream>
15 #include <string>
16 #include <tuple>
17 #include <xyz/openbmc_project/Common/error.hpp>
18 
19 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
20 using namespace phosphor::logging;
21 using sdbusplus::exception::SdBusError;
22 
23 // When you see server:: you know we're referencing our base class
24 namespace server = sdbusplus::xyz::openbmc_project::Software::server;
25 namespace fs = std::filesystem;
26 
27 namespace utils
28 {
29 
30 template <typename... Ts>
31 std::string concat_string(Ts const&... ts)
32 {
33     std::stringstream s;
34     ((s << ts << " "), ...) << std::endl;
35     return s.str();
36 }
37 
38 // Helper function to run pflash command
39 // Returns return code and the stdout
40 template <typename... Ts>
41 std::pair<int, std::string> pflash(Ts const&... ts)
42 {
43     std::array<char, 512> buffer;
44     std::string cmd = concat_string("pflash", ts...);
45     std::stringstream result;
46     int rc;
47     FILE* pipe = popen(cmd.c_str(), "r");
48     if (!pipe)
49     {
50         throw std::runtime_error("popen() failed!");
51     }
52     while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
53     {
54         result << buffer.data();
55     }
56     rc = pclose(pipe);
57     return {rc, result.str()};
58 }
59 
60 std::string getPNORVersion()
61 {
62     // A signed version partition will have an extra 4K header starting with
63     // the magic number 17082011 in big endian:
64     // https://github.com/open-power/skiboot/blob/master/libstb/container.h#L47
65 
66     constexpr uint8_t MAGIC[] = {0x17, 0x08, 0x20, 0x11};
67     constexpr auto MAGIC_SIZE = sizeof(MAGIC);
68     static_assert(MAGIC_SIZE == 4);
69 
70     auto tmp = fs::temp_directory_path();
71     std::string tmpDir(tmp / "versionXXXXXX");
72     if (!mkdtemp(tmpDir.data()))
73     {
74         log<level::ERR>("Failed to create temp dir");
75         return {};
76     }
77 
78     fs::path versionFile = tmpDir;
79     versionFile /= "version";
80 
81     auto [rc, r] =
82         pflash("-P VERSION -r", versionFile.string(), "2>&1 > /dev/null");
83     if (rc != 0)
84     {
85         log<level::ERR>("Failed to read VERSION", entry("RETURNCODE=%d", rc));
86         return {};
87     }
88 
89     std::ifstream f(versionFile.c_str(), std::ios::in | std::ios::binary);
90     uint8_t magic[MAGIC_SIZE];
91     std::string version;
92 
93     f.read(reinterpret_cast<char*>(magic), MAGIC_SIZE);
94     f.seekg(0, std::ios::beg);
95     if (std::memcmp(magic, MAGIC, MAGIC_SIZE) == 0)
96     {
97         // Skip the first 4K header
98         f.ignore(4096);
99     }
100 
101     getline(f, version, '\0');
102     f.close();
103 
104     // Clear the temp dir
105     std::error_code ec;
106     fs::remove_all(tmpDir, ec);
107     if (ec)
108     {
109         log<level::ERR>("Failed to remove temp dir",
110                         entry("DIR=%s", tmpDir.c_str()),
111                         entry("ERR=%s", ec.message().c_str()));
112     }
113 
114     return version;
115 }
116 
117 void pnorClear(const std::string& part, bool shouldEcc = true)
118 {
119     int rc;
120     std::tie(rc, std::ignore) =
121         utils::pflash("-P", part, shouldEcc ? "-c" : "-e", "-f >/dev/null");
122     if (rc != 0)
123     {
124         log<level::ERR>("Failed to clear partition",
125                         entry("PART=%s", part.c_str()),
126                         entry("RETURNCODE=%d", rc));
127     }
128     else
129     {
130         log<level::INFO>("Clear partition successfully",
131                          entry("PART=%s", part.c_str()));
132     }
133 }
134 
135 // The pair contains the partition name and if it should use ECC clear
136 using PartClear = std::pair<std::string, bool>;
137 
138 std::vector<PartClear> getPartsToClear(const std::string& info)
139 {
140     std::vector<PartClear> ret;
141     std::istringstream iss(info);
142     std::string line;
143 
144     while (std::getline(iss, line))
145     {
146         // Each line looks like
147         // ID=06 MVPD 0x0012d000..0x001bd000 (actual=0x00090000) [E--P--F-C-]
148         // Flag 'F' means REPROVISION
149         // Flag 'C' means CLEARECC
150         auto flags = line.substr(line.find('['));
151         if (flags.find('F') != std::string::npos)
152         {
153             // This is a partition to be cleared
154             line = line.substr(line.find_first_of(' '));     // Skiping "ID=xx"
155             line = line.substr(line.find_first_not_of(' ')); // Skipping spaces
156             line = line.substr(0, line.find_first_of(' '));  // The part name
157             bool ecc = flags.find('C') != std::string::npos;
158             ret.emplace_back(line, ecc);
159         }
160     }
161     return ret;
162 }
163 
164 // Get partitions that should be cleared
165 std::vector<PartClear> getPartsToClear()
166 {
167     const auto& [rc, pflashInfo] = pflash("-i | grep ^ID | grep 'F'");
168     return getPartsToClear(pflashInfo);
169 }
170 
171 } // namespace utils
172 
173 namespace openpower
174 {
175 namespace software
176 {
177 namespace updater
178 {
179 // TODO: Change paths once openbmc/openbmc#1663 is completed.
180 constexpr auto MBOXD_INTERFACE = "org.openbmc.mboxd";
181 constexpr auto MBOXD_PATH = "/org/openbmc/mboxd";
182 
183 std::unique_ptr<Activation> ItemUpdaterStatic::createActivationObject(
184     const std::string& path, const std::string& versionId,
185     const std::string& extVersion,
186     sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations
187         activationStatus,
188     AssociationList& assocs)
189 {
190     return std::make_unique<ActivationStatic>(
191         bus, path, *this, versionId, extVersion, activationStatus, assocs);
192 }
193 
194 std::unique_ptr<Version> ItemUpdaterStatic::createVersionObject(
195     const std::string& objPath, const std::string& versionId,
196     const std::string& versionString,
197     sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
198         versionPurpose,
199     const std::string& filePath)
200 {
201     auto version = std::make_unique<Version>(
202         bus, objPath, *this, versionId, versionString, versionPurpose, filePath,
203         std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1));
204     version->deleteObject = std::make_unique<Delete>(bus, objPath, *version);
205     return version;
206 }
207 
208 bool ItemUpdaterStatic::validateImage(const std::string& path)
209 {
210     // There is no need to validate static layout pnor
211     return true;
212 }
213 
214 void ItemUpdaterStatic::processPNORImage()
215 {
216     auto fullVersion = utils::getPNORVersion();
217 
218     const auto& [version, extendedVersion] = Version::getVersions(fullVersion);
219     auto id = Version::getId(version);
220 
221     if (id.empty())
222     {
223         // Possibly a corrupted PNOR
224         return;
225     }
226 
227     auto activationState = server::Activation::Activations::Active;
228     if (version.empty())
229     {
230         log<level::ERR>("Failed to read version",
231                         entry("VERSION=%s", fullVersion.c_str()));
232         activationState = server::Activation::Activations::Invalid;
233     }
234 
235     if (extendedVersion.empty())
236     {
237         log<level::ERR>("Failed to read extendedVersion",
238                         entry("VERSION=%s", fullVersion.c_str()));
239         activationState = server::Activation::Activations::Invalid;
240     }
241 
242     auto purpose = server::Version::VersionPurpose::Host;
243     auto path = fs::path(SOFTWARE_OBJPATH) / id;
244     AssociationList associations = {};
245 
246     if (activationState == server::Activation::Activations::Active)
247     {
248         // Create an association to the host inventory item
249         associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
250                                                   ACTIVATION_REV_ASSOCIATION,
251                                                   HOST_INVENTORY_PATH));
252 
253         // Create an active association since this image is active
254         createActiveAssociation(path);
255     }
256 
257     // Create Activation instance for this version.
258     activations.insert(std::make_pair(
259         id, std::make_unique<ActivationStatic>(bus, path, *this, id,
260                                                extendedVersion, activationState,
261                                                associations)));
262 
263     // If Active, create RedundancyPriority instance for this version.
264     if (activationState == server::Activation::Activations::Active)
265     {
266         // For now only one PNOR is supported with static layout
267         activations.find(id)->second->redundancyPriority =
268             std::make_unique<RedundancyPriority>(
269                 bus, path, *(activations.find(id)->second), 0);
270     }
271 
272     // Create Version instance for this version.
273     auto versionPtr = std::make_unique<Version>(
274         bus, path, *this, id, version, purpose, "",
275         std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1));
276     versionPtr->deleteObject = std::make_unique<Delete>(bus, path, *versionPtr);
277     versions.insert(std::make_pair(id, std::move(versionPtr)));
278 
279     if (!id.empty())
280     {
281         updateFunctionalAssociation(id);
282     }
283 }
284 
285 void ItemUpdaterStatic::reset()
286 {
287     auto partitions = utils::getPartsToClear();
288     std::vector<uint8_t> mboxdArgs;
289 
290     // Suspend mboxd - no args required.
291     auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
292                                         MBOXD_INTERFACE, "cmd");
293     dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
294 
295     try
296     {
297         bus.call_noreply(dbusCall);
298     }
299     catch (const SdBusError& e)
300     {
301         log<level::ERR>("Error in mboxd suspend call",
302                         entry("ERROR=%s", e.what()));
303         elog<InternalFailure>();
304     }
305     for (auto p : partitions)
306     {
307         utils::pnorClear(p.first, p.second);
308     }
309 
310     // Resume mboxd with arg 1, indicating that the flash was modified.
311     dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
312                                    "cmd");
313     mboxdArgs.push_back(1);
314     dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
315 
316     try
317     {
318         bus.call_noreply(dbusCall);
319     }
320     catch (const SdBusError& e)
321     {
322         log<level::ERR>("Error in mboxd resume call",
323                         entry("ERROR=%s", e.what()));
324         elog<InternalFailure>();
325     }
326 }
327 
328 bool ItemUpdaterStatic::isVersionFunctional(const std::string& versionId)
329 {
330     return versionId == functionalVersionId;
331 }
332 
333 void ItemUpdaterStatic::freePriority(uint8_t value,
334                                      const std::string& versionId)
335 {
336 }
337 
338 void ItemUpdaterStatic::deleteAll()
339 {
340     // Static layout has only one active and function pnor
341     // There is no implementation for this interface
342 }
343 
344 bool ItemUpdaterStatic::freeSpace()
345 {
346     // For now assume static layout only has 1 active PNOR,
347     // so erase the active PNOR
348     for (const auto& iter : activations)
349     {
350         if (iter.second.get()->activation() ==
351             server::Activation::Activations::Active)
352         {
353             return erase(iter.second->versionId);
354         }
355     }
356     // No active PNOR means PNOR is empty or corrupted
357     return true;
358 }
359 
360 void ItemUpdaterStatic::updateFunctionalAssociation(
361     const std::string& versionId)
362 {
363     functionalVersionId = versionId;
364     ItemUpdater::updateFunctionalAssociation(versionId);
365 }
366 
367 void GardResetStatic::reset()
368 {
369     // Clear gard partition
370     std::vector<uint8_t> mboxdArgs;
371 
372     auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH,
373                                         MBOXD_INTERFACE, "cmd");
374     // Suspend mboxd - no args required.
375     dbusCall.append(static_cast<uint8_t>(3), mboxdArgs);
376 
377     try
378     {
379         bus.call_noreply(dbusCall);
380     }
381     catch (const SdBusError& e)
382     {
383         log<level::ERR>("Error in mboxd suspend call",
384                         entry("ERROR=%s", e.what()));
385         elog<InternalFailure>();
386     }
387 
388     // Clear guard partition
389     utils::pnorClear("GUARD");
390 
391     dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE,
392                                    "cmd");
393     // Resume mboxd with arg 1, indicating that the flash is modified.
394     mboxdArgs.push_back(1);
395     dbusCall.append(static_cast<uint8_t>(4), mboxdArgs);
396 
397     try
398     {
399         bus.call_noreply(dbusCall);
400     }
401     catch (const SdBusError& e)
402     {
403         log<level::ERR>("Error in mboxd resume call",
404                         entry("ERROR=%s", e.what()));
405         elog<InternalFailure>();
406     }
407 }
408 
409 } // namespace updater
410 } // namespace software
411 } // namespace openpower
412