1 // Copyright (c) 2018 Intel Corporation 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #pragma once 16 #include "filesystem.hpp" 17 18 #include <crow/app.h> 19 #include <tinyxml2.h> 20 21 #include <async_resp.hpp> 22 #include <boost/algorithm/string.hpp> 23 #include <boost/container/flat_set.hpp> 24 #include <dbus_singleton.hpp> 25 #include <dbus_utility.hpp> 26 #include <fstream> 27 #include <sdbusplus/message/types.hpp> 28 29 namespace crow 30 { 31 namespace openbmc_mapper 32 { 33 34 using GetSubTreeType = std::vector< 35 std::pair<std::string, 36 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 37 38 const std::string notFoundMsg = "404 Not Found"; 39 const std::string badReqMsg = "400 Bad Request"; 40 const std::string methodNotAllowedMsg = "405 Method Not Allowed"; 41 const std::string forbiddenMsg = "403 Forbidden"; 42 const std::string methodFailedMsg = "500 Method Call Failed"; 43 const std::string methodOutputFailedMsg = "500 Method Output Error"; 44 45 const std::string notFoundDesc = 46 "org.freedesktop.DBus.Error.FileNotFound: path or object not found"; 47 const std::string propNotFoundDesc = "The specified property cannot be found"; 48 const std::string noJsonDesc = "No JSON object could be decoded"; 49 const std::string methodNotFoundDesc = "The specified method cannot be found"; 50 const std::string methodNotAllowedDesc = "Method not allowed"; 51 const std::string forbiddenPropDesc = 52 "The specified property cannot be created"; 53 const std::string forbiddenResDesc = "The specified resource cannot be created"; 54 55 void setErrorResponse(crow::Response &res, boost::beast::http::status result, 56 const std::string &desc, const std::string &msg) 57 { 58 res.result(result); 59 res.jsonValue = {{"data", {{"description", desc}}}, 60 {"message", msg}, 61 {"status", "error"}}; 62 } 63 64 void introspectObjects(const std::string &processName, 65 const std::string &objectPath, 66 std::shared_ptr<bmcweb::AsyncResp> transaction) 67 { 68 if (transaction->res.jsonValue.is_null()) 69 { 70 transaction->res.jsonValue = {{"status", "ok"}, 71 {"bus_name", processName}, 72 {"objects", nlohmann::json::array()}}; 73 } 74 75 crow::connections::systemBus->async_method_call( 76 [transaction, processName{std::string(processName)}, 77 objectPath{std::string(objectPath)}]( 78 const boost::system::error_code ec, 79 const std::string &introspect_xml) { 80 if (ec) 81 { 82 BMCWEB_LOG_ERROR 83 << "Introspect call failed with error: " << ec.message() 84 << " on process: " << processName << " path: " << objectPath 85 << "\n"; 86 return; 87 } 88 transaction->res.jsonValue["objects"].push_back( 89 {{"path", objectPath}}); 90 91 tinyxml2::XMLDocument doc; 92 93 doc.Parse(introspect_xml.c_str()); 94 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 95 if (pRoot == nullptr) 96 { 97 BMCWEB_LOG_ERROR << "XML document failed to parse " 98 << processName << " " << objectPath << "\n"; 99 } 100 else 101 { 102 tinyxml2::XMLElement *node = pRoot->FirstChildElement("node"); 103 while (node != nullptr) 104 { 105 const char *childPath = node->Attribute("name"); 106 if (childPath != nullptr) 107 { 108 std::string newpath; 109 if (objectPath != "/") 110 { 111 newpath += objectPath; 112 } 113 newpath += std::string("/") + childPath; 114 // introspect the subobjects as well 115 introspectObjects(processName, newpath, transaction); 116 } 117 118 node = node->NextSiblingElement("node"); 119 } 120 } 121 }, 122 processName, objectPath, "org.freedesktop.DBus.Introspectable", 123 "Introspect"); 124 } 125 126 void getPropertiesForEnumerate(const std::string &objectPath, 127 const std::string &service, 128 const std::string &interface, 129 std::shared_ptr<bmcweb::AsyncResp> asyncResp) 130 { 131 BMCWEB_LOG_DEBUG << "getPropertiesForEnumerate " << objectPath << " " 132 << service << " " << interface; 133 134 crow::connections::systemBus->async_method_call( 135 [asyncResp, objectPath, service, 136 interface](const boost::system::error_code ec, 137 const std::vector< 138 std::pair<std::string, dbus::utility::DbusVariantType>> 139 &propertiesList) { 140 if (ec) 141 { 142 BMCWEB_LOG_ERROR << "GetAll on path " << objectPath << " iface " 143 << interface << " service " << service 144 << " failed with code " << ec; 145 return; 146 } 147 148 nlohmann::json &dataJson = asyncResp->res.jsonValue["data"]; 149 nlohmann::json &objectJson = dataJson[objectPath]; 150 if (objectJson.is_null()) 151 { 152 objectJson = nlohmann::json::object(); 153 } 154 155 for (const auto &[name, value] : propertiesList) 156 { 157 nlohmann::json &propertyJson = objectJson[name]; 158 sdbusplus::message::variant_ns::visit( 159 [&propertyJson](auto &&val) { propertyJson = val; }, value); 160 } 161 }, 162 service, objectPath, "org.freedesktop.DBus.Properties", "GetAll", 163 interface); 164 } 165 166 // Find any results that weren't picked up by ObjectManagers, to be 167 // called after all ObjectManagers are searched for and called. 168 void findRemainingObjectsForEnumerate( 169 const std::string &objectPath, std::shared_ptr<GetSubTreeType> subtree, 170 std::shared_ptr<bmcweb::AsyncResp> asyncResp) 171 { 172 BMCWEB_LOG_DEBUG << "findRemainingObjectsForEnumerate"; 173 const nlohmann::json &dataJson = asyncResp->res.jsonValue["data"]; 174 175 for (const auto &[path, interface_map] : *subtree) 176 { 177 if (path == objectPath) 178 { 179 // An enumerate does not return the target path's properties 180 continue; 181 } 182 if (dataJson.find(path) == dataJson.end()) 183 { 184 for (const auto &[service, interfaces] : interface_map) 185 { 186 for (const auto &interface : interfaces) 187 { 188 if (!boost::starts_with(interface, "org.freedesktop.DBus")) 189 { 190 getPropertiesForEnumerate(path, service, interface, 191 asyncResp); 192 } 193 } 194 } 195 } 196 } 197 } 198 199 struct InProgressEnumerateData 200 { 201 InProgressEnumerateData(const std::string &objectPath, 202 std::shared_ptr<bmcweb::AsyncResp> asyncResp) : 203 objectPath(objectPath), 204 asyncResp(asyncResp) 205 { 206 } 207 208 ~InProgressEnumerateData() 209 { 210 findRemainingObjectsForEnumerate(objectPath, subtree, asyncResp); 211 } 212 213 const std::string objectPath; 214 std::shared_ptr<GetSubTreeType> subtree; 215 std::shared_ptr<bmcweb::AsyncResp> asyncResp; 216 }; 217 218 void getManagedObjectsForEnumerate( 219 const std::string &object_name, const std::string &object_manager_path, 220 const std::string &connection_name, 221 std::shared_ptr<InProgressEnumerateData> transaction) 222 { 223 BMCWEB_LOG_DEBUG << "getManagedObjectsForEnumerate " << object_name 224 << " object_manager_path " << object_manager_path 225 << " connection_name " << connection_name; 226 crow::connections::systemBus->async_method_call( 227 [transaction, object_name, 228 connection_name](const boost::system::error_code ec, 229 const dbus::utility::ManagedObjectType &objects) { 230 if (ec) 231 { 232 BMCWEB_LOG_ERROR << "GetManagedObjects on path " << object_name 233 << " on connection " << connection_name 234 << " failed with code " << ec; 235 return; 236 } 237 238 nlohmann::json &dataJson = 239 transaction->asyncResp->res.jsonValue["data"]; 240 241 for (const auto &objectPath : objects) 242 { 243 if (boost::starts_with(objectPath.first.str, object_name)) 244 { 245 BMCWEB_LOG_DEBUG << "Reading object " 246 << objectPath.first.str; 247 nlohmann::json &objectJson = dataJson[objectPath.first.str]; 248 if (objectJson.is_null()) 249 { 250 objectJson = nlohmann::json::object(); 251 } 252 for (const auto &interface : objectPath.second) 253 { 254 for (const auto &property : interface.second) 255 { 256 nlohmann::json &propertyJson = 257 objectJson[property.first]; 258 sdbusplus::message::variant_ns::visit( 259 [&propertyJson](auto &&val) { 260 propertyJson = val; 261 }, 262 property.second); 263 } 264 } 265 } 266 for (const auto &interface : objectPath.second) 267 { 268 if (interface.first == "org.freedesktop.DBus.ObjectManager") 269 { 270 getManagedObjectsForEnumerate( 271 objectPath.first.str, objectPath.first.str, 272 connection_name, transaction); 273 } 274 } 275 } 276 }, 277 connection_name, object_manager_path, 278 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 279 } 280 281 void findObjectManagerPathForEnumerate( 282 const std::string &object_name, const std::string &connection_name, 283 std::shared_ptr<InProgressEnumerateData> transaction) 284 { 285 BMCWEB_LOG_DEBUG << "Finding objectmanager for path " << object_name 286 << " on connection:" << connection_name; 287 crow::connections::systemBus->async_method_call( 288 [transaction, object_name, connection_name]( 289 const boost::system::error_code ec, 290 const boost::container::flat_map< 291 std::string, boost::container::flat_map< 292 std::string, std::vector<std::string>>> 293 &objects) { 294 if (ec) 295 { 296 BMCWEB_LOG_ERROR << "GetAncestors on path " << object_name 297 << " failed with code " << ec; 298 return; 299 } 300 301 for (const auto &pathGroup : objects) 302 { 303 for (const auto &connectionGroup : pathGroup.second) 304 { 305 if (connectionGroup.first == connection_name) 306 { 307 // Found the object manager path for this resource. 308 getManagedObjectsForEnumerate( 309 object_name, pathGroup.first, connection_name, 310 transaction); 311 return; 312 } 313 } 314 } 315 }, 316 "xyz.openbmc_project.ObjectMapper", 317 "/xyz/openbmc_project/object_mapper", 318 "xyz.openbmc_project.ObjectMapper", "GetAncestors", object_name, 319 std::array<const char *, 1>{"org.freedesktop.DBus.ObjectManager"}); 320 } 321 322 // Uses GetObject to add the object info about the target /enumerate path to the 323 // results of GetSubTree, as GetSubTree will not return info for the 324 // target path, and then continues on enumerating the rest of the tree. 325 void getObjectAndEnumerate(std::shared_ptr<InProgressEnumerateData> transaction) 326 { 327 using GetObjectType = 328 std::vector<std::pair<std::string, std::vector<std::string>>>; 329 330 crow::connections::systemBus->async_method_call( 331 [transaction](const boost::system::error_code ec, 332 const GetObjectType &objects) { 333 if (ec) 334 { 335 BMCWEB_LOG_ERROR << "GetObject for path " 336 << transaction->objectPath 337 << " failed with code " << ec; 338 return; 339 } 340 341 BMCWEB_LOG_DEBUG << "GetObject for " << transaction->objectPath 342 << " has " << objects.size() << " entries"; 343 if (!objects.empty()) 344 { 345 transaction->subtree->emplace_back(transaction->objectPath, 346 objects); 347 } 348 349 // Map indicating connection name, and the path where the object 350 // manager exists 351 boost::container::flat_map<std::string, std::string> connections; 352 353 for (const auto &object : *(transaction->subtree)) 354 { 355 for (const auto &connection : object.second) 356 { 357 std::string &objectManagerPath = 358 connections[connection.first]; 359 for (const auto &interface : connection.second) 360 { 361 BMCWEB_LOG_DEBUG << connection.first 362 << " has interface " << interface; 363 if (interface == "org.freedesktop.DBus.ObjectManager") 364 { 365 BMCWEB_LOG_DEBUG << "found object manager path " 366 << object.first; 367 objectManagerPath = object.first; 368 } 369 } 370 } 371 } 372 BMCWEB_LOG_DEBUG << "Got " << connections.size() << " connections"; 373 374 for (const auto &connection : connections) 375 { 376 // If we already know where the object manager is, we don't need 377 // to search for it, we can call directly in to 378 // getManagedObjects 379 if (!connection.second.empty()) 380 { 381 getManagedObjectsForEnumerate( 382 transaction->objectPath, connection.second, 383 connection.first, transaction); 384 } 385 else 386 { 387 // otherwise we need to find the object manager path before 388 // we can continue 389 findObjectManagerPathForEnumerate( 390 transaction->objectPath, connection.first, transaction); 391 } 392 } 393 }, 394 "xyz.openbmc_project.ObjectMapper", 395 "/xyz/openbmc_project/object_mapper", 396 "xyz.openbmc_project.ObjectMapper", "GetObject", 397 transaction->objectPath, std::array<const char *, 0>()); 398 } 399 400 // Structure for storing data on an in progress action 401 struct InProgressActionData 402 { 403 InProgressActionData(crow::Response &res) : res(res){}; 404 ~InProgressActionData() 405 { 406 // Methods could have been called across different owners 407 // and interfaces, where some calls failed and some passed. 408 // 409 // The rules for this are: 410 // * if no method was called - error 411 // * if a method failed and none passed - error 412 // (converse: if at least one method passed - OK) 413 // * for the method output: 414 // * if output processing didn't fail, return the data 415 416 // Only deal with method returns if nothing failed earlier 417 if (res.result() == boost::beast::http::status::ok) 418 { 419 if (!methodPassed) 420 { 421 if (methodFailed) 422 { 423 setErrorResponse(res, 424 boost::beast::http::status::bad_request, 425 "Method call failed", methodFailedMsg); 426 } 427 else 428 { 429 setErrorResponse(res, boost::beast::http::status::not_found, 430 methodNotFoundDesc, notFoundMsg); 431 } 432 } 433 else 434 { 435 if (outputFailed) 436 { 437 setErrorResponse( 438 res, boost::beast::http::status::internal_server_error, 439 "Method output failure", methodOutputFailedMsg); 440 } 441 else 442 { 443 res.jsonValue = {{"status", "ok"}, 444 {"message", "200 OK"}, 445 {"data", methodResponse}}; 446 } 447 } 448 } 449 450 res.end(); 451 } 452 453 void setErrorStatus(const std::string &desc) 454 { 455 setErrorResponse(res, boost::beast::http::status::bad_request, desc, 456 badReqMsg); 457 } 458 crow::Response &res; 459 std::string path; 460 std::string methodName; 461 std::string interfaceName; 462 bool methodPassed = false; 463 bool methodFailed = false; 464 bool outputFailed = false; 465 nlohmann::json methodResponse; 466 nlohmann::json arguments; 467 }; 468 469 std::vector<std::string> dbusArgSplit(const std::string &string) 470 { 471 std::vector<std::string> ret; 472 if (string.empty()) 473 { 474 return ret; 475 } 476 ret.push_back(""); 477 int containerDepth = 0; 478 479 for (std::string::const_iterator character = string.begin(); 480 character != string.end(); character++) 481 { 482 ret.back() += *character; 483 switch (*character) 484 { 485 case ('a'): 486 break; 487 case ('('): 488 case ('{'): 489 containerDepth++; 490 break; 491 case ('}'): 492 case (')'): 493 containerDepth--; 494 if (containerDepth == 0) 495 { 496 if (character + 1 != string.end()) 497 { 498 ret.push_back(""); 499 } 500 } 501 break; 502 default: 503 if (containerDepth == 0) 504 { 505 if (character + 1 != string.end()) 506 { 507 ret.push_back(""); 508 } 509 } 510 break; 511 } 512 } 513 514 return ret; 515 } 516 517 int convertJsonToDbus(sd_bus_message *m, const std::string &arg_type, 518 const nlohmann::json &input_json) 519 { 520 int r = 0; 521 BMCWEB_LOG_DEBUG << "Converting " << input_json.dump() 522 << " to type: " << arg_type; 523 const std::vector<std::string> argTypes = dbusArgSplit(arg_type); 524 525 // Assume a single object for now. 526 const nlohmann::json *j = &input_json; 527 nlohmann::json::const_iterator jIt = input_json.begin(); 528 529 for (const std::string &argCode : argTypes) 530 { 531 // If we are decoding multiple objects, grab the pointer to the 532 // iterator, and increment it for the next loop 533 if (argTypes.size() > 1) 534 { 535 if (jIt == input_json.end()) 536 { 537 return -2; 538 } 539 j = &*jIt; 540 jIt++; 541 } 542 const int64_t *intValue = j->get_ptr<const int64_t *>(); 543 const uint64_t *uintValue = j->get_ptr<const uint64_t *>(); 544 const std::string *stringValue = j->get_ptr<const std::string *>(); 545 const double *doubleValue = j->get_ptr<const double *>(); 546 const bool *b = j->get_ptr<const bool *>(); 547 int64_t v = 0; 548 double d = 0.0; 549 550 // Do some basic type conversions that make sense. uint can be 551 // converted to int. int and uint can be converted to double 552 if (uintValue != nullptr && intValue == nullptr) 553 { 554 v = static_cast<int64_t>(*uintValue); 555 intValue = &v; 556 } 557 if (uintValue != nullptr && doubleValue == nullptr) 558 { 559 d = static_cast<double>(*uintValue); 560 doubleValue = &d; 561 } 562 if (intValue != nullptr && doubleValue == nullptr) 563 { 564 d = static_cast<double>(*intValue); 565 doubleValue = &d; 566 } 567 568 if (argCode == "s") 569 { 570 if (stringValue == nullptr) 571 { 572 return -1; 573 } 574 r = sd_bus_message_append_basic(m, argCode[0], 575 (void *)stringValue->c_str()); 576 if (r < 0) 577 { 578 return r; 579 } 580 } 581 else if (argCode == "i") 582 { 583 if (intValue == nullptr) 584 { 585 return -1; 586 } 587 int32_t i = static_cast<int32_t>(*intValue); 588 r = sd_bus_message_append_basic(m, argCode[0], &i); 589 if (r < 0) 590 { 591 return r; 592 } 593 } 594 else if (argCode == "b") 595 { 596 // lots of ways bool could be represented here. Try them all 597 int boolInt = false; 598 if (intValue != nullptr) 599 { 600 boolInt = *intValue > 0 ? 1 : 0; 601 } 602 else if (b != nullptr) 603 { 604 boolInt = *b ? 1 : 0; 605 } 606 else if (stringValue != nullptr) 607 { 608 boolInt = boost::istarts_with(*stringValue, "t") ? 1 : 0; 609 } 610 else 611 { 612 return -1; 613 } 614 r = sd_bus_message_append_basic(m, argCode[0], &boolInt); 615 if (r < 0) 616 { 617 return r; 618 } 619 } 620 else if (argCode == "n") 621 { 622 if (intValue == nullptr) 623 { 624 return -1; 625 } 626 int16_t n = static_cast<int16_t>(*intValue); 627 r = sd_bus_message_append_basic(m, argCode[0], &n); 628 if (r < 0) 629 { 630 return r; 631 } 632 } 633 else if (argCode == "x") 634 { 635 if (intValue == nullptr) 636 { 637 return -1; 638 } 639 r = sd_bus_message_append_basic(m, argCode[0], intValue); 640 if (r < 0) 641 { 642 return r; 643 } 644 } 645 else if (argCode == "y") 646 { 647 if (uintValue == nullptr) 648 { 649 return -1; 650 } 651 uint8_t y = static_cast<uint8_t>(*uintValue); 652 r = sd_bus_message_append_basic(m, argCode[0], &y); 653 } 654 else if (argCode == "q") 655 { 656 if (uintValue == nullptr) 657 { 658 return -1; 659 } 660 uint16_t q = static_cast<uint16_t>(*uintValue); 661 r = sd_bus_message_append_basic(m, argCode[0], &q); 662 } 663 else if (argCode == "u") 664 { 665 if (uintValue == nullptr) 666 { 667 return -1; 668 } 669 uint32_t u = static_cast<uint32_t>(*uintValue); 670 r = sd_bus_message_append_basic(m, argCode[0], &u); 671 } 672 else if (argCode == "t") 673 { 674 if (uintValue == nullptr) 675 { 676 return -1; 677 } 678 r = sd_bus_message_append_basic(m, argCode[0], uintValue); 679 } 680 else if (argCode == "d") 681 { 682 sd_bus_message_append_basic(m, argCode[0], doubleValue); 683 } 684 else if (boost::starts_with(argCode, "a")) 685 { 686 std::string containedType = argCode.substr(1); 687 r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, 688 containedType.c_str()); 689 if (r < 0) 690 { 691 return r; 692 } 693 694 for (nlohmann::json::const_iterator it = j->begin(); it != j->end(); 695 ++it) 696 { 697 r = convertJsonToDbus(m, containedType, *it); 698 if (r < 0) 699 { 700 return r; 701 } 702 } 703 sd_bus_message_close_container(m); 704 } 705 else if (boost::starts_with(argCode, "v")) 706 { 707 std::string containedType = argCode.substr(1); 708 BMCWEB_LOG_DEBUG << "variant type: " << argCode 709 << " appending variant of type: " << containedType; 710 r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, 711 containedType.c_str()); 712 if (r < 0) 713 { 714 return r; 715 } 716 717 r = convertJsonToDbus(m, containedType, input_json); 718 if (r < 0) 719 { 720 return r; 721 } 722 723 r = sd_bus_message_close_container(m); 724 if (r < 0) 725 { 726 return r; 727 } 728 } 729 else if (boost::starts_with(argCode, "(") && 730 boost::ends_with(argCode, ")")) 731 { 732 std::string containedType = argCode.substr(1, argCode.size() - 1); 733 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, 734 containedType.c_str()); 735 nlohmann::json::const_iterator it = j->begin(); 736 for (const std::string &argCode : dbusArgSplit(arg_type)) 737 { 738 if (it == j->end()) 739 { 740 return -1; 741 } 742 r = convertJsonToDbus(m, argCode, *it); 743 if (r < 0) 744 { 745 return r; 746 } 747 it++; 748 } 749 r = sd_bus_message_close_container(m); 750 } 751 else if (boost::starts_with(argCode, "{") && 752 boost::ends_with(argCode, "}")) 753 { 754 std::string containedType = argCode.substr(1, argCode.size() - 1); 755 r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY, 756 containedType.c_str()); 757 std::vector<std::string> codes = dbusArgSplit(containedType); 758 if (codes.size() != 2) 759 { 760 return -1; 761 } 762 const std::string &key_type = codes[0]; 763 const std::string &value_type = codes[1]; 764 for (auto it : j->items()) 765 { 766 r = convertJsonToDbus(m, key_type, it.key()); 767 if (r < 0) 768 { 769 return r; 770 } 771 772 r = convertJsonToDbus(m, value_type, it.value()); 773 if (r < 0) 774 { 775 return r; 776 } 777 } 778 r = sd_bus_message_close_container(m); 779 } 780 else 781 { 782 return -2; 783 } 784 if (r < 0) 785 { 786 return r; 787 } 788 789 if (argTypes.size() > 1) 790 { 791 jIt++; 792 } 793 } 794 795 return r; 796 } 797 798 template <typename T> 799 int readMessageItem(const std::string &typeCode, sdbusplus::message::message &m, 800 nlohmann::json &data) 801 { 802 T value; 803 804 int r = sd_bus_message_read_basic(m.get(), typeCode.front(), &value); 805 if (r < 0) 806 { 807 BMCWEB_LOG_ERROR << "sd_bus_message_read_basic on type " << typeCode 808 << " failed!"; 809 return r; 810 } 811 812 data = value; 813 return 0; 814 } 815 816 int convertDBusToJSON(const std::string &returnType, 817 sdbusplus::message::message &m, nlohmann::json &response); 818 819 int readDictEntryFromMessage(const std::string &typeCode, 820 sdbusplus::message::message &m, 821 nlohmann::json &object) 822 { 823 std::vector<std::string> types = dbusArgSplit(typeCode); 824 if (types.size() != 2) 825 { 826 BMCWEB_LOG_ERROR << "wrong number contained types in dictionary: " 827 << types.size(); 828 return -1; 829 } 830 831 int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_DICT_ENTRY, 832 typeCode.c_str()); 833 if (r < 0) 834 { 835 BMCWEB_LOG_ERROR << "sd_bus_message_enter_container with rc " << r; 836 return r; 837 } 838 839 nlohmann::json key; 840 r = convertDBusToJSON(types[0], m, key); 841 if (r < 0) 842 { 843 return r; 844 } 845 846 const std::string *keyPtr = key.get_ptr<const std::string *>(); 847 if (keyPtr == nullptr) 848 { 849 // json doesn't support non-string keys. If we hit this condition, 850 // convert the result to a string so we can proceed 851 key = key.dump(); 852 keyPtr = key.get_ptr<const std::string *>(); 853 // in theory this can't fail now, but lets be paranoid about it anyway 854 if (keyPtr == nullptr) 855 { 856 return -1; 857 } 858 } 859 nlohmann::json &value = object[*keyPtr]; 860 861 r = convertDBusToJSON(types[1], m, value); 862 if (r < 0) 863 { 864 return r; 865 } 866 867 r = sd_bus_message_exit_container(m.get()); 868 if (r < 0) 869 { 870 BMCWEB_LOG_ERROR << "sd_bus_message_exit_container failed"; 871 return r; 872 } 873 874 return 0; 875 } 876 877 int readArrayFromMessage(const std::string &typeCode, 878 sdbusplus::message::message &m, nlohmann::json &data) 879 { 880 if (typeCode.size() < 2) 881 { 882 BMCWEB_LOG_ERROR << "Type code " << typeCode 883 << " too small for an array"; 884 return -1; 885 } 886 887 std::string containedType = typeCode.substr(1); 888 889 int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_ARRAY, 890 containedType.c_str()); 891 if (r < 0) 892 { 893 BMCWEB_LOG_ERROR << "sd_bus_message_enter_container failed with rc " 894 << r; 895 return r; 896 } 897 898 bool dict = boost::starts_with(containedType, "{") && 899 boost::ends_with(containedType, "}"); 900 901 if (dict) 902 { 903 // Remove the { } 904 containedType = containedType.substr(1, containedType.size() - 2); 905 data = nlohmann::json::object(); 906 } 907 else 908 { 909 data = nlohmann::json::array(); 910 } 911 912 while (true) 913 { 914 r = sd_bus_message_at_end(m.get(), false); 915 if (r < 0) 916 { 917 BMCWEB_LOG_ERROR << "sd_bus_message_at_end failed"; 918 return r; 919 } 920 921 if (r > 0) 922 { 923 break; 924 } 925 926 // Dictionaries are only ever seen in an array 927 if (dict) 928 { 929 r = readDictEntryFromMessage(containedType, m, data); 930 if (r < 0) 931 { 932 return r; 933 } 934 } 935 else 936 { 937 data.push_back(nlohmann::json()); 938 939 r = convertDBusToJSON(containedType, m, data.back()); 940 if (r < 0) 941 { 942 return r; 943 } 944 } 945 } 946 947 r = sd_bus_message_exit_container(m.get()); 948 if (r < 0) 949 { 950 BMCWEB_LOG_ERROR << "sd_bus_message_exit_container failed"; 951 return r; 952 } 953 954 return 0; 955 } 956 957 int convertDBusToJSON(const std::string &returnType, 958 sdbusplus::message::message &m, nlohmann::json &response) 959 { 960 int r = 0; 961 const std::vector<std::string> returnTypes = dbusArgSplit(returnType); 962 963 nlohmann::json &thisElement = response; 964 for (const std::string &typeCode : returnTypes) 965 { 966 if (returnType.size() > 1) 967 { 968 response.push_back(nlohmann::json{}); 969 thisElement = response.back(); 970 } 971 972 if (typeCode == "s") 973 { 974 r = readMessageItem<char *>(typeCode, m, thisElement); 975 if (r < 0) 976 { 977 return r; 978 } 979 } 980 else if (typeCode == "g") 981 { 982 r = readMessageItem<char *>(typeCode, m, thisElement); 983 if (r < 0) 984 { 985 return r; 986 } 987 } 988 else if (typeCode == "o") 989 { 990 r = readMessageItem<char *>(typeCode, m, thisElement); 991 if (r < 0) 992 { 993 return r; 994 } 995 } 996 else if (typeCode == "b") 997 { 998 r = readMessageItem<int>(typeCode, m, thisElement); 999 if (r < 0) 1000 { 1001 return r; 1002 } 1003 1004 thisElement = static_cast<bool>(thisElement.get<int>()); 1005 } 1006 else if (typeCode == "u") 1007 { 1008 r = readMessageItem<uint32_t>(typeCode, m, thisElement); 1009 if (r < 0) 1010 { 1011 return r; 1012 } 1013 } 1014 else if (typeCode == "i") 1015 { 1016 r = readMessageItem<int32_t>(typeCode, m, thisElement); 1017 if (r < 0) 1018 { 1019 return r; 1020 } 1021 } 1022 else if (typeCode == "x") 1023 { 1024 r = readMessageItem<int64_t>(typeCode, m, thisElement); 1025 if (r < 0) 1026 { 1027 return r; 1028 } 1029 } 1030 else if (typeCode == "t") 1031 { 1032 r = readMessageItem<uint64_t>(typeCode, m, thisElement); 1033 if (r < 0) 1034 { 1035 return r; 1036 } 1037 } 1038 else if (typeCode == "n") 1039 { 1040 r = readMessageItem<int16_t>(typeCode, m, thisElement); 1041 if (r < 0) 1042 { 1043 return r; 1044 } 1045 } 1046 else if (typeCode == "q") 1047 { 1048 r = readMessageItem<uint16_t>(typeCode, m, thisElement); 1049 if (r < 0) 1050 { 1051 return r; 1052 } 1053 } 1054 else if (typeCode == "y") 1055 { 1056 r = readMessageItem<uint8_t>(typeCode, m, thisElement); 1057 if (r < 0) 1058 { 1059 return r; 1060 } 1061 } 1062 else if (typeCode == "d") 1063 { 1064 r = readMessageItem<double>(typeCode, m, thisElement); 1065 if (r < 0) 1066 { 1067 return r; 1068 } 1069 } 1070 else if (typeCode == "h") 1071 { 1072 r = readMessageItem<int>(typeCode, m, thisElement); 1073 if (r < 0) 1074 { 1075 return r; 1076 } 1077 } 1078 else if (boost::starts_with(typeCode, "a")) 1079 { 1080 r = readArrayFromMessage(typeCode, m, thisElement); 1081 if (r < 0) 1082 { 1083 return r; 1084 } 1085 } 1086 else 1087 { 1088 // TODO: add struct, variant support 1089 BMCWEB_LOG_ERROR << "Invalid D-Bus signature type " << typeCode; 1090 return -2; 1091 } 1092 } 1093 1094 return 0; 1095 } 1096 1097 void handleMethodResponse(std::shared_ptr<InProgressActionData> transaction, 1098 sdbusplus::message::message &m, 1099 const std::string &returnType) 1100 { 1101 } 1102 1103 void findActionOnInterface(std::shared_ptr<InProgressActionData> transaction, 1104 const std::string &connectionName) 1105 { 1106 BMCWEB_LOG_DEBUG << "findActionOnInterface for connection " 1107 << connectionName; 1108 crow::connections::systemBus->async_method_call( 1109 [transaction, connectionName{std::string(connectionName)}]( 1110 const boost::system::error_code ec, 1111 const std::string &introspect_xml) { 1112 BMCWEB_LOG_DEBUG << "got xml:\n " << introspect_xml; 1113 if (ec) 1114 { 1115 BMCWEB_LOG_ERROR 1116 << "Introspect call failed with error: " << ec.message() 1117 << " on process: " << connectionName << "\n"; 1118 return; 1119 } 1120 tinyxml2::XMLDocument doc; 1121 1122 doc.Parse(introspect_xml.data(), introspect_xml.size()); 1123 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 1124 if (pRoot == nullptr) 1125 { 1126 BMCWEB_LOG_ERROR << "XML document failed to parse " 1127 << connectionName << "\n"; 1128 return; 1129 } 1130 tinyxml2::XMLElement *interfaceNode = 1131 pRoot->FirstChildElement("interface"); 1132 while (interfaceNode != nullptr) 1133 { 1134 const char *thisInterfaceName = 1135 interfaceNode->Attribute("name"); 1136 if (thisInterfaceName != nullptr) 1137 { 1138 if (!transaction->interfaceName.empty() && 1139 (transaction->interfaceName != thisInterfaceName)) 1140 { 1141 interfaceNode = 1142 interfaceNode->NextSiblingElement("interface"); 1143 continue; 1144 } 1145 1146 tinyxml2::XMLElement *methodNode = 1147 interfaceNode->FirstChildElement("method"); 1148 while (methodNode != nullptr) 1149 { 1150 const char *thisMethodName = 1151 methodNode->Attribute("name"); 1152 BMCWEB_LOG_DEBUG << "Found method: " << thisMethodName; 1153 if (thisMethodName != nullptr && 1154 thisMethodName == transaction->methodName) 1155 { 1156 BMCWEB_LOG_DEBUG 1157 << "Found method named " << thisMethodName 1158 << " on interface " << thisInterfaceName; 1159 sdbusplus::message::message m = 1160 crow::connections::systemBus->new_method_call( 1161 connectionName.c_str(), 1162 transaction->path.c_str(), 1163 thisInterfaceName, 1164 transaction->methodName.c_str()); 1165 1166 tinyxml2::XMLElement *argumentNode = 1167 methodNode->FirstChildElement("arg"); 1168 1169 std::string returnType; 1170 1171 // Find the output type 1172 while (argumentNode != nullptr) 1173 { 1174 const char *argDirection = 1175 argumentNode->Attribute("direction"); 1176 const char *argType = 1177 argumentNode->Attribute("type"); 1178 if (argDirection != nullptr && 1179 argType != nullptr && 1180 std::string(argDirection) == "out") 1181 { 1182 returnType = argType; 1183 break; 1184 } 1185 argumentNode = 1186 argumentNode->NextSiblingElement("arg"); 1187 } 1188 1189 nlohmann::json::const_iterator argIt = 1190 transaction->arguments.begin(); 1191 1192 argumentNode = methodNode->FirstChildElement("arg"); 1193 1194 while (argumentNode != nullptr) 1195 { 1196 const char *argDirection = 1197 argumentNode->Attribute("direction"); 1198 const char *argType = 1199 argumentNode->Attribute("type"); 1200 if (argDirection != nullptr && 1201 argType != nullptr && 1202 std::string(argDirection) == "in") 1203 { 1204 if (argIt == transaction->arguments.end()) 1205 { 1206 transaction->setErrorStatus( 1207 "Invalid method args"); 1208 return; 1209 } 1210 if (convertJsonToDbus(m.get(), 1211 std::string(argType), 1212 *argIt) < 0) 1213 { 1214 transaction->setErrorStatus( 1215 "Invalid method arg type"); 1216 return; 1217 } 1218 1219 argIt++; 1220 } 1221 argumentNode = 1222 argumentNode->NextSiblingElement("arg"); 1223 } 1224 1225 crow::connections::systemBus->async_send( 1226 m, [transaction, returnType]( 1227 boost::system::error_code ec, 1228 sdbusplus::message::message &m) { 1229 if (ec) 1230 { 1231 transaction->methodFailed = true; 1232 return; 1233 } 1234 else 1235 { 1236 transaction->methodPassed = true; 1237 } 1238 1239 handleMethodResponse(transaction, m, 1240 returnType); 1241 }); 1242 break; 1243 } 1244 methodNode = methodNode->NextSiblingElement("method"); 1245 } 1246 } 1247 interfaceNode = interfaceNode->NextSiblingElement("interface"); 1248 } 1249 }, 1250 connectionName, transaction->path, 1251 "org.freedesktop.DBus.Introspectable", "Introspect"); 1252 } 1253 1254 void handleAction(const crow::Request &req, crow::Response &res, 1255 const std::string &objectPath, const std::string &methodName) 1256 { 1257 BMCWEB_LOG_DEBUG << "handleAction on path: " << objectPath << " and method " 1258 << methodName; 1259 nlohmann::json requestDbusData = 1260 nlohmann::json::parse(req.body, nullptr, false); 1261 1262 if (requestDbusData.is_discarded()) 1263 { 1264 setErrorResponse(res, boost::beast::http::status::bad_request, 1265 noJsonDesc, badReqMsg); 1266 res.end(); 1267 return; 1268 } 1269 nlohmann::json::iterator data = requestDbusData.find("data"); 1270 if (data == requestDbusData.end()) 1271 { 1272 setErrorResponse(res, boost::beast::http::status::bad_request, 1273 noJsonDesc, badReqMsg); 1274 res.end(); 1275 return; 1276 } 1277 1278 if (!data->is_array()) 1279 { 1280 setErrorResponse(res, boost::beast::http::status::bad_request, 1281 noJsonDesc, badReqMsg); 1282 res.end(); 1283 return; 1284 } 1285 auto transaction = std::make_shared<InProgressActionData>(res); 1286 1287 transaction->path = objectPath; 1288 transaction->methodName = methodName; 1289 transaction->arguments = std::move(*data); 1290 crow::connections::systemBus->async_method_call( 1291 [transaction]( 1292 const boost::system::error_code ec, 1293 const std::vector<std::pair<std::string, std::vector<std::string>>> 1294 &interfaceNames) { 1295 if (ec || interfaceNames.size() <= 0) 1296 { 1297 BMCWEB_LOG_ERROR << "Can't find object"; 1298 setErrorResponse(transaction->res, 1299 boost::beast::http::status::not_found, 1300 notFoundDesc, notFoundMsg); 1301 return; 1302 } 1303 1304 BMCWEB_LOG_DEBUG << "GetObject returned " << interfaceNames.size() 1305 << " object(s)"; 1306 1307 for (const std::pair<std::string, std::vector<std::string>> 1308 &object : interfaceNames) 1309 { 1310 findActionOnInterface(transaction, object.first); 1311 } 1312 }, 1313 "xyz.openbmc_project.ObjectMapper", 1314 "/xyz/openbmc_project/object_mapper", 1315 "xyz.openbmc_project.ObjectMapper", "GetObject", objectPath, 1316 std::array<std::string, 0>()); 1317 } 1318 1319 void handleDelete(const crow::Request &req, crow::Response &res, 1320 const std::string &objectPath) 1321 { 1322 BMCWEB_LOG_DEBUG << "handleDelete on path: " << objectPath; 1323 1324 crow::connections::systemBus->async_method_call( 1325 [&res, objectPath]( 1326 const boost::system::error_code ec, 1327 const std::vector<std::pair<std::string, std::vector<std::string>>> 1328 &interfaceNames) { 1329 if (ec || interfaceNames.size() <= 0) 1330 { 1331 BMCWEB_LOG_ERROR << "Can't find object"; 1332 setErrorResponse(res, boost::beast::http::status::not_found, 1333 notFoundDesc, notFoundMsg); 1334 res.end(); 1335 return; 1336 } 1337 1338 auto transaction = std::make_shared<InProgressActionData>(res); 1339 transaction->path = objectPath; 1340 transaction->methodName = "Delete"; 1341 transaction->interfaceName = "xyz.openbmc_project.Object.Delete"; 1342 1343 for (const std::pair<std::string, std::vector<std::string>> 1344 &object : interfaceNames) 1345 { 1346 findActionOnInterface(transaction, object.first); 1347 } 1348 }, 1349 "xyz.openbmc_project.ObjectMapper", 1350 "/xyz/openbmc_project/object_mapper", 1351 "xyz.openbmc_project.ObjectMapper", "GetObject", objectPath, 1352 std::array<const char *, 0>()); 1353 } 1354 1355 void handleList(crow::Response &res, const std::string &objectPath, 1356 int32_t depth = 0) 1357 { 1358 crow::connections::systemBus->async_method_call( 1359 [&res](const boost::system::error_code ec, 1360 std::vector<std::string> &objectPaths) { 1361 if (ec) 1362 { 1363 setErrorResponse(res, boost::beast::http::status::not_found, 1364 notFoundDesc, notFoundMsg); 1365 } 1366 else 1367 { 1368 res.jsonValue = {{"status", "ok"}, 1369 {"message", "200 OK"}, 1370 {"data", std::move(objectPaths)}}; 1371 } 1372 res.end(); 1373 }, 1374 "xyz.openbmc_project.ObjectMapper", 1375 "/xyz/openbmc_project/object_mapper", 1376 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", objectPath, 1377 depth, std::array<std::string, 0>()); 1378 } 1379 1380 void handleEnumerate(crow::Response &res, const std::string &objectPath) 1381 { 1382 BMCWEB_LOG_DEBUG << "Doing enumerate on " << objectPath; 1383 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(res); 1384 1385 asyncResp->res.jsonValue = {{"message", "200 OK"}, 1386 {"status", "ok"}, 1387 {"data", nlohmann::json::object()}}; 1388 1389 crow::connections::systemBus->async_method_call( 1390 [objectPath, asyncResp](const boost::system::error_code ec, 1391 GetSubTreeType &object_names) { 1392 auto transaction = std::make_shared<InProgressEnumerateData>( 1393 objectPath, asyncResp); 1394 1395 transaction->subtree = 1396 std::make_shared<GetSubTreeType>(std::move(object_names)); 1397 1398 if (ec) 1399 { 1400 BMCWEB_LOG_ERROR << "GetSubTree failed on " 1401 << transaction->objectPath; 1402 setErrorResponse(transaction->asyncResp->res, 1403 boost::beast::http::status::not_found, 1404 notFoundDesc, notFoundMsg); 1405 return; 1406 } 1407 1408 // Add the data for the path passed in to the results 1409 // as if GetSubTree returned it, and continue on enumerating 1410 getObjectAndEnumerate(transaction); 1411 }, 1412 "xyz.openbmc_project.ObjectMapper", 1413 "/xyz/openbmc_project/object_mapper", 1414 "xyz.openbmc_project.ObjectMapper", "GetSubTree", objectPath, 1415 static_cast<int32_t>(0), std::array<const char *, 0>()); 1416 } 1417 1418 void handleGet(crow::Response &res, std::string &objectPath, 1419 std::string &destProperty) 1420 { 1421 BMCWEB_LOG_DEBUG << "handleGet: " << objectPath << " prop:" << destProperty; 1422 std::shared_ptr<std::string> propertyName = 1423 std::make_shared<std::string>(std::move(destProperty)); 1424 1425 std::shared_ptr<std::string> path = 1426 std::make_shared<std::string>(std::move(objectPath)); 1427 1428 using GetObjectType = 1429 std::vector<std::pair<std::string, std::vector<std::string>>>; 1430 crow::connections::systemBus->async_method_call( 1431 [&res, path, propertyName](const boost::system::error_code ec, 1432 const GetObjectType &object_names) { 1433 if (ec || object_names.size() <= 0) 1434 { 1435 setErrorResponse(res, boost::beast::http::status::not_found, 1436 notFoundDesc, notFoundMsg); 1437 res.end(); 1438 return; 1439 } 1440 std::shared_ptr<nlohmann::json> response = 1441 std::make_shared<nlohmann::json>(nlohmann::json::object()); 1442 // The mapper should never give us an empty interface names list, 1443 // but check anyway 1444 for (const std::pair<std::string, std::vector<std::string>> 1445 connection : object_names) 1446 { 1447 const std::vector<std::string> &interfaceNames = 1448 connection.second; 1449 1450 if (interfaceNames.size() <= 0) 1451 { 1452 setErrorResponse(res, boost::beast::http::status::not_found, 1453 notFoundDesc, notFoundMsg); 1454 res.end(); 1455 return; 1456 } 1457 1458 for (const std::string &interface : interfaceNames) 1459 { 1460 crow::connections::systemBus->async_method_call( 1461 [&res, response, propertyName]( 1462 const boost::system::error_code ec, 1463 const std::vector<std::pair< 1464 std::string, dbus::utility::DbusVariantType>> 1465 &properties) { 1466 if (ec) 1467 { 1468 BMCWEB_LOG_ERROR << "Bad dbus request error: " 1469 << ec; 1470 } 1471 else 1472 { 1473 for (const std::pair< 1474 std::string, 1475 dbus::utility::DbusVariantType> 1476 &property : properties) 1477 { 1478 // if property name is empty, or matches our 1479 // search query, add it to the response json 1480 1481 if (propertyName->empty()) 1482 { 1483 sdbusplus::message::variant_ns::visit( 1484 [&response, &property](auto &&val) { 1485 (*response)[property.first] = 1486 val; 1487 }, 1488 property.second); 1489 } 1490 else if (property.first == *propertyName) 1491 { 1492 sdbusplus::message::variant_ns::visit( 1493 [&response](auto &&val) { 1494 (*response) = val; 1495 }, 1496 property.second); 1497 } 1498 } 1499 } 1500 if (response.use_count() == 1) 1501 { 1502 if (!propertyName->empty() && response->empty()) 1503 { 1504 setErrorResponse( 1505 res, 1506 boost::beast::http::status::not_found, 1507 propNotFoundDesc, notFoundMsg); 1508 } 1509 else 1510 { 1511 res.jsonValue = {{"status", "ok"}, 1512 {"message", "200 OK"}, 1513 {"data", *response}}; 1514 } 1515 res.end(); 1516 } 1517 }, 1518 connection.first, *path, 1519 "org.freedesktop.DBus.Properties", "GetAll", interface); 1520 } 1521 } 1522 }, 1523 "xyz.openbmc_project.ObjectMapper", 1524 "/xyz/openbmc_project/object_mapper", 1525 "xyz.openbmc_project.ObjectMapper", "GetObject", *path, 1526 std::array<std::string, 0>()); 1527 } 1528 1529 struct AsyncPutRequest 1530 { 1531 AsyncPutRequest(crow::Response &res) : res(res) 1532 { 1533 } 1534 ~AsyncPutRequest() 1535 { 1536 if (res.jsonValue.empty()) 1537 { 1538 setErrorResponse(res, boost::beast::http::status::forbidden, 1539 forbiddenMsg, forbiddenPropDesc); 1540 } 1541 1542 res.end(); 1543 } 1544 1545 void setErrorStatus(const std::string &desc) 1546 { 1547 setErrorResponse(res, boost::beast::http::status::internal_server_error, 1548 desc, badReqMsg); 1549 } 1550 1551 crow::Response &res; 1552 std::string objectPath; 1553 std::string propertyName; 1554 nlohmann::json propertyValue; 1555 }; 1556 1557 void handlePut(const crow::Request &req, crow::Response &res, 1558 const std::string &objectPath, const std::string &destProperty) 1559 { 1560 if (destProperty.empty()) 1561 { 1562 setErrorResponse(res, boost::beast::http::status::forbidden, 1563 forbiddenResDesc, forbiddenMsg); 1564 res.end(); 1565 return; 1566 } 1567 1568 nlohmann::json requestDbusData = 1569 nlohmann::json::parse(req.body, nullptr, false); 1570 1571 if (requestDbusData.is_discarded()) 1572 { 1573 setErrorResponse(res, boost::beast::http::status::bad_request, 1574 noJsonDesc, badReqMsg); 1575 res.end(); 1576 return; 1577 } 1578 1579 nlohmann::json::const_iterator propertyIt = requestDbusData.find("data"); 1580 if (propertyIt == requestDbusData.end()) 1581 { 1582 setErrorResponse(res, boost::beast::http::status::bad_request, 1583 noJsonDesc, badReqMsg); 1584 res.end(); 1585 return; 1586 } 1587 const nlohmann::json &propertySetValue = *propertyIt; 1588 auto transaction = std::make_shared<AsyncPutRequest>(res); 1589 transaction->objectPath = objectPath; 1590 transaction->propertyName = destProperty; 1591 transaction->propertyValue = propertySetValue; 1592 1593 using GetObjectType = 1594 std::vector<std::pair<std::string, std::vector<std::string>>>; 1595 1596 crow::connections::systemBus->async_method_call( 1597 [transaction](const boost::system::error_code ec, 1598 const GetObjectType &object_names) { 1599 if (!ec && object_names.size() <= 0) 1600 { 1601 setErrorResponse(transaction->res, 1602 boost::beast::http::status::not_found, 1603 propNotFoundDesc, notFoundMsg); 1604 return; 1605 } 1606 1607 for (const std::pair<std::string, std::vector<std::string>> 1608 connection : object_names) 1609 { 1610 const std::string &connectionName = connection.first; 1611 1612 crow::connections::systemBus->async_method_call( 1613 [connectionName{std::string(connectionName)}, 1614 transaction](const boost::system::error_code ec, 1615 const std::string &introspectXml) { 1616 if (ec) 1617 { 1618 BMCWEB_LOG_ERROR 1619 << "Introspect call failed with error: " 1620 << ec.message() 1621 << " on process: " << connectionName; 1622 transaction->setErrorStatus("Unexpected Error"); 1623 return; 1624 } 1625 tinyxml2::XMLDocument doc; 1626 1627 doc.Parse(introspectXml.c_str()); 1628 tinyxml2::XMLNode *pRoot = 1629 doc.FirstChildElement("node"); 1630 if (pRoot == nullptr) 1631 { 1632 BMCWEB_LOG_ERROR << "XML document failed to parse: " 1633 << introspectXml; 1634 transaction->setErrorStatus("Unexpected Error"); 1635 return; 1636 } 1637 tinyxml2::XMLElement *ifaceNode = 1638 pRoot->FirstChildElement("interface"); 1639 while (ifaceNode != nullptr) 1640 { 1641 const char *interfaceName = 1642 ifaceNode->Attribute("name"); 1643 BMCWEB_LOG_DEBUG << "found interface " 1644 << interfaceName; 1645 tinyxml2::XMLElement *propNode = 1646 ifaceNode->FirstChildElement("property"); 1647 while (propNode != nullptr) 1648 { 1649 const char *propertyName = 1650 propNode->Attribute("name"); 1651 BMCWEB_LOG_DEBUG << "Found property " 1652 << propertyName; 1653 if (propertyName == transaction->propertyName) 1654 { 1655 const char *argType = 1656 propNode->Attribute("type"); 1657 if (argType != nullptr) 1658 { 1659 sdbusplus::message::message m = 1660 crow::connections::systemBus 1661 ->new_method_call( 1662 connectionName.c_str(), 1663 transaction->objectPath 1664 .c_str(), 1665 "org.freedesktop.DBus." 1666 "Properties", 1667 "Set"); 1668 m.append(interfaceName, 1669 transaction->propertyName); 1670 int r = sd_bus_message_open_container( 1671 m.get(), SD_BUS_TYPE_VARIANT, 1672 argType); 1673 if (r < 0) 1674 { 1675 transaction->setErrorStatus( 1676 "Unexpected Error"); 1677 return; 1678 } 1679 r = convertJsonToDbus( 1680 m.get(), argType, 1681 transaction->propertyValue); 1682 if (r < 0) 1683 { 1684 transaction->setErrorStatus( 1685 "Invalid arg type"); 1686 return; 1687 } 1688 r = sd_bus_message_close_container( 1689 m.get()); 1690 if (r < 0) 1691 { 1692 transaction->setErrorStatus( 1693 "Unexpected Error"); 1694 return; 1695 } 1696 1697 crow::connections::systemBus 1698 ->async_send( 1699 m, 1700 [transaction]( 1701 boost::system::error_code 1702 ec, 1703 sdbusplus::message::message 1704 &m) { 1705 BMCWEB_LOG_DEBUG << "sent"; 1706 if (ec) 1707 { 1708 setErrorResponse( 1709 transaction->res, 1710 boost::beast::http:: 1711 status:: 1712 forbidden, 1713 forbiddenPropDesc, 1714 ec.message()); 1715 } 1716 else 1717 { 1718 transaction->res 1719 .jsonValue = { 1720 {"status", "ok"}, 1721 {"message", 1722 "200 OK"}, 1723 {"data", nullptr}}; 1724 } 1725 }); 1726 } 1727 } 1728 propNode = 1729 propNode->NextSiblingElement("property"); 1730 } 1731 ifaceNode = 1732 ifaceNode->NextSiblingElement("interface"); 1733 } 1734 }, 1735 connectionName, transaction->objectPath, 1736 "org.freedesktop.DBus.Introspectable", "Introspect"); 1737 } 1738 }, 1739 "xyz.openbmc_project.ObjectMapper", 1740 "/xyz/openbmc_project/object_mapper", 1741 "xyz.openbmc_project.ObjectMapper", "GetObject", 1742 transaction->objectPath, std::array<std::string, 0>()); 1743 } 1744 1745 inline void handleDBusUrl(const crow::Request &req, crow::Response &res, 1746 std::string &objectPath) 1747 { 1748 1749 // If accessing a single attribute, fill in and update objectPath, 1750 // otherwise leave destProperty blank 1751 std::string destProperty = ""; 1752 const char *attrSeperator = "/attr/"; 1753 size_t attrPosition = objectPath.find(attrSeperator); 1754 if (attrPosition != objectPath.npos) 1755 { 1756 destProperty = objectPath.substr(attrPosition + strlen(attrSeperator), 1757 objectPath.length()); 1758 objectPath = objectPath.substr(0, attrPosition); 1759 } 1760 1761 if (req.method() == "POST"_method) 1762 { 1763 constexpr const char *actionSeperator = "/action/"; 1764 size_t actionPosition = objectPath.find(actionSeperator); 1765 if (actionPosition != objectPath.npos) 1766 { 1767 std::string postProperty = 1768 objectPath.substr((actionPosition + strlen(actionSeperator)), 1769 objectPath.length()); 1770 objectPath = objectPath.substr(0, actionPosition); 1771 handleAction(req, res, objectPath, postProperty); 1772 return; 1773 } 1774 } 1775 else if (req.method() == "GET"_method) 1776 { 1777 if (boost::ends_with(objectPath, "/enumerate")) 1778 { 1779 objectPath.erase(objectPath.end() - sizeof("enumerate"), 1780 objectPath.end()); 1781 handleEnumerate(res, objectPath); 1782 } 1783 else if (boost::ends_with(objectPath, "/list")) 1784 { 1785 objectPath.erase(objectPath.end() - sizeof("list"), 1786 objectPath.end()); 1787 handleList(res, objectPath); 1788 } 1789 else 1790 { 1791 // Trim any trailing "/" at the end 1792 if (boost::ends_with(objectPath, "/")) 1793 { 1794 objectPath.pop_back(); 1795 handleList(res, objectPath, 1); 1796 } 1797 else 1798 { 1799 handleGet(res, objectPath, destProperty); 1800 } 1801 } 1802 return; 1803 } 1804 else if (req.method() == "PUT"_method) 1805 { 1806 handlePut(req, res, objectPath, destProperty); 1807 return; 1808 } 1809 else if (req.method() == "DELETE"_method) 1810 { 1811 handleDelete(req, res, objectPath); 1812 return; 1813 } 1814 1815 setErrorResponse(res, boost::beast::http::status::method_not_allowed, 1816 methodNotAllowedDesc, methodNotAllowedMsg); 1817 res.end(); 1818 } 1819 1820 template <typename... Middlewares> void requestRoutes(Crow<Middlewares...> &app) 1821 { 1822 BMCWEB_ROUTE(app, "/bus/") 1823 .methods("GET"_method)( 1824 [](const crow::Request &req, crow::Response &res) { 1825 res.jsonValue = {{"busses", {{{"name", "system"}}}}, 1826 {"status", "ok"}}; 1827 res.end(); 1828 }); 1829 1830 BMCWEB_ROUTE(app, "/bus/system/") 1831 .methods("GET"_method)( 1832 [](const crow::Request &req, crow::Response &res) { 1833 auto myCallback = [&res](const boost::system::error_code ec, 1834 std::vector<std::string> &names) { 1835 if (ec) 1836 { 1837 BMCWEB_LOG_ERROR << "Dbus call failed with code " << ec; 1838 res.result( 1839 boost::beast::http::status::internal_server_error); 1840 } 1841 else 1842 { 1843 std::sort(names.begin(), names.end()); 1844 res.jsonValue = {{"status", "ok"}}; 1845 auto &objectsSub = res.jsonValue["objects"]; 1846 for (auto &name : names) 1847 { 1848 objectsSub.push_back({{"name", name}}); 1849 } 1850 } 1851 res.end(); 1852 }; 1853 crow::connections::systemBus->async_method_call( 1854 std::move(myCallback), "org.freedesktop.DBus", "/", 1855 "org.freedesktop.DBus", "ListNames"); 1856 }); 1857 1858 BMCWEB_ROUTE(app, "/list/") 1859 .methods("GET"_method)( 1860 [](const crow::Request &req, crow::Response &res) { 1861 handleList(res, "/"); 1862 }); 1863 1864 BMCWEB_ROUTE(app, "/xyz/<path>") 1865 .methods("GET"_method, "PUT"_method, "POST"_method, "DELETE"_method)( 1866 [](const crow::Request &req, crow::Response &res, 1867 const std::string &path) { 1868 std::string objectPath = "/xyz/" + path; 1869 handleDBusUrl(req, res, objectPath); 1870 }); 1871 1872 BMCWEB_ROUTE(app, "/org/<path>") 1873 .methods("GET"_method, "PUT"_method, "POST"_method, "DELETE"_method)( 1874 [](const crow::Request &req, crow::Response &res, 1875 const std::string &path) { 1876 std::string objectPath = "/org/" + path; 1877 handleDBusUrl(req, res, objectPath); 1878 }); 1879 1880 BMCWEB_ROUTE(app, "/download/dump/<str>/") 1881 .methods("GET"_method)([](const crow::Request &req, crow::Response &res, 1882 const std::string &dumpId) { 1883 std::regex validFilename("^[\\w\\- ]+(\\.?[\\w\\- ]*)$"); 1884 if (!std::regex_match(dumpId, validFilename)) 1885 { 1886 res.result(boost::beast::http::status::bad_request); 1887 res.end(); 1888 return; 1889 } 1890 std::filesystem::path loc( 1891 "/var/lib/phosphor-debug-collector/dumps"); 1892 1893 loc /= dumpId; 1894 1895 if (!std::filesystem::exists(loc) || 1896 !std::filesystem::is_directory(loc)) 1897 { 1898 BMCWEB_LOG_ERROR << loc << "Not found"; 1899 res.result(boost::beast::http::status::not_found); 1900 res.end(); 1901 return; 1902 } 1903 std::filesystem::directory_iterator files(loc); 1904 1905 for (auto &file : files) 1906 { 1907 std::ifstream readFile(file.path()); 1908 if (!readFile.good()) 1909 { 1910 continue; 1911 } 1912 res.addHeader("Content-Type", "application/octet-stream"); 1913 res.body() = {std::istreambuf_iterator<char>(readFile), 1914 std::istreambuf_iterator<char>()}; 1915 res.end(); 1916 return; 1917 } 1918 res.result(boost::beast::http::status::not_found); 1919 res.end(); 1920 return; 1921 }); 1922 1923 BMCWEB_ROUTE(app, "/bus/system/<str>/") 1924 .methods("GET"_method)([](const crow::Request &req, crow::Response &res, 1925 const std::string &Connection) { 1926 introspectObjects(Connection, "/", 1927 std::make_shared<bmcweb::AsyncResp>(res)); 1928 }); 1929 1930 BMCWEB_ROUTE(app, "/bus/system/<str>/<path>") 1931 .methods("GET"_method, 1932 "POST"_method)([](const crow::Request &req, 1933 crow::Response &res, 1934 const std::string &processName, 1935 const std::string &requestedPath) { 1936 std::vector<std::string> strs; 1937 boost::split(strs, requestedPath, boost::is_any_of("/")); 1938 std::string objectPath; 1939 std::string interfaceName; 1940 std::string methodName; 1941 auto it = strs.begin(); 1942 if (it == strs.end()) 1943 { 1944 objectPath = "/"; 1945 } 1946 while (it != strs.end()) 1947 { 1948 // Check if segment contains ".". If it does, it must be an 1949 // interface 1950 if (it->find(".") != std::string::npos) 1951 { 1952 break; 1953 // This check is neccesary as the trailing slash gets parsed 1954 // as part of our <path> specifier above, which causes the 1955 // normal trailing backslash redirector to fail. 1956 } 1957 else if (!it->empty()) 1958 { 1959 objectPath += "/" + *it; 1960 } 1961 it++; 1962 } 1963 if (it != strs.end()) 1964 { 1965 interfaceName = *it; 1966 it++; 1967 1968 // after interface, we might have a method name 1969 if (it != strs.end()) 1970 { 1971 methodName = *it; 1972 it++; 1973 } 1974 } 1975 if (it != strs.end()) 1976 { 1977 // if there is more levels past the method name, something went 1978 // wrong, return not found 1979 res.result(boost::beast::http::status::not_found); 1980 res.end(); 1981 return; 1982 } 1983 if (interfaceName.empty()) 1984 { 1985 crow::connections::systemBus->async_method_call( 1986 [&, processName, 1987 objectPath](const boost::system::error_code ec, 1988 const std::string &introspect_xml) { 1989 if (ec) 1990 { 1991 BMCWEB_LOG_ERROR 1992 << "Introspect call failed with error: " 1993 << ec.message() 1994 << " on process: " << processName 1995 << " path: " << objectPath << "\n"; 1996 return; 1997 } 1998 tinyxml2::XMLDocument doc; 1999 2000 doc.Parse(introspect_xml.c_str()); 2001 tinyxml2::XMLNode *pRoot = 2002 doc.FirstChildElement("node"); 2003 if (pRoot == nullptr) 2004 { 2005 BMCWEB_LOG_ERROR << "XML document failed to parse " 2006 << processName << " " << objectPath 2007 << "\n"; 2008 res.jsonValue = {{"status", "XML parse error"}}; 2009 res.result(boost::beast::http::status:: 2010 internal_server_error); 2011 return; 2012 } 2013 2014 BMCWEB_LOG_DEBUG << introspect_xml; 2015 res.jsonValue = {{"status", "ok"}, 2016 {"bus_name", processName}, 2017 {"object_path", objectPath}}; 2018 nlohmann::json &interfacesArray = 2019 res.jsonValue["interfaces"]; 2020 interfacesArray = nlohmann::json::array(); 2021 tinyxml2::XMLElement *interface = 2022 pRoot->FirstChildElement("interface"); 2023 2024 while (interface != nullptr) 2025 { 2026 const char *ifaceName = 2027 interface->Attribute("name"); 2028 if (ifaceName != nullptr) 2029 { 2030 interfacesArray.push_back( 2031 {{"name", ifaceName}}); 2032 } 2033 2034 interface = 2035 interface->NextSiblingElement("interface"); 2036 } 2037 2038 res.end(); 2039 }, 2040 processName, objectPath, 2041 "org.freedesktop.DBus.Introspectable", "Introspect"); 2042 } 2043 else if (methodName.empty()) 2044 { 2045 crow::connections::systemBus->async_method_call( 2046 [&, processName, objectPath, 2047 interfaceName{std::move(interfaceName)}]( 2048 const boost::system::error_code ec, 2049 const std::string &introspect_xml) { 2050 if (ec) 2051 { 2052 BMCWEB_LOG_ERROR 2053 << "Introspect call failed with error: " 2054 << ec.message() 2055 << " on process: " << processName 2056 << " path: " << objectPath << "\n"; 2057 } 2058 else 2059 { 2060 tinyxml2::XMLDocument doc; 2061 2062 doc.Parse(introspect_xml.c_str()); 2063 tinyxml2::XMLNode *pRoot = 2064 doc.FirstChildElement("node"); 2065 if (pRoot == nullptr) 2066 { 2067 BMCWEB_LOG_ERROR 2068 << "XML document failed to parse " 2069 << processName << " " << objectPath << "\n"; 2070 res.result(boost::beast::http::status:: 2071 internal_server_error); 2072 } 2073 else 2074 { 2075 tinyxml2::XMLElement *node = 2076 pRoot->FirstChildElement("node"); 2077 2078 // if we know we're the only call, build the 2079 // json directly 2080 tinyxml2::XMLElement *interface = 2081 pRoot->FirstChildElement("interface"); 2082 2083 res.jsonValue = { 2084 {"status", "ok"}, 2085 {"bus_name", processName}, 2086 {"interface", interfaceName}, 2087 {"object_path", objectPath}, 2088 {"properties", nlohmann::json::object()}}; 2089 2090 nlohmann::json &methodsArray = 2091 res.jsonValue["methods"]; 2092 methodsArray = nlohmann::json::array(); 2093 2094 nlohmann::json &signalsArray = 2095 res.jsonValue["signals"]; 2096 signalsArray = nlohmann::json::array(); 2097 2098 while (interface != nullptr) 2099 { 2100 const char *ifaceName = 2101 interface->Attribute("name"); 2102 2103 if (ifaceName != nullptr && 2104 ifaceName == interfaceName) 2105 { 2106 tinyxml2::XMLElement *methods = 2107 interface->FirstChildElement( 2108 "method"); 2109 while (methods != nullptr) 2110 { 2111 nlohmann::json argsArray = 2112 nlohmann::json::array(); 2113 tinyxml2::XMLElement *arg = 2114 methods->FirstChildElement( 2115 "arg"); 2116 while (arg != nullptr) 2117 { 2118 nlohmann::json thisArg; 2119 for (const char *fieldName : 2120 std::array<const char *, 2121 3>{"name", 2122 "direction", 2123 "type"}) 2124 { 2125 const char *fieldValue = 2126 arg->Attribute( 2127 fieldName); 2128 if (fieldValue != nullptr) 2129 { 2130 thisArg[fieldName] = 2131 fieldValue; 2132 } 2133 } 2134 argsArray.push_back( 2135 std::move(thisArg)); 2136 arg = arg->NextSiblingElement( 2137 "arg"); 2138 } 2139 2140 const char *name = 2141 methods->Attribute("name"); 2142 if (name != nullptr) 2143 { 2144 methodsArray.push_back( 2145 {{"name", name}, 2146 {"uri", "/bus/system/" + 2147 processName + 2148 objectPath + 2149 "/" + 2150 interfaceName + 2151 "/" + name}, 2152 {"args", argsArray}}); 2153 } 2154 methods = 2155 methods->NextSiblingElement( 2156 "method"); 2157 } 2158 tinyxml2::XMLElement *signals = 2159 interface->FirstChildElement( 2160 "signal"); 2161 while (signals != nullptr) 2162 { 2163 nlohmann::json argsArray = 2164 nlohmann::json::array(); 2165 2166 tinyxml2::XMLElement *arg = 2167 signals->FirstChildElement( 2168 "arg"); 2169 while (arg != nullptr) 2170 { 2171 const char *name = 2172 arg->Attribute("name"); 2173 const char *type = 2174 arg->Attribute("type"); 2175 if (name != nullptr && 2176 type != nullptr) 2177 { 2178 argsArray.push_back({ 2179 {"name", name}, 2180 {"type", type}, 2181 }); 2182 } 2183 arg = arg->NextSiblingElement( 2184 "arg"); 2185 } 2186 const char *name = 2187 signals->Attribute("name"); 2188 if (name != nullptr) 2189 { 2190 signalsArray.push_back( 2191 {{"name", name}, 2192 {"args", argsArray}}); 2193 } 2194 2195 signals = 2196 signals->NextSiblingElement( 2197 "signal"); 2198 } 2199 2200 break; 2201 } 2202 2203 interface = interface->NextSiblingElement( 2204 "interface"); 2205 } 2206 if (interface == nullptr) 2207 { 2208 // if we got to the end of the list and 2209 // never found a match, throw 404 2210 res.result( 2211 boost::beast::http::status::not_found); 2212 } 2213 } 2214 } 2215 res.end(); 2216 }, 2217 processName, objectPath, 2218 "org.freedesktop.DBus.Introspectable", "Introspect"); 2219 } 2220 else 2221 { 2222 if (req.method() != "POST"_method) 2223 { 2224 res.result(boost::beast::http::status::not_found); 2225 res.end(); 2226 return; 2227 } 2228 2229 nlohmann::json requestDbusData = 2230 nlohmann::json::parse(req.body, nullptr, false); 2231 2232 if (requestDbusData.is_discarded()) 2233 { 2234 res.result(boost::beast::http::status::bad_request); 2235 res.end(); 2236 return; 2237 } 2238 if (!requestDbusData.is_array()) 2239 { 2240 res.result(boost::beast::http::status::bad_request); 2241 res.end(); 2242 return; 2243 } 2244 auto transaction = std::make_shared<InProgressActionData>(res); 2245 2246 transaction->path = objectPath; 2247 transaction->methodName = methodName; 2248 transaction->arguments = std::move(requestDbusData); 2249 2250 findActionOnInterface(transaction, processName); 2251 } 2252 }); 2253 } 2254 } // namespace openbmc_mapper 2255 } // namespace crow 2256