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 #include <fmt/format.h>
9 
10 #include <nlohmann/json.hpp>
11 #include <phosphor-logging/elog-errors.hpp>
12 
13 #include <cstdio>
14 #include <filesystem>
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 using FILE_Ptr = std::unique_ptr<FILE, decltype(&::fclose)>;
27 namespace fs = std::filesystem;
28 
29 void applyAttrOverride(fs::path& devtreeFile)
30 {
31     constexpr auto DEVTREE_ATTR_OVERRIDE_PATH = "/tmp/devtree_attr_override";
32     auto overrideFile = fs::path(DEVTREE_ATTR_OVERRIDE_PATH);
33     if (!fs::exists(overrideFile))
34     {
35         return;
36     }
37 
38     // Open attribute override file in r/o mode
39     FILE_Ptr fpOverride(fopen(DEVTREE_ATTR_OVERRIDE_PATH, "r"), fclose);
40 
41     // Update Devtree with attribute override data.
42     auto ret = dtree_cronus_import(devtreeFile.c_str(), CEC_INFODB_PATH,
43                                    fpOverride.get());
44     if (ret)
45     {
46         log<level::ERR>(
47             fmt::format("Failed({}) to update attribute override data", ret)
48                 .c_str());
49         throw std::runtime_error(
50             "applyAttrOverride: dtree_cronus_import failed");
51     }
52     log<level::INFO>("DEVTREE: Applied attribute override data");
53 }
54 
55 /**
56  * @brief reinitialize the devtree attributes.
57  * In the regular host boot path devtree attribute need to
58  * initialize the default data and also some of the selected
59  * attributes need to preserve with previous boot value.
60  * Preserve attribute list is available BMC pre-defined location.
61  * This function helps to meet the host ipl requirement
62  * related to attribute persistency management for host ipl.
63  * Steps involved
64  * 1. Create attribute data file from devtree r/w version based on
65  *    the reinit attribute list file bmc /usr/share/pdata path.
66  * 2. Create temporary devtree file by copying devtree r/o file
67  * 3. Override temporary copy of devtree with attribute data file
68  *    from step 1.
69  * 3a. Apply user provided attribute override if present in the
70  *     predefined location.
71  * 4. Copy  temporary copy devtree to r/w devtree version file.
72  */
73 
74 void reinitDevtree()
75 {
76     using json = nlohmann::json;
77 
78     log<level::INFO>("reinitDevtree: started");
79 
80     // All the file operations is done on temporary copy
81     // This is to avoid any file corruption issue during
82     // copy or attribute import path.
83     openpower::util::TemporaryFile tmpDevtreeFile{};
84     const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
85     auto tmpDevtreePath = tmpDevtreeFile.getPath();
86     bool tmpReinitDone = false;
87     // To store callouts details in json format as per pel expectation.
88     json jsonCalloutDataList;
89     jsonCalloutDataList = json::array();
90 
91     try
92     {
93         // Check devtree reinit attributes list file is present
94         auto attrFile = fs::path(DEVTREE_REINIT_ATTRS_LIST);
95         if (!fs::exists(attrFile))
96         {
97             log<level::ERR>(
98                 fmt::format(
99                     "devtree attribute export list file is not available: ({})",
100                     DEVTREE_REINIT_ATTRS_LIST)
101                     .c_str());
102             throw std::runtime_error("reinitDevtree: missing export list file");
103         }
104 
105         // create temporary data file to store the devtree export data
106         openpower::util::TemporaryFile tmpFile{};
107 
108         {
109             // get temporary datafile pointer.
110             FILE_Ptr fpExport(fopen(tmpFile.getPath().c_str(), "w+"), fclose);
111 
112             if (fpExport.get() == nullptr)
113             {
114                 log<level::ERR>(
115                     fmt::format("Temporary data file failed to open: ({})",
116                                 tmpFile.getPath().c_str())
117                         .c_str());
118                 throw std::runtime_error(
119                     "reinitDevtree: failed to open temporaray data file");
120             }
121 
122             // Step 1: export devtree data based on the reinit attribute list.
123             auto ret =
124                 dtree_cronus_export(CEC_DEVTREE_RW_PATH, CEC_INFODB_PATH,
125                                     DEVTREE_REINIT_ATTRS_LIST, fpExport.get());
126             if (ret)
127             {
128                 log<level::ERR>(
129                     fmt::format("Failed({}) to collect attribute export data",
130                                 ret)
131                         .c_str());
132                 throw std::runtime_error(
133                     "reinitDevtree: dtree_cronus_export function failed");
134             }
135         }
136 
137         // Step 2: Create temporary devtree file by copying devtree r/o version
138         std::filesystem::copy(CEC_DEVTREE_RO_PATH, tmpDevtreePath, copyOptions);
139 
140         // get r/o version data file pointer
141         FILE_Ptr fpImport(fopen(tmpFile.getPath().c_str(), "r"), fclose);
142         if (fpImport.get() == nullptr)
143         {
144             log<level::ERR>(
145                 fmt::format("import, temporary data file failed to open: ({})",
146                             tmpFile.getPath().c_str())
147                     .c_str());
148             throw std::runtime_error(
149                 "reinitDevtree: import, failed to open temporaray data file");
150         }
151 
152         // Step 3: Update Devtree r/w version with data file attribute data.
153         auto ret = dtree_cronus_import(tmpDevtreePath.c_str(), CEC_INFODB_PATH,
154                                        fpImport.get());
155         if (ret)
156         {
157             log<level::ERR>(
158                 fmt::format("Failed({}) to update attribute data", ret)
159                     .c_str());
160             throw std::runtime_error(
161                 "reinitDevtree: dtree_cronus_import function failed");
162         }
163         // Step 3.a: Apply user provided attribute override data if present.
164         applyAttrOverride(tmpDevtreePath);
165 
166         // Temporary file reinit is success.
167         tmpReinitDone = true;
168     }
169     catch (const std::exception& e)
170     {
171         // Any failures during temporary file re-init should create PEL
172         // and continue with current version of devtree file to allow boot.
173         log<level::ERR>(
174             fmt::format("reinitDevtree failed ({})", e.what()).c_str());
175         json jsonCalloutDataList;
176         jsonCalloutDataList = json::array();
177         json jsonCalloutData;
178         jsonCalloutData["Procedure"] = "BMC0001";
179         jsonCalloutData["Priority"] = "M";
180         jsonCalloutDataList.emplace_back(jsonCalloutData);
181         openpower::pel::createErrorPEL(
182             "org.open_power.PHAL.Error.devtreeReinit", jsonCalloutDataList);
183     }
184 
185     // Step 4: Update devtree r/w file
186     try
187     {
188         if (tmpReinitDone)
189         {
190             // Step 4: Copy temporary version devtree file r/w version file.
191             // Any copy failures should results service failure.
192             std::filesystem::copy(tmpDevtreePath, CEC_DEVTREE_RW_PATH,
193                                   copyOptions);
194             log<level::INFO>("reinitDevtree: completed successfully");
195         }
196         else
197         {
198             // Attempt boot with genesis mode attribute data.
199             log<level::WARNING>("reinitDevtree: DEVTREE(r/w) initilizing with "
200                                 "genesis mode attribute data");
201             std::filesystem::copy(CEC_DEVTREE_RO_PATH, CEC_DEVTREE_RW_PATH,
202                                   copyOptions);
203         }
204     }
205     catch (const std::exception& e)
206     {
207         // Any failures during update on r/w file is serious error should create
208         // PEL, with code callout. Also failed the service.
209         // and continue with current version of devtree file to allow boot.
210         log<level::ERR>(
211             fmt::format("reinitDevtree r/w version update failed ({})",
212                         e.what())
213                 .c_str());
214         json jsonCalloutDataList;
215         jsonCalloutDataList = json::array();
216         json jsonCalloutData;
217         jsonCalloutData["Procedure"] = "BMC0001";
218         jsonCalloutData["Priority"] = "H";
219         jsonCalloutDataList.emplace_back(jsonCalloutData);
220         openpower::pel::createErrorPEL(
221             "org.open_power.PHAL.Error.devtreeReinit", jsonCalloutDataList);
222         throw;
223     }
224 }
225 
226 REGISTER_PROCEDURE("reinitDevtree", reinitDevtree)
227 
228 } // namespace phal
229 } // namespace openpower
230