1 #include "config.h"
2 
3 #include "extensions/phal/create_pel.hpp"
4 #include "registration.hpp"
5 #include "temporary_file.hpp"
6 
7 #include <fcntl.h>
8 
9 #include <nlohmann/json.hpp>
10 #include <phosphor-logging/elog-errors.hpp>
11 
12 #include <cstdio>
13 #include <filesystem>
14 #include <format>
15 
16 extern "C"
17 {
18 #include <dtree.h>
19 }
20 
21 namespace openpower
22 {
23 namespace phal
24 {
25 using namespace phosphor::logging;
26 
27 struct FileCloser
28 {
29     void operator()(FILE* fp) const
30     {
31         fclose(fp);
32     }
33 };
34 using FILE_Ptr = std::unique_ptr<FILE, FileCloser>;
35 
36 namespace fs = std::filesystem;
37 
38 void applyAttrOverride(fs::path& devtreeFile)
39 {
40     constexpr auto DEVTREE_ATTR_OVERRIDE_PATH = "/tmp/devtree_attr_override";
41     auto overrideFile = fs::path(DEVTREE_ATTR_OVERRIDE_PATH);
42     if (!fs::exists(overrideFile))
43     {
44         return;
45     }
46 
47     // Open attribute override file in r/o mode
48     FILE_Ptr fpOverride(fopen(DEVTREE_ATTR_OVERRIDE_PATH, "r"), FileCloser());
49 
50     // Update Devtree with attribute override data.
51     auto ret = dtree_cronus_import(devtreeFile.c_str(), CEC_INFODB_PATH,
52                                    fpOverride.get());
53     if (ret)
54     {
55         log<level::ERR>(
56             std::format("Failed({}) to update attribute override data", ret)
57                 .c_str());
58         throw std::runtime_error(
59             "applyAttrOverride: dtree_cronus_import failed");
60     }
61     log<level::INFO>("DEVTREE: Applied attribute override data");
62 }
63 
64 /**
65  * @brief Compute RO device tree file path from RW symbolic link
66  * @return RO file path one failure exception will be thrown
67  */
68 fs::path computeRODeviceTreePath()
69 {
70     // Symbolic links are not created for RO files, compute the lid name
71     // for the RW symbolic link and use it to compute RO file.
72     // Example:
73     // RW file = /media/hostfw/running/DEVTREE -> 81e00672.lid
74     // RO file = /media/hostfw/running-ro/ + 81e00672.lid
75     fs::path rwFileName = fs::read_symlink(CEC_DEVTREE_RW_PATH);
76     if (rwFileName.empty())
77     {
78         std::string err =
79             std::format("Failed to read the target file "
80                         "for the RW device tree symbolic link ({})",
81                         CEC_DEVTREE_RW_PATH);
82         log<level::ERR>(err.c_str());
83         throw std::runtime_error(err);
84     }
85     fs::path roFilePath = CEC_DEVTREE_RO_BASE_PATH / rwFileName;
86     if (!fs::exists(roFilePath))
87     {
88         auto err = std::format("RO device tree file ({}) does not "
89                                "exit ",
90                                roFilePath.string());
91         log<level::ERR>(err.c_str());
92         throw std::runtime_error(err);
93     }
94     return roFilePath;
95 }
96 
97 /**
98  * @brief reinitialize the devtree attributes.
99  * In the regular host boot path devtree attribute need to
100  * initialize the default data and also some of the selected
101  * attributes need to preserve with previous boot value.
102  * Preserve attribute list is available BMC pre-defined location.
103  * This function helps to meet the host ipl requirement
104  * related to attribute persistency management for host ipl.
105  * Steps involved
106  * 1. Create attribute data file from devtree r/w version based on
107  *    the reinit attribute list file bmc /usr/share/pdata path.
108  * 2. Create temporary devtree file by copying devtree r/o file
109  * 3. Override temporary copy of devtree with attribute data file
110  *    from step 1.
111  * 3a. Apply user provided attribute override if present in the
112  *     predefined location.
113  * 4. Copy  temporary copy devtree to r/w devtree version file.
114  */
115 
116 void reinitDevtree()
117 {
118     using json = nlohmann::json;
119     using Severity =
120         sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level;
121 
122     log<level::INFO>("reinitDevtree: started");
123 
124     // All the file operations is done on temporary copy
125     // This is to avoid any file corruption issue during
126     // copy or attribute import path.
127     openpower::util::TemporaryFile tmpDevtreeFile{};
128     const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
129     auto tmpDevtreePath = tmpDevtreeFile.getPath();
130     bool tmpReinitDone = false;
131     // To store callouts details in json format as per pel expectation.
132     json jsonCalloutDataList;
133     jsonCalloutDataList = json::array();
134 
135     try
136     {
137         // Check devtree reinit attributes list file is present
138         auto attrFile = fs::path(DEVTREE_REINIT_ATTRS_LIST);
139         if (!fs::exists(attrFile))
140         {
141             log<level::ERR>(
142                 std::format(
143                     "devtree attribute export list file is not available: ({})",
144                     DEVTREE_REINIT_ATTRS_LIST)
145                     .c_str());
146             throw std::runtime_error("reinitDevtree: missing export list file");
147         }
148 
149         // create temporary data file to store the devtree export data
150         openpower::util::TemporaryFile tmpFile{};
151 
152         {
153             // get temporary datafile pointer.
154             FILE_Ptr fpExport(fopen(tmpFile.getPath().c_str(), "w+"),
155                               FileCloser());
156 
157             if (fpExport.get() == nullptr)
158             {
159                 log<level::ERR>(
160                     std::format("Temporary data file failed to open: ({})",
161                                 tmpFile.getPath().c_str())
162                         .c_str());
163                 throw std::runtime_error(
164                     "reinitDevtree: failed to open temporaray data file");
165             }
166 
167             // Step 1: export devtree data based on the reinit attribute list.
168             auto ret = dtree_cronus_export(CEC_DEVTREE_RW_PATH, CEC_INFODB_PATH,
169                                            DEVTREE_REINIT_ATTRS_LIST,
170                                            fpExport.get());
171             if (ret)
172             {
173                 log<level::ERR>(
174                     std::format("Failed({}) to collect attribute export data",
175                                 ret)
176                         .c_str());
177                 throw std::runtime_error(
178                     "reinitDevtree: dtree_cronus_export function failed");
179             }
180         }
181 
182         // Step 2: Create temporary devtree file by copying devtree r/o version
183         fs::path roFilePath = computeRODeviceTreePath();
184         std::filesystem::copy(roFilePath, tmpDevtreePath, copyOptions);
185 
186         // get r/o version data file pointer
187         FILE_Ptr fpImport(fopen(tmpFile.getPath().c_str(), "r"), FileCloser());
188         if (fpImport.get() == nullptr)
189         {
190             log<level::ERR>(
191                 std::format("import, temporary data file failed to open: ({})",
192                             tmpFile.getPath().c_str())
193                     .c_str());
194             throw std::runtime_error(
195                 "reinitDevtree: import, failed to open temporaray data file");
196         }
197 
198         // Step 3: Update Devtree r/w version with data file attribute data.
199         auto ret = dtree_cronus_import(tmpDevtreePath.c_str(), CEC_INFODB_PATH,
200                                        fpImport.get());
201         if (ret)
202         {
203             log<level::ERR>(
204                 std::format("Failed({}) to update attribute data", ret)
205                     .c_str());
206             throw std::runtime_error(
207                 "reinitDevtree: dtree_cronus_import function failed");
208         }
209         // Step 3.a: Apply user provided attribute override data if present.
210         applyAttrOverride(tmpDevtreePath);
211 
212         // Temporary file reinit is success.
213         tmpReinitDone = true;
214     }
215     catch (const std::exception& e)
216     {
217         // Any failures during temporary file re-init should create PEL
218         // and continue with current version of devtree file to allow boot.
219         log<level::ERR>(
220             std::format("reinitDevtree failed ({})", e.what()).c_str());
221         json jsonCalloutDataList;
222         jsonCalloutDataList = json::array();
223         json jsonCalloutData;
224         jsonCalloutData["Procedure"] = "BMC0001";
225         jsonCalloutData["Priority"] = "M";
226         jsonCalloutDataList.emplace_back(jsonCalloutData);
227         openpower::pel::createErrorPEL(
228             "org.open_power.PHAL.Error.devtreeReinit", jsonCalloutDataList, {},
229             Severity::Error);
230     }
231 
232     // Step 4: Update devtree r/w file
233     try
234     {
235         if (tmpReinitDone)
236         {
237             // Step 4: Copy temporary version devtree file r/w version file.
238             // Any copy failures should results service failure.
239             std::filesystem::copy(tmpDevtreePath, CEC_DEVTREE_RW_PATH,
240                                   copyOptions);
241             log<level::INFO>("reinitDevtree: completed successfully");
242         }
243         else
244         {
245             // Attempt boot with genesis mode attribute data.
246             fs::path roFilePath = computeRODeviceTreePath();
247             log<level::WARNING>("reinitDevtree: DEVTREE(r/w) initilizing with "
248                                 "genesis mode attribute data");
249             std::filesystem::copy(roFilePath, CEC_DEVTREE_RW_PATH, copyOptions);
250         }
251     }
252     catch (const std::exception& e)
253     {
254         // Any failures during update on r/w file is serious error should create
255         // PEL, with code callout. Also failed the service.
256         // and continue with current version of devtree file to allow boot.
257         log<level::ERR>(
258             std::format("reinitDevtree r/w version update failed ({})",
259                         e.what())
260                 .c_str());
261         json jsonCalloutDataList;
262         jsonCalloutDataList = json::array();
263         json jsonCalloutData;
264         jsonCalloutData["Procedure"] = "BMC0001";
265         jsonCalloutData["Priority"] = "H";
266         jsonCalloutDataList.emplace_back(jsonCalloutData);
267         openpower::pel::createErrorPEL(
268             "org.open_power.PHAL.Error.devtreeReinit", jsonCalloutDataList, {},
269             Severity::Error);
270         throw;
271     }
272 }
273 
274 REGISTER_PROCEDURE("reinitDevtree", reinitDevtree)
275 
276 } // namespace phal
277 } // namespace openpower
278