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