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