blob: 4e29805c2ff3351855d388fc33c66ab89abc0822 [file] [log] [blame]
Ed Tanous911ac312017-08-15 09:37:42 -07001#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
11namespace crow {
12namespace openbmc_mapper {
Ed Tanousba9f9a62017-10-11 16:40:35 -070013
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
Ed Tanous911ac312017-08-15 09:37:42 -070018std::atomic<std::size_t> outstanding_async_calls(0);
19nlohmann::json object_paths;
20
21void 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
77template <typename... Middlewares>
78void 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
Ed Tanousba9f9a62017-10-11 16:40:35 -0700109 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 });
Ed Tanous911ac312017-08-15 09:37:42 -0700197 CROW_ROUTE(app, "/bus/system/<str>/")
198 .methods("GET"_method)([](const crow::request &req, crow::response &res,
199 const std::string &connection) {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700200 if (outstanding_async_calls != 0) {
Ed Tanous911ac312017-08-15 09:37:42 -0700201 res.code = 500;
Ed Tanousba9f9a62017-10-11 16:40:35 -0700202 res.body = "request in progress";
Ed Tanous911ac312017-08-15 09:37:42 -0700203 res.end();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700204 return;
Ed Tanous911ac312017-08-15 09:37:42 -0700205 }
Ed Tanousba9f9a62017-10-11 16:40:35 -0700206 introspect_objects(res, connection, "/");
Ed Tanous911ac312017-08-15 09:37:42 -0700207 });
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;
Ed Tanousba9f9a62017-10-11 16:40:35 -0700228 // THis check is neccesary as the trailing slash gets parsed as
229 // part
Ed Tanous911ac312017-08-15 09:37:42 -0700230 // 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")},
Ed Tanousba9f9a62017-10-11 16:40:35 -0700354 {"uri",
355 "/bus/system/" + process_name + object_path +
356 "/" + interface_name + "/" +
357 methods->Attribute("name")},
Ed Tanous911ac312017-08-15 09:37:42 -0700358 {"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