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 std::atomic<std::size_t> outstanding_async_calls(0); 14 nlohmann::json object_paths; 15 16 void introspect_objects(crow::response &res, std::string process_name, 17 std::string path) { 18 dbus::endpoint introspect_endpoint( 19 process_name, path, "org.freedesktop.DBus.Introspectable", "Introspect"); 20 outstanding_async_calls++; 21 crow::connections::system_bus->async_method_call( 22 [&, process_name{std::move(process_name)}, object_path{std::move(path)} ]( 23 const boost::system::error_code ec, 24 const std::string &introspect_xml) { 25 outstanding_async_calls--; 26 if (ec) { 27 std::cerr << "Introspect call failed with error: " << ec.message() 28 << " on process: " << process_name 29 << " path: " << object_path << "\n"; 30 31 } else { 32 object_paths.push_back({{"path", object_path}}); 33 34 tinyxml2::XMLDocument doc; 35 36 doc.Parse(introspect_xml.c_str()); 37 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 38 if (pRoot == nullptr) { 39 std::cerr << "XML document failed to parse " << process_name << " " 40 << path << "\n"; 41 42 } else { 43 tinyxml2::XMLElement *node = pRoot->FirstChildElement("node"); 44 while (node != nullptr) { 45 std::string child_path = node->Attribute("name"); 46 std::string newpath; 47 if (object_path != "/") { 48 newpath += object_path; 49 } 50 newpath += "/" + child_path; 51 // intropect the subobjects as well 52 introspect_objects(res, process_name, newpath); 53 54 node = node->NextSiblingElement("node"); 55 } 56 } 57 } 58 // if we're the last outstanding caller, finish the request 59 if (outstanding_async_calls == 0) { 60 nlohmann::json j{{"status", "ok"}, 61 {"bus_name", process_name}, 62 {"objects", object_paths}}; 63 64 res.write(j.dump()); 65 object_paths.clear(); 66 res.end(); 67 } 68 }, 69 introspect_endpoint); 70 } 71 72 template <typename... Middlewares> 73 void request_routes(Crow<Middlewares...> &app) { 74 CROW_ROUTE(app, "/bus/").methods("GET"_method)([](const crow::request &req) { 75 return nlohmann::json{{"busses", {{{"name", "system"}}}}, {"status", "ok"}}; 76 77 }); 78 79 CROW_ROUTE(app, "/bus/system/") 80 .methods("GET"_method)([](const crow::request &req, crow::response &res) { 81 crow::connections::system_bus->async_method_call( 82 [&](const boost::system::error_code ec, 83 std::vector<std::string> &names) { 84 std::sort(names.begin(), names.end()); 85 if (ec) { 86 res.code = 500; 87 } else { 88 nlohmann::json j{{"status", "ok"}}; 89 auto &objects_sub = j["objects"]; 90 for (auto &name : names) { 91 objects_sub.push_back({{"name", name}}); 92 } 93 94 res.write(j.dump()); 95 } 96 97 res.end(); 98 99 }, 100 {"org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"}); 101 102 }); 103 104 CROW_ROUTE(app, "/bus/system/<str>/") 105 .methods("GET"_method)([](const crow::request &req, crow::response &res, 106 const std::string &connection) { 107 // Can only do one call at a time (for now) 108 if (outstanding_async_calls == 0) { 109 // TODO(ed) sanitize paths 110 introspect_objects(res, connection, "/"); 111 } else { 112 nlohmann::json j{{"status", "failed"}}; 113 res.code = 500; 114 res.write(j.dump()); 115 res.end(); 116 } 117 }); 118 119 CROW_ROUTE(app, "/bus/system/<str>/<path>") 120 .methods("GET"_method)([](const crow::request &req, crow::response &res, 121 const std::string &process_name, 122 const std::string &requested_path) { 123 124 std::vector<std::string> strs; 125 boost::split(strs, requested_path, boost::is_any_of("/")); 126 std::string object_path; 127 std::string interface_name; 128 std::string method_name; 129 auto it = strs.begin(); 130 if (it == strs.end()) { 131 object_path = "/"; 132 } 133 while (it != strs.end()) { 134 // Check if segment contains ".". If it does, it must be an 135 // interface 136 if ((*it).find(".") != std::string::npos) { 137 break; 138 // THis check is neccesary as the trailing slash gets parsed as part 139 // of our <path> specifier above, which causes the normal trailing 140 // backslash redirector to fail. 141 } else if (!it->empty()) { 142 object_path += "/" + *it; 143 } 144 it++; 145 } 146 if (it != strs.end()) { 147 interface_name = *it; 148 it++; 149 150 // after interface, we might have a method name 151 if (it != strs.end()) { 152 method_name = *it; 153 it++; 154 } 155 } 156 if (it != strs.end()) { 157 // if there is more levels past the method name, something went 158 // wrong, throw an error 159 res.code = 404; 160 res.end(); 161 return; 162 } 163 dbus::endpoint introspect_endpoint( 164 process_name, object_path, "org.freedesktop.DBus.Introspectable", 165 "Introspect"); 166 if (interface_name.empty()) { 167 crow::connections::system_bus->async_method_call( 168 [ 169 &, process_name{std::move(process_name)}, 170 object_path{std::move(object_path)} 171 ](const boost::system::error_code ec, 172 const std::string &introspect_xml) { 173 if (ec) { 174 std::cerr 175 << "Introspect call failed with error: " << ec.message() 176 << " on process: " << process_name 177 << " path: " << object_path << "\n"; 178 179 } else { 180 tinyxml2::XMLDocument doc; 181 182 doc.Parse(introspect_xml.c_str()); 183 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 184 if (pRoot == nullptr) { 185 std::cerr << "XML document failed to parse " << process_name 186 << " " << object_path << "\n"; 187 res.write(nlohmann::json{{"status", "XML parse error"}}); 188 res.code = 500; 189 } else { 190 nlohmann::json interfaces_array = nlohmann::json::array(); 191 tinyxml2::XMLElement *interface = 192 pRoot->FirstChildElement("interface"); 193 194 while (interface != nullptr) { 195 std::string iface_name = interface->Attribute("name"); 196 interfaces_array.push_back({{"name", iface_name}}); 197 198 interface = interface->NextSiblingElement("interface"); 199 } 200 nlohmann::json j{{"status", "ok"}, 201 {"bus_name", process_name}, 202 {"interfaces", interfaces_array}, 203 {"object_path", object_path}}; 204 res.write(j.dump()); 205 } 206 } 207 res.end(); 208 }, 209 introspect_endpoint); 210 } else { 211 crow::connections::system_bus->async_method_call( 212 [ 213 &, process_name{std::move(process_name)}, 214 interface_name{std::move(interface_name)}, 215 object_path{std::move(object_path)} 216 ](const boost::system::error_code ec, 217 const std::string &introspect_xml) { 218 if (ec) { 219 std::cerr 220 << "Introspect call failed with error: " << ec.message() 221 << " on process: " << process_name 222 << " path: " << object_path << "\n"; 223 224 } else { 225 tinyxml2::XMLDocument doc; 226 227 doc.Parse(introspect_xml.c_str()); 228 tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); 229 if (pRoot == nullptr) { 230 std::cerr << "XML document failed to parse " << process_name 231 << " " << object_path << "\n"; 232 res.code = 500; 233 234 } else { 235 tinyxml2::XMLElement *node = 236 pRoot->FirstChildElement("node"); 237 238 // if we know we're the only call, build the json directly 239 nlohmann::json methods_array = nlohmann::json::array(); 240 nlohmann::json signals_array = nlohmann::json::array(); 241 tinyxml2::XMLElement *interface = 242 pRoot->FirstChildElement("interface"); 243 244 while (interface != nullptr) { 245 std::string iface_name = interface->Attribute("name"); 246 247 if (iface_name == interface_name) { 248 tinyxml2::XMLElement *methods = 249 interface->FirstChildElement("method"); 250 while (methods != nullptr) { 251 nlohmann::json args_array = nlohmann::json::array(); 252 tinyxml2::XMLElement *arg = 253 methods->FirstChildElement("arg"); 254 while (arg != nullptr) { 255 args_array.push_back( 256 {{"name", arg->Attribute("name")}, 257 {"type", arg->Attribute("type")}, 258 {"direction", arg->Attribute("direction")}}); 259 arg = arg->NextSiblingElement("arg"); 260 } 261 methods_array.push_back( 262 {{"name", methods->Attribute("name")}, 263 {"uri", "/bus/system/" + process_name + 264 object_path + "/" + interface_name + 265 "/" + methods->Attribute("name")}, 266 {"args", args_array}}); 267 methods = methods->NextSiblingElement("method"); 268 } 269 tinyxml2::XMLElement *signals = 270 interface->FirstChildElement("signal"); 271 while (signals != nullptr) { 272 nlohmann::json args_array = nlohmann::json::array(); 273 274 tinyxml2::XMLElement *arg = 275 signals->FirstChildElement("arg"); 276 while (arg != nullptr) { 277 std::string name = arg->Attribute("name"); 278 std::string type = arg->Attribute("type"); 279 args_array.push_back({ 280 {"name", name}, {"type", type}, 281 }); 282 arg = arg->NextSiblingElement("arg"); 283 } 284 signals_array.push_back( 285 {{"name", signals->Attribute("name")}, 286 {"args", args_array}}); 287 signals = signals->NextSiblingElement("signal"); 288 } 289 290 nlohmann::json j{ 291 {"status", "ok"}, 292 {"bus_name", process_name}, 293 {"interface", interface_name}, 294 {"methods", methods_array}, 295 {"object_path", object_path}, 296 {"properties", nlohmann::json::object()}, 297 {"signals", signals_array}}; 298 299 res.write(j.dump()); 300 break; 301 } 302 303 interface = interface->NextSiblingElement("interface"); 304 } 305 if (interface == nullptr) { 306 // if we got to the end of the list and never found a 307 // match, throw 404 308 res.code = 404; 309 } 310 } 311 } 312 res.end(); 313 }, 314 introspect_endpoint); 315 } 316 317 }); 318 } 319 } // namespace openbmc_mapper 320 } // namespace crow 321