1 /* 2 // Copyright (c) 2018 Intel 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 /// \file entity_manager.cpp 17 18 #include "entity_manager.hpp" 19 20 #include "overlay.hpp" 21 #include "topology.hpp" 22 #include "utils.hpp" 23 #include "variant_visitors.hpp" 24 25 #include <boost/algorithm/string/case_conv.hpp> 26 #include <boost/algorithm/string/classification.hpp> 27 #include <boost/algorithm/string/predicate.hpp> 28 #include <boost/algorithm/string/replace.hpp> 29 #include <boost/algorithm/string/split.hpp> 30 #include <boost/asio/io_context.hpp> 31 #include <boost/asio/post.hpp> 32 #include <boost/asio/steady_timer.hpp> 33 #include <boost/container/flat_map.hpp> 34 #include <boost/container/flat_set.hpp> 35 #include <boost/range/iterator_range.hpp> 36 #include <nlohmann/json.hpp> 37 #include <sdbusplus/asio/connection.hpp> 38 #include <sdbusplus/asio/object_server.hpp> 39 40 #include <charconv> 41 #include <filesystem> 42 #include <fstream> 43 #include <functional> 44 #include <iostream> 45 #include <map> 46 #include <regex> 47 #include <variant> 48 constexpr const char* hostConfigurationDirectory = SYSCONF_DIR "configurations"; 49 constexpr const char* configurationDirectory = PACKAGE_DIR "configurations"; 50 constexpr const char* schemaDirectory = PACKAGE_DIR "configurations/schemas"; 51 constexpr const char* tempConfigDir = "/tmp/configuration/"; 52 constexpr const char* lastConfiguration = "/tmp/configuration/last.json"; 53 constexpr const char* currentConfiguration = "/var/configuration/system.json"; 54 constexpr const char* globalSchema = "global.json"; 55 56 const boost::container::flat_map<const char*, probe_type_codes, CmpStr> 57 probeTypes{{{"FALSE", probe_type_codes::FALSE_T}, 58 {"TRUE", probe_type_codes::TRUE_T}, 59 {"AND", probe_type_codes::AND}, 60 {"OR", probe_type_codes::OR}, 61 {"FOUND", probe_type_codes::FOUND}, 62 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}}; 63 64 static constexpr std::array<const char*, 6> settableInterfaces = { 65 "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"}; 66 using JsonVariantType = 67 std::variant<std::vector<std::string>, std::vector<double>, std::string, 68 int64_t, uint64_t, double, int32_t, uint32_t, int16_t, 69 uint16_t, uint8_t, bool>; 70 71 // store reference to all interfaces so we can destroy them later 72 boost::container::flat_map< 73 std::string, std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>> 74 inventory; 75 76 // todo: pass this through nicer 77 std::shared_ptr<sdbusplus::asio::connection> systemBus; 78 nlohmann::json lastJson; 79 80 boost::asio::io_context io; 81 82 const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]"); 83 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]"); 84 85 FoundProbeTypeT findProbeType(const std::string& probe) 86 { 87 boost::container::flat_map<const char*, probe_type_codes, 88 CmpStr>::const_iterator probeType; 89 for (probeType = probeTypes.begin(); probeType != probeTypes.end(); 90 ++probeType) 91 { 92 if (probe.find(probeType->first) != std::string::npos) 93 { 94 return probeType; 95 } 96 } 97 98 return std::nullopt; 99 } 100 101 static std::shared_ptr<sdbusplus::asio::dbus_interface> 102 createInterface(sdbusplus::asio::object_server& objServer, 103 const std::string& path, const std::string& interface, 104 const std::string& parent, bool checkNull = false) 105 { 106 // on first add we have no reason to check for null before add, as there 107 // won't be any. For dynamically added interfaces, we check for null so that 108 // a constant delete/add will not create a memory leak 109 110 auto ptr = objServer.add_interface(path, interface); 111 auto& dataVector = inventory[parent]; 112 if (checkNull) 113 { 114 auto it = std::find_if(dataVector.begin(), dataVector.end(), 115 [](const auto& p) { return p.expired(); }); 116 if (it != dataVector.end()) 117 { 118 *it = ptr; 119 return ptr; 120 } 121 } 122 dataVector.emplace_back(ptr); 123 return ptr; 124 } 125 126 // writes output files to persist data 127 bool writeJsonFiles(const nlohmann::json& systemConfiguration) 128 { 129 std::filesystem::create_directory(configurationOutDir); 130 std::ofstream output(currentConfiguration); 131 if (!output.good()) 132 { 133 return false; 134 } 135 output << systemConfiguration.dump(4); 136 output.close(); 137 return true; 138 } 139 140 template <typename JsonType> 141 bool setJsonFromPointer(const std::string& ptrStr, const JsonType& value, 142 nlohmann::json& systemConfiguration) 143 { 144 try 145 { 146 nlohmann::json::json_pointer ptr(ptrStr); 147 nlohmann::json& ref = systemConfiguration[ptr]; 148 ref = value; 149 return true; 150 } 151 catch (const std::out_of_range&) 152 { 153 return false; 154 } 155 } 156 157 // template function to add array as dbus property 158 template <typename PropertyType> 159 void addArrayToDbus(const std::string& name, const nlohmann::json& array, 160 sdbusplus::asio::dbus_interface* iface, 161 sdbusplus::asio::PropertyPermission permission, 162 nlohmann::json& systemConfiguration, 163 const std::string& jsonPointerString) 164 { 165 std::vector<PropertyType> values; 166 for (const auto& property : array) 167 { 168 auto ptr = property.get_ptr<const PropertyType*>(); 169 if (ptr != nullptr) 170 { 171 values.emplace_back(*ptr); 172 } 173 } 174 175 if (permission == sdbusplus::asio::PropertyPermission::readOnly) 176 { 177 iface->register_property(name, values); 178 } 179 else 180 { 181 iface->register_property( 182 name, values, 183 [&systemConfiguration, 184 jsonPointerString{std::string(jsonPointerString)}]( 185 const std::vector<PropertyType>& newVal, 186 std::vector<PropertyType>& val) { 187 val = newVal; 188 if (!setJsonFromPointer(jsonPointerString, val, 189 systemConfiguration)) 190 { 191 std::cerr << "error setting json field\n"; 192 return -1; 193 } 194 if (!writeJsonFiles(systemConfiguration)) 195 { 196 std::cerr << "error setting json file\n"; 197 return -1; 198 } 199 return 1; 200 }); 201 } 202 } 203 204 template <typename PropertyType> 205 void addProperty(const std::string& name, const PropertyType& value, 206 sdbusplus::asio::dbus_interface* iface, 207 nlohmann::json& systemConfiguration, 208 const std::string& jsonPointerString, 209 sdbusplus::asio::PropertyPermission permission) 210 { 211 if (permission == sdbusplus::asio::PropertyPermission::readOnly) 212 { 213 iface->register_property(name, value); 214 return; 215 } 216 iface->register_property( 217 name, value, 218 [&systemConfiguration, 219 jsonPointerString{std::string(jsonPointerString)}]( 220 const PropertyType& newVal, PropertyType& val) { 221 val = newVal; 222 if (!setJsonFromPointer(jsonPointerString, val, 223 systemConfiguration)) 224 { 225 std::cerr << "error setting json field\n"; 226 return -1; 227 } 228 if (!writeJsonFiles(systemConfiguration)) 229 { 230 std::cerr << "error setting json file\n"; 231 return -1; 232 } 233 return 1; 234 }); 235 } 236 237 void createDeleteObjectMethod( 238 const std::string& jsonPointerPath, 239 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 240 sdbusplus::asio::object_server& objServer, 241 nlohmann::json& systemConfiguration) 242 { 243 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface; 244 iface->register_method( 245 "Delete", [&objServer, &systemConfiguration, interface, 246 jsonPointerPath{std::string(jsonPointerPath)}]() { 247 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface = 248 interface.lock(); 249 if (!dbusInterface) 250 { 251 // this technically can't happen as the pointer is pointing to 252 // us 253 throw DBusInternalError(); 254 } 255 nlohmann::json::json_pointer ptr(jsonPointerPath); 256 systemConfiguration[ptr] = nullptr; 257 258 // todo(james): dig through sdbusplus to find out why we can't 259 // delete it in a method call 260 boost::asio::post(io, [&objServer, dbusInterface]() mutable { 261 objServer.remove_interface(dbusInterface); 262 }); 263 264 if (!writeJsonFiles(systemConfiguration)) 265 { 266 std::cerr << "error setting json file\n"; 267 throw DBusInternalError(); 268 } 269 }); 270 } 271 272 // adds simple json types to interface's properties 273 void populateInterfaceFromJson( 274 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath, 275 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 276 nlohmann::json& dict, sdbusplus::asio::object_server& objServer, 277 sdbusplus::asio::PropertyPermission permission = 278 sdbusplus::asio::PropertyPermission::readOnly) 279 { 280 for (const auto& [key, value] : dict.items()) 281 { 282 auto type = value.type(); 283 bool array = false; 284 if (value.type() == nlohmann::json::value_t::array) 285 { 286 array = true; 287 if (value.empty()) 288 { 289 continue; 290 } 291 type = value[0].type(); 292 bool isLegal = true; 293 for (const auto& arrayItem : value) 294 { 295 if (arrayItem.type() != type) 296 { 297 isLegal = false; 298 break; 299 } 300 } 301 if (!isLegal) 302 { 303 std::cerr << "dbus format error" << value << "\n"; 304 continue; 305 } 306 } 307 if (type == nlohmann::json::value_t::object) 308 { 309 continue; // handled elsewhere 310 } 311 312 std::string path = jsonPointerPath; 313 path.append("/").append(key); 314 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 315 { 316 // all setable numbers are doubles as it is difficult to always 317 // create a configuration file with all whole numbers as decimals 318 // i.e. 1.0 319 if (array) 320 { 321 if (value[0].is_number()) 322 { 323 type = nlohmann::json::value_t::number_float; 324 } 325 } 326 else if (value.is_number()) 327 { 328 type = nlohmann::json::value_t::number_float; 329 } 330 } 331 332 switch (type) 333 { 334 case (nlohmann::json::value_t::boolean): 335 { 336 if (array) 337 { 338 // todo: array of bool isn't detected correctly by 339 // sdbusplus, change it to numbers 340 addArrayToDbus<uint64_t>(key, value, iface.get(), 341 permission, systemConfiguration, 342 path); 343 } 344 345 else 346 { 347 addProperty(key, value.get<bool>(), iface.get(), 348 systemConfiguration, path, permission); 349 } 350 break; 351 } 352 case (nlohmann::json::value_t::number_integer): 353 { 354 if (array) 355 { 356 addArrayToDbus<int64_t>(key, value, iface.get(), permission, 357 systemConfiguration, path); 358 } 359 else 360 { 361 addProperty(key, value.get<int64_t>(), iface.get(), 362 systemConfiguration, path, 363 sdbusplus::asio::PropertyPermission::readOnly); 364 } 365 break; 366 } 367 case (nlohmann::json::value_t::number_unsigned): 368 { 369 if (array) 370 { 371 addArrayToDbus<uint64_t>(key, value, iface.get(), 372 permission, systemConfiguration, 373 path); 374 } 375 else 376 { 377 addProperty(key, value.get<uint64_t>(), iface.get(), 378 systemConfiguration, path, 379 sdbusplus::asio::PropertyPermission::readOnly); 380 } 381 break; 382 } 383 case (nlohmann::json::value_t::number_float): 384 { 385 if (array) 386 { 387 addArrayToDbus<double>(key, value, iface.get(), permission, 388 systemConfiguration, path); 389 } 390 391 else 392 { 393 addProperty(key, value.get<double>(), iface.get(), 394 systemConfiguration, path, permission); 395 } 396 break; 397 } 398 case (nlohmann::json::value_t::string): 399 { 400 if (array) 401 { 402 addArrayToDbus<std::string>(key, value, iface.get(), 403 permission, systemConfiguration, 404 path); 405 } 406 else 407 { 408 addProperty(key, value.get<std::string>(), iface.get(), 409 systemConfiguration, path, permission); 410 } 411 break; 412 } 413 default: 414 { 415 std::cerr << "Unexpected json type in system configuration " 416 << key << ": " << value.type_name() << "\n"; 417 break; 418 } 419 } 420 } 421 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 422 { 423 createDeleteObjectMethod(jsonPointerPath, iface, objServer, 424 systemConfiguration); 425 } 426 iface->initialize(); 427 } 428 429 sdbusplus::asio::PropertyPermission getPermission(const std::string& interface) 430 { 431 return std::find(settableInterfaces.begin(), settableInterfaces.end(), 432 interface) != settableInterfaces.end() 433 ? sdbusplus::asio::PropertyPermission::readWrite 434 : sdbusplus::asio::PropertyPermission::readOnly; 435 } 436 437 void createAddObjectMethod(const std::string& jsonPointerPath, 438 const std::string& path, 439 nlohmann::json& systemConfiguration, 440 sdbusplus::asio::object_server& objServer, 441 const std::string& board) 442 { 443 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface( 444 objServer, path, "xyz.openbmc_project.AddObject", board); 445 446 iface->register_method( 447 "AddObject", 448 [&systemConfiguration, &objServer, 449 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)}, 450 board](const boost::container::flat_map<std::string, JsonVariantType>& 451 data) { 452 nlohmann::json::json_pointer ptr(jsonPointerPath); 453 nlohmann::json& base = systemConfiguration[ptr]; 454 auto findExposes = base.find("Exposes"); 455 456 if (findExposes == base.end()) 457 { 458 throw std::invalid_argument("Entity must have children."); 459 } 460 461 // this will throw invalid-argument to sdbusplus if invalid json 462 nlohmann::json newData{}; 463 for (const auto& item : data) 464 { 465 nlohmann::json& newJson = newData[item.first]; 466 std::visit( 467 [&newJson](auto&& val) { 468 newJson = std::forward<decltype(val)>(val); 469 }, 470 item.second); 471 } 472 473 auto findName = newData.find("Name"); 474 auto findType = newData.find("Type"); 475 if (findName == newData.end() || findType == newData.end()) 476 { 477 throw std::invalid_argument("AddObject missing Name or Type"); 478 } 479 const std::string* type = findType->get_ptr<const std::string*>(); 480 const std::string* name = findName->get_ptr<const std::string*>(); 481 if (type == nullptr || name == nullptr) 482 { 483 throw std::invalid_argument("Type and Name must be a string."); 484 } 485 486 bool foundNull = false; 487 size_t lastIndex = 0; 488 // we add in the "exposes" 489 for (const auto& expose : *findExposes) 490 { 491 if (expose.is_null()) 492 { 493 foundNull = true; 494 continue; 495 } 496 497 if (expose["Name"] == *name && expose["Type"] == *type) 498 { 499 throw std::invalid_argument( 500 "Field already in JSON, not adding"); 501 } 502 503 if (foundNull) 504 { 505 continue; 506 } 507 508 lastIndex++; 509 } 510 511 std::ifstream schemaFile(std::string(schemaDirectory) + "/" + 512 boost::to_lower_copy(*type) + ".json"); 513 // todo(james) we might want to also make a list of 'can add' 514 // interfaces but for now I think the assumption if there is a 515 // schema avaliable that it is allowed to update is fine 516 if (!schemaFile.good()) 517 { 518 throw std::invalid_argument( 519 "No schema avaliable, cannot validate."); 520 } 521 nlohmann::json schema = 522 nlohmann::json::parse(schemaFile, nullptr, false); 523 if (schema.is_discarded()) 524 { 525 std::cerr << "Schema not legal" << *type << ".json\n"; 526 throw DBusInternalError(); 527 } 528 if (!validateJson(schema, newData)) 529 { 530 throw std::invalid_argument("Data does not match schema"); 531 } 532 if (foundNull) 533 { 534 findExposes->at(lastIndex) = newData; 535 } 536 else 537 { 538 findExposes->push_back(newData); 539 } 540 if (!writeJsonFiles(systemConfiguration)) 541 { 542 std::cerr << "Error writing json files\n"; 543 throw DBusInternalError(); 544 } 545 std::string dbusName = *name; 546 547 std::regex_replace(dbusName.begin(), dbusName.begin(), 548 dbusName.end(), illegalDbusMemberRegex, "_"); 549 550 std::shared_ptr<sdbusplus::asio::dbus_interface> interface = 551 createInterface(objServer, path + "/" + dbusName, 552 "xyz.openbmc_project.Configuration." + *type, 553 board, true); 554 // permission is read-write, as since we just created it, must be 555 // runtime modifiable 556 populateInterfaceFromJson( 557 systemConfiguration, 558 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), 559 interface, newData, objServer, 560 sdbusplus::asio::PropertyPermission::readWrite); 561 }); 562 iface->initialize(); 563 } 564 565 void postToDbus(const nlohmann::json& newConfiguration, 566 nlohmann::json& systemConfiguration, 567 sdbusplus::asio::object_server& objServer) 568 569 { 570 Topology topology; 571 572 // iterate through boards 573 for (const auto& [boardId, boardConfig] : newConfiguration.items()) 574 { 575 std::string boardKey = boardConfig["Name"]; 576 std::string boardKeyOrig = boardConfig["Name"]; 577 std::string jsonPointerPath = "/" + boardId; 578 // loop through newConfiguration, but use values from system 579 // configuration to be able to modify via dbus later 580 auto boardValues = systemConfiguration[boardId]; 581 auto findBoardType = boardValues.find("Type"); 582 std::string boardType; 583 if (findBoardType != boardValues.end() && 584 findBoardType->type() == nlohmann::json::value_t::string) 585 { 586 boardType = findBoardType->get<std::string>(); 587 std::regex_replace(boardType.begin(), boardType.begin(), 588 boardType.end(), illegalDbusMemberRegex, "_"); 589 } 590 else 591 { 592 std::cerr << "Unable to find type for " << boardKey 593 << " reverting to Chassis.\n"; 594 boardType = "Chassis"; 595 } 596 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType); 597 598 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(), 599 illegalDbusMemberRegex, "_"); 600 std::string boardName = "/xyz/openbmc_project/inventory/system/"; 601 boardName += boardtypeLower; 602 boardName += "/"; 603 boardName += boardKey; 604 605 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface = 606 createInterface(objServer, boardName, 607 "xyz.openbmc_project.Inventory.Item", boardKey); 608 609 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface = 610 createInterface(objServer, boardName, 611 "xyz.openbmc_project.Inventory.Item." + boardType, 612 boardKeyOrig); 613 614 createAddObjectMethod(jsonPointerPath, boardName, systemConfiguration, 615 objServer, boardKeyOrig); 616 617 populateInterfaceFromJson(systemConfiguration, jsonPointerPath, 618 boardIface, boardValues, objServer); 619 jsonPointerPath += "/"; 620 // iterate through board properties 621 for (const auto& [propName, propValue] : boardValues.items()) 622 { 623 if (propValue.type() == nlohmann::json::value_t::object) 624 { 625 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 626 createInterface(objServer, boardName, propName, 627 boardKeyOrig); 628 629 populateInterfaceFromJson(systemConfiguration, 630 jsonPointerPath + propName, iface, 631 propValue, objServer); 632 } 633 } 634 635 auto exposes = boardValues.find("Exposes"); 636 if (exposes == boardValues.end()) 637 { 638 continue; 639 } 640 // iterate through exposes 641 jsonPointerPath += "Exposes/"; 642 643 // store the board level pointer so we can modify it on the way down 644 std::string jsonPointerPathBoard = jsonPointerPath; 645 size_t exposesIndex = -1; 646 for (auto& item : *exposes) 647 { 648 exposesIndex++; 649 jsonPointerPath = jsonPointerPathBoard; 650 jsonPointerPath += std::to_string(exposesIndex); 651 652 auto findName = item.find("Name"); 653 if (findName == item.end()) 654 { 655 std::cerr << "cannot find name in field " << item << "\n"; 656 continue; 657 } 658 auto findStatus = item.find("Status"); 659 // if status is not found it is assumed to be status = 'okay' 660 if (findStatus != item.end()) 661 { 662 if (*findStatus == "disabled") 663 { 664 continue; 665 } 666 } 667 auto findType = item.find("Type"); 668 std::string itemType; 669 if (findType != item.end()) 670 { 671 itemType = findType->get<std::string>(); 672 std::regex_replace(itemType.begin(), itemType.begin(), 673 itemType.end(), illegalDbusPathRegex, "_"); 674 } 675 else 676 { 677 itemType = "unknown"; 678 } 679 std::string itemName = findName->get<std::string>(); 680 std::regex_replace(itemName.begin(), itemName.begin(), 681 itemName.end(), illegalDbusMemberRegex, "_"); 682 std::string ifacePath = boardName; 683 ifacePath += "/"; 684 ifacePath += itemName; 685 686 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface = 687 createInterface(objServer, ifacePath, 688 "xyz.openbmc_project.Configuration." + itemType, 689 boardKeyOrig); 690 691 if (itemType == "BMC") 692 { 693 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface = 694 createInterface(objServer, ifacePath, 695 "xyz.openbmc_project.Inventory.Item.Bmc", 696 boardKeyOrig); 697 populateInterfaceFromJson(systemConfiguration, jsonPointerPath, 698 bmcIface, item, objServer, 699 getPermission(itemType)); 700 } 701 702 populateInterfaceFromJson(systemConfiguration, jsonPointerPath, 703 itemIface, item, objServer, 704 getPermission(itemType)); 705 706 for (const auto& [name, config] : item.items()) 707 { 708 jsonPointerPath = jsonPointerPathBoard; 709 jsonPointerPath.append(std::to_string(exposesIndex)) 710 .append("/") 711 .append(name); 712 if (config.type() == nlohmann::json::value_t::object) 713 { 714 std::string ifaceName = 715 "xyz.openbmc_project.Configuration."; 716 ifaceName.append(itemType).append(".").append(name); 717 718 std::shared_ptr<sdbusplus::asio::dbus_interface> 719 objectIface = createInterface(objServer, ifacePath, 720 ifaceName, boardKeyOrig); 721 722 populateInterfaceFromJson( 723 systemConfiguration, jsonPointerPath, objectIface, 724 config, objServer, getPermission(name)); 725 } 726 else if (config.type() == nlohmann::json::value_t::array) 727 { 728 size_t index = 0; 729 if (config.empty()) 730 { 731 continue; 732 } 733 bool isLegal = true; 734 auto type = config[0].type(); 735 if (type != nlohmann::json::value_t::object) 736 { 737 continue; 738 } 739 740 // verify legal json 741 for (const auto& arrayItem : config) 742 { 743 if (arrayItem.type() != type) 744 { 745 isLegal = false; 746 break; 747 } 748 } 749 if (!isLegal) 750 { 751 std::cerr << "dbus format error" << config << "\n"; 752 break; 753 } 754 755 for (auto& arrayItem : config) 756 { 757 std::string ifaceName = 758 "xyz.openbmc_project.Configuration."; 759 ifaceName.append(itemType).append(".").append(name); 760 ifaceName.append(std::to_string(index)); 761 762 std::shared_ptr<sdbusplus::asio::dbus_interface> 763 objectIface = createInterface( 764 objServer, ifacePath, ifaceName, boardKeyOrig); 765 766 populateInterfaceFromJson( 767 systemConfiguration, 768 jsonPointerPath + "/" + std::to_string(index), 769 objectIface, arrayItem, objServer, 770 getPermission(name)); 771 index++; 772 } 773 } 774 } 775 776 topology.addBoard(boardName, boardType, item); 777 } 778 } 779 780 for (const auto& boardAssoc : topology.getAssocs()) 781 { 782 auto ifacePtr = objServer.add_interface( 783 boardAssoc.first, "xyz.openbmc_project.Association.Definitions"); 784 785 ifacePtr->register_property("Associations", boardAssoc.second); 786 ifacePtr->initialize(); 787 } 788 } 789 790 // reads json files out of the filesystem 791 bool loadConfigurations(std::list<nlohmann::json>& configurations) 792 { 793 // find configuration files 794 std::vector<std::filesystem::path> jsonPaths; 795 if (!findFiles( 796 std::vector<std::filesystem::path>{configurationDirectory, 797 hostConfigurationDirectory}, 798 R"(.*\.json)", jsonPaths)) 799 { 800 std::cerr << "Unable to find any configuration files in " 801 << configurationDirectory << "\n"; 802 return false; 803 } 804 805 std::ifstream schemaStream(std::string(schemaDirectory) + "/" + 806 globalSchema); 807 if (!schemaStream.good()) 808 { 809 std::cerr 810 << "Cannot open schema file, cannot validate JSON, exiting\n\n"; 811 std::exit(EXIT_FAILURE); 812 return false; 813 } 814 nlohmann::json schema = nlohmann::json::parse(schemaStream, nullptr, false); 815 if (schema.is_discarded()) 816 { 817 std::cerr 818 << "Illegal schema file detected, cannot validate JSON, exiting\n"; 819 std::exit(EXIT_FAILURE); 820 return false; 821 } 822 823 for (auto& jsonPath : jsonPaths) 824 { 825 std::ifstream jsonStream(jsonPath.c_str()); 826 if (!jsonStream.good()) 827 { 828 std::cerr << "unable to open " << jsonPath.string() << "\n"; 829 continue; 830 } 831 auto data = nlohmann::json::parse(jsonStream, nullptr, false); 832 if (data.is_discarded()) 833 { 834 std::cerr << "syntax error in " << jsonPath.string() << "\n"; 835 continue; 836 } 837 /* 838 * todo(james): reenable this once less things are in flight 839 * 840 if (!validateJson(schema, data)) 841 { 842 std::cerr << "Error validating " << jsonPath.string() << "\n"; 843 continue; 844 } 845 */ 846 847 if (data.type() == nlohmann::json::value_t::array) 848 { 849 for (auto& d : data) 850 { 851 configurations.emplace_back(d); 852 } 853 } 854 else 855 { 856 configurations.emplace_back(data); 857 } 858 } 859 return true; 860 } 861 862 static bool deviceRequiresPowerOn(const nlohmann::json& entity) 863 { 864 auto powerState = entity.find("PowerState"); 865 if (powerState == entity.end()) 866 { 867 return false; 868 } 869 870 const auto* ptr = powerState->get_ptr<const std::string*>(); 871 if (ptr == nullptr) 872 { 873 return false; 874 } 875 876 return *ptr == "On" || *ptr == "BiosPost"; 877 } 878 879 static void pruneDevice(const nlohmann::json& systemConfiguration, 880 const bool powerOff, const bool scannedPowerOff, 881 const std::string& name, const nlohmann::json& device) 882 { 883 if (systemConfiguration.contains(name)) 884 { 885 return; 886 } 887 888 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff)) 889 { 890 return; 891 } 892 893 logDeviceRemoved(device); 894 } 895 896 void startRemovedTimer(boost::asio::steady_timer& timer, 897 nlohmann::json& systemConfiguration) 898 { 899 static bool scannedPowerOff = false; 900 static bool scannedPowerOn = false; 901 902 if (systemConfiguration.empty() || lastJson.empty()) 903 { 904 return; // not ready yet 905 } 906 if (scannedPowerOn) 907 { 908 return; 909 } 910 911 if (!isPowerOn() && scannedPowerOff) 912 { 913 return; 914 } 915 916 timer.expires_after(std::chrono::seconds(10)); 917 timer.async_wait( 918 [&systemConfiguration](const boost::system::error_code& ec) { 919 if (ec == boost::asio::error::operation_aborted) 920 { 921 return; 922 } 923 924 bool powerOff = !isPowerOn(); 925 for (const auto& [name, device] : lastJson.items()) 926 { 927 pruneDevice(systemConfiguration, powerOff, scannedPowerOff, 928 name, device); 929 } 930 931 scannedPowerOff = true; 932 if (!powerOff) 933 { 934 scannedPowerOn = true; 935 } 936 }); 937 } 938 939 static std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>& 940 getDeviceInterfaces(const nlohmann::json& device) 941 { 942 return inventory[device["Name"].get<std::string>()]; 943 } 944 945 static void pruneConfiguration(nlohmann::json& systemConfiguration, 946 sdbusplus::asio::object_server& objServer, 947 bool powerOff, const std::string& name, 948 const nlohmann::json& device) 949 { 950 if (powerOff && deviceRequiresPowerOn(device)) 951 { 952 // power not on yet, don't know if it's there or not 953 return; 954 } 955 956 auto& ifaces = getDeviceInterfaces(device); 957 for (auto& iface : ifaces) 958 { 959 auto sharedPtr = iface.lock(); 960 if (!!sharedPtr) 961 { 962 objServer.remove_interface(sharedPtr); 963 } 964 } 965 966 ifaces.clear(); 967 systemConfiguration.erase(name); 968 logDeviceRemoved(device); 969 } 970 971 static void deriveNewConfiguration(const nlohmann::json& oldConfiguration, 972 nlohmann::json& newConfiguration) 973 { 974 for (auto it = newConfiguration.begin(); it != newConfiguration.end();) 975 { 976 auto findKey = oldConfiguration.find(it.key()); 977 if (findKey != oldConfiguration.end()) 978 { 979 it = newConfiguration.erase(it); 980 } 981 else 982 { 983 it++; 984 } 985 } 986 } 987 988 static void publishNewConfiguration( 989 const size_t& instance, const size_t count, 990 boost::asio::steady_timer& timer, nlohmann::json& systemConfiguration, 991 // Gerrit discussion: 992 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6 993 // 994 // Discord discussion: 995 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854 996 // 997 // NOLINTNEXTLINE(performance-unnecessary-value-param) 998 const nlohmann::json newConfiguration, 999 sdbusplus::asio::object_server& objServer) 1000 { 1001 loadOverlays(newConfiguration); 1002 1003 boost::asio::post(io, [systemConfiguration]() { 1004 if (!writeJsonFiles(systemConfiguration)) 1005 { 1006 std::cerr << "Error writing json files\n"; 1007 } 1008 }); 1009 1010 boost::asio::post(io, [&instance, count, &timer, newConfiguration, 1011 &systemConfiguration, &objServer]() { 1012 postToDbus(newConfiguration, systemConfiguration, objServer); 1013 if (count == instance) 1014 { 1015 startRemovedTimer(timer, systemConfiguration); 1016 } 1017 }); 1018 } 1019 1020 // main properties changed entry 1021 void propertiesChangedCallback(nlohmann::json& systemConfiguration, 1022 sdbusplus::asio::object_server& objServer) 1023 { 1024 static bool inProgress = false; 1025 static boost::asio::steady_timer timer(io); 1026 static size_t instance = 0; 1027 instance++; 1028 size_t count = instance; 1029 1030 timer.expires_after(std::chrono::seconds(5)); 1031 1032 // setup an async wait as we normally get flooded with new requests 1033 timer.async_wait([&systemConfiguration, &objServer, 1034 count](const boost::system::error_code& ec) { 1035 if (ec == boost::asio::error::operation_aborted) 1036 { 1037 // we were cancelled 1038 return; 1039 } 1040 if (ec) 1041 { 1042 std::cerr << "async wait error " << ec << "\n"; 1043 return; 1044 } 1045 1046 if (inProgress) 1047 { 1048 propertiesChangedCallback(systemConfiguration, objServer); 1049 return; 1050 } 1051 inProgress = true; 1052 1053 nlohmann::json oldConfiguration = systemConfiguration; 1054 auto missingConfigurations = std::make_shared<nlohmann::json>(); 1055 *missingConfigurations = systemConfiguration; 1056 1057 std::list<nlohmann::json> configurations; 1058 if (!loadConfigurations(configurations)) 1059 { 1060 std::cerr << "Could not load configurations\n"; 1061 inProgress = false; 1062 return; 1063 } 1064 1065 auto perfScan = std::make_shared<PerformScan>( 1066 systemConfiguration, *missingConfigurations, configurations, 1067 objServer, 1068 [&systemConfiguration, &objServer, count, oldConfiguration, 1069 missingConfigurations]() { 1070 // this is something that since ac has been applied to the bmc 1071 // we saw, and we no longer see it 1072 bool powerOff = !isPowerOn(); 1073 for (const auto& [name, device] : 1074 missingConfigurations->items()) 1075 { 1076 pruneConfiguration(systemConfiguration, objServer, powerOff, 1077 name, device); 1078 } 1079 1080 nlohmann::json newConfiguration = systemConfiguration; 1081 1082 deriveNewConfiguration(oldConfiguration, newConfiguration); 1083 1084 for (const auto& [_, device] : newConfiguration.items()) 1085 { 1086 logDeviceAdded(device); 1087 } 1088 1089 inProgress = false; 1090 1091 boost::asio::post( 1092 io, std::bind_front( 1093 publishNewConfiguration, std::ref(instance), count, 1094 std::ref(timer), std::ref(systemConfiguration), 1095 newConfiguration, std::ref(objServer))); 1096 }); 1097 perfScan->run(); 1098 }); 1099 } 1100 1101 int main() 1102 { 1103 // setup connection to dbus 1104 systemBus = std::make_shared<sdbusplus::asio::connection>(io); 1105 systemBus->request_name("xyz.openbmc_project.EntityManager"); 1106 1107 // The EntityManager object itself doesn't expose any properties. 1108 // No need to set up ObjectManager for the |EntityManager| object. 1109 sdbusplus::asio::object_server objServer(systemBus, /*skipManager=*/true); 1110 1111 // All other objects that EntityManager currently support are under the 1112 // inventory subtree. 1113 // See the discussion at 1114 // https://discord.com/channels/775381525260664832/1018929092009144380 1115 objServer.add_manager("/xyz/openbmc_project/inventory"); 1116 1117 std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface = 1118 objServer.add_interface("/xyz/openbmc_project/EntityManager", 1119 "xyz.openbmc_project.EntityManager"); 1120 1121 // to keep reference to the match / filter objects so they don't get 1122 // destroyed 1123 1124 nlohmann::json systemConfiguration = nlohmann::json::object(); 1125 1126 // We need a poke from DBus for static providers that create all their 1127 // objects prior to claiming a well-known name, and thus don't emit any 1128 // org.freedesktop.DBus.Properties signals. Similarly if a process exits 1129 // for any reason, expected or otherwise, we'll need a poke to remove 1130 // entities from DBus. 1131 sdbusplus::bus::match_t nameOwnerChangedMatch( 1132 static_cast<sdbusplus::bus_t&>(*systemBus), 1133 sdbusplus::bus::match::rules::nameOwnerChanged(), 1134 [&](sdbusplus::message_t& m) { 1135 auto [name, oldOwner, newOwner] = 1136 m.unpack<std::string, std::string, std::string>(); 1137 1138 if (name.starts_with(':')) 1139 { 1140 // We should do nothing with unique-name connections. 1141 return; 1142 } 1143 1144 propertiesChangedCallback(systemConfiguration, objServer); 1145 }); 1146 // We also need a poke from DBus when new interfaces are created or 1147 // destroyed. 1148 sdbusplus::bus::match_t interfacesAddedMatch( 1149 static_cast<sdbusplus::bus_t&>(*systemBus), 1150 sdbusplus::bus::match::rules::interfacesAdded(), 1151 [&](sdbusplus::message_t&) { 1152 propertiesChangedCallback(systemConfiguration, objServer); 1153 }); 1154 sdbusplus::bus::match_t interfacesRemovedMatch( 1155 static_cast<sdbusplus::bus_t&>(*systemBus), 1156 sdbusplus::bus::match::rules::interfacesRemoved(), 1157 [&](sdbusplus::message_t&) { 1158 propertiesChangedCallback(systemConfiguration, objServer); 1159 }); 1160 1161 boost::asio::post(io, [&]() { 1162 propertiesChangedCallback(systemConfiguration, objServer); 1163 }); 1164 1165 entityIface->register_method("ReScan", [&]() { 1166 propertiesChangedCallback(systemConfiguration, objServer); 1167 }); 1168 entityIface->initialize(); 1169 1170 if (fwVersionIsSame()) 1171 { 1172 if (std::filesystem::is_regular_file(currentConfiguration)) 1173 { 1174 // this file could just be deleted, but it's nice for debug 1175 std::filesystem::create_directory(tempConfigDir); 1176 std::filesystem::remove(lastConfiguration); 1177 std::filesystem::copy(currentConfiguration, lastConfiguration); 1178 std::filesystem::remove(currentConfiguration); 1179 1180 std::ifstream jsonStream(lastConfiguration); 1181 if (jsonStream.good()) 1182 { 1183 auto data = nlohmann::json::parse(jsonStream, nullptr, false); 1184 if (data.is_discarded()) 1185 { 1186 std::cerr << "syntax error in " << lastConfiguration 1187 << "\n"; 1188 } 1189 else 1190 { 1191 lastJson = std::move(data); 1192 } 1193 } 1194 else 1195 { 1196 std::cerr << "unable to open " << lastConfiguration << "\n"; 1197 } 1198 } 1199 } 1200 else 1201 { 1202 // not an error, just logging at this level to make it in the journal 1203 std::cerr << "Clearing previous configuration\n"; 1204 std::filesystem::remove(currentConfiguration); 1205 } 1206 1207 // some boards only show up after power is on, we want to not say they are 1208 // removed until the same state happens 1209 setupPowerMatch(systemBus); 1210 1211 io.run(); 1212 1213 return 0; 1214 } 1215