xref: /openbmc/bmcweb/include/openbmc_dbus_rest.hpp (revision ba9f9a6cebfbb7a12ecf869afa0cdc5aef9c8372)
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