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