/** * Copyright © 2019 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "registry.hpp" #include "json_utils.hpp" #include "pel_types.hpp" #include "pel_values.hpp" #include #include #include namespace openpower { namespace pels { namespace message { namespace pv = pel_values; namespace fs = std::filesystem; constexpr auto debugFilePath = "/etc/phosphor-logging/"; namespace helper { uint8_t getSubsystem(const std::string& subsystemName) { // Get the actual value to use in the PEL for the string name auto ss = pv::findByName(subsystemName, pv::subsystemValues); if (ss == pv::subsystemValues.end()) { // Schema validation should be catching this. lg2::error("Invalid subsystem name used in message registry: {SUBSYS}", "SUBSYS", subsystemName); throw std::runtime_error("Invalid subsystem used in message registry"); } return std::get(*ss); } uint8_t getSeverity(const std::string& severityName) { auto s = pv::findByName(severityName, pv::severityValues); if (s == pv::severityValues.end()) { // Schema validation should be catching this. lg2::error("Invalid severity name used in message registry: {SEV}", "SEV", severityName); throw std::runtime_error("Invalid severity used in message registry"); } return std::get(*s); } std::vector getSeverities(const nlohmann::json& severity) { std::vector severities; // The plain string value, like "unrecoverable" if (severity.is_string()) { RegistrySeverity s; s.severity = getSeverity(severity.get()); severities.push_back(std::move(s)); } else { // An array, with an element like: // { // "SevValue": "unrecoverable", // "System", "systemA" // } for (const auto& sev : severity) { RegistrySeverity s; s.severity = getSeverity(sev["SevValue"].get()); if (sev.contains("System")) { s.system = sev["System"].get(); } severities.push_back(std::move(s)); } } return severities; } uint16_t getActionFlags(const std::vector& flags) { uint16_t actionFlags = 0; // Make the bitmask based on the array of flag names for (const auto& flag : flags) { auto s = pv::findByName(flag, pv::actionFlagsValues); if (s == pv::actionFlagsValues.end()) { // Schema validation should be catching this. lg2::error( "Invalid action flag name used in message registry: {FLAG}", "FLAG", flag); throw std::runtime_error( "Invalid action flag used in message registry"); } actionFlags |= std::get(*s); } return actionFlags; } uint8_t getEventType(const std::string& eventTypeName) { auto t = pv::findByName(eventTypeName, pv::eventTypeValues); if (t == pv::eventTypeValues.end()) { lg2::error("Invalid event type used in message registry: {TYPE}", "TYPE", eventTypeName); throw std::runtime_error("Invalid event type used in message registry"); } return std::get(*t); } uint8_t getEventScope(const std::string& eventScopeName) { auto s = pv::findByName(eventScopeName, pv::eventScopeValues); if (s == pv::eventScopeValues.end()) { lg2::error("Invalid event scope used in registry: {SCOPE}", "SCOPE", eventScopeName); throw std::runtime_error( "Invalid event scope used in message registry"); } return std::get(*s); } uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name) { std::string rc = src["ReasonCode"]; uint16_t reasonCode = strtoul(rc.c_str(), nullptr, 16); if (reasonCode == 0) { lg2::error( "Invalid reason code {RC} in message registry, error name = {ERROR}", "RC", rc, "ERROR", name); throw std::runtime_error("Invalid reason code in message registry"); } return reasonCode; } uint8_t getSRCType(const nlohmann::json& src, const std::string& name) { // Looks like: "22" std::string srcType = src["Type"]; size_t type = strtoul(srcType.c_str(), nullptr, 16); if ((type == 0) || (srcType.size() != 2)) // 1 hex byte { lg2::error( "Invalid SRC Type {TYPE} in message registry, error name = {ERROR}", "TYPE", srcType, "ERROR", name); throw std::runtime_error("Invalid SRC Type in message registry"); } return type; } bool getSRCDeconfigFlag(const nlohmann::json& src) { return src["DeconfigFlag"].get(); } bool getSRCCheckstopFlag(const nlohmann::json& src) { return src["CheckstopFlag"].get(); } std::optional> getSRCHexwordFields(const nlohmann::json& src, const std::string& name) { std::map hexwordFields; // Build the map of which AdditionalData fields to use for which SRC words // Like: // { // "8": // { // "AdditionalDataPropSource": "TEST" // } // // } for (const auto& word : src["Words6To9"].items()) { std::string num = word.key(); size_t wordNum = std::strtoul(num.c_str(), nullptr, 10); if (wordNum == 0) { lg2::error( "Invalid SRC word number {NUM} in message registry, error name = {ERROR}", "NUM", num, "ERROR", name); throw std::runtime_error("Invalid SRC word in message registry"); } auto attributes = word.value(); // Use an empty string for the description if it does not exist. auto itr = attributes.find("Description"); std::string desc = (attributes.end() != itr) ? *itr : ""; std::tuple adPropSourceDesc( attributes["AdditionalDataPropSource"], desc); hexwordFields[wordNum] = std::move(adPropSourceDesc); } if (!hexwordFields.empty()) { return hexwordFields; } return std::nullopt; } std::optional> getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name) { std::vector symptomIDFields; // Looks like: // "SymptomIDFields": ["SRCWord3", "SRCWord6"], for (const std::string field : src["SymptomIDFields"]) { // Just need the last digit off the end, e.g. SRCWord6. // The schema enforces the format of these. auto srcWordNum = field.substr(field.size() - 1); size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10); if (num == 0) { lg2::error( "Invalid symptom ID field {FIELD} in message registry, error name = {ERROR}", "FIELD", field, "ERROR", name); throw std::runtime_error("Invalid symptom ID in message registry"); } symptomIDFields.push_back(num); } if (!symptomIDFields.empty()) { return symptomIDFields; } return std::nullopt; } uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode, const nlohmann::json& pelEntry, const std::string& name) { uint16_t id = 0; // If the ComponentID field is there, use that. Otherwise, if it's a // 0xBD BMC error SRC, use the reasoncode. if (pelEntry.contains("ComponentID")) { std::string componentID = pelEntry["ComponentID"]; id = strtoul(componentID.c_str(), nullptr, 16); } else { // On BMC error SRCs (BD), can just get the component ID from // the first byte of the reason code. if (srcType == static_cast(SRCType::bmcError)) { id = reasonCode & 0xFF00; } else { lg2::error( "Missing component ID field in message registry, error name = {ERROR}", "ERROR", name); throw std::runtime_error( "Missing component ID field in message registry"); } } return id; } /** * @brief Says if the JSON is the format that contains AdditionalData keys * as in index into them. * * @param[in] json - The highest level callout JSON * * @return bool - If it is the AdditionalData format or not */ bool calloutUsesAdditionalData(const nlohmann::json& json) { return (json.contains("ADName") && json.contains("CalloutsWithTheirADValues")); } /** * @brief Finds the callouts to use when there is no AdditionalData, * but the system type may be used as a key. * * A sample calloutList array looks like the following. The System and Systems * key are optional. * * System key - Value of the key will be the system name as a string. The * callouts for a specific system can define under this key. * * Systems key - Value of the key will be an array of system names in the form * of string. The callouts common to the systems mentioned in the array can * define under this key. * * If both System and Systems not present it means that entry applies to every * configuration that doesn't have another entry with a matching System and * Systems key. * * { * "System": "system1", * "CalloutList": * [ * { * "Priority": "high", * "LocCode": "P1-C1" * }, * { * "Priority": "low", * "LocCode": "P1" * } * ] * }, * { * "Systems": ["system1", 'system2"], * "CalloutList": * [ * { * "Priority": "high", * "LocCode": "P0-C1" * }, * { * "Priority": "low", * "LocCode": "P0" * } * ] * } * * @param[in] json - The callout JSON * @param[in] systemNames - List of compatible system type names * @param[out] calloutLists - The JSON array which will hold the calloutlist to * use specific to the system. * * @return - Throws runtime exception if json is not an array or if calloutLists * is empty. */ static void findCalloutList(const nlohmann::json& json, const std::vector& systemNames, nlohmann::json& calloutLists) { if (!json.is_array()) { throw std::runtime_error{"findCalloutList was not passed a JSON array"}; } // Flag to indicate whether system specific callouts found or not bool foundCallouts = false; for (const auto& callouts : json) { if (callouts.contains("System")) { if (std::ranges::find(systemNames, callouts["System"].get()) != systemNames.end()) { calloutLists.insert(calloutLists.end(), callouts["CalloutList"].begin(), callouts["CalloutList"].end()); foundCallouts = true; } continue; } if (callouts.contains("Systems")) { std::vector systems = callouts["Systems"].get>(); auto inSystemNames = [systemNames](const auto& system) { return (std::ranges::find(systemNames, system) != systemNames.end()); }; if (std::ranges::any_of(systems, inSystemNames)) { calloutLists.insert(calloutLists.end(), callouts["CalloutList"].begin(), callouts["CalloutList"].end()); foundCallouts = true; } continue; } // Any entry if neither System/Systems key matches with system name if (!foundCallouts) { calloutLists.insert(calloutLists.end(), callouts["CalloutList"].begin(), callouts["CalloutList"].end()); } } if (calloutLists.empty()) { std::string types; std::for_each(systemNames.begin(), systemNames.end(), [&types](const auto& t) { types += t + '|'; }); lg2::warning( "No matching system name entry or default system name entry " " for PEL callout list, names = {TYPES}", "TYPES", types); throw std::runtime_error{ "Could not find a CalloutList JSON for this error and system name"}; } } /** * @brief Creates a RegistryCallout based on the input JSON. * * The JSON looks like: * { * "Priority": "high", * "LocCode": "E1" * ... * } * * Schema validation enforces what keys are present. * * @param[in] json - The JSON dictionary entry for a callout * * @return RegistryCallout - A filled in RegistryCallout */ RegistryCallout makeRegistryCallout(const nlohmann::json& json) { RegistryCallout callout; callout.priority = "high"; callout.useInventoryLocCode = false; if (json.contains("Priority")) { callout.priority = json["Priority"].get(); } if (json.contains("LocCode")) { callout.locCode = json["LocCode"].get(); } if (json.contains("Procedure")) { callout.procedure = json["Procedure"].get(); } else if (json.contains("SymbolicFRU")) { callout.symbolicFRU = json["SymbolicFRU"].get(); } else if (json.contains("SymbolicFRUTrusted")) { callout.symbolicFRUTrusted = json["SymbolicFRUTrusted"].get(); } if (json.contains("UseInventoryLocCode")) { callout.useInventoryLocCode = json["UseInventoryLocCode"].get(); } return callout; } /** * @brief Returns the callouts to use when an AdditionalData key is * required to find the correct entries. * * The System property is used to find which CalloutList to use. * If System is missing, then that CalloutList is valid for * everything. * * The JSON looks like: * { * "System": "system1", * "CalloutList": * [ * { * "Priority": "high", * "LocCode": "P1-C1" * }, * { * "Priority": "low", * "LocCode": "P1" * } * ] * }, * { * "Systems": ["system1", 'system2"], * "CalloutList": * [ * { * "Priority": "high", * "LocCode": "P0-C1" * }, * { * "Priority": "low", * "LocCode": "P0" * } * ] * } * * @param[in] json - The callout JSON * @param[in] systemNames - List of compatible system type names * * @return std::vector - The callouts to use */ std::vector getCalloutsWithoutAD( const nlohmann::json& json, const std::vector& systemNames) { std::vector calloutEntries; nlohmann::json calloutLists = nlohmann::json::array(); // Find the CalloutList to use based on the system type findCalloutList(json, systemNames, calloutLists); // We finally found the callouts, make the objects. for (const auto& callout : calloutLists) { calloutEntries.push_back(std::move(makeRegistryCallout(callout))); } return calloutEntries; } /** * @brief Returns the callouts to use when an AdditionalData key is * required to find the correct entries. * * The JSON looks like: * { * "ADName": "PROC_NUM", * "CalloutsWithTheirADValues": * [ * { * "ADValue": "0", * "Callouts": * [ * { * "CalloutList": * [ * { * "Priority": "high", * "LocCode": "P1-C5" * } * ] * } * ] * } * ] * } * * Note that the "Callouts" entry above is the same as the top level * entry used when there is no AdditionalData key. * * @param[in] json - The callout JSON * @param[in] systemNames - List of compatible system type names * @param[in] additionalData - The AdditionalData property * * @return std::vector - The callouts to use */ std::vector getCalloutsUsingAD( const nlohmann::json& json, const std::vector& systemNames, const AdditionalData& additionalData) { // This indicates which AD field we'll be using auto keyName = json["ADName"].get(); // Get the actual value from the AD data auto adValue = additionalData.getValue(keyName); if (!adValue) { // The AdditionalData did not contain the necessary key lg2::warning("The PEL message registry callouts JSON " "said to use an AdditionalData key that isn't in the " "AdditionalData event log property, key = {KEY}", "KEY", keyName); throw std::runtime_error{ "Missing AdditionalData entry for this callout"}; } const auto& callouts = json["CalloutsWithTheirADValues"]; // find the entry with that AD value auto it = std::find_if( callouts.begin(), callouts.end(), [adValue](const nlohmann::json& j) { return *adValue == j["ADValue"].get(); }); if (it == callouts.end()) { // This can happen if not all possible values were in the // message registry and that's fine. There may be a // "CalloutsWhenNoADMatch" section that contains callouts // to use in this case. if (json.contains("CalloutsWhenNoADMatch")) { return getCalloutsWithoutAD(json["CalloutsWhenNoADMatch"], systemNames); } return std::vector{}; } // Proceed to find the callouts possibly based on system type. return getCalloutsWithoutAD((*it)["Callouts"], systemNames); } /** * @brief Returns the journal capture information * * The JSON looks like: * "JournalCapture": { * "NumLines": 30 * } * * "JournalCapture": * { * "Sections": [ * { * "SyslogID": "phosphor-log-manager", * "NumLines": 20 * } * ] * } * * @param json - The journal capture JSON * @return JournalCapture - The filled in variant */ JournalCapture getJournalCapture(const nlohmann::json& json) { JournalCapture capt; // Primary key is either NumLines or Sections. if (json.contains("NumLines")) { capt = json.at("NumLines").get(); } else if (json.contains("Sections")) { AppCaptureList captures; for (const auto& capture : json.at("Sections")) { AppCapture ac; ac.syslogID = capture.at("SyslogID").get(); ac.numLines = capture.at("NumLines").get(); captures.push_back(std::move(ac)); } capt = captures; } else { lg2::error("JournalCapture section not the right format"); throw std::runtime_error{"JournalCapture section not the right format"}; } return capt; } } // namespace helper std::optional Registry::lookup(const std::string& name, LookupType type, bool toCache) { std::optional registryTmp; auto& registryOpt = (_registry) ? _registry : registryTmp; if (!registryOpt) { registryOpt = readRegistry(_registryFile); if (!registryOpt) { return std::nullopt; } else if (toCache) { // Save message registry in memory for peltool _registry = std::move(registryTmp); } } auto& reg = (_registry) ? _registry : registryTmp; const auto& registry = reg.value(); // Find an entry with this name in the PEL array. auto e = std::find_if( registry["PELs"].begin(), registry["PELs"].end(), [&name, &type](const nlohmann::json& j) { return ((name == j.at("Name").get() && type == LookupType::name) || (name == j.at("SRC").at("ReasonCode").get() && type == LookupType::reasonCode)); }); if (e != registry["PELs"].end()) { // Fill in the Entry structure from the JSON. Most, but not all, fields // are optional. try { Entry entry; entry.name = (*e)["Name"]; if (e->contains("Subsystem")) { entry.subsystem = helper::getSubsystem((*e)["Subsystem"]); } if (e->contains("ActionFlags")) { entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]); } if (e->contains("MfgActionFlags")) { entry.mfgActionFlags = helper::getActionFlags((*e)["MfgActionFlags"]); } if (e->contains("Severity")) { entry.severity = helper::getSeverities((*e)["Severity"]); } if (e->contains("MfgSeverity")) { entry.mfgSeverity = helper::getSeverities((*e)["MfgSeverity"]); } if (e->contains("EventType")) { entry.eventType = helper::getEventType((*e)["EventType"]); } if (e->contains("EventScope")) { entry.eventScope = helper::getEventScope((*e)["EventScope"]); } auto& src = (*e)["SRC"]; entry.src.reasonCode = helper::getSRCReasonCode(src, name); if (src.contains("Type")) { entry.src.type = helper::getSRCType(src, name); } else { entry.src.type = static_cast(SRCType::bmcError); } // Now that we know the SRC type and reason code, // we can get the component ID. entry.componentID = helper::getComponentID( entry.src.type, entry.src.reasonCode, *e, name); if (src.contains("Words6To9")) { entry.src.hexwordADFields = helper::getSRCHexwordFields(src, name); } if (src.contains("SymptomIDFields")) { entry.src.symptomID = helper::getSRCSymptomIDFields(src, name); } if (src.contains("DeconfigFlag")) { entry.src.deconfigFlag = helper::getSRCDeconfigFlag(src); } if (src.contains("CheckstopFlag")) { entry.src.checkstopFlag = helper::getSRCCheckstopFlag(src); } auto& doc = (*e)["Documentation"]; entry.doc.message = doc["Message"]; entry.doc.description = doc["Description"]; if (doc.contains("MessageArgSources")) { entry.doc.messageArgSources = doc["MessageArgSources"]; } // If there are callouts defined, save the JSON for later if (_loadCallouts) { if (e->contains("Callouts")) { entry.callouts = (*e)["Callouts"]; } else if (e->contains("CalloutsUsingAD")) { entry.callouts = (*e)["CalloutsUsingAD"]; } } if (e->contains("JournalCapture")) { entry.journalCapture = helper::getJournalCapture((*e)["JournalCapture"]); } return entry; } catch (const std::exception& ex) { lg2::error("Found invalid message registry field. Error: {ERROR}", "ERROR", ex); } } return std::nullopt; } std::optional Registry::readRegistry(const std::filesystem::path& registryFile) { // Look in /etc first in case someone put a test file there fs::path debugFile{fs::path{debugFilePath} / registryFileName}; nlohmann::json registry; std::ifstream file; if (fs::exists(debugFile)) { lg2::info("Using debug PEL message registry"); file.open(debugFile); } else { file.open(registryFile); } try { registry = nlohmann::json::parse(file); } catch (const std::exception& e) { lg2::error("Error parsing message registry JSON. Error: {ERROR}", "ERROR", e); return std::nullopt; } return registry; } std::vector Registry::getCallouts(const nlohmann::json& calloutJSON, const std::vector& systemNames, const AdditionalData& additionalData) { // The JSON may either use an AdditionalData key // as an index, or not. if (helper::calloutUsesAdditionalData(calloutJSON)) { return helper::getCalloutsUsingAD(calloutJSON, systemNames, additionalData); } return helper::getCalloutsWithoutAD(calloutJSON, systemNames); } } // namespace message } // namespace pels } // namespace openpower