blob: 9e5e5401370bf0a330bf880037002a1fd7a43482 [file] [log] [blame]
#include <crow/app.h>
#include <tinyxml2.h>
#include <dbus/connection.hpp>
#include <dbus/endpoint.hpp>
#include <dbus/filter.hpp>
#include <dbus/match.hpp>
#include <dbus/message.hpp>
#include <dbus_singleton.hpp>
namespace crow {
namespace openbmc_mapper {
// TODO(ed) having these as scope globals, and as simple as they are limits the
// ability to queue multiple async operations at once. Being able to register
// "done" callbacks to a queue here that also had a count attached would allow
// multiple requests to be running at once
std::atomic<std::size_t> outstanding_async_calls(0);
nlohmann::json object_paths;
bool property_matched=false;
void introspect_objects(crow::response &res, std::string process_name,
std::string path) {
dbus::endpoint introspect_endpoint(
process_name, path, "org.freedesktop.DBus.Introspectable", "Introspect");
outstanding_async_calls++;
crow::connections::system_bus->async_method_call(
[&, process_name{std::move(process_name)}, object_path{std::move(path)} ](
const boost::system::error_code ec,
const std::string &introspect_xml) {
outstanding_async_calls--;
if (ec) {
std::cerr << "Introspect call failed with error: " << ec.message()
<< " on process: " << process_name
<< " path: " << object_path << "\n";
} else {
object_paths.push_back({{"path", object_path}});
tinyxml2::XMLDocument doc;
doc.Parse(introspect_xml.c_str());
tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
if (pRoot == nullptr) {
std::cerr << "XML document failed to parse " << process_name << " "
<< path << "\n";
} else {
tinyxml2::XMLElement *node = pRoot->FirstChildElement("node");
while (node != nullptr) {
std::string child_path = node->Attribute("name");
std::string newpath;
if (object_path != "/") {
newpath += object_path;
}
newpath += "/" + child_path;
// intropect the subobjects as well
introspect_objects(res, process_name, newpath);
node = node->NextSiblingElement("node");
}
}
}
// if we're the last outstanding caller, finish the request
if (outstanding_async_calls == 0) {
nlohmann::json j{{"status", "ok"},
{"bus_name", process_name},
{"objects", object_paths}};
res.write(j.dump());
object_paths.clear();
res.end();
}
},
introspect_endpoint);
}
template <typename... Middlewares>
void request_routes(Crow<Middlewares...> &app) {
CROW_ROUTE(app, "/bus/").methods("GET"_method)([](const crow::request &req) {
return nlohmann::json{{"busses", {{{"name", "system"}}}}, {"status", "ok"}};
});
CROW_ROUTE(app, "/bus/system/")
.methods("GET"_method)([](const crow::request &req, crow::response &res) {
crow::connections::system_bus->async_method_call(
[&](const boost::system::error_code ec,
std::vector<std::string> &names) {
std::sort(names.begin(), names.end());
if (ec) {
res.code = 500;
} else {
nlohmann::json j{{"status", "ok"}};
auto &objects_sub = j["objects"];
for (auto &name : names) {
objects_sub.push_back({{"name", name}});
}
res.write(j.dump());
}
res.end();
},
{"org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"});
});
CROW_ROUTE(app, "/list/")
.methods("GET"_method)([](const crow::request &req, crow::response &res) {
crow::connections::system_bus->async_method_call(
[&](const boost::system::error_code ec,
const std::vector<std::string> &object_paths) {
if (ec) {
res.code = 500;
} else {
nlohmann::json j{{"status", "ok"},
{"message", "200 OK"},
{"data", object_paths}};
res.body = j.dump();
}
res.end();
},
{"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTreePaths"},
"", static_cast<int32_t>(99), std::array<std::string, 0>());
});
CROW_ROUTE(app, "/xyz/<path>")
.methods("GET"_method,
"PUT"_method)([](const crow::request &req, crow::response &res,
const std::string &path) {
if (outstanding_async_calls != 0) {
res.code = 500;
res.body = "request in progress";
res.end();
return;
}
using GetObjectType =
std::vector<std::pair<std::string, std::vector<std::string>>>;
std::string object_path;
std::string dest_property;
std::string property_set_value;
size_t attr_position = path.find("/attr/");
if (attr_position == path.npos)
object_path = "/xyz/" + path;
else {
object_path = "/xyz/" + path.substr(0, attr_position);
dest_property =
path.substr((attr_position + strlen("/attr/")), path.length());
auto request_dbus_data =
nlohmann::json::parse(req.body, nullptr, false);
if (request_dbus_data.is_discarded()) {
res.code = 400;
res.end();
return;
}
auto property_value_it = request_dbus_data.find("data");
if (property_value_it == request_dbus_data.end()) {
res.code = 400;
res.end();
return;
}
property_set_value = property_value_it->get<const std::string>();
if (property_set_value.empty()) {
res.code = 400;
res.end();
return;
}
}
crow::connections::system_bus->async_method_call(
// object_path intentially captured by value
[
&, object_path, dest_property{std::move(dest_property)},
property_set_value{std::move(property_set_value)}
](const boost::system::error_code ec,
const GetObjectType &object_names) {
if (ec) {
res.code = 500;
res.end();
return;
}
if (object_names.size() != 1) {
res.code = 404;
res.end();
return;
}
if (req.method == "GET"_method) {
for (auto &interface : object_names[0].second) {
outstanding_async_calls++;
crow::connections::system_bus->async_method_call(
[&](const boost::system::error_code ec,
const std::vector<std::pair<
std::string, dbus::dbus_variant>> &properties) {
outstanding_async_calls--;
if (ec) {
std::cerr << "Bad dbus request error: " << ec;
} else {
for (auto &property : properties) {
boost::apply_visitor(
[&](auto val) {
object_paths[property.first] = val;
},
property.second);
}
}
if (outstanding_async_calls == 0) {
nlohmann::json j{{"status", "ok"},
{"message", "200 OK"},
{"data", object_paths}};
res.body = j.dump();
res.end();
object_paths.clear();
}
},
{object_names[0].first, object_path,
"org.freedesktop.DBus.Properties", "GetAll"},
interface);
}
} else if (req.method == "PUT"_method) {
for (auto &interface : object_names[0].second) {
outstanding_async_calls++;
crow::connections::system_bus->async_method_call(
[
&, interface{std::move(interface)},
object_names{std::move(object_names)},
object_path{std::move(object_path)},
dest_property{std::move(dest_property)},
property_set_value{std::move(property_set_value)}
](const boost::system::error_code ec,
const std::vector<std::pair<
std::string, dbus::dbus_variant>> &properties) {
outstanding_async_calls--;
if (ec) {
std::cerr << "Bad dbus request error: " << ec;
} else {
for (auto &property : properties) {
// search all the properties in the interfaces
if (dest_property.compare(property.first) == 0) {
// find the matched property in the interface
property_matched = true;
dbus::dbus_variant property_value(
property_set_value); // create the dbus
// variant for dbus call
crow::connections::system_bus->async_method_call(
[&](const boost::system::error_code ec) {
// use the method "Set" to set the property
// value
if (ec) {
std::cerr << "Bad dbus request error: "
<< ec;
res.code = 500;
res.end();
} else {
// find the matched property and send the
// response
res.json_value = {{"status", "ok"},
{"message", "200 OK"},
{"data", NULL}};
res.end();
return;
}
},
{object_names[0].first, object_path,
"org.freedesktop.DBus.Properties", "Set"},
interface, dest_property, property_value);
}
}
if (outstanding_async_calls == 0) {
// all search has been finished
if (property_matched == false) {
nlohmann::json j{{"status", "error"},
{"message", "403 Forbidden"},
{"data",
{{"message",
"The specified property "
"cannot be created: " +
dest_property}}}};
res.json_value = j;
res.end();
return;
} else
property_matched = false;
}
}
},
{object_names[0].first, object_path,
"org.freedesktop.DBus.Properties", "GetAll"},
interface);
}
}
},
{"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetObject"},
object_path, std::array<std::string, 0>());
});
CROW_ROUTE(app, "/bus/system/<str>/")
.methods("GET"_method)([](const crow::request &req, crow::response &res,
const std::string &connection) {
if (outstanding_async_calls != 0) {
res.code = 500;
res.body = "request in progress";
res.end();
return;
}
introspect_objects(res, connection, "/");
});
CROW_ROUTE(app, "/bus/system/<str>/<path>")
.methods("GET"_method)([](const crow::request &req, crow::response &res,
const std::string &process_name,
const std::string &requested_path) {
std::vector<std::string> strs;
boost::split(strs, requested_path, boost::is_any_of("/"));
std::string object_path;
std::string interface_name;
std::string method_name;
auto it = strs.begin();
if (it == strs.end()) {
object_path = "/";
}
while (it != strs.end()) {
// Check if segment contains ".". If it does, it must be an
// interface
if ((*it).find(".") != std::string::npos) {
break;
// THis check is neccesary as the trailing slash gets parsed as
// part
// of our <path> specifier above, which causes the normal trailing
// backslash redirector to fail.
} else if (!it->empty()) {
object_path += "/" + *it;
}
it++;
}
if (it != strs.end()) {
interface_name = *it;
it++;
// after interface, we might have a method name
if (it != strs.end()) {
method_name = *it;
it++;
}
}
if (it != strs.end()) {
// if there is more levels past the method name, something went
// wrong, throw an error
res.code = 404;
res.end();
return;
}
dbus::endpoint introspect_endpoint(
process_name, object_path, "org.freedesktop.DBus.Introspectable",
"Introspect");
if (interface_name.empty()) {
crow::connections::system_bus->async_method_call(
[
&, process_name{std::move(process_name)},
object_path{std::move(object_path)}
](const boost::system::error_code ec,
const std::string &introspect_xml) {
if (ec) {
std::cerr
<< "Introspect call failed with error: " << ec.message()
<< " on process: " << process_name
<< " path: " << object_path << "\n";
} else {
tinyxml2::XMLDocument doc;
doc.Parse(introspect_xml.c_str());
tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
if (pRoot == nullptr) {
std::cerr << "XML document failed to parse " << process_name
<< " " << object_path << "\n";
res.write(nlohmann::json{{"status", "XML parse error"}});
res.code = 500;
} else {
nlohmann::json interfaces_array = nlohmann::json::array();
tinyxml2::XMLElement *interface =
pRoot->FirstChildElement("interface");
while (interface != nullptr) {
std::string iface_name = interface->Attribute("name");
interfaces_array.push_back({{"name", iface_name}});
interface = interface->NextSiblingElement("interface");
}
nlohmann::json j{{"status", "ok"},
{"bus_name", process_name},
{"interfaces", interfaces_array},
{"object_path", object_path}};
res.write(j.dump());
}
}
res.end();
},
introspect_endpoint);
} else {
crow::connections::system_bus->async_method_call(
[
&, process_name{std::move(process_name)},
interface_name{std::move(interface_name)},
object_path{std::move(object_path)}
](const boost::system::error_code ec,
const std::string &introspect_xml) {
if (ec) {
std::cerr
<< "Introspect call failed with error: " << ec.message()
<< " on process: " << process_name
<< " path: " << object_path << "\n";
} else {
tinyxml2::XMLDocument doc;
doc.Parse(introspect_xml.c_str());
tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
if (pRoot == nullptr) {
std::cerr << "XML document failed to parse " << process_name
<< " " << object_path << "\n";
res.code = 500;
} else {
tinyxml2::XMLElement *node =
pRoot->FirstChildElement("node");
// if we know we're the only call, build the json directly
nlohmann::json methods_array = nlohmann::json::array();
nlohmann::json signals_array = nlohmann::json::array();
tinyxml2::XMLElement *interface =
pRoot->FirstChildElement("interface");
while (interface != nullptr) {
std::string iface_name = interface->Attribute("name");
if (iface_name == interface_name) {
tinyxml2::XMLElement *methods =
interface->FirstChildElement("method");
while (methods != nullptr) {
nlohmann::json args_array = nlohmann::json::array();
tinyxml2::XMLElement *arg =
methods->FirstChildElement("arg");
while (arg != nullptr) {
args_array.push_back(
{{"name", arg->Attribute("name")},
{"type", arg->Attribute("type")},
{"direction", arg->Attribute("direction")}});
arg = arg->NextSiblingElement("arg");
}
methods_array.push_back(
{{"name", methods->Attribute("name")},
{"uri",
"/bus/system/" + process_name + object_path +
"/" + interface_name + "/" +
methods->Attribute("name")},
{"args", args_array}});
methods = methods->NextSiblingElement("method");
}
tinyxml2::XMLElement *signals =
interface->FirstChildElement("signal");
while (signals != nullptr) {
nlohmann::json args_array = nlohmann::json::array();
tinyxml2::XMLElement *arg =
signals->FirstChildElement("arg");
while (arg != nullptr) {
std::string name = arg->Attribute("name");
std::string type = arg->Attribute("type");
args_array.push_back({
{"name", name}, {"type", type},
});
arg = arg->NextSiblingElement("arg");
}
signals_array.push_back(
{{"name", signals->Attribute("name")},
{"args", args_array}});
signals = signals->NextSiblingElement("signal");
}
nlohmann::json j{
{"status", "ok"},
{"bus_name", process_name},
{"interface", interface_name},
{"methods", methods_array},
{"object_path", object_path},
{"properties", nlohmann::json::object()},
{"signals", signals_array}};
res.write(j.dump());
break;
}
interface = interface->NextSiblingElement("interface");
}
if (interface == nullptr) {
// if we got to the end of the list and never found a
// match, throw 404
res.code = 404;
}
}
}
res.end();
},
introspect_endpoint);
}
});
}
} // namespace openbmc_mapper
} // namespace crow