1 #pragma once 2 3 #include <crow/app.h> 4 #include <tinyxml2.h> 5 6 #include <async_resp.hpp> 7 #include <boost/algorithm/string.hpp> 8 #include <boost/container/flat_set.hpp> 9 #include <dbus_singleton.hpp> 10 #include <experimental/filesystem> 11 #include <fstream> 12 13 namespace crow 14 { 15 namespace openbmc_mapper 16 { 17 18 void introspectObjects(const std::string &processName, 19 const std::string &objectPath, 20 std::shared_ptr<bmcweb::AsyncResp> transaction) 21 { 22 if (transaction->res.jsonValue.is_null()) 23 { 24 transaction->res.jsonValue = {{"status", "ok"}, 25 {"bus_name", processName}, 26 {"objects", nlohmann::json::array()}}; 27 } 28 29 crow::connections::systemBus->async_method_call( 30 [transaction, processName{std::string(processName)}, 31 objectPath{std::string(objectPath)}]( 32 const boost::system::error_code ec, 33 const std::string &introspect_xml) { 34 if (ec) 35 { 36 BMCWEB_LOG_ERROR 37 << "Introspect call failed with error: " << ec.message() 38 << " on process: " << processName << " path: " << objectPath 39 << "\n"; 40 return; 41 } 42 transaction->res.jsonValue["objects"].push_back( 43 {{"path", objectPath}}); 44 45 tinyxml2::XMLDocument doc; 46 47 doc.Parse(introspect_xml.c_str()); 48 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 49 if (pRoot == nullptr) 50 { 51 BMCWEB_LOG_ERROR << "XML document failed to parse " 52 << processName << " " << objectPath << "\n"; 53 } 54 else 55 { 56 tinyxml2::XMLElement *node = pRoot->FirstChildElement("node"); 57 while (node != nullptr) 58 { 59 const char *childPath = node->Attribute("name"); 60 if (childPath != nullptr) 61 { 62 std::string newpath; 63 if (objectPath != "/") 64 { 65 newpath += objectPath; 66 } 67 newpath += std::string("/") + childPath; 68 // introspect the subobjects as well 69 introspectObjects(processName, newpath, transaction); 70 } 71 72 node = node->NextSiblingElement("node"); 73 } 74 } 75 }, 76 processName, objectPath, "org.freedesktop.DBus.Introspectable", 77 "Introspect"); 78 } 79 80 // A smattering of common types to unpack. TODO(ed) this should really iterate 81 // the sdbusplus object directly and build the json response 82 using DbusRestVariantType = sdbusplus::message::variant< 83 std::vector<std::tuple<std::string, std::string, std::string>>, std::string, 84 int64_t, uint64_t, double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, 85 bool>; 86 87 using ManagedObjectType = std::vector<std::pair< 88 sdbusplus::message::object_path, 89 boost::container::flat_map< 90 std::string, 91 boost::container::flat_map<std::string, DbusRestVariantType>>>>; 92 93 void getManagedObjectsForEnumerate(const std::string &object_name, 94 const std::string &object_manager_path, 95 const std::string &connection_name, 96 crow::Response &res, 97 std::shared_ptr<nlohmann::json> transaction) 98 { 99 crow::connections::systemBus->async_method_call( 100 [&res, transaction, object_name{std::string(object_name)}]( 101 const boost::system::error_code ec, 102 const ManagedObjectType &objects) { 103 if (ec) 104 { 105 BMCWEB_LOG_ERROR << ec; 106 } 107 else 108 { 109 nlohmann::json &dataJson = *transaction; 110 111 for (auto &objectPath : objects) 112 { 113 BMCWEB_LOG_DEBUG 114 << "Reading object " 115 << static_cast<const std::string &>(objectPath.first); 116 nlohmann::json &objectJson = 117 dataJson[static_cast<const std::string &>( 118 objectPath.first)]; 119 if (objectJson.is_null()) 120 { 121 objectJson = nlohmann::json::object(); 122 } 123 for (const auto &interface : objectPath.second) 124 { 125 for (const auto &property : interface.second) 126 { 127 nlohmann::json &propertyJson = 128 objectJson[property.first]; 129 mapbox::util::apply_visitor( 130 [&propertyJson](auto &&val) { 131 propertyJson = val; 132 }, 133 property.second); 134 } 135 } 136 } 137 } 138 139 if (transaction.use_count() == 1) 140 { 141 res.jsonValue = {{"message", "200 OK"}, 142 {"status", "ok"}, 143 {"data", std::move(*transaction)}}; 144 res.end(); 145 } 146 }, 147 connection_name, object_manager_path, 148 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 149 } 150 151 void findObjectManagerPathForEnumerate( 152 const std::string &object_name, const std::string &connection_name, 153 crow::Response &res, std::shared_ptr<nlohmann::json> transaction) 154 { 155 crow::connections::systemBus->async_method_call( 156 [&res, transaction, object_name{std::string(object_name)}, 157 connection_name{std::string(connection_name)}]( 158 const boost::system::error_code ec, 159 const boost::container::flat_map< 160 std::string, boost::container::flat_map< 161 std::string, std::vector<std::string>>> 162 &objects) { 163 if (ec) 164 { 165 BMCWEB_LOG_ERROR << ec; 166 return; 167 } 168 169 for (const auto &pathGroup : objects) 170 { 171 for (const auto &connectionGroup : pathGroup.second) 172 { 173 if (connectionGroup.first == connection_name) 174 { 175 // Found the object manager path for this resource. 176 getManagedObjectsForEnumerate( 177 object_name, pathGroup.first, connection_name, res, 178 transaction); 179 return; 180 } 181 } 182 } 183 }, 184 "xyz.openbmc_project.ObjectMapper", 185 "/xyz/openbmc_project/object_mapper", 186 "xyz.openbmc_project.ObjectMapper", "GetAncestors", object_name, 187 std::array<const char *, 1>{"org.freedesktop.DBus.ObjectManager"}); 188 } 189 190 using GetSubTreeType = std::vector< 191 std::pair<std::string, 192 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 193 194 // Structure for storing data on an in progress action 195 struct InProgressActionData 196 { 197 InProgressActionData(crow::Response &res) : res(res){}; 198 ~InProgressActionData() 199 { 200 if (res.result() == boost::beast::http::status::internal_server_error) 201 { 202 // Reset the json object to clear out any data that made it in 203 // before the error happened todo(ed) handle error condition with 204 // proper code 205 res.jsonValue = nlohmann::json::object(); 206 } 207 res.end(); 208 } 209 210 void setErrorStatus() 211 { 212 res.result(boost::beast::http::status::internal_server_error); 213 } 214 crow::Response &res; 215 std::string path; 216 std::string methodName; 217 nlohmann::json arguments; 218 }; 219 220 std::vector<std::string> dbusArgSplit(const std::string &string) 221 { 222 std::vector<std::string> ret; 223 if (string.empty()) 224 { 225 return ret; 226 } 227 ret.push_back(""); 228 int containerDepth = 0; 229 230 for (std::string::const_iterator character = string.begin(); 231 character != string.end(); character++) 232 { 233 ret.back() += *character; 234 switch (*character) 235 { 236 case ('a'): 237 break; 238 case ('('): 239 case ('{'): 240 containerDepth++; 241 break; 242 case ('}'): 243 case (')'): 244 containerDepth--; 245 if (containerDepth == 0) 246 { 247 if (character + 1 != string.end()) 248 { 249 ret.push_back(""); 250 } 251 } 252 break; 253 default: 254 if (containerDepth == 0) 255 { 256 if (character + 1 != string.end()) 257 { 258 ret.push_back(""); 259 } 260 } 261 break; 262 } 263 } 264 } 265 266 int convertJsonToDbus(sd_bus_message *m, const std::string &arg_type, 267 const nlohmann::json &input_json) 268 { 269 int r = 0; 270 BMCWEB_LOG_DEBUG << "Converting " << input_json.dump() 271 << " to type: " << arg_type; 272 const std::vector<std::string> argTypes = dbusArgSplit(arg_type); 273 274 // Assume a single object for now. 275 const nlohmann::json *j = &input_json; 276 nlohmann::json::const_iterator jIt = input_json.begin(); 277 278 for (const std::string &argCode : argTypes) 279 { 280 // If we are decoding multiple objects, grab the pointer to the 281 // iterator, and increment it for the next loop 282 if (argTypes.size() > 1) 283 { 284 if (jIt == input_json.end()) 285 { 286 return -2; 287 } 288 j = &*jIt; 289 jIt++; 290 } 291 const int64_t *intValue = j->get_ptr<const int64_t *>(); 292 const uint64_t *uintValue = j->get_ptr<const uint64_t *>(); 293 const std::string *stringValue = j->get_ptr<const std::string *>(); 294 const double *doubleValue = j->get_ptr<const double *>(); 295 const bool *b = j->get_ptr<const bool *>(); 296 int64_t v = 0; 297 double d = 0.0; 298 299 // Do some basic type conversions that make sense. uint can be 300 // converted to int. int and uint can be converted to double 301 if (uintValue != nullptr && intValue == nullptr) 302 { 303 v = static_cast<int64_t>(*uintValue); 304 intValue = &v; 305 } 306 if (uintValue != nullptr && doubleValue == nullptr) 307 { 308 d = static_cast<double>(*uintValue); 309 doubleValue = &d; 310 } 311 if (intValue != nullptr && doubleValue == nullptr) 312 { 313 d = static_cast<double>(*intValue); 314 doubleValue = &d; 315 } 316 317 if (argCode == "s") 318 { 319 if (stringValue == nullptr) 320 { 321 return -1; 322 } 323 r = sd_bus_message_append_basic(m, argCode[0], 324 (void *)stringValue->c_str()); 325 if (r < 0) 326 { 327 return r; 328 } 329 } 330 else if (argCode == "i") 331 { 332 if (intValue == nullptr) 333 { 334 return -1; 335 } 336 int32_t i = static_cast<int32_t>(*intValue); 337 r = sd_bus_message_append_basic(m, argCode[0], &i); 338 if (r < 0) 339 { 340 return r; 341 } 342 } 343 else if (argCode == "b") 344 { 345 // lots of ways bool could be represented here. Try them all 346 int boolInt = false; 347 if (intValue != nullptr) 348 { 349 boolInt = *intValue > 0 ? 1 : 0; 350 } 351 else if (b != nullptr) 352 { 353 boolInt = b ? 1 : 0; 354 } 355 else if (stringValue != nullptr) 356 { 357 boolInt = boost::istarts_with(*stringValue, "t") ? 1 : 0; 358 } 359 else 360 { 361 return -1; 362 } 363 r = sd_bus_message_append_basic(m, argCode[0], &boolInt); 364 if (r < 0) 365 { 366 return r; 367 } 368 } 369 else if (argCode == "n") 370 { 371 if (intValue == nullptr) 372 { 373 return -1; 374 } 375 int16_t n = static_cast<int16_t>(*intValue); 376 r = sd_bus_message_append_basic(m, argCode[0], &n); 377 if (r < 0) 378 { 379 return r; 380 } 381 } 382 else if (argCode == "x") 383 { 384 if (intValue == nullptr) 385 { 386 return -1; 387 } 388 r = sd_bus_message_append_basic(m, argCode[0], intValue); 389 if (r < 0) 390 { 391 return r; 392 } 393 } 394 else if (argCode == "y") 395 { 396 if (uintValue == nullptr) 397 { 398 return -1; 399 } 400 uint8_t y = static_cast<uint8_t>(*uintValue); 401 r = sd_bus_message_append_basic(m, argCode[0], &y); 402 } 403 else if (argCode == "q") 404 { 405 if (uintValue == nullptr) 406 { 407 return -1; 408 } 409 uint16_t q = static_cast<uint16_t>(*uintValue); 410 r = sd_bus_message_append_basic(m, argCode[0], &q); 411 } 412 else if (argCode == "u") 413 { 414 if (uintValue == nullptr) 415 { 416 return -1; 417 } 418 uint32_t u = static_cast<uint32_t>(*uintValue); 419 r = sd_bus_message_append_basic(m, argCode[0], &u); 420 } 421 else if (argCode == "t") 422 { 423 if (uintValue == nullptr) 424 { 425 return -1; 426 } 427 r = sd_bus_message_append_basic(m, argCode[0], uintValue); 428 } 429 else if (argCode == "d") 430 { 431 sd_bus_message_append_basic(m, argCode[0], doubleValue); 432 } 433 else if (boost::starts_with(argCode, "a")) 434 { 435 std::string containedType = argCode.substr(1); 436 r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, 437 containedType.c_str()); 438 if (r < 0) 439 { 440 return r; 441 } 442 443 for (nlohmann::json::const_iterator it = j->begin(); it != j->end(); 444 ++it) 445 { 446 r = convertJsonToDbus(m, containedType, *it); 447 if (r < 0) 448 { 449 return r; 450 } 451 452 it++; 453 } 454 sd_bus_message_close_container(m); 455 } 456 else if (boost::starts_with(argCode, "v")) 457 { 458 std::string containedType = argCode.substr(1); 459 BMCWEB_LOG_DEBUG << "variant type: " << argCode 460 << " appending variant of type: " << containedType; 461 r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, 462 containedType.c_str()); 463 if (r < 0) 464 { 465 return r; 466 } 467 468 r = convertJsonToDbus(m, containedType, input_json); 469 if (r < 0) 470 { 471 return r; 472 } 473 474 r = sd_bus_message_close_container(m); 475 if (r < 0) 476 { 477 return r; 478 } 479 } 480 else if (boost::starts_with(argCode, "(") && 481 boost::ends_with(argCode, ")")) 482 { 483 std::string containedType = argCode.substr(1, argCode.size() - 1); 484 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, 485 containedType.c_str()); 486 nlohmann::json::const_iterator it = j->begin(); 487 for (const std::string &argCode : dbusArgSplit(arg_type)) 488 { 489 if (it == j->end()) 490 { 491 return -1; 492 } 493 r = convertJsonToDbus(m, argCode, *it); 494 if (r < 0) 495 { 496 return r; 497 } 498 it++; 499 } 500 r = sd_bus_message_close_container(m); 501 } 502 else if (boost::starts_with(argCode, "{") && 503 boost::ends_with(argCode, "}")) 504 { 505 std::string containedType = argCode.substr(1, argCode.size() - 1); 506 r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY, 507 containedType.c_str()); 508 std::vector<std::string> codes = dbusArgSplit(containedType); 509 if (codes.size() != 2) 510 { 511 return -1; 512 } 513 const std::string &key_type = codes[0]; 514 const std::string &value_type = codes[1]; 515 for (auto it : j->items()) 516 { 517 r = convertJsonToDbus(m, key_type, it.key()); 518 if (r < 0) 519 { 520 return r; 521 } 522 523 r = convertJsonToDbus(m, value_type, it.value()); 524 if (r < 0) 525 { 526 return r; 527 } 528 } 529 r = sd_bus_message_close_container(m); 530 } 531 else 532 { 533 return -2; 534 } 535 if (r < 0) 536 { 537 return r; 538 } 539 540 if (argTypes.size() > 1) 541 { 542 jIt++; 543 } 544 } 545 } 546 547 void findActionOnInterface(std::shared_ptr<InProgressActionData> transaction, 548 const std::string &connectionName) 549 { 550 BMCWEB_LOG_DEBUG << "findActionOnInterface for connection " 551 << connectionName; 552 crow::connections::systemBus->async_method_call( 553 [transaction, connectionName{std::string(connectionName)}]( 554 const boost::system::error_code ec, 555 const std::string &introspect_xml) { 556 BMCWEB_LOG_DEBUG << "got xml:\n " << introspect_xml; 557 if (ec) 558 { 559 BMCWEB_LOG_ERROR 560 << "Introspect call failed with error: " << ec.message() 561 << " on process: " << connectionName << "\n"; 562 } 563 else 564 { 565 tinyxml2::XMLDocument doc; 566 567 doc.Parse(introspect_xml.data(), introspect_xml.size()); 568 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 569 if (pRoot == nullptr) 570 { 571 BMCWEB_LOG_ERROR << "XML document failed to parse " 572 << connectionName << "\n"; 573 return; 574 } 575 tinyxml2::XMLElement *interfaceNode = 576 pRoot->FirstChildElement("interface"); 577 while (interfaceNode != nullptr) 578 { 579 const char *thisInterfaceName = 580 interfaceNode->Attribute("name"); 581 if (thisInterfaceName != nullptr) 582 { 583 tinyxml2::XMLElement *methodNode = 584 interfaceNode->FirstChildElement("method"); 585 while (methodNode != nullptr) 586 { 587 const char *thisMethodName = 588 methodNode->Attribute("name"); 589 BMCWEB_LOG_DEBUG << "Found method: " 590 << thisMethodName; 591 if (thisMethodName != nullptr && 592 thisMethodName == transaction->methodName) 593 { 594 BMCWEB_LOG_DEBUG 595 << "Found method named " << thisMethodName 596 << " on interface " << thisInterfaceName; 597 sdbusplus::message::message m = 598 crow::connections::systemBus 599 ->new_method_call( 600 connectionName.c_str(), 601 transaction->path.c_str(), 602 thisInterfaceName, 603 transaction->methodName.c_str()); 604 605 tinyxml2::XMLElement *argumentNode = 606 methodNode->FirstChildElement("arg"); 607 608 nlohmann::json::const_iterator argIt = 609 transaction->arguments.begin(); 610 611 while (argumentNode != nullptr) 612 { 613 const char *argDirection = 614 argumentNode->Attribute("direction"); 615 const char *argType = 616 argumentNode->Attribute("type"); 617 if (argDirection != nullptr && 618 argType != nullptr && 619 std::string(argDirection) == "in") 620 { 621 622 if (argIt == 623 transaction->arguments.end()) 624 { 625 transaction->setErrorStatus(); 626 return; 627 } 628 if (convertJsonToDbus( 629 m.get(), std::string(argType), 630 *argIt) < 0) 631 { 632 transaction->setErrorStatus(); 633 return; 634 } 635 636 argIt++; 637 } 638 argumentNode = 639 methodNode->NextSiblingElement("arg"); 640 } 641 642 crow::connections::systemBus->async_send( 643 m, [transaction]( 644 boost::system::error_code ec, 645 sdbusplus::message::message &m) { 646 if (ec) 647 { 648 transaction->setErrorStatus(); 649 return; 650 } 651 transaction->res.jsonValue = { 652 {"status", "ok"}, 653 {"message", "200 OK"}, 654 {"data", nullptr}}; 655 }); 656 break; 657 } 658 methodNode = 659 methodNode->NextSiblingElement("method"); 660 } 661 } 662 interfaceNode = 663 interfaceNode->NextSiblingElement("interface"); 664 } 665 } 666 }, 667 connectionName, transaction->path, 668 "org.freedesktop.DBus.Introspectable", "Introspect"); 669 } 670 671 void handleAction(const crow::Request &req, crow::Response &res, 672 const std::string &objectPath, const std::string &methodName) 673 { 674 BMCWEB_LOG_DEBUG << "handleAction on path: " << objectPath << " and method " 675 << methodName; 676 nlohmann::json requestDbusData = 677 nlohmann::json::parse(req.body, nullptr, false); 678 679 if (requestDbusData.is_discarded()) 680 { 681 res.result(boost::beast::http::status::bad_request); 682 res.end(); 683 return; 684 } 685 nlohmann::json::iterator data = requestDbusData.find("data"); 686 if (data == requestDbusData.end()) 687 { 688 res.result(boost::beast::http::status::bad_request); 689 res.end(); 690 return; 691 } 692 693 if (!data->is_array()) 694 { 695 res.result(boost::beast::http::status::bad_request); 696 res.end(); 697 return; 698 } 699 auto transaction = std::make_shared<InProgressActionData>(res); 700 701 transaction->path = objectPath; 702 transaction->methodName = methodName; 703 transaction->arguments = std::move(*data); 704 crow::connections::systemBus->async_method_call( 705 [transaction]( 706 const boost::system::error_code ec, 707 const std::vector<std::pair<std::string, std::vector<std::string>>> 708 &interfaceNames) { 709 if (ec || interfaceNames.size() <= 0) 710 { 711 BMCWEB_LOG_ERROR << "Can't find object"; 712 transaction->setErrorStatus(); 713 return; 714 } 715 716 BMCWEB_LOG_DEBUG << "GetObject returned " << interfaceNames.size() 717 << " object(s)"; 718 719 for (const std::pair<std::string, std::vector<std::string>> 720 &object : interfaceNames) 721 { 722 findActionOnInterface(transaction, object.first); 723 } 724 }, 725 "xyz.openbmc_project.ObjectMapper", 726 "/xyz/openbmc_project/object_mapper", 727 "xyz.openbmc_project.ObjectMapper", "GetObject", objectPath, 728 std::array<std::string, 0>()); 729 } 730 731 void handleList(crow::Response &res, const std::string &objectPath) 732 { 733 crow::connections::systemBus->async_method_call( 734 [&res](const boost::system::error_code ec, 735 std::vector<std::string> &objectPaths) { 736 if (ec) 737 { 738 res.result(boost::beast::http::status::internal_server_error); 739 } 740 else 741 { 742 res.jsonValue = {{"status", "ok"}, 743 {"message", "200 OK"}, 744 {"data", std::move(objectPaths)}}; 745 } 746 res.end(); 747 }, 748 "xyz.openbmc_project.ObjectMapper", 749 "/xyz/openbmc_project/object_mapper", 750 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", objectPath, 751 static_cast<int32_t>(0), std::array<std::string, 0>()); 752 } 753 754 void handleEnumerate(crow::Response &res, const std::string &objectPath) 755 { 756 crow::connections::systemBus->async_method_call( 757 [&res, objectPath{std::string(objectPath)}]( 758 const boost::system::error_code ec, 759 const GetSubTreeType &object_names) { 760 if (ec) 761 { 762 res.jsonValue = {{"message", "200 OK"}, 763 {"status", "ok"}, 764 {"data", nlohmann::json::object()}}; 765 766 res.end(); 767 return; 768 } 769 770 boost::container::flat_set<std::string> connections; 771 772 for (const auto &object : object_names) 773 { 774 for (const auto &Connection : object.second) 775 { 776 connections.insert(Connection.first); 777 } 778 } 779 780 if (connections.size() <= 0) 781 { 782 res.result(boost::beast::http::status::not_found); 783 res.end(); 784 return; 785 } 786 auto transaction = 787 std::make_shared<nlohmann::json>(nlohmann::json::object()); 788 for (const std::string &Connection : connections) 789 { 790 findObjectManagerPathForEnumerate(objectPath, Connection, res, 791 transaction); 792 } 793 }, 794 "xyz.openbmc_project.ObjectMapper", 795 "/xyz/openbmc_project/object_mapper", 796 "xyz.openbmc_project.ObjectMapper", "GetSubTree", objectPath, 797 (int32_t)0, std::array<std::string, 0>()); 798 } 799 800 void handleGet(crow::Response &res, std::string &objectPath, 801 std::string &destProperty) 802 { 803 BMCWEB_LOG_DEBUG << "handleGet: " << objectPath << " prop:" << destProperty; 804 std::shared_ptr<std::string> propertyName = 805 std::make_shared<std::string>(std::move(destProperty)); 806 807 std::shared_ptr<std::string> path = 808 std::make_shared<std::string>(std::move(objectPath)); 809 810 using GetObjectType = 811 std::vector<std::pair<std::string, std::vector<std::string>>>; 812 crow::connections::systemBus->async_method_call( 813 [&res, path, propertyName](const boost::system::error_code ec, 814 const GetObjectType &object_names) { 815 if (ec || object_names.size() <= 0) 816 { 817 res.result(boost::beast::http::status::not_found); 818 res.end(); 819 return; 820 } 821 std::shared_ptr<nlohmann::json> response = 822 std::make_shared<nlohmann::json>(nlohmann::json::object()); 823 // The mapper should never give us an empty interface names list, 824 // but check anyway 825 for (const std::pair<std::string, std::vector<std::string>> 826 connection : object_names) 827 { 828 const std::vector<std::string> &interfaceNames = 829 connection.second; 830 831 if (interfaceNames.size() <= 0) 832 { 833 res.result(boost::beast::http::status::not_found); 834 res.end(); 835 return; 836 } 837 838 for (const std::string &interface : interfaceNames) 839 { 840 crow::connections::systemBus->async_method_call( 841 [&res, response, propertyName]( 842 const boost::system::error_code ec, 843 const std::vector< 844 std::pair<std::string, DbusRestVariantType>> 845 &properties) { 846 if (ec) 847 { 848 BMCWEB_LOG_ERROR << "Bad dbus request error: " 849 << ec; 850 } 851 else 852 { 853 for (const std::pair<std::string, 854 DbusRestVariantType> 855 &property : properties) 856 { 857 // if property name is empty, or matches our 858 // search query, add it to the response json 859 860 if (propertyName->empty()) 861 { 862 mapbox::util::apply_visitor( 863 [&response, &property](auto &&val) { 864 (*response)[property.first] = 865 val; 866 }, 867 property.second); 868 } 869 else if (property.first == *propertyName) 870 { 871 mapbox::util::apply_visitor( 872 [&response](auto &&val) { 873 (*response) = val; 874 }, 875 property.second); 876 } 877 } 878 } 879 if (response.use_count() == 1) 880 { 881 res.jsonValue = {{"status", "ok"}, 882 {"message", "200 OK"}, 883 {"data", *response}}; 884 885 res.end(); 886 } 887 }, 888 connection.first, *path, 889 "org.freedesktop.DBus.Properties", "GetAll", interface); 890 } 891 } 892 }, 893 "xyz.openbmc_project.ObjectMapper", 894 "/xyz/openbmc_project/object_mapper", 895 "xyz.openbmc_project.ObjectMapper", "GetObject", *path, 896 std::array<std::string, 0>()); 897 } 898 899 struct AsyncPutRequest 900 { 901 AsyncPutRequest(crow::Response &res) : res(res) 902 { 903 res.jsonValue = { 904 {"status", "ok"}, {"message", "200 OK"}, {"data", nullptr}}; 905 } 906 ~AsyncPutRequest() 907 { 908 if (res.result() == boost::beast::http::status::internal_server_error) 909 { 910 // Reset the json object to clear out any data that made it in 911 // before the error happened todo(ed) handle error condition with 912 // proper code 913 res.jsonValue = nlohmann::json::object(); 914 } 915 916 if (res.jsonValue.empty()) 917 { 918 res.result(boost::beast::http::status::forbidden); 919 res.jsonValue = { 920 {"status", "error"}, 921 {"message", "403 Forbidden"}, 922 {"data", 923 {{"message", "The specified property cannot be created: " + 924 propertyName}}}}; 925 } 926 927 res.end(); 928 } 929 930 void setErrorStatus() 931 { 932 res.result(boost::beast::http::status::internal_server_error); 933 } 934 935 crow::Response &res; 936 std::string objectPath; 937 std::string propertyName; 938 nlohmann::json propertyValue; 939 }; 940 941 void handlePut(const crow::Request &req, crow::Response &res, 942 const std::string &objectPath, const std::string &destProperty) 943 { 944 nlohmann::json requestDbusData = 945 nlohmann::json::parse(req.body, nullptr, false); 946 947 if (requestDbusData.is_discarded()) 948 { 949 res.result(boost::beast::http::status::bad_request); 950 res.end(); 951 return; 952 } 953 954 nlohmann::json::const_iterator propertyIt = requestDbusData.find("data"); 955 if (propertyIt == requestDbusData.end()) 956 { 957 res.result(boost::beast::http::status::bad_request); 958 res.end(); 959 return; 960 } 961 const nlohmann::json &propertySetValue = *propertyIt; 962 auto transaction = std::make_shared<AsyncPutRequest>(res); 963 transaction->objectPath = objectPath; 964 transaction->propertyName = destProperty; 965 transaction->propertyValue = propertySetValue; 966 967 using GetObjectType = 968 std::vector<std::pair<std::string, std::vector<std::string>>>; 969 970 crow::connections::systemBus->async_method_call( 971 [transaction](const boost::system::error_code ec, 972 const GetObjectType &object_names) { 973 if (!ec && object_names.size() <= 0) 974 { 975 transaction->res.result(boost::beast::http::status::not_found); 976 return; 977 } 978 979 for (const std::pair<std::string, std::vector<std::string>> 980 connection : object_names) 981 { 982 const std::string &connectionName = connection.first; 983 984 crow::connections::systemBus->async_method_call( 985 [connectionName{std::string(connectionName)}, 986 transaction](const boost::system::error_code ec, 987 const std::string &introspectXml) { 988 if (ec) 989 { 990 BMCWEB_LOG_ERROR 991 << "Introspect call failed with error: " 992 << ec.message() 993 << " on process: " << connectionName; 994 transaction->setErrorStatus(); 995 return; 996 } 997 tinyxml2::XMLDocument doc; 998 999 doc.Parse(introspectXml.c_str()); 1000 tinyxml2::XMLNode *pRoot = 1001 doc.FirstChildElement("node"); 1002 if (pRoot == nullptr) 1003 { 1004 BMCWEB_LOG_ERROR << "XML document failed to parse: " 1005 << introspectXml; 1006 transaction->setErrorStatus(); 1007 return; 1008 } 1009 tinyxml2::XMLElement *ifaceNode = 1010 pRoot->FirstChildElement("interface"); 1011 while (ifaceNode != nullptr) 1012 { 1013 const char *interfaceName = 1014 ifaceNode->Attribute("name"); 1015 BMCWEB_LOG_DEBUG << "found interface " 1016 << interfaceName; 1017 tinyxml2::XMLElement *propNode = 1018 ifaceNode->FirstChildElement("property"); 1019 while (propNode != nullptr) 1020 { 1021 const char *propertyName = 1022 propNode->Attribute("name"); 1023 BMCWEB_LOG_DEBUG << "Found property " 1024 << propertyName; 1025 if (propertyName == transaction->propertyName) 1026 { 1027 const char *argType = 1028 propNode->Attribute("type"); 1029 if (argType != nullptr) 1030 { 1031 sdbusplus::message::message m = 1032 crow::connections::systemBus 1033 ->new_method_call( 1034 connectionName.c_str(), 1035 transaction->objectPath 1036 .c_str(), 1037 "org.freedesktop.DBus." 1038 "Properties", 1039 "Set"); 1040 m.append(interfaceName, 1041 transaction->propertyName); 1042 int r = sd_bus_message_open_container( 1043 m.get(), SD_BUS_TYPE_VARIANT, 1044 argType); 1045 if (r < 0) 1046 { 1047 transaction->setErrorStatus(); 1048 return; 1049 } 1050 r = convertJsonToDbus( 1051 m.get(), argType, 1052 transaction->propertyValue); 1053 if (r < 0) 1054 { 1055 transaction->setErrorStatus(); 1056 return; 1057 } 1058 r = sd_bus_message_close_container( 1059 m.get()); 1060 if (r < 0) 1061 { 1062 transaction->setErrorStatus(); 1063 return; 1064 } 1065 1066 crow::connections::systemBus 1067 ->async_send( 1068 m, 1069 [transaction]( 1070 boost::system::error_code 1071 ec, 1072 sdbusplus::message::message 1073 &m) { 1074 BMCWEB_LOG_DEBUG << "sent"; 1075 if (ec) 1076 { 1077 transaction->res 1078 .jsonValue 1079 ["status"] = 1080 "error"; 1081 transaction->res 1082 .jsonValue 1083 ["message"] = 1084 ec.message(); 1085 } 1086 }); 1087 } 1088 } 1089 propNode = 1090 propNode->NextSiblingElement("property"); 1091 } 1092 ifaceNode = 1093 ifaceNode->NextSiblingElement("interface"); 1094 } 1095 }, 1096 connectionName, transaction->objectPath, 1097 "org.freedesktop.DBus.Introspectable", "Introspect"); 1098 } 1099 }, 1100 "xyz.openbmc_project.ObjectMapper", 1101 "/xyz/openbmc_project/object_mapper", 1102 "xyz.openbmc_project.ObjectMapper", "GetObject", 1103 transaction->objectPath, std::array<std::string, 0>()); 1104 } 1105 1106 template <typename... Middlewares> void requestRoutes(Crow<Middlewares...> &app) 1107 { 1108 BMCWEB_ROUTE(app, "/bus/") 1109 .methods("GET"_method)( 1110 [](const crow::Request &req, crow::Response &res) { 1111 res.jsonValue = {{"busses", {{{"name", "system"}}}}, 1112 {"status", "ok"}}; 1113 res.end(); 1114 }); 1115 1116 BMCWEB_ROUTE(app, "/bus/system/") 1117 .methods("GET"_method)( 1118 [](const crow::Request &req, crow::Response &res) { 1119 auto myCallback = [&res](const boost::system::error_code ec, 1120 std::vector<std::string> &names) { 1121 if (ec) 1122 { 1123 BMCWEB_LOG_ERROR << "Dbus call failed with code " << ec; 1124 res.result( 1125 boost::beast::http::status::internal_server_error); 1126 } 1127 else 1128 { 1129 std::sort(names.begin(), names.end()); 1130 res.jsonValue = {{"status", "ok"}}; 1131 auto &objectsSub = res.jsonValue["objects"]; 1132 for (auto &name : names) 1133 { 1134 objectsSub.push_back({{"name", name}}); 1135 } 1136 } 1137 res.end(); 1138 }; 1139 crow::connections::systemBus->async_method_call( 1140 std::move(myCallback), "org.freedesktop.DBus", "/", 1141 "org.freedesktop.DBus", "ListNames"); 1142 }); 1143 1144 BMCWEB_ROUTE(app, "/list/") 1145 .methods("GET"_method)( 1146 [](const crow::Request &req, crow::Response &res) { 1147 handleList(res, "/"); 1148 }); 1149 1150 BMCWEB_ROUTE(app, "/xyz/<path>") 1151 .methods("GET"_method, "PUT"_method, 1152 "POST"_method)([](const crow::Request &req, 1153 crow::Response &res, 1154 const std::string &path) { 1155 std::string objectPath = "/xyz/" + path; 1156 1157 // Trim any trailing "/" at the end 1158 if (boost::ends_with(objectPath, "/")) 1159 { 1160 objectPath.pop_back(); 1161 } 1162 1163 // If accessing a single attribute, fill in and update objectPath, 1164 // otherwise leave destProperty blank 1165 std::string destProperty = ""; 1166 const char *attrSeperator = "/attr/"; 1167 size_t attrPosition = path.find(attrSeperator); 1168 if (attrPosition != path.npos) 1169 { 1170 objectPath = "/xyz/" + path.substr(0, attrPosition); 1171 destProperty = path.substr(attrPosition + strlen(attrSeperator), 1172 path.length()); 1173 } 1174 1175 if (req.method() == "POST"_method) 1176 { 1177 constexpr const char *actionSeperator = "/action/"; 1178 size_t actionPosition = path.find(actionSeperator); 1179 if (actionPosition != path.npos) 1180 { 1181 objectPath = "/xyz/" + path.substr(0, actionPosition); 1182 std::string postProperty = 1183 path.substr((actionPosition + strlen(actionSeperator)), 1184 path.length()); 1185 handleAction(req, res, objectPath, postProperty); 1186 return; 1187 } 1188 } 1189 else if (req.method() == "GET"_method) 1190 { 1191 if (boost::ends_with(objectPath, "/enumerate")) 1192 { 1193 objectPath.erase(objectPath.end() - 10, objectPath.end()); 1194 handleEnumerate(res, objectPath); 1195 } 1196 else if (boost::ends_with(objectPath, "/list")) 1197 { 1198 objectPath.erase(objectPath.end() - 5, objectPath.end()); 1199 handleList(res, objectPath); 1200 } 1201 else 1202 { 1203 handleGet(res, objectPath, destProperty); 1204 } 1205 return; 1206 } 1207 else if (req.method() == "PUT"_method) 1208 { 1209 handlePut(req, res, objectPath, destProperty); 1210 return; 1211 } 1212 1213 res.result(boost::beast::http::status::method_not_allowed); 1214 res.end(); 1215 }); 1216 1217 BMCWEB_ROUTE(app, "/download/dump/<str>/") 1218 .methods("GET"_method)([](const crow::Request &req, crow::Response &res, 1219 const std::string &dumpId) { 1220 std::regex validFilename("^[\\w\\- ]+(\\.?[\\w\\- ]+)$"); 1221 if (!std::regex_match(dumpId, validFilename)) 1222 { 1223 res.result(boost::beast::http::status::not_found); 1224 res.end(); 1225 return; 1226 } 1227 std::experimental::filesystem::path loc( 1228 "/var/lib/phosphor-debug-collector/dumps"); 1229 1230 loc += dumpId; 1231 1232 if (!std::experimental::filesystem::exists(loc) || 1233 !std::experimental::filesystem::is_directory(loc)) 1234 { 1235 res.result(boost::beast::http::status::not_found); 1236 res.end(); 1237 return; 1238 } 1239 std::experimental::filesystem::directory_iterator files(loc); 1240 for (auto &file : files) 1241 { 1242 std::ifstream readFile(file.path()); 1243 if (readFile.good()) 1244 { 1245 continue; 1246 } 1247 res.addHeader("Content-Type", "application/octet-stream"); 1248 res.body() = {std::istreambuf_iterator<char>(readFile), 1249 std::istreambuf_iterator<char>()}; 1250 res.end(); 1251 } 1252 res.result(boost::beast::http::status::not_found); 1253 res.end(); 1254 return; 1255 }); 1256 1257 BMCWEB_ROUTE(app, "/bus/system/<str>/") 1258 .methods("GET"_method)([](const crow::Request &req, crow::Response &res, 1259 const std::string &Connection) { 1260 introspectObjects(Connection, "/", 1261 std::make_shared<bmcweb::AsyncResp>(res)); 1262 }); 1263 1264 BMCWEB_ROUTE(app, "/bus/system/<str>/<path>") 1265 .methods("GET"_method, 1266 "POST"_method)([](const crow::Request &req, 1267 crow::Response &res, 1268 const std::string &processName, 1269 const std::string &requestedPath) { 1270 std::vector<std::string> strs; 1271 boost::split(strs, requestedPath, boost::is_any_of("/")); 1272 std::string objectPath; 1273 std::string interfaceName; 1274 std::string methodName; 1275 auto it = strs.begin(); 1276 if (it == strs.end()) 1277 { 1278 objectPath = "/"; 1279 } 1280 while (it != strs.end()) 1281 { 1282 // Check if segment contains ".". If it does, it must be an 1283 // interface 1284 if (it->find(".") != std::string::npos) 1285 { 1286 break; 1287 // This check is neccesary as the trailing slash gets parsed 1288 // as part of our <path> specifier above, which causes the 1289 // normal trailing backslash redirector to fail. 1290 } 1291 else if (!it->empty()) 1292 { 1293 objectPath += "/" + *it; 1294 } 1295 it++; 1296 } 1297 if (it != strs.end()) 1298 { 1299 interfaceName = *it; 1300 it++; 1301 1302 // after interface, we might have a method name 1303 if (it != strs.end()) 1304 { 1305 methodName = *it; 1306 it++; 1307 } 1308 } 1309 if (it != strs.end()) 1310 { 1311 // if there is more levels past the method name, something went 1312 // wrong, return not found 1313 res.result(boost::beast::http::status::not_found); 1314 res.end(); 1315 return; 1316 } 1317 if (interfaceName.empty()) 1318 { 1319 crow::connections::systemBus->async_method_call( 1320 [&, processName, 1321 objectPath](const boost::system::error_code ec, 1322 const std::string &introspect_xml) { 1323 if (ec) 1324 { 1325 BMCWEB_LOG_ERROR 1326 << "Introspect call failed with error: " 1327 << ec.message() 1328 << " on process: " << processName 1329 << " path: " << objectPath << "\n"; 1330 return; 1331 } 1332 tinyxml2::XMLDocument doc; 1333 1334 doc.Parse(introspect_xml.c_str()); 1335 tinyxml2::XMLNode *pRoot = 1336 doc.FirstChildElement("node"); 1337 if (pRoot == nullptr) 1338 { 1339 BMCWEB_LOG_ERROR << "XML document failed to parse " 1340 << processName << " " << objectPath 1341 << "\n"; 1342 res.jsonValue = {{"status", "XML parse error"}}; 1343 res.result(boost::beast::http::status:: 1344 internal_server_error); 1345 return; 1346 } 1347 1348 BMCWEB_LOG_DEBUG << introspect_xml; 1349 res.jsonValue = {{"status", "ok"}, 1350 {"bus_name", processName}, 1351 {"object_path", objectPath}}; 1352 nlohmann::json &interfacesArray = 1353 res.jsonValue["interfaces"]; 1354 interfacesArray = nlohmann::json::array(); 1355 tinyxml2::XMLElement *interface = 1356 pRoot->FirstChildElement("interface"); 1357 1358 while (interface != nullptr) 1359 { 1360 const char *ifaceName = 1361 interface->Attribute("name"); 1362 if (ifaceName != nullptr) 1363 { 1364 interfacesArray.push_back( 1365 {{"name", ifaceName}}); 1366 } 1367 1368 interface = 1369 interface->NextSiblingElement("interface"); 1370 } 1371 1372 res.end(); 1373 }, 1374 processName, objectPath, 1375 "org.freedesktop.DBus.Introspectable", "Introspect"); 1376 } 1377 else if (methodName.empty()) 1378 { 1379 crow::connections::systemBus->async_method_call( 1380 [&, processName, objectPath, 1381 interfaceName{std::move(interfaceName)}]( 1382 const boost::system::error_code ec, 1383 const std::string &introspect_xml) { 1384 if (ec) 1385 { 1386 BMCWEB_LOG_ERROR 1387 << "Introspect call failed with error: " 1388 << ec.message() 1389 << " on process: " << processName 1390 << " path: " << objectPath << "\n"; 1391 } 1392 else 1393 { 1394 tinyxml2::XMLDocument doc; 1395 1396 doc.Parse(introspect_xml.c_str()); 1397 tinyxml2::XMLNode *pRoot = 1398 doc.FirstChildElement("node"); 1399 if (pRoot == nullptr) 1400 { 1401 BMCWEB_LOG_ERROR 1402 << "XML document failed to parse " 1403 << processName << " " << objectPath << "\n"; 1404 res.result(boost::beast::http::status:: 1405 internal_server_error); 1406 } 1407 else 1408 { 1409 tinyxml2::XMLElement *node = 1410 pRoot->FirstChildElement("node"); 1411 1412 // if we know we're the only call, build the 1413 // json directly 1414 tinyxml2::XMLElement *interface = 1415 pRoot->FirstChildElement("interface"); 1416 1417 res.jsonValue = { 1418 {"status", "ok"}, 1419 {"bus_name", processName}, 1420 {"interface", interfaceName}, 1421 {"object_path", objectPath}, 1422 {"properties", nlohmann::json::object()}}; 1423 1424 nlohmann::json &methodsArray = 1425 res.jsonValue["methods"]; 1426 methodsArray = nlohmann::json::array(); 1427 1428 nlohmann::json &signalsArray = 1429 res.jsonValue["signals"]; 1430 signalsArray = nlohmann::json::array(); 1431 1432 while (interface != nullptr) 1433 { 1434 const char *ifaceName = 1435 interface->Attribute("name"); 1436 1437 if (ifaceName != nullptr && 1438 ifaceName == interfaceName) 1439 { 1440 tinyxml2::XMLElement *methods = 1441 interface->FirstChildElement( 1442 "method"); 1443 while (methods != nullptr) 1444 { 1445 nlohmann::json argsArray = 1446 nlohmann::json::array(); 1447 tinyxml2::XMLElement *arg = 1448 methods->FirstChildElement( 1449 "arg"); 1450 while (arg != nullptr) 1451 { 1452 nlohmann::json thisArg; 1453 for (const char *fieldName : 1454 std::array<const char *, 1455 3>{"name", 1456 "direction", 1457 "type"}) 1458 { 1459 const char *fieldValue = 1460 arg->Attribute( 1461 fieldName); 1462 if (fieldValue != nullptr) 1463 { 1464 thisArg[fieldName] = 1465 fieldValue; 1466 } 1467 } 1468 argsArray.push_back( 1469 std::move(thisArg)); 1470 arg = arg->NextSiblingElement( 1471 "arg"); 1472 } 1473 1474 const char *name = 1475 methods->Attribute("name"); 1476 if (name != nullptr) 1477 { 1478 methodsArray.push_back( 1479 {{"name", name}, 1480 {"uri", "/bus/system/" + 1481 processName + 1482 objectPath + 1483 "/" + 1484 interfaceName + 1485 "/" + name}, 1486 {"args", argsArray}}); 1487 } 1488 methods = 1489 methods->NextSiblingElement( 1490 "method"); 1491 } 1492 tinyxml2::XMLElement *signals = 1493 interface->FirstChildElement( 1494 "signal"); 1495 while (signals != nullptr) 1496 { 1497 nlohmann::json argsArray = 1498 nlohmann::json::array(); 1499 1500 tinyxml2::XMLElement *arg = 1501 signals->FirstChildElement( 1502 "arg"); 1503 while (arg != nullptr) 1504 { 1505 const char *name = 1506 arg->Attribute("name"); 1507 const char *type = 1508 arg->Attribute("type"); 1509 if (name != nullptr && 1510 type != nullptr) 1511 { 1512 argsArray.push_back({ 1513 {"name", name}, 1514 {"type", type}, 1515 }); 1516 } 1517 arg = arg->NextSiblingElement( 1518 "arg"); 1519 } 1520 const char *name = 1521 signals->Attribute("name"); 1522 if (name != nullptr) 1523 { 1524 signalsArray.push_back( 1525 {{"name", name}, 1526 {"args", argsArray}}); 1527 } 1528 1529 signals = 1530 signals->NextSiblingElement( 1531 "signal"); 1532 } 1533 1534 break; 1535 } 1536 1537 interface = interface->NextSiblingElement( 1538 "interface"); 1539 } 1540 if (interface == nullptr) 1541 { 1542 // if we got to the end of the list and 1543 // never found a match, throw 404 1544 res.result( 1545 boost::beast::http::status::not_found); 1546 } 1547 } 1548 } 1549 res.end(); 1550 }, 1551 processName, objectPath, 1552 "org.freedesktop.DBus.Introspectable", "Introspect"); 1553 } 1554 else 1555 { 1556 if (req.method() != "POST"_method) 1557 { 1558 res.result(boost::beast::http::status::not_found); 1559 res.end(); 1560 return; 1561 } 1562 1563 nlohmann::json requestDbusData = 1564 nlohmann::json::parse(req.body, nullptr, false); 1565 1566 if (requestDbusData.is_discarded()) 1567 { 1568 res.result(boost::beast::http::status::bad_request); 1569 res.end(); 1570 return; 1571 } 1572 if (!requestDbusData.is_array()) 1573 { 1574 res.result(boost::beast::http::status::bad_request); 1575 res.end(); 1576 return; 1577 } 1578 auto transaction = std::make_shared<InProgressActionData>(res); 1579 1580 transaction->path = objectPath; 1581 transaction->methodName = methodName; 1582 transaction->arguments = std::move(requestDbusData); 1583 1584 findActionOnInterface(transaction, processName); 1585 } 1586 }); 1587 } 1588 } // namespace openbmc_mapper 1589 } // namespace crow 1590