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