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 225 // Use an empty string for the description if it does not exist. 226 auto itr = attributes.find("Description"); 227 std::string desc = (attributes.end() != itr) ? *itr : ""; 228 229 std::tuple<std::string, std::string> adPropSourceDesc( 230 attributes["AdditionalDataPropSource"], desc); 231 hexwordFields[wordNum] = std::move(adPropSourceDesc); 232 } 233 234 if (!hexwordFields.empty()) 235 { 236 return hexwordFields; 237 } 238 239 return std::nullopt; 240 } 241 std::optional<std::vector<SRC::WordNum>> 242 getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name) 243 { 244 std::vector<SRC::WordNum> symptomIDFields; 245 246 // Looks like: 247 // "SymptomIDFields": ["SRCWord3", "SRCWord6"], 248 249 for (const std::string field : src["SymptomIDFields"]) 250 { 251 // Just need the last digit off the end, e.g. SRCWord6. 252 // The schema enforces the format of these. 253 auto srcWordNum = field.substr(field.size() - 1); 254 size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10); 255 if (num == 0) 256 { 257 log<phosphor::logging::level::ERR>( 258 "Invalid symptom ID field in message registry", 259 entry("ERROR_NAME=%s", name.c_str()), 260 entry("FIELD_NAME=%s", srcWordNum.c_str())); 261 262 throw std::runtime_error("Invalid symptom ID in message registry"); 263 } 264 symptomIDFields.push_back(num); 265 } 266 if (!symptomIDFields.empty()) 267 { 268 return symptomIDFields; 269 } 270 271 return std::nullopt; 272 } 273 274 uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode, 275 const nlohmann::json& pelEntry, const std::string& name) 276 { 277 uint16_t id = 0; 278 279 // If the ComponentID field is there, use that. Otherwise, if it's a 280 // 0xBD BMC error SRC, use the reasoncode. 281 if (pelEntry.contains("ComponentID")) 282 { 283 std::string componentID = pelEntry["ComponentID"]; 284 id = strtoul(componentID.c_str(), nullptr, 16); 285 } 286 else 287 { 288 // On BMC error SRCs (BD), can just get the component ID from 289 // the first byte of the reason code. 290 if (srcType == static_cast<uint8_t>(SRCType::bmcError)) 291 { 292 id = reasonCode & 0xFF00; 293 } 294 else 295 { 296 log<level::ERR>("Missing component ID field in message registry", 297 entry("ERROR_NAME=%s", name.c_str())); 298 299 throw std::runtime_error( 300 "Missing component ID field in message registry"); 301 } 302 } 303 304 return id; 305 } 306 307 /** 308 * @brief Says if the JSON is the format that contains AdditionalData keys 309 * as in index into them. 310 * 311 * @param[in] json - The highest level callout JSON 312 * 313 * @return bool - If it is the AdditionalData format or not 314 */ 315 bool calloutUsesAdditionalData(const nlohmann::json& json) 316 { 317 return (json.contains("ADName") && 318 json.contains("CalloutsWithTheirADValues")); 319 } 320 321 /** 322 * @brief Finds the callouts to use when there is no AdditionalData, 323 * but the system type may be used as a key. 324 * 325 * One entry in the array looks like the following. The System key 326 * is optional and if not present it means that entry applies to 327 * every configuration that doesn't have another entry with a matching 328 * System key. 329 * 330 * { 331 * "System": "system1", 332 * "CalloutList": 333 * [ 334 * { 335 * "Priority": "high", 336 * "LocCode": "P1-C1" 337 * }, 338 * { 339 * "Priority": "low", 340 * "LocCode": "P1" 341 * } 342 * ] 343 * } 344 */ 345 const nlohmann::json& 346 findCalloutList(const nlohmann::json& json, 347 const std::vector<std::string>& systemNames) 348 { 349 const nlohmann::json* callouts = nullptr; 350 351 if (!json.is_array()) 352 { 353 throw std::runtime_error{"findCalloutList was not passed a JSON array"}; 354 } 355 356 // The entry with the system type match will take precedence over the entry 357 // without any "System" field in it at all, which will match all other 358 // cases. 359 for (const auto& calloutList : json) 360 { 361 if (calloutList.contains("System")) 362 { 363 if (std::find(systemNames.begin(), systemNames.end(), 364 calloutList["System"].get<std::string>()) != 365 systemNames.end()) 366 { 367 callouts = &calloutList["CalloutList"]; 368 break; 369 } 370 } 371 else 372 { 373 // Any entry with no System key 374 callouts = &calloutList["CalloutList"]; 375 } 376 } 377 378 if (!callouts) 379 { 380 std::string types; 381 std::for_each(systemNames.begin(), systemNames.end(), 382 [&types](const auto& t) { types += t + '|'; }); 383 log<level::WARNING>( 384 "No matching system name entry or default system name entry " 385 " for PEL callout list", 386 entry("SYSTEMNAMES=%s", types.c_str())); 387 388 throw std::runtime_error{ 389 "Could not find a CalloutList JSON for this error and system name"}; 390 } 391 392 return *callouts; 393 } 394 395 /** 396 * @brief Creates a RegistryCallout based on the input JSON. 397 * 398 * The JSON looks like: 399 * { 400 * "Priority": "high", 401 * "LocCode": "E1" 402 * ... 403 * } 404 * 405 * Schema validation enforces what keys are present. 406 * 407 * @param[in] json - The JSON dictionary entry for a callout 408 * 409 * @return RegistryCallout - A filled in RegistryCallout 410 */ 411 RegistryCallout makeRegistryCallout(const nlohmann::json& json) 412 { 413 RegistryCallout callout; 414 415 callout.priority = "high"; 416 callout.useInventoryLocCode = false; 417 418 if (json.contains("Priority")) 419 { 420 callout.priority = json["Priority"].get<std::string>(); 421 } 422 423 if (json.contains("LocCode")) 424 { 425 callout.locCode = json["LocCode"].get<std::string>(); 426 } 427 428 if (json.contains("Procedure")) 429 { 430 callout.procedure = json["Procedure"].get<std::string>(); 431 } 432 else if (json.contains("SymbolicFRU")) 433 { 434 callout.symbolicFRU = json["SymbolicFRU"].get<std::string>(); 435 } 436 else if (json.contains("SymbolicFRUTrusted")) 437 { 438 callout.symbolicFRUTrusted = 439 json["SymbolicFRUTrusted"].get<std::string>(); 440 } 441 442 if (json.contains("UseInventoryLocCode")) 443 { 444 callout.useInventoryLocCode = json["UseInventoryLocCode"].get<bool>(); 445 } 446 447 return callout; 448 } 449 450 /** 451 * @brief Returns the callouts to use when an AdditionalData key is 452 * required to find the correct entries. 453 * 454 * The System property is used to find which CalloutList to use. 455 * If System is missing, then that CalloutList is valid for 456 * everything. 457 * 458 * The JSON looks like: 459 * [ 460 * { 461 * "System": "systemA", 462 * "CalloutList": 463 * [ 464 * { 465 * "Priority": "high", 466 * "LocCode": "P1-C5" 467 * } 468 * ] 469 * } 470 * ] 471 * 472 * @param[in] json - The callout JSON 473 * @param[in] systemNames - List of compatible system type names 474 * 475 * @return std::vector<RegistryCallout> - The callouts to use 476 */ 477 std::vector<RegistryCallout> 478 getCalloutsWithoutAD(const nlohmann::json& json, 479 const std::vector<std::string>& systemNames) 480 { 481 std::vector<RegistryCallout> calloutEntries; 482 483 // Find the CalloutList to use based on the system type 484 const auto& calloutList = findCalloutList(json, systemNames); 485 486 // We finally found the callouts, make the objects. 487 for (const auto& callout : calloutList) 488 { 489 calloutEntries.push_back(std::move(makeRegistryCallout(callout))); 490 } 491 492 return calloutEntries; 493 } 494 495 /** 496 * @brief Returns the callouts to use when an AdditionalData key is 497 * required to find the correct entries. 498 * 499 * The JSON looks like: 500 * { 501 * "ADName": "PROC_NUM", 502 * "CalloutsWithTheirADValues": 503 * [ 504 * { 505 * "ADValue": "0", 506 * "Callouts": 507 * [ 508 * { 509 * "CalloutList": 510 * [ 511 * { 512 * "Priority": "high", 513 * "LocCode": "P1-C5" 514 * } 515 * ] 516 * } 517 * ] 518 * } 519 * ] 520 * } 521 * 522 * Note that the "Callouts" entry above is the same as the top level 523 * entry used when there is no AdditionalData key. 524 * 525 * @param[in] json - The callout JSON 526 * @param[in] systemNames - List of compatible system type names 527 * @param[in] additionalData - The AdditionalData property 528 * 529 * @return std::vector<RegistryCallout> - The callouts to use 530 */ 531 std::vector<RegistryCallout> 532 getCalloutsUsingAD(const nlohmann::json& json, 533 const std::vector<std::string>& systemNames, 534 const AdditionalData& additionalData) 535 { 536 // This indicates which AD field we'll be using 537 auto keyName = json["ADName"].get<std::string>(); 538 539 // Get the actual value from the AD data 540 auto adValue = additionalData.getValue(keyName); 541 542 if (!adValue) 543 { 544 // The AdditionalData did not contain the necessary key 545 log<level::WARNING>( 546 "The PEL message registry callouts JSON " 547 "said to use an AdditionalData key that isn't in the " 548 "AdditionalData event log property", 549 entry("ADNAME=%s\n", keyName.c_str())); 550 throw std::runtime_error{ 551 "Missing AdditionalData entry for this callout"}; 552 } 553 554 const auto& callouts = json["CalloutsWithTheirADValues"]; 555 556 // find the entry with that AD value 557 auto it = std::find_if( 558 callouts.begin(), callouts.end(), [adValue](const nlohmann::json& j) { 559 return *adValue == j["ADValue"].get<std::string>(); 560 }); 561 562 if (it == callouts.end()) 563 { 564 // This can happen if not all possible values were in the 565 // message registry and that's fine. 566 return std::vector<RegistryCallout>{}; 567 } 568 569 // Proceed to find the callouts possibly based on system type. 570 return getCalloutsWithoutAD((*it)["Callouts"], systemNames); 571 } 572 573 } // namespace helper 574 575 std::optional<Entry> Registry::lookup(const std::string& name, LookupType type, 576 bool toCache) 577 { 578 std::optional<nlohmann::json> registryTmp; 579 auto& registryOpt = (_registry) ? _registry : registryTmp; 580 if (!registryOpt) 581 { 582 registryOpt = readRegistry(_registryFile); 583 if (!registryOpt) 584 { 585 return std::nullopt; 586 } 587 else if (toCache) 588 { 589 // Save message registry in memory for peltool 590 _registry = std::move(registryTmp); 591 } 592 } 593 auto& reg = (_registry) ? _registry : registryTmp; 594 const auto& registry = reg.value(); 595 // Find an entry with this name in the PEL array. 596 auto e = std::find_if( 597 registry["PELs"].begin(), registry["PELs"].end(), 598 [&name, &type](const auto& j) { 599 return ((name == j["Name"] && type == LookupType::name) || 600 (name == j["SRC"]["ReasonCode"] && 601 type == LookupType::reasonCode)); 602 }); 603 604 if (e != registry["PELs"].end()) 605 { 606 // Fill in the Entry structure from the JSON. Most, but not all, fields 607 // are optional. 608 609 try 610 { 611 Entry entry; 612 entry.name = (*e)["Name"]; 613 614 if (e->contains("Subsystem")) 615 { 616 entry.subsystem = helper::getSubsystem((*e)["Subsystem"]); 617 } 618 619 if (e->contains("ActionFlags")) 620 { 621 entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]); 622 } 623 624 if (e->contains("MfgActionFlags")) 625 { 626 entry.mfgActionFlags = 627 helper::getActionFlags((*e)["MfgActionFlags"]); 628 } 629 630 if (e->contains("Severity")) 631 { 632 entry.severity = helper::getSeverities((*e)["Severity"]); 633 } 634 635 if (e->contains("MfgSeverity")) 636 { 637 entry.mfgSeverity = helper::getSeverities((*e)["MfgSeverity"]); 638 } 639 640 if (e->contains("EventType")) 641 { 642 entry.eventType = helper::getEventType((*e)["EventType"]); 643 } 644 645 if (e->contains("EventScope")) 646 { 647 entry.eventScope = helper::getEventScope((*e)["EventScope"]); 648 } 649 650 auto& src = (*e)["SRC"]; 651 entry.src.reasonCode = helper::getSRCReasonCode(src, name); 652 653 if (src.contains("Type")) 654 { 655 entry.src.type = helper::getSRCType(src, name); 656 } 657 else 658 { 659 entry.src.type = static_cast<uint8_t>(SRCType::bmcError); 660 } 661 662 // Now that we know the SRC type and reason code, 663 // we can get the component ID. 664 entry.componentID = helper::getComponentID( 665 entry.src.type, entry.src.reasonCode, *e, name); 666 667 if (src.contains("Words6To9")) 668 { 669 entry.src.hexwordADFields = 670 helper::getSRCHexwordFields(src, name); 671 } 672 673 if (src.contains("SymptomIDFields")) 674 { 675 entry.src.symptomID = helper::getSRCSymptomIDFields(src, name); 676 } 677 678 auto& doc = (*e)["Documentation"]; 679 entry.doc.message = doc["Message"]; 680 entry.doc.description = doc["Description"]; 681 if (doc.contains("MessageArgSources")) 682 { 683 entry.doc.messageArgSources = doc["MessageArgSources"]; 684 } 685 686 // If there are callouts defined, save the JSON for later 687 if (_loadCallouts) 688 { 689 if (e->contains("Callouts")) 690 { 691 entry.callouts = (*e)["Callouts"]; 692 } 693 else if (e->contains("CalloutsUsingAD")) 694 { 695 entry.callouts = (*e)["CalloutsUsingAD"]; 696 } 697 } 698 699 return entry; 700 } 701 catch (const std::exception& e) 702 { 703 log<level::ERR>("Found invalid message registry field", 704 entry("ERROR=%s", e.what())); 705 } 706 } 707 708 return std::nullopt; 709 } 710 711 std::optional<nlohmann::json> 712 Registry::readRegistry(const std::filesystem::path& registryFile) 713 { 714 // Look in /etc first in case someone put a test file there 715 fs::path debugFile{fs::path{debugFilePath} / registryFileName}; 716 nlohmann::json registry; 717 std::ifstream file; 718 719 if (fs::exists(debugFile)) 720 { 721 log<level::INFO>("Using debug PEL message registry"); 722 file.open(debugFile); 723 } 724 else 725 { 726 file.open(registryFile); 727 } 728 729 try 730 { 731 registry = nlohmann::json::parse(file); 732 } 733 catch (const std::exception& e) 734 { 735 log<level::ERR>("Error parsing message registry JSON", 736 entry("JSON_ERROR=%s", e.what())); 737 return std::nullopt; 738 } 739 return registry; 740 } 741 742 std::vector<RegistryCallout> 743 Registry::getCallouts(const nlohmann::json& calloutJSON, 744 const std::vector<std::string>& systemNames, 745 const AdditionalData& additionalData) 746 { 747 // The JSON may either use an AdditionalData key 748 // as an index, or not. 749 if (helper::calloutUsesAdditionalData(calloutJSON)) 750 { 751 return helper::getCalloutsUsingAD(calloutJSON, systemNames, 752 additionalData); 753 } 754 755 return helper::getCalloutsWithoutAD(calloutJSON, systemNames); 756 } 757 758 } // namespace message 759 } // namespace pels 760 } // namespace openpower 761