1 /**
2  * Copyright © 2019 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "registry.hpp"
17 
18 #include "pel_types.hpp"
19 #include "pel_values.hpp"
20 
21 #include <fstream>
22 #include <phosphor-logging/log.hpp>
23 
24 namespace openpower
25 {
26 namespace pels
27 {
28 namespace message
29 {
30 
31 namespace pv = pel_values;
32 namespace fs = std::filesystem;
33 using namespace phosphor::logging;
34 
35 constexpr auto debugFilePath = "/etc/phosphor-logging/";
36 
37 namespace helper
38 {
39 
40 uint8_t getSubsystem(const std::string& subsystemName)
41 {
42     // Get the actual value to use in the PEL for the string name
43     auto ss = pv::findByName(subsystemName, pv::subsystemValues);
44     if (ss == pv::subsystemValues.end())
45     {
46         // Schema validation should be catching this.
47         log<level::ERR>("Invalid subsystem name used in message registry",
48                         entry("SUBSYSTEM=%s", subsystemName.c_str()));
49 
50         throw std::runtime_error("Invalid subsystem used in message registry");
51     }
52 
53     return std::get<pv::fieldValuePos>(*ss);
54 }
55 
56 uint8_t getSeverity(const std::string& severityName)
57 {
58     auto s = pv::findByName(severityName, pv::severityValues);
59     if (s == pv::severityValues.end())
60     {
61         // Schema validation should be catching this.
62         log<level::ERR>("Invalid severity name used in message registry",
63                         entry("SEVERITY=%s", severityName.c_str()));
64 
65         throw std::runtime_error("Invalid severity used in message registry");
66     }
67 
68     return std::get<pv::fieldValuePos>(*s);
69 }
70 
71 uint16_t getActionFlags(const std::vector<std::string>& flags)
72 {
73     uint16_t actionFlags = 0;
74 
75     // Make the bitmask based on the array of flag names
76     for (const auto& flag : flags)
77     {
78         auto s = pv::findByName(flag, pv::actionFlagsValues);
79         if (s == pv::actionFlagsValues.end())
80         {
81             // Schema validation should be catching this.
82             log<level::ERR>("Invalid action flag name used in message registry",
83                             entry("FLAG=%s", flag.c_str()));
84 
85             throw std::runtime_error(
86                 "Invalid action flag used in message registry");
87         }
88 
89         actionFlags |= std::get<pv::fieldValuePos>(*s);
90     }
91 
92     return actionFlags;
93 }
94 
95 uint8_t getEventType(const std::string& eventTypeName)
96 {
97     auto t = pv::findByName(eventTypeName, pv::eventTypeValues);
98     if (t == pv::eventTypeValues.end())
99     {
100         log<level::ERR>("Invalid event type used in message registry",
101                         entry("EVENT_TYPE=%s", eventTypeName.c_str()));
102 
103         throw std::runtime_error("Invalid event type used in message registry");
104     }
105     return std::get<pv::fieldValuePos>(*t);
106 }
107 
108 uint8_t getEventScope(const std::string& eventScopeName)
109 {
110     auto s = pv::findByName(eventScopeName, pv::eventScopeValues);
111     if (s == pv::eventScopeValues.end())
112     {
113         log<level::ERR>("Invalid event scope used in registry",
114                         entry("EVENT_SCOPE=%s", eventScopeName.c_str()));
115 
116         throw std::runtime_error(
117             "Invalid event scope used in message registry");
118     }
119     return std::get<pv::fieldValuePos>(*s);
120 }
121 
122 uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name)
123 {
124     std::string rc = src["ReasonCode"];
125     uint16_t reasonCode = strtoul(rc.c_str(), nullptr, 16);
126     if (reasonCode == 0)
127     {
128         log<phosphor::logging::level::ERR>(
129             "Invalid reason code in message registry",
130             entry("ERROR_NAME=%s", name.c_str()),
131             entry("REASON_CODE=%s", rc.c_str()));
132 
133         throw std::runtime_error("Invalid reason code in message registry");
134     }
135     return reasonCode;
136 }
137 
138 uint8_t getSRCType(const nlohmann::json& src, const std::string& name)
139 {
140     // Looks like: "22"
141     std::string srcType = src["Type"];
142     size_t type = strtoul(srcType.c_str(), nullptr, 16);
143     if ((type == 0) || (srcType.size() != 2)) // 1 hex byte
144     {
145         log<phosphor::logging::level::ERR>(
146             "Invalid SRC Type in message registry",
147             entry("ERROR_NAME=%s", name.c_str()),
148             entry("SRC_TYPE=%s", srcType.c_str()));
149 
150         throw std::runtime_error("Invalid SRC Type in message registry");
151     }
152 
153     return type;
154 }
155 
156 std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
157     getSRCHexwordFields(const nlohmann::json& src, const std::string& name)
158 {
159     std::map<SRC::WordNum, SRC::AdditionalDataField> hexwordFields;
160 
161     // Build the map of which AdditionalData fields to use for which SRC words
162 
163     // Like:
164     // {
165     //   "8":
166     //   {
167     //     "AdditionalDataPropSource": "TEST"
168     //   }
169     //
170     // }
171 
172     for (const auto& word : src["Words6To9"].items())
173     {
174         std::string num = word.key();
175         size_t wordNum = std::strtoul(num.c_str(), nullptr, 10);
176 
177         if (wordNum == 0)
178         {
179             log<phosphor::logging::level::ERR>(
180                 "Invalid SRC word number in message registry",
181                 entry("ERROR_NAME=%s", name.c_str()),
182                 entry("SRC_WORD_NUM=%s", num.c_str()));
183 
184             throw std::runtime_error("Invalid SRC word in message registry");
185         }
186 
187         auto attributes = word.value();
188         std::string adPropName = attributes["AdditionalDataPropSource"];
189         hexwordFields[wordNum] = std::move(adPropName);
190     }
191 
192     if (!hexwordFields.empty())
193     {
194         return hexwordFields;
195     }
196 
197     return std::nullopt;
198 }
199 std::optional<std::vector<SRC::WordNum>>
200     getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name)
201 {
202     std::vector<SRC::WordNum> symptomIDFields;
203 
204     // Looks like:
205     // "SymptomIDFields": ["SRCWord3", "SRCWord6"],
206 
207     for (const std::string& field : src["SymptomIDFields"])
208     {
209         // Just need the last digit off the end, e.g. SRCWord6.
210         // The schema enforces the format of these.
211         auto srcWordNum = field.substr(field.size() - 1);
212         size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10);
213         if (num == 0)
214         {
215             log<phosphor::logging::level::ERR>(
216                 "Invalid symptom ID field in message registry",
217                 entry("ERROR_NAME=%s", name.c_str()),
218                 entry("FIELD_NAME=%s", srcWordNum.c_str()));
219 
220             throw std::runtime_error("Invalid symptom ID in message registry");
221         }
222         symptomIDFields.push_back(num);
223     }
224     if (!symptomIDFields.empty())
225     {
226         return symptomIDFields;
227     }
228 
229     return std::nullopt;
230 }
231 
232 uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
233                         const nlohmann::json& pelEntry, const std::string& name)
234 {
235     uint16_t id = 0;
236 
237     // If the ComponentID field is there, use that.  Otherwise, if it's a
238     // 0xBD BMC error SRC, use the reasoncode.
239     if (pelEntry.find("ComponentID") != pelEntry.end())
240     {
241         std::string componentID = pelEntry["ComponentID"];
242         id = strtoul(componentID.c_str(), nullptr, 16);
243     }
244     else
245     {
246         // On BMC error SRCs (BD), can just get the component ID from
247         // the first byte of the reason code.
248         if (srcType == static_cast<uint8_t>(SRCType::bmcError))
249         {
250             id = reasonCode & 0xFF00;
251         }
252         else
253         {
254             log<level::ERR>("Missing component ID field in message registry",
255                             entry("ERROR_NAME=%s", name.c_str()));
256 
257             throw std::runtime_error(
258                 "Missing component ID field in message registry");
259         }
260     }
261 
262     return id;
263 }
264 
265 } // namespace helper
266 
267 std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
268                                       bool toCache)
269 {
270     std::optional<nlohmann::json> registryTmp;
271     auto& registryOpt = (_registry) ? _registry : registryTmp;
272     if (!registryOpt)
273     {
274         registryOpt = readRegistry(_registryFile);
275         if (!registryOpt)
276         {
277             return std::nullopt;
278         }
279         else if (toCache)
280         {
281             // Save message registry in memory for peltool
282             _registry = std::move(registryTmp);
283         }
284     }
285     auto& reg = (_registry) ? _registry : registryTmp;
286     const auto& registry = reg.value();
287     // Find an entry with this name in the PEL array.
288     auto e = std::find_if(
289         registry["PELs"].begin(), registry["PELs"].end(),
290         [&name, &type](const auto& j) {
291             return ((name == j["Name"] && type == LookupType::name) ||
292                     (name == j["SRC"]["ReasonCode"] &&
293                      type == LookupType::reasonCode));
294         });
295 
296     if (e != registry["PELs"].end())
297     {
298         // Fill in the Entry structure from the JSON.  Most, but not all, fields
299         // are optional.
300 
301         try
302         {
303             Entry entry;
304             entry.name = (*e)["Name"];
305             entry.subsystem = helper::getSubsystem((*e)["Subsystem"]);
306 
307             if (e->find("ActionFlags") != e->end())
308             {
309                 entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]);
310             }
311 
312             if (e->find("MfgActionFlags") != e->end())
313             {
314                 entry.mfgActionFlags =
315                     helper::getActionFlags((*e)["MfgActionFlags"]);
316             }
317 
318             if (e->find("Severity") != e->end())
319             {
320                 entry.severity = helper::getSeverity((*e)["Severity"]);
321             }
322 
323             if (e->find("MfgSeverity") != e->end())
324             {
325                 entry.mfgSeverity = helper::getSeverity((*e)["MfgSeverity"]);
326             }
327 
328             if (e->find("EventType") != e->end())
329             {
330                 entry.eventType = helper::getEventType((*e)["EventType"]);
331             }
332 
333             if (e->find("EventScope") != e->end())
334             {
335                 entry.eventScope = helper::getEventScope((*e)["EventScope"]);
336             }
337 
338             auto& src = (*e)["SRC"];
339             entry.src.reasonCode = helper::getSRCReasonCode(src, name);
340 
341             if (src.find("Type") != src.end())
342             {
343                 entry.src.type = helper::getSRCType(src, name);
344             }
345             else
346             {
347                 entry.src.type = static_cast<uint8_t>(SRCType::bmcError);
348             }
349 
350             // Now that we know the SRC type and reason code,
351             // we can get the component ID.
352             entry.componentID = helper::getComponentID(
353                 entry.src.type, entry.src.reasonCode, *e, name);
354 
355             if (src.find("Words6To9") != src.end())
356             {
357                 entry.src.hexwordADFields =
358                     helper::getSRCHexwordFields(src, name);
359             }
360 
361             if (src.find("SymptomIDFields") != src.end())
362             {
363                 entry.src.symptomID = helper::getSRCSymptomIDFields(src, name);
364             }
365 
366             if (src.find("PowerFault") != src.end())
367             {
368                 entry.src.powerFault = src["PowerFault"];
369             }
370 
371             auto& doc = (*e)["Documentation"];
372             entry.doc.message = doc["Message"];
373             entry.doc.description = doc["Description"];
374             if (doc.find("MessageArgSources") != doc.end())
375             {
376                 entry.doc.messageArgSources = doc["MessageArgSources"];
377             }
378 
379             return entry;
380         }
381         catch (std::exception& e)
382         {
383             log<level::ERR>("Found invalid message registry field",
384                             entry("ERROR=%s", e.what()));
385         }
386     }
387 
388     return std::nullopt;
389 }
390 
391 std::optional<nlohmann::json>
392     Registry::readRegistry(const std::filesystem::path& registryFile)
393 {
394     // Look in /etc first in case someone put a test file there
395     fs::path debugFile{fs::path{debugFilePath} / registryFileName};
396     nlohmann::json registry;
397     std::ifstream file;
398 
399     if (fs::exists(debugFile))
400     {
401         log<level::INFO>("Using debug PEL message registry");
402         file.open(debugFile);
403     }
404     else
405     {
406         file.open(registryFile);
407     }
408 
409     try
410     {
411         registry = nlohmann::json::parse(file);
412     }
413     catch (std::exception& e)
414     {
415         log<level::ERR>("Error parsing message registry JSON",
416                         entry("JSON_ERROR=%s", e.what()));
417         return std::nullopt;
418     }
419     return registry;
420 }
421 
422 } // namespace message
423 } // namespace pels
424 } // namespace openpower
425