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