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