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