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