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