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 std::vector<RegistrySeverity> getSeverities(const nlohmann::json& severity)
72 {
73     std::vector<RegistrySeverity> severities;
74 
75     // The plain string value, like "unrecoverable"
76     if (severity.is_string())
77     {
78         RegistrySeverity s;
79         s.severity = getSeverity(severity.get<std::string>());
80         severities.push_back(std::move(s));
81     }
82     else
83     {
84         // An array, with an element like:
85         // {
86         //    "SevValue": "unrecoverable",
87         //    "System", "systemA"
88         // }
89         for (const auto& sev : severity)
90         {
91             RegistrySeverity s;
92             s.severity = getSeverity(sev["SevValue"].get<std::string>());
93 
94             if (sev.contains("System"))
95             {
96                 s.system = sev["System"].get<std::string>();
97             }
98 
99             severities.push_back(std::move(s));
100         }
101     }
102 
103     return severities;
104 }
105 
106 uint16_t getActionFlags(const std::vector<std::string>& flags)
107 {
108     uint16_t actionFlags = 0;
109 
110     // Make the bitmask based on the array of flag names
111     for (const auto& flag : flags)
112     {
113         auto s = pv::findByName(flag, pv::actionFlagsValues);
114         if (s == pv::actionFlagsValues.end())
115         {
116             // Schema validation should be catching this.
117             log<level::ERR>("Invalid action flag name used in message registry",
118                             entry("FLAG=%s", flag.c_str()));
119 
120             throw std::runtime_error(
121                 "Invalid action flag used in message registry");
122         }
123 
124         actionFlags |= std::get<pv::fieldValuePos>(*s);
125     }
126 
127     return actionFlags;
128 }
129 
130 uint8_t getEventType(const std::string& eventTypeName)
131 {
132     auto t = pv::findByName(eventTypeName, pv::eventTypeValues);
133     if (t == pv::eventTypeValues.end())
134     {
135         log<level::ERR>("Invalid event type used in message registry",
136                         entry("EVENT_TYPE=%s", eventTypeName.c_str()));
137 
138         throw std::runtime_error("Invalid event type used in message registry");
139     }
140     return std::get<pv::fieldValuePos>(*t);
141 }
142 
143 uint8_t getEventScope(const std::string& eventScopeName)
144 {
145     auto s = pv::findByName(eventScopeName, pv::eventScopeValues);
146     if (s == pv::eventScopeValues.end())
147     {
148         log<level::ERR>("Invalid event scope used in registry",
149                         entry("EVENT_SCOPE=%s", eventScopeName.c_str()));
150 
151         throw std::runtime_error(
152             "Invalid event scope used in message registry");
153     }
154     return std::get<pv::fieldValuePos>(*s);
155 }
156 
157 uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name)
158 {
159     std::string rc = src["ReasonCode"];
160     uint16_t reasonCode = strtoul(rc.c_str(), nullptr, 16);
161     if (reasonCode == 0)
162     {
163         log<phosphor::logging::level::ERR>(
164             "Invalid reason code in message registry",
165             entry("ERROR_NAME=%s", name.c_str()),
166             entry("REASON_CODE=%s", rc.c_str()));
167 
168         throw std::runtime_error("Invalid reason code in message registry");
169     }
170     return reasonCode;
171 }
172 
173 uint8_t getSRCType(const nlohmann::json& src, const std::string& name)
174 {
175     // Looks like: "22"
176     std::string srcType = src["Type"];
177     size_t type = strtoul(srcType.c_str(), nullptr, 16);
178     if ((type == 0) || (srcType.size() != 2)) // 1 hex byte
179     {
180         log<phosphor::logging::level::ERR>(
181             "Invalid SRC Type in message registry",
182             entry("ERROR_NAME=%s", name.c_str()),
183             entry("SRC_TYPE=%s", srcType.c_str()));
184 
185         throw std::runtime_error("Invalid SRC Type in message registry");
186     }
187 
188     return type;
189 }
190 
191 std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
192     getSRCHexwordFields(const nlohmann::json& src, const std::string& name)
193 {
194     std::map<SRC::WordNum, SRC::AdditionalDataField> hexwordFields;
195 
196     // Build the map of which AdditionalData fields to use for which SRC words
197 
198     // Like:
199     // {
200     //   "8":
201     //   {
202     //     "AdditionalDataPropSource": "TEST"
203     //   }
204     //
205     // }
206 
207     for (const auto& word : src["Words6To9"].items())
208     {
209         std::string num = word.key();
210         size_t wordNum = std::strtoul(num.c_str(), nullptr, 10);
211 
212         if (wordNum == 0)
213         {
214             log<phosphor::logging::level::ERR>(
215                 "Invalid SRC word number in message registry",
216                 entry("ERROR_NAME=%s", name.c_str()),
217                 entry("SRC_WORD_NUM=%s", num.c_str()));
218 
219             throw std::runtime_error("Invalid SRC word in message registry");
220         }
221 
222         auto attributes = word.value();
223         std::string adPropName = attributes["AdditionalDataPropSource"];
224         hexwordFields[wordNum] = std::move(adPropName);
225     }
226 
227     if (!hexwordFields.empty())
228     {
229         return hexwordFields;
230     }
231 
232     return std::nullopt;
233 }
234 std::optional<std::vector<SRC::WordNum>>
235     getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name)
236 {
237     std::vector<SRC::WordNum> symptomIDFields;
238 
239     // Looks like:
240     // "SymptomIDFields": ["SRCWord3", "SRCWord6"],
241 
242     for (const std::string& field : src["SymptomIDFields"])
243     {
244         // Just need the last digit off the end, e.g. SRCWord6.
245         // The schema enforces the format of these.
246         auto srcWordNum = field.substr(field.size() - 1);
247         size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10);
248         if (num == 0)
249         {
250             log<phosphor::logging::level::ERR>(
251                 "Invalid symptom ID field in message registry",
252                 entry("ERROR_NAME=%s", name.c_str()),
253                 entry("FIELD_NAME=%s", srcWordNum.c_str()));
254 
255             throw std::runtime_error("Invalid symptom ID in message registry");
256         }
257         symptomIDFields.push_back(num);
258     }
259     if (!symptomIDFields.empty())
260     {
261         return symptomIDFields;
262     }
263 
264     return std::nullopt;
265 }
266 
267 uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
268                         const nlohmann::json& pelEntry, const std::string& name)
269 {
270     uint16_t id = 0;
271 
272     // If the ComponentID field is there, use that.  Otherwise, if it's a
273     // 0xBD BMC error SRC, use the reasoncode.
274     if (pelEntry.contains("ComponentID"))
275     {
276         std::string componentID = pelEntry["ComponentID"];
277         id = strtoul(componentID.c_str(), nullptr, 16);
278     }
279     else
280     {
281         // On BMC error SRCs (BD), can just get the component ID from
282         // the first byte of the reason code.
283         if (srcType == static_cast<uint8_t>(SRCType::bmcError))
284         {
285             id = reasonCode & 0xFF00;
286         }
287         else
288         {
289             log<level::ERR>("Missing component ID field in message registry",
290                             entry("ERROR_NAME=%s", name.c_str()));
291 
292             throw std::runtime_error(
293                 "Missing component ID field in message registry");
294         }
295     }
296 
297     return id;
298 }
299 
300 /**
301  * @brief Says if the JSON is the format that contains AdditionalData keys
302  *        as in index into them.
303  *
304  * @param[in] json - The highest level callout JSON
305  *
306  * @return bool - If it is the AdditionalData format or not
307  */
308 bool calloutUsesAdditionalData(const nlohmann::json& json)
309 {
310     return (json.contains("ADName") &&
311             json.contains("CalloutsWithTheirADValues"));
312 }
313 
314 /**
315  * @brief Finds the callouts to use when there is no AdditionalData,
316  *        but the system type may be used as a key.
317  *
318  * One entry in the array looks like the following.  The System key
319  * is optional and if not present it means that entry applies to
320  * every configuration that doesn't have another entry with a matching
321  * System key.
322  *
323  *    {
324  *        "System": "system1",
325  *        "CalloutList":
326  *        [
327  *            {
328  *                "Priority": "high",
329  *                "LocCode": "P1-C1"
330  *            },
331  *            {
332  *                "Priority": "low",
333  *                "LocCode": "P1"
334  *            }
335  *        ]
336  *    }
337  */
338 const nlohmann::json& findCalloutList(const nlohmann::json& json,
339                                       const std::string& systemType)
340 {
341     const nlohmann::json* callouts = nullptr;
342 
343     if (!json.is_array())
344     {
345         throw std::runtime_error{"findCalloutList was not passed a JSON array"};
346     }
347 
348     // The entry with the system type match will take precedence over the entry
349     // without any "System" field in it at all, which will match all other
350     // cases.
351     for (const auto& calloutList : json)
352     {
353         if (calloutList.contains("System"))
354         {
355             if (systemType == calloutList["System"].get<std::string>())
356             {
357                 callouts = &calloutList["CalloutList"];
358                 break;
359             }
360         }
361         else
362         {
363             // Any entry with no System key
364             callouts = &calloutList["CalloutList"];
365         }
366     }
367 
368     if (!callouts)
369     {
370         log<level::WARNING>(
371             "No matching system type entry or default system type entry "
372             " for PEL callout list",
373             entry("SYSTEMTYPE=%s", systemType.c_str()));
374 
375         throw std::runtime_error{
376             "Could not find a CalloutList JSON for this error and system type"};
377     }
378 
379     return *callouts;
380 }
381 
382 /**
383  * @brief Creates a RegistryCallout based on the input JSON.
384  *
385  * The JSON looks like:
386  *     {
387  *          "Priority": "high",
388  *          "LocCode": "E1"
389  *          ...
390  *     }
391  *
392  * Schema validation enforces what keys are present.
393  *
394  * @param[in] json - The JSON dictionary entry for a callout
395  *
396  * @return RegistryCallout - A filled in RegistryCallout
397  */
398 RegistryCallout makeRegistryCallout(const nlohmann::json& json)
399 {
400     RegistryCallout callout;
401 
402     callout.priority = "high";
403 
404     if (json.contains("Priority"))
405     {
406         callout.priority = json["Priority"].get<std::string>();
407     }
408 
409     if (json.contains("LocCode"))
410     {
411         callout.locCode = json["LocCode"].get<std::string>();
412     }
413 
414     if (json.contains("Procedure"))
415     {
416         callout.procedure = json["Procedure"].get<std::string>();
417     }
418     else if (json.contains("SymbolicFRU"))
419     {
420         callout.symbolicFRU = json["SymbolicFRU"].get<std::string>();
421     }
422     else if (json.contains("SymbolicFRUTrusted"))
423     {
424         callout.symbolicFRUTrusted =
425             json["SymbolicFRUTrusted"].get<std::string>();
426     }
427 
428     return callout;
429 }
430 
431 /**
432  * @brief Returns the callouts to use when an AdditionalData key is
433  *        required to find the correct entries.
434  *
435  *       The System property is used to find which CalloutList to use.
436  *       If System is missing, then that CalloutList is valid for
437  *       everything.
438  *
439  * The JSON looks like:
440  *    [
441  *        {
442  *            "System": "systemA",
443  *            "CalloutList":
444  *            [
445  *                {
446  *                    "Priority": "high",
447  *                    "LocCode": "P1-C5"
448  *                }
449  *            ]
450  *         }
451  *    ]
452  *
453  * @param[in] json - The callout JSON
454  * @param[in] systemType - The system type from EntityManager
455  *
456  * @return std::vector<RegistryCallout> - The callouts to use
457  */
458 std::vector<RegistryCallout> getCalloutsWithoutAD(const nlohmann::json& json,
459                                                   const std::string& systemType)
460 {
461     std::vector<RegistryCallout> calloutEntries;
462 
463     // Find the CalloutList to use based on the system type
464     const auto& calloutList = findCalloutList(json, systemType);
465 
466     // We finally found the callouts, make the objects.
467     for (const auto& callout : calloutList)
468     {
469         calloutEntries.push_back(std::move(makeRegistryCallout(callout)));
470     }
471 
472     return calloutEntries;
473 }
474 
475 /**
476  * @brief Returns the callouts to use when an AdditionalData key is
477  *        required to find the correct entries.
478  *
479  * The JSON looks like:
480  *    {
481  *        "ADName": "PROC_NUM",
482  *        "CalloutsWithTheirADValues":
483  *        [
484  *            {
485  *                "ADValue": "0",
486  *                "Callouts":
487  *                [
488  *                    {
489  *                        "CalloutList":
490  *                        [
491  *                            {
492  *                                "Priority": "high",
493  *                                "LocCode": "P1-C5"
494  *                            }
495  *                        ]
496  *                    }
497  *                ]
498  *            }
499  *        ]
500  *     }
501  *
502  * Note that the "Callouts" entry above is the same as the top level
503  * entry used when there is no AdditionalData key.
504  *
505  * @param[in] json - The callout JSON
506  * @param[in] systemType - The system type from EntityManager
507  * @param[in] additionalData - The AdditionalData property
508  *
509  * @return std::vector<RegistryCallout> - The callouts to use
510  */
511 std::vector<RegistryCallout>
512     getCalloutsUsingAD(const nlohmann::json& json,
513                        const std::string& systemType,
514                        const AdditionalData& additionalData)
515 {
516     // This indicates which AD field we'll be using
517     auto keyName = json["ADName"].get<std::string>();
518 
519     // Get the actual value from the AD data
520     auto adValue = additionalData.getValue(keyName);
521 
522     if (!adValue)
523     {
524         // The AdditionalData did not contain the necessary key
525         log<level::WARNING>(
526             "The PEL message registry callouts JSON "
527             "said to use an AdditionalData key that isn't in the "
528             "AdditionalData event log property",
529             entry("ADNAME=%s\n", keyName.c_str()));
530         throw std::runtime_error{
531             "Missing AdditionalData entry for this callout"};
532     }
533 
534     const auto& callouts = json["CalloutsWithTheirADValues"];
535 
536     // find the entry with that AD value
537     auto it = std::find_if(
538         callouts.begin(), callouts.end(), [adValue](const nlohmann::json& j) {
539             return *adValue == j["ADValue"].get<std::string>();
540         });
541 
542     if (it == callouts.end())
543     {
544         log<level::WARNING>(
545             "No callout entry found for the AdditionalData value used",
546             entry("AD_VALUE=%s", adValue->c_str()));
547 
548         throw std::runtime_error{
549             "No callout entry found for the AdditionalData value used"};
550     }
551 
552     // Proceed to find the callouts possibly based on system type.
553     return getCalloutsWithoutAD((*it)["Callouts"], systemType);
554 }
555 
556 } // namespace helper
557 
558 std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
559                                       bool toCache)
560 {
561     std::optional<nlohmann::json> registryTmp;
562     auto& registryOpt = (_registry) ? _registry : registryTmp;
563     if (!registryOpt)
564     {
565         registryOpt = readRegistry(_registryFile);
566         if (!registryOpt)
567         {
568             return std::nullopt;
569         }
570         else if (toCache)
571         {
572             // Save message registry in memory for peltool
573             _registry = std::move(registryTmp);
574         }
575     }
576     auto& reg = (_registry) ? _registry : registryTmp;
577     const auto& registry = reg.value();
578     // Find an entry with this name in the PEL array.
579     auto e = std::find_if(
580         registry["PELs"].begin(), registry["PELs"].end(),
581         [&name, &type](const auto& j) {
582             return ((name == j["Name"] && type == LookupType::name) ||
583                     (name == j["SRC"]["ReasonCode"] &&
584                      type == LookupType::reasonCode));
585         });
586 
587     if (e != registry["PELs"].end())
588     {
589         // Fill in the Entry structure from the JSON.  Most, but not all, fields
590         // are optional.
591 
592         try
593         {
594             Entry entry;
595             entry.name = (*e)["Name"];
596             entry.subsystem = helper::getSubsystem((*e)["Subsystem"]);
597 
598             if (e->contains("ActionFlags"))
599             {
600                 entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]);
601             }
602 
603             if (e->contains("MfgActionFlags"))
604             {
605                 entry.mfgActionFlags =
606                     helper::getActionFlags((*e)["MfgActionFlags"]);
607             }
608 
609             if (e->contains("Severity"))
610             {
611                 entry.severity = helper::getSeverities((*e)["Severity"]);
612             }
613 
614             if (e->contains("MfgSeverity"))
615             {
616                 entry.mfgSeverity = helper::getSeverities((*e)["MfgSeverity"]);
617             }
618 
619             if (e->contains("EventType"))
620             {
621                 entry.eventType = helper::getEventType((*e)["EventType"]);
622             }
623 
624             if (e->contains("EventScope"))
625             {
626                 entry.eventScope = helper::getEventScope((*e)["EventScope"]);
627             }
628 
629             auto& src = (*e)["SRC"];
630             entry.src.reasonCode = helper::getSRCReasonCode(src, name);
631 
632             if (src.contains("Type"))
633             {
634                 entry.src.type = helper::getSRCType(src, name);
635             }
636             else
637             {
638                 entry.src.type = static_cast<uint8_t>(SRCType::bmcError);
639             }
640 
641             // Now that we know the SRC type and reason code,
642             // we can get the component ID.
643             entry.componentID = helper::getComponentID(
644                 entry.src.type, entry.src.reasonCode, *e, name);
645 
646             if (src.contains("Words6To9"))
647             {
648                 entry.src.hexwordADFields =
649                     helper::getSRCHexwordFields(src, name);
650             }
651 
652             if (src.contains("SymptomIDFields"))
653             {
654                 entry.src.symptomID = helper::getSRCSymptomIDFields(src, name);
655             }
656 
657             if (src.contains("PowerFault"))
658             {
659                 entry.src.powerFault = src["PowerFault"];
660             }
661 
662             auto& doc = (*e)["Documentation"];
663             entry.doc.message = doc["Message"];
664             entry.doc.description = doc["Description"];
665             if (doc.contains("MessageArgSources"))
666             {
667                 entry.doc.messageArgSources = doc["MessageArgSources"];
668             }
669 
670             // If there are callouts defined, save the JSON for later
671             if (_loadCallouts)
672             {
673                 if (e->contains("Callouts"))
674                 {
675                     entry.callouts = (*e)["Callouts"];
676                 }
677                 else if (e->contains("CalloutsUsingAD"))
678                 {
679                     entry.callouts = (*e)["CalloutsUsingAD"];
680                 }
681             }
682 
683             return entry;
684         }
685         catch (std::exception& e)
686         {
687             log<level::ERR>("Found invalid message registry field",
688                             entry("ERROR=%s", e.what()));
689         }
690     }
691 
692     return std::nullopt;
693 }
694 
695 std::optional<nlohmann::json>
696     Registry::readRegistry(const std::filesystem::path& registryFile)
697 {
698     // Look in /etc first in case someone put a test file there
699     fs::path debugFile{fs::path{debugFilePath} / registryFileName};
700     nlohmann::json registry;
701     std::ifstream file;
702 
703     if (fs::exists(debugFile))
704     {
705         log<level::INFO>("Using debug PEL message registry");
706         file.open(debugFile);
707     }
708     else
709     {
710         file.open(registryFile);
711     }
712 
713     try
714     {
715         registry = nlohmann::json::parse(file);
716     }
717     catch (std::exception& e)
718     {
719         log<level::ERR>("Error parsing message registry JSON",
720                         entry("JSON_ERROR=%s", e.what()));
721         return std::nullopt;
722     }
723     return registry;
724 }
725 
726 std::vector<RegistryCallout>
727     Registry::getCallouts(const nlohmann::json& calloutJSON,
728                           const std::string& systemType,
729                           const AdditionalData& additionalData)
730 {
731     // The JSON may either use an AdditionalData key
732     // as an index, or not.
733     if (helper::calloutUsesAdditionalData(calloutJSON))
734     {
735         return helper::getCalloutsUsingAD(calloutJSON, systemType,
736                                           additionalData);
737     }
738 
739     return helper::getCalloutsWithoutAD(calloutJSON, systemType);
740 }
741 
742 } // namespace message
743 } // namespace pels
744 } // namespace openpower
745