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 11 namespace crow { 12 namespace openbmc_mapper { 13 14 // TODO(ed) having these as scope globals, and as simple as they are limits the 15 // ability to queue multiple async operations at once. Being able to register 16 // "done" callbacks to a queue here that also had a count attached would allow 17 // multiple requests to be running at once 18 std::atomic<std::size_t> outstanding_async_calls(0); 19 nlohmann::json object_paths; 20 21 void introspect_objects(crow::response &res, std::string process_name, 22 std::string path) { 23 dbus::endpoint introspect_endpoint( 24 process_name, path, "org.freedesktop.DBus.Introspectable", "Introspect"); 25 outstanding_async_calls++; 26 crow::connections::system_bus->async_method_call( 27 [&, process_name{std::move(process_name)}, object_path{std::move(path)} ]( 28 const boost::system::error_code ec, 29 const std::string &introspect_xml) { 30 outstanding_async_calls--; 31 if (ec) { 32 std::cerr << "Introspect call failed with error: " << ec.message() 33 << " on process: " << process_name 34 << " path: " << object_path << "\n"; 35 36 } else { 37 object_paths.push_back({{"path", object_path}}); 38 39 tinyxml2::XMLDocument doc; 40 41 doc.Parse(introspect_xml.c_str()); 42 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 43 if (pRoot == nullptr) { 44 std::cerr << "XML document failed to parse " << process_name << " " 45 << path << "\n"; 46 47 } else { 48 tinyxml2::XMLElement *node = pRoot->FirstChildElement("node"); 49 while (node != nullptr) { 50 std::string child_path = node->Attribute("name"); 51 std::string newpath; 52 if (object_path != "/") { 53 newpath += object_path; 54 } 55 newpath += "/" + child_path; 56 // intropect the subobjects as well 57 introspect_objects(res, process_name, newpath); 58 59 node = node->NextSiblingElement("node"); 60 } 61 } 62 } 63 // if we're the last outstanding caller, finish the request 64 if (outstanding_async_calls == 0) { 65 nlohmann::json j{{"status", "ok"}, 66 {"bus_name", process_name}, 67 {"objects", object_paths}}; 68 69 res.write(j.dump()); 70 object_paths.clear(); 71 res.end(); 72 } 73 }, 74 introspect_endpoint); 75 } 76 77 template <typename... Middlewares> 78 void request_routes(Crow<Middlewares...> &app) { 79 CROW_ROUTE(app, "/bus/").methods("GET"_method)([](const crow::request &req) { 80 return nlohmann::json{{"busses", {{{"name", "system"}}}}, {"status", "ok"}}; 81 82 }); 83 84 CROW_ROUTE(app, "/bus/system/") 85 .methods("GET"_method)([](const crow::request &req, crow::response &res) { 86 crow::connections::system_bus->async_method_call( 87 [&](const boost::system::error_code ec, 88 std::vector<std::string> &names) { 89 std::sort(names.begin(), names.end()); 90 if (ec) { 91 res.code = 500; 92 } else { 93 nlohmann::json j{{"status", "ok"}}; 94 auto &objects_sub = j["objects"]; 95 for (auto &name : names) { 96 objects_sub.push_back({{"name", name}}); 97 } 98 99 res.write(j.dump()); 100 } 101 102 res.end(); 103 104 }, 105 {"org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"}); 106 107 }); 108 109 CROW_ROUTE(app, "/list/") 110 .methods("GET"_method)([](const crow::request &req, crow::response &res) { 111 crow::connections::system_bus->async_method_call( 112 [&](const boost::system::error_code ec, 113 const std::vector<std::string> &object_paths) { 114 115 if (ec) { 116 res.code = 500; 117 } else { 118 nlohmann::json j{{"status", "ok"}, 119 {"message", "200 OK"}, 120 {"data", object_paths}}; 121 res.body = j.dump(); 122 } 123 res.end(); 124 }, 125 {"xyz.openbmc_project.ObjectMapper", 126 "/xyz/openbmc_project/object_mapper", 127 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths"}, 128 "", static_cast<int32_t>(99), std::array<std::string, 0>()); 129 }); 130 131 CROW_ROUTE(app, "/xyz/<path>") 132 .methods("GET"_method)([](const crow::request &req, crow::response &res, 133 const std::string &path) { 134 if (outstanding_async_calls != 0) { 135 res.code = 500; 136 res.body = "request in progress"; 137 res.end(); 138 return; 139 } 140 using GetObjectType = 141 std::vector<std::pair<std::string, std::vector<std::string>>>; 142 143 std::string object_path = "/xyz/" + path; 144 crow::connections::system_bus->async_method_call( 145 // object_path intentially captured by value 146 [&, object_path](const boost::system::error_code ec, 147 const GetObjectType &object_names) { 148 149 if (ec) { 150 res.code = 500; 151 res.end(); 152 return; 153 } 154 if (object_names.size() != 1) { 155 res.code = 404; 156 res.end(); 157 return; 158 } 159 160 for (auto &interface : object_names[0].second) { 161 outstanding_async_calls++; 162 crow::connections::system_bus->async_method_call( 163 [&](const boost::system::error_code ec, 164 const std::vector<std::pair< 165 std::string, dbus::dbus_variant>> &properties) { 166 outstanding_async_calls--; 167 if (ec) { 168 std::cerr << "Bad dbus request error: " << ec; 169 } else { 170 for (auto &property : properties) { 171 boost::apply_visitor( 172 [&](auto val) { 173 object_paths[property.first] = val; 174 }, 175 property.second); 176 } 177 } 178 if (outstanding_async_calls == 0) { 179 nlohmann::json j{{"status", "ok"}, 180 {"message", "200 OK"}, 181 {"data", object_paths}}; 182 res.body = j.dump(); 183 res.end(); 184 object_paths.clear(); 185 } 186 }, 187 {object_names[0].first, object_path, 188 "org.freedesktop.DBus.Properties", "GetAll"}, 189 interface); 190 } 191 }, 192 {"xyz.openbmc_project.ObjectMapper", 193 "/xyz/openbmc_project/object_mapper", 194 "xyz.openbmc_project.ObjectMapper", "GetObject"}, 195 object_path, std::array<std::string, 0>()); 196 }); 197 CROW_ROUTE(app, "/bus/system/<str>/") 198 .methods("GET"_method)([](const crow::request &req, crow::response &res, 199 const std::string &connection) { 200 if (outstanding_async_calls != 0) { 201 res.code = 500; 202 res.body = "request in progress"; 203 res.end(); 204 return; 205 } 206 introspect_objects(res, connection, "/"); 207 }); 208 209 CROW_ROUTE(app, "/bus/system/<str>/<path>") 210 .methods("GET"_method)([](const crow::request &req, crow::response &res, 211 const std::string &process_name, 212 const std::string &requested_path) { 213 214 std::vector<std::string> strs; 215 boost::split(strs, requested_path, boost::is_any_of("/")); 216 std::string object_path; 217 std::string interface_name; 218 std::string method_name; 219 auto it = strs.begin(); 220 if (it == strs.end()) { 221 object_path = "/"; 222 } 223 while (it != strs.end()) { 224 // Check if segment contains ".". If it does, it must be an 225 // interface 226 if ((*it).find(".") != std::string::npos) { 227 break; 228 // THis check is neccesary as the trailing slash gets parsed as 229 // part 230 // of our <path> specifier above, which causes the normal trailing 231 // backslash redirector to fail. 232 } else if (!it->empty()) { 233 object_path += "/" + *it; 234 } 235 it++; 236 } 237 if (it != strs.end()) { 238 interface_name = *it; 239 it++; 240 241 // after interface, we might have a method name 242 if (it != strs.end()) { 243 method_name = *it; 244 it++; 245 } 246 } 247 if (it != strs.end()) { 248 // if there is more levels past the method name, something went 249 // wrong, throw an error 250 res.code = 404; 251 res.end(); 252 return; 253 } 254 dbus::endpoint introspect_endpoint( 255 process_name, object_path, "org.freedesktop.DBus.Introspectable", 256 "Introspect"); 257 if (interface_name.empty()) { 258 crow::connections::system_bus->async_method_call( 259 [ 260 &, process_name{std::move(process_name)}, 261 object_path{std::move(object_path)} 262 ](const boost::system::error_code ec, 263 const std::string &introspect_xml) { 264 if (ec) { 265 std::cerr 266 << "Introspect call failed with error: " << ec.message() 267 << " on process: " << process_name 268 << " path: " << object_path << "\n"; 269 270 } else { 271 tinyxml2::XMLDocument doc; 272 273 doc.Parse(introspect_xml.c_str()); 274 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 275 if (pRoot == nullptr) { 276 std::cerr << "XML document failed to parse " << process_name 277 << " " << object_path << "\n"; 278 res.write(nlohmann::json{{"status", "XML parse error"}}); 279 res.code = 500; 280 } else { 281 nlohmann::json interfaces_array = nlohmann::json::array(); 282 tinyxml2::XMLElement *interface = 283 pRoot->FirstChildElement("interface"); 284 285 while (interface != nullptr) { 286 std::string iface_name = interface->Attribute("name"); 287 interfaces_array.push_back({{"name", iface_name}}); 288 289 interface = interface->NextSiblingElement("interface"); 290 } 291 nlohmann::json j{{"status", "ok"}, 292 {"bus_name", process_name}, 293 {"interfaces", interfaces_array}, 294 {"object_path", object_path}}; 295 res.write(j.dump()); 296 } 297 } 298 res.end(); 299 }, 300 introspect_endpoint); 301 } else { 302 crow::connections::system_bus->async_method_call( 303 [ 304 &, process_name{std::move(process_name)}, 305 interface_name{std::move(interface_name)}, 306 object_path{std::move(object_path)} 307 ](const boost::system::error_code ec, 308 const std::string &introspect_xml) { 309 if (ec) { 310 std::cerr 311 << "Introspect call failed with error: " << ec.message() 312 << " on process: " << process_name 313 << " path: " << object_path << "\n"; 314 315 } else { 316 tinyxml2::XMLDocument doc; 317 318 doc.Parse(introspect_xml.c_str()); 319 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 320 if (pRoot == nullptr) { 321 std::cerr << "XML document failed to parse " << process_name 322 << " " << object_path << "\n"; 323 res.code = 500; 324 325 } else { 326 tinyxml2::XMLElement *node = 327 pRoot->FirstChildElement("node"); 328 329 // if we know we're the only call, build the json directly 330 nlohmann::json methods_array = nlohmann::json::array(); 331 nlohmann::json signals_array = nlohmann::json::array(); 332 tinyxml2::XMLElement *interface = 333 pRoot->FirstChildElement("interface"); 334 335 while (interface != nullptr) { 336 std::string iface_name = interface->Attribute("name"); 337 338 if (iface_name == interface_name) { 339 tinyxml2::XMLElement *methods = 340 interface->FirstChildElement("method"); 341 while (methods != nullptr) { 342 nlohmann::json args_array = nlohmann::json::array(); 343 tinyxml2::XMLElement *arg = 344 methods->FirstChildElement("arg"); 345 while (arg != nullptr) { 346 args_array.push_back( 347 {{"name", arg->Attribute("name")}, 348 {"type", arg->Attribute("type")}, 349 {"direction", arg->Attribute("direction")}}); 350 arg = arg->NextSiblingElement("arg"); 351 } 352 methods_array.push_back( 353 {{"name", methods->Attribute("name")}, 354 {"uri", 355 "/bus/system/" + process_name + object_path + 356 "/" + interface_name + "/" + 357 methods->Attribute("name")}, 358 {"args", args_array}}); 359 methods = methods->NextSiblingElement("method"); 360 } 361 tinyxml2::XMLElement *signals = 362 interface->FirstChildElement("signal"); 363 while (signals != nullptr) { 364 nlohmann::json args_array = nlohmann::json::array(); 365 366 tinyxml2::XMLElement *arg = 367 signals->FirstChildElement("arg"); 368 while (arg != nullptr) { 369 std::string name = arg->Attribute("name"); 370 std::string type = arg->Attribute("type"); 371 args_array.push_back({ 372 {"name", name}, {"type", type}, 373 }); 374 arg = arg->NextSiblingElement("arg"); 375 } 376 signals_array.push_back( 377 {{"name", signals->Attribute("name")}, 378 {"args", args_array}}); 379 signals = signals->NextSiblingElement("signal"); 380 } 381 382 nlohmann::json j{ 383 {"status", "ok"}, 384 {"bus_name", process_name}, 385 {"interface", interface_name}, 386 {"methods", methods_array}, 387 {"object_path", object_path}, 388 {"properties", nlohmann::json::object()}, 389 {"signals", signals_array}}; 390 391 res.write(j.dump()); 392 break; 393 } 394 395 interface = interface->NextSiblingElement("interface"); 396 } 397 if (interface == nullptr) { 398 // if we got to the end of the list and never found a 399 // match, throw 404 400 res.code = 404; 401 } 402 } 403 } 404 res.end(); 405 }, 406 introspect_endpoint); 407 } 408 409 }); 410 } 411 } // namespace openbmc_mapper 412 } // namespace crow 413