1 #include <crow/app.h> 2 3 #include <tinyxml2.h> 4 #include <dbus/connection.hpp> 5 #include <dbus/endpoint.hpp> 6 #include <dbus/filter.hpp> 7 #include <dbus/match.hpp> 8 #include <dbus/message.hpp> 9 #include <dbus_singleton.hpp> 10 #include <boost/container/flat_set.hpp> 11 12 namespace crow { 13 namespace openbmc_mapper { 14 15 void introspect_objects(crow::response &res, std::string process_name, 16 std::string path, 17 std::shared_ptr<nlohmann::json> transaction) { 18 dbus::endpoint introspect_endpoint( 19 process_name, path, "org.freedesktop.DBus.Introspectable", "Introspect"); 20 crow::connections::system_bus->async_method_call( 21 [&, process_name{std::move(process_name)}, object_path{std::move(path)} ]( 22 const boost::system::error_code ec, 23 const std::string &introspect_xml) { 24 if (ec) { 25 CROW_LOG_ERROR << "Introspect call failed with error: " 26 << ec.message() << " on process: " << process_name 27 << " path: " << object_path << "\n"; 28 29 } else { 30 transaction->push_back({{"path", object_path}}); 31 32 tinyxml2::XMLDocument doc; 33 34 doc.Parse(introspect_xml.c_str()); 35 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 36 if (pRoot == nullptr) { 37 CROW_LOG_ERROR << "XML document failed to parse " << process_name 38 << " " << path << "\n"; 39 40 } else { 41 tinyxml2::XMLElement *node = pRoot->FirstChildElement("node"); 42 while (node != nullptr) { 43 std::string child_path = node->Attribute("name"); 44 std::string newpath; 45 if (object_path != "/") { 46 newpath += object_path; 47 } 48 newpath += "/" + child_path; 49 // introspect the subobjects as well 50 introspect_objects(res, process_name, newpath, transaction); 51 52 node = node->NextSiblingElement("node"); 53 } 54 } 55 } 56 // if we're the last outstanding caller, finish the request 57 if (transaction.use_count() == 1) { 58 res.json_value = {{"status", "ok"}, 59 {"bus_name", process_name}, 60 {"objects", *transaction}}; 61 res.end(); 62 } 63 }, 64 introspect_endpoint); 65 } 66 using ManagedObjectType = std::vector<std::pair< 67 dbus::object_path, boost::container::flat_map< 68 std::string, boost::container::flat_map< 69 std::string, dbus::dbus_variant>>>>; 70 71 void get_manged_objects_for_enumerate( 72 const std::string &object_name, const std::string &connection_name, 73 crow::response &res, std::shared_ptr<nlohmann::json> transaction) { 74 crow::connections::system_bus->async_method_call( 75 [&res, transaction](const boost::system::error_code ec, 76 const ManagedObjectType &objects) { 77 if (ec) { 78 CROW_LOG_ERROR << ec; 79 } else { 80 nlohmann::json &data_json = *transaction; 81 for (auto &object_path : objects) { 82 nlohmann::json &object_json = data_json[object_path.first.value]; 83 for (const auto &interface : object_path.second) { 84 for (const auto &property : interface.second) { 85 boost::apply_visitor( 86 [&](auto &&val) { object_json[property.first] = val; }, 87 property.second); 88 } 89 } 90 } 91 } 92 93 if (transaction.use_count() == 1) { 94 res.json_value = {{"message", "200 OK"}, 95 {"status", "ok"}, 96 {"data", std::move(*transaction)}}; 97 res.end(); 98 } 99 }, 100 {connection_name, object_name, "org.freedesktop.DBus.ObjectManager", 101 "GetManagedObjects"}); 102 } // namespace openbmc_mapper 103 104 using GetSubTreeType = std::vector< 105 std::pair<std::string, 106 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 107 108 void handle_enumerate(crow::response &res, const std::string &object_path) { 109 crow::connections::system_bus->async_method_call( 110 [&res, object_path{std::string(object_path)} ]( 111 const boost::system::error_code ec, 112 const GetSubTreeType &object_names) { 113 if (ec) { 114 res.code = 500; 115 res.end(); 116 return; 117 } 118 119 boost::container::flat_set<std::string> connections; 120 121 for (const auto &object : object_names) { 122 for (const auto &connection : object.second) { 123 connections.insert(connection.first); 124 } 125 } 126 127 if (connections.size() <= 0) { 128 res.code = 404; 129 res.end(); 130 return; 131 } 132 auto transaction = 133 std::make_shared<nlohmann::json>(nlohmann::json::object()); 134 for (const std::string &connection : connections) { 135 get_manged_objects_for_enumerate(object_path, connection, res, 136 transaction); 137 } 138 139 }, 140 {"xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", 141 "xyz.openbmc_project.ObjectMapper", "GetSubTree"}, 142 object_path, (int32_t)0, std::array<std::string, 0>()); 143 } 144 145 template <typename... Middlewares> 146 void request_routes(Crow<Middlewares...> &app) { 147 CROW_ROUTE(app, "/bus/").methods("GET"_method)([](const crow::request &req) { 148 return nlohmann::json{{"busses", {{{"name", "system"}}}}, {"status", "ok"}}; 149 150 }); 151 152 CROW_ROUTE(app, "/bus/system/") 153 .methods("GET"_method)([](const crow::request &req, crow::response &res) { 154 crow::connections::system_bus->async_method_call( 155 [&](const boost::system::error_code ec, 156 std::vector<std::string> &names) { 157 158 if (ec) { 159 res.code = 500; 160 } else { 161 std::sort(names.begin(), names.end()); 162 nlohmann::json j{{"status", "ok"}}; 163 auto &objects_sub = j["objects"]; 164 for (auto &name : names) { 165 objects_sub.push_back({{"name", name}}); 166 } 167 res.json_value = std::move(j); 168 } 169 res.end(); 170 }, 171 {"org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"}); 172 173 }); 174 175 CROW_ROUTE(app, "/list/") 176 .methods("GET"_method)([](const crow::request &req, crow::response &res) { 177 crow::connections::system_bus->async_method_call( 178 [&](const boost::system::error_code ec, 179 const std::vector<std::string> &object_paths) { 180 181 if (ec) { 182 res.code = 500; 183 } else { 184 res.json_value = {{"status", "ok"}, 185 {"message", "200 OK"}, 186 {"data", std::move(object_paths)}}; 187 } 188 res.end(); 189 }, 190 {"xyz.openbmc_project.ObjectMapper", 191 "/xyz/openbmc_project/object_mapper", 192 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths"}, 193 "", static_cast<int32_t>(99), std::array<std::string, 0>()); 194 }); 195 196 CROW_ROUTE(app, "/xyz/<path>") 197 .methods("GET"_method, 198 "PUT"_method)([](const crow::request &req, crow::response &res, 199 const std::string &path) { 200 std::shared_ptr<nlohmann::json> transaction = 201 std::make_shared<nlohmann::json>(nlohmann::json::object()); 202 using GetObjectType = 203 std::vector<std::pair<std::string, std::vector<std::string>>>; 204 std::string object_path; 205 std::string dest_property; 206 std::string property_set_value; 207 size_t attr_position = path.find("/attr/"); 208 if (attr_position == path.npos) { 209 object_path = "/xyz/" + path; 210 } else { 211 object_path = "/xyz/" + path.substr(0, attr_position); 212 dest_property = 213 path.substr((attr_position + strlen("/attr/")), path.length()); 214 auto request_dbus_data = 215 nlohmann::json::parse(req.body, nullptr, false); 216 if (request_dbus_data.is_discarded()) { 217 res.code = 400; 218 res.end(); 219 return; 220 } 221 222 auto property_value_it = request_dbus_data.find("data"); 223 if (property_value_it == request_dbus_data.end()) { 224 res.code = 400; 225 res.end(); 226 return; 227 } 228 229 property_set_value = property_value_it->get<const std::string>(); 230 if (property_set_value.empty()) { 231 res.code = 400; 232 res.end(); 233 return; 234 } 235 } 236 237 if (boost::ends_with(object_path, "/enumerate")) { 238 object_path.erase(object_path.end() - 10, object_path.end()); 239 handle_enumerate(res, object_path); 240 return; 241 } 242 243 crow::connections::system_bus->async_method_call( 244 [ 245 &, object_path{std::move(object_path)}, 246 dest_property{std::move(dest_property)}, 247 property_set_value{std::move(property_set_value)}, transaction 248 ](const boost::system::error_code ec, 249 const GetObjectType &object_names) { 250 if (ec) { 251 res.code = 500; 252 res.end(); 253 return; 254 } 255 if (object_names.size() != 1) { 256 res.code = 404; 257 res.end(); 258 return; 259 } 260 if (req.method == "GET"_method) { 261 for (auto &interface : object_names[0].second) { 262 crow::connections::system_bus->async_method_call( 263 [&](const boost::system::error_code ec, 264 const std::vector<std::pair< 265 std::string, dbus::dbus_variant>> &properties) { 266 if (ec) { 267 CROW_LOG_ERROR << "Bad dbus request error: " << ec; 268 } else { 269 for (auto &property : properties) { 270 boost::apply_visitor( 271 [&](auto val) { 272 (*transaction)[property.first] = val; 273 }, 274 property.second); 275 } 276 } 277 if (transaction.use_count() == 1) { 278 res.json_value = {{"status", "ok"}, 279 {"message", "200 OK"}, 280 {"data", *transaction}}; 281 282 res.end(); 283 } 284 }, 285 {object_names[0].first, object_path, 286 "org.freedesktop.DBus.Properties", "GetAll"}, 287 interface); 288 } 289 } else if (req.method == "PUT"_method) { 290 for (auto &interface : object_names[0].second) { 291 crow::connections::system_bus->async_method_call( 292 [ 293 &, interface{std::move(interface)}, 294 object_names{std::move(object_names)}, 295 object_path{std::move(object_path)}, 296 dest_property{std::move(dest_property)}, 297 property_set_value{std::move(property_set_value)}, 298 transaction 299 ](const boost::system::error_code ec, 300 const boost::container::flat_map< 301 std::string, dbus::dbus_variant> &properties) { 302 if (ec) { 303 CROW_LOG_ERROR << "Bad dbus request error: " << ec; 304 } else { 305 auto it = properties.find(dest_property); 306 if (it != properties.end()) { 307 // find the matched property in the interface 308 dbus::dbus_variant property_value( 309 property_set_value); // create the dbus 310 // variant for dbus call 311 crow::connections::system_bus->async_method_call( 312 [&](const boost::system::error_code ec) { 313 // use the method "Set" to set the property 314 // value 315 if (ec) { 316 CROW_LOG_ERROR << "Bad dbus request error: " 317 << ec; 318 } 319 // find the matched property and send the 320 // response 321 *transaction = {{"status", "ok"}, 322 {"message", "200 OK"}, 323 {"data", nullptr}}; 324 325 }, 326 {object_names[0].first, object_path, 327 "org.freedesktop.DBus.Properties", "Set"}, 328 interface, dest_property, property_value); 329 } 330 } 331 // if we are the last caller, finish the transaction 332 if (transaction.use_count() == 1) { 333 // if nobody filled in the property, all calls either 334 // errored, or failed 335 if (transaction == nullptr) { 336 res.code = 403; 337 res.json_value = {{"status", "error"}, 338 {"message", "403 Forbidden"}, 339 {"data", 340 {{"message", 341 "The specified property " 342 "cannot be created: " + 343 dest_property}}}}; 344 345 } else { 346 res.json_value = *transaction; 347 } 348 349 res.end(); 350 return; 351 } 352 }, 353 {object_names[0].first, object_path, 354 "org.freedesktop.DBus.Properties", "GetAll"}, 355 interface); 356 } 357 } 358 }, 359 {"xyz.openbmc_project.ObjectMapper", 360 "/xyz/openbmc_project/object_mapper", 361 "xyz.openbmc_project.ObjectMapper", "GetObject"}, 362 object_path, std::array<std::string, 0>()); 363 }); 364 365 CROW_ROUTE(app, "/bus/system/<str>/") 366 .methods("GET"_method)([](const crow::request &req, crow::response &res, 367 const std::string &connection) { 368 std::shared_ptr<nlohmann::json> transaction; 369 introspect_objects(res, connection, "/", transaction); 370 }); 371 372 CROW_ROUTE(app, "/bus/system/<str>/<path>") 373 .methods("GET"_method)([](const crow::request &req, crow::response &res, 374 const std::string &process_name, 375 const std::string &requested_path) { 376 377 std::vector<std::string> strs; 378 boost::split(strs, requested_path, boost::is_any_of("/")); 379 std::string object_path; 380 std::string interface_name; 381 std::string method_name; 382 auto it = strs.begin(); 383 if (it == strs.end()) { 384 object_path = "/"; 385 } 386 while (it != strs.end()) { 387 // Check if segment contains ".". If it does, it must be an 388 // interface 389 if ((*it).find(".") != std::string::npos) { 390 break; 391 // THis check is neccesary as the trailing slash gets parsed as 392 // part of our <path> specifier above, which causes the normal 393 // trailing backslash redirector to fail. 394 } else if (!it->empty()) { 395 object_path += "/" + *it; 396 } 397 it++; 398 } 399 if (it != strs.end()) { 400 interface_name = *it; 401 it++; 402 403 // after interface, we might have a method name 404 if (it != strs.end()) { 405 method_name = *it; 406 it++; 407 } 408 } 409 if (it != strs.end()) { 410 // if there is more levels past the method name, something went 411 // wrong, throw an error 412 res.code = 404; 413 res.end(); 414 return; 415 } 416 dbus::endpoint introspect_endpoint( 417 process_name, object_path, "org.freedesktop.DBus.Introspectable", 418 "Introspect"); 419 if (interface_name.empty()) { 420 crow::connections::system_bus->async_method_call( 421 [ 422 &, process_name{std::move(process_name)}, 423 object_path{std::move(object_path)} 424 ](const boost::system::error_code ec, 425 const std::string &introspect_xml) { 426 if (ec) { 427 CROW_LOG_ERROR 428 << "Introspect call failed with error: " << ec.message() 429 << " on process: " << process_name 430 << " path: " << object_path << "\n"; 431 432 } else { 433 tinyxml2::XMLDocument doc; 434 435 doc.Parse(introspect_xml.c_str()); 436 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 437 if (pRoot == nullptr) { 438 CROW_LOG_ERROR << "XML document failed to parse " 439 << process_name << " " << object_path 440 << "\n"; 441 res.write(nlohmann::json{{"status", "XML parse error"}}); 442 res.code = 500; 443 } else { 444 nlohmann::json interfaces_array = nlohmann::json::array(); 445 tinyxml2::XMLElement *interface = 446 pRoot->FirstChildElement("interface"); 447 448 while (interface != nullptr) { 449 std::string iface_name = interface->Attribute("name"); 450 interfaces_array.push_back({{"name", iface_name}}); 451 452 interface = interface->NextSiblingElement("interface"); 453 } 454 res.json_value = {{"status", "ok"}, 455 {"bus_name", process_name}, 456 {"interfaces", interfaces_array}, 457 {"object_path", object_path}}; 458 } 459 } 460 res.end(); 461 }, 462 introspect_endpoint); 463 } else { 464 crow::connections::system_bus->async_method_call( 465 [ 466 &, process_name{std::move(process_name)}, 467 interface_name{std::move(interface_name)}, 468 object_path{std::move(object_path)} 469 ](const boost::system::error_code ec, 470 const std::string &introspect_xml) { 471 if (ec) { 472 CROW_LOG_ERROR 473 << "Introspect call failed with error: " << ec.message() 474 << " on process: " << process_name 475 << " path: " << object_path << "\n"; 476 477 } else { 478 tinyxml2::XMLDocument doc; 479 480 doc.Parse(introspect_xml.c_str()); 481 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 482 if (pRoot == nullptr) { 483 CROW_LOG_ERROR << "XML document failed to parse " 484 << process_name << " " << object_path 485 << "\n"; 486 res.code = 500; 487 488 } else { 489 tinyxml2::XMLElement *node = 490 pRoot->FirstChildElement("node"); 491 492 // if we know we're the only call, build the json directly 493 nlohmann::json methods_array = nlohmann::json::array(); 494 nlohmann::json signals_array = nlohmann::json::array(); 495 tinyxml2::XMLElement *interface = 496 pRoot->FirstChildElement("interface"); 497 498 while (interface != nullptr) { 499 std::string iface_name = interface->Attribute("name"); 500 501 if (iface_name == interface_name) { 502 tinyxml2::XMLElement *methods = 503 interface->FirstChildElement("method"); 504 while (methods != nullptr) { 505 nlohmann::json args_array = nlohmann::json::array(); 506 tinyxml2::XMLElement *arg = 507 methods->FirstChildElement("arg"); 508 while (arg != nullptr) { 509 args_array.push_back( 510 {{"name", arg->Attribute("name")}, 511 {"type", arg->Attribute("type")}, 512 {"direction", arg->Attribute("direction")}}); 513 arg = arg->NextSiblingElement("arg"); 514 } 515 methods_array.push_back( 516 {{"name", methods->Attribute("name")}, 517 {"uri", "/bus/system/" + process_name + 518 object_path + "/" + interface_name + 519 "/" + methods->Attribute("name")}, 520 {"args", args_array}}); 521 methods = methods->NextSiblingElement("method"); 522 } 523 tinyxml2::XMLElement *signals = 524 interface->FirstChildElement("signal"); 525 while (signals != nullptr) { 526 nlohmann::json args_array = nlohmann::json::array(); 527 528 tinyxml2::XMLElement *arg = 529 signals->FirstChildElement("arg"); 530 while (arg != nullptr) { 531 std::string name = arg->Attribute("name"); 532 std::string type = arg->Attribute("type"); 533 args_array.push_back({ 534 {"name", name}, 535 {"type", type}, 536 }); 537 arg = arg->NextSiblingElement("arg"); 538 } 539 signals_array.push_back( 540 {{"name", signals->Attribute("name")}, 541 {"args", args_array}}); 542 signals = signals->NextSiblingElement("signal"); 543 } 544 545 nlohmann::json j{ 546 {"status", "ok"}, 547 {"bus_name", process_name}, 548 {"interface", interface_name}, 549 {"methods", methods_array}, 550 {"object_path", object_path}, 551 {"properties", nlohmann::json::object()}, 552 {"signals", signals_array}}; 553 554 res.write(j.dump()); 555 break; 556 } 557 558 interface = interface->NextSiblingElement("interface"); 559 } 560 if (interface == nullptr) { 561 // if we got to the end of the list and never found a 562 // match, throw 404 563 res.code = 404; 564 } 565 } 566 } 567 res.end(); 568 }, 569 introspect_endpoint); 570 } 571 572 }); 573 } 574 } // namespace openbmc_mapper 575 } // namespace crow 576