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