// Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace crow { namespace openbmc_mapper { using GetSubTreeType = std::vector< std::pair>>>>; const std::string notFoundMsg = "404 Not Found"; const std::string badReqMsg = "400 Bad Request"; const std::string methodNotAllowedMsg = "405 Method Not Allowed"; const std::string forbiddenMsg = "403 Forbidden"; const std::string methodFailedMsg = "500 Method Call Failed"; const std::string methodOutputFailedMsg = "500 Method Output Error"; const std::string notFoundDesc = "org.freedesktop.DBus.Error.FileNotFound: path or object not found"; const std::string propNotFoundDesc = "The specified property cannot be found"; const std::string noJsonDesc = "No JSON object could be decoded"; const std::string methodNotFoundDesc = "The specified method cannot be found"; const std::string methodNotAllowedDesc = "Method not allowed"; const std::string forbiddenPropDesc = "The specified property cannot be created"; const std::string forbiddenResDesc = "The specified resource cannot be created"; void setErrorResponse(crow::Response &res, boost::beast::http::status result, const std::string &desc, const std::string &msg) { res.result(result); res.jsonValue = {{"data", {{"description", desc}}}, {"message", msg}, {"status", "error"}}; } void introspectObjects(const std::string &processName, const std::string &objectPath, std::shared_ptr transaction) { if (transaction->res.jsonValue.is_null()) { transaction->res.jsonValue = {{"status", "ok"}, {"bus_name", processName}, {"objects", nlohmann::json::array()}}; } crow::connections::systemBus->async_method_call( [transaction, processName{std::string(processName)}, objectPath{std::string(objectPath)}]( const boost::system::error_code ec, const std::string &introspect_xml) { if (ec) { BMCWEB_LOG_ERROR << "Introspect call failed with error: " << ec.message() << " on process: " << processName << " path: " << objectPath << "\n"; return; } transaction->res.jsonValue["objects"].push_back( {{"path", objectPath}}); tinyxml2::XMLDocument doc; doc.Parse(introspect_xml.c_str()); tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR << "XML document failed to parse " << processName << " " << objectPath << "\n"; } else { tinyxml2::XMLElement *node = pRoot->FirstChildElement("node"); while (node != nullptr) { const char *childPath = node->Attribute("name"); if (childPath != nullptr) { std::string newpath; if (objectPath != "/") { newpath += objectPath; } newpath += std::string("/") + childPath; // introspect the subobjects as well introspectObjects(processName, newpath, transaction); } node = node->NextSiblingElement("node"); } } }, processName, objectPath, "org.freedesktop.DBus.Introspectable", "Introspect"); } void getPropertiesForEnumerate(const std::string &objectPath, const std::string &service, const std::string &interface, std::shared_ptr asyncResp) { BMCWEB_LOG_DEBUG << "getPropertiesForEnumerate " << objectPath << " " << service << " " << interface; crow::connections::systemBus->async_method_call( [asyncResp, objectPath, service, interface](const boost::system::error_code ec, const std::vector< std::pair> &propertiesList) { if (ec) { BMCWEB_LOG_ERROR << "GetAll on path " << objectPath << " iface " << interface << " service " << service << " failed with code " << ec; return; } nlohmann::json &dataJson = asyncResp->res.jsonValue["data"]; nlohmann::json &objectJson = dataJson[objectPath]; if (objectJson.is_null()) { objectJson = nlohmann::json::object(); } for (const auto &[name, value] : propertiesList) { nlohmann::json &propertyJson = objectJson[name]; std::visit([&propertyJson](auto &&val) { propertyJson = val; }, value); } }, service, objectPath, "org.freedesktop.DBus.Properties", "GetAll", interface); } // Find any results that weren't picked up by ObjectManagers, to be // called after all ObjectManagers are searched for and called. void findRemainingObjectsForEnumerate( const std::string &objectPath, std::shared_ptr subtree, std::shared_ptr asyncResp) { BMCWEB_LOG_DEBUG << "findRemainingObjectsForEnumerate"; const nlohmann::json &dataJson = asyncResp->res.jsonValue["data"]; for (const auto &[path, interface_map] : *subtree) { if (path == objectPath) { // An enumerate does not return the target path's properties continue; } if (dataJson.find(path) == dataJson.end()) { for (const auto &[service, interfaces] : interface_map) { for (const auto &interface : interfaces) { if (!boost::starts_with(interface, "org.freedesktop.DBus")) { getPropertiesForEnumerate(path, service, interface, asyncResp); } } } } } } struct InProgressEnumerateData { InProgressEnumerateData(const std::string &objectPath, std::shared_ptr asyncResp) : objectPath(objectPath), asyncResp(asyncResp) { } ~InProgressEnumerateData() { findRemainingObjectsForEnumerate(objectPath, subtree, asyncResp); } const std::string objectPath; std::shared_ptr subtree; std::shared_ptr asyncResp; }; void getManagedObjectsForEnumerate( const std::string &object_name, const std::string &object_manager_path, const std::string &connection_name, std::shared_ptr transaction) { BMCWEB_LOG_DEBUG << "getManagedObjectsForEnumerate " << object_name << " object_manager_path " << object_manager_path << " connection_name " << connection_name; crow::connections::systemBus->async_method_call( [transaction, object_name, connection_name](const boost::system::error_code ec, const dbus::utility::ManagedObjectType &objects) { if (ec) { BMCWEB_LOG_ERROR << "GetManagedObjects on path " << object_name << " on connection " << connection_name << " failed with code " << ec; return; } nlohmann::json &dataJson = transaction->asyncResp->res.jsonValue["data"]; for (const auto &objectPath : objects) { if (boost::starts_with(objectPath.first.str, object_name)) { BMCWEB_LOG_DEBUG << "Reading object " << objectPath.first.str; nlohmann::json &objectJson = dataJson[objectPath.first.str]; if (objectJson.is_null()) { objectJson = nlohmann::json::object(); } for (const auto &interface : objectPath.second) { for (const auto &property : interface.second) { nlohmann::json &propertyJson = objectJson[property.first]; std::visit([&propertyJson]( auto &&val) { propertyJson = val; }, property.second); } } } for (const auto &interface : objectPath.second) { if (interface.first == "org.freedesktop.DBus.ObjectManager") { getManagedObjectsForEnumerate( objectPath.first.str, objectPath.first.str, connection_name, transaction); } } } }, connection_name, object_manager_path, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); } void findObjectManagerPathForEnumerate( const std::string &object_name, const std::string &connection_name, std::shared_ptr transaction) { BMCWEB_LOG_DEBUG << "Finding objectmanager for path " << object_name << " on connection:" << connection_name; crow::connections::systemBus->async_method_call( [transaction, object_name, connection_name]( const boost::system::error_code ec, const boost::container::flat_map< std::string, boost::container::flat_map< std::string, std::vector>> &objects) { if (ec) { BMCWEB_LOG_ERROR << "GetAncestors on path " << object_name << " failed with code " << ec; return; } for (const auto &pathGroup : objects) { for (const auto &connectionGroup : pathGroup.second) { if (connectionGroup.first == connection_name) { // Found the object manager path for this resource. getManagedObjectsForEnumerate( object_name, pathGroup.first, connection_name, transaction); return; } } } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetAncestors", object_name, std::array{"org.freedesktop.DBus.ObjectManager"}); } // Uses GetObject to add the object info about the target /enumerate path to // the results of GetSubTree, as GetSubTree will not return info for the // target path, and then continues on enumerating the rest of the tree. void getObjectAndEnumerate(std::shared_ptr transaction) { using GetObjectType = std::vector>>; crow::connections::systemBus->async_method_call( [transaction](const boost::system::error_code ec, const GetObjectType &objects) { if (ec) { BMCWEB_LOG_ERROR << "GetObject for path " << transaction->objectPath << " failed with code " << ec; return; } BMCWEB_LOG_DEBUG << "GetObject for " << transaction->objectPath << " has " << objects.size() << " entries"; if (!objects.empty()) { transaction->subtree->emplace_back(transaction->objectPath, objects); } // Map indicating connection name, and the path where the object // manager exists boost::container::flat_map connections; for (const auto &object : *(transaction->subtree)) { for (const auto &connection : object.second) { std::string &objectManagerPath = connections[connection.first]; for (const auto &interface : connection.second) { BMCWEB_LOG_DEBUG << connection.first << " has interface " << interface; if (interface == "org.freedesktop.DBus.ObjectManager") { BMCWEB_LOG_DEBUG << "found object manager path " << object.first; objectManagerPath = object.first; } } } } BMCWEB_LOG_DEBUG << "Got " << connections.size() << " connections"; for (const auto &connection : connections) { // If we already know where the object manager is, we don't // need to search for it, we can call directly in to // getManagedObjects if (!connection.second.empty()) { getManagedObjectsForEnumerate( transaction->objectPath, connection.second, connection.first, transaction); } else { // otherwise we need to find the object manager path // before we can continue findObjectManagerPathForEnumerate( transaction->objectPath, connection.first, transaction); } } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject", transaction->objectPath, std::array()); } // Structure for storing data on an in progress action struct InProgressActionData { InProgressActionData(crow::Response &res) : res(res){}; ~InProgressActionData() { // Methods could have been called across different owners // and interfaces, where some calls failed and some passed. // // The rules for this are: // * if no method was called - error // * if a method failed and none passed - error // (converse: if at least one method passed - OK) // * for the method output: // * if output processing didn't fail, return the data // Only deal with method returns if nothing failed earlier if (res.result() == boost::beast::http::status::ok) { if (!methodPassed) { if (!methodFailed) { setErrorResponse(res, boost::beast::http::status::not_found, methodNotFoundDesc, notFoundMsg); } } else { if (outputFailed) { setErrorResponse( res, boost::beast::http::status::internal_server_error, "Method output failure", methodOutputFailedMsg); } else { res.jsonValue = {{"status", "ok"}, {"message", "200 OK"}, {"data", methodResponse}}; } } } res.end(); } void setErrorStatus(const std::string &desc) { setErrorResponse(res, boost::beast::http::status::bad_request, desc, badReqMsg); } crow::Response &res; std::string path; std::string methodName; std::string interfaceName; bool methodPassed = false; bool methodFailed = false; bool outputFailed = false; bool convertedToArray = false; nlohmann::json methodResponse; nlohmann::json arguments; }; std::vector dbusArgSplit(const std::string &string) { std::vector ret; if (string.empty()) { return ret; } ret.push_back(""); int containerDepth = 0; for (std::string::const_iterator character = string.begin(); character != string.end(); character++) { ret.back() += *character; switch (*character) { case ('a'): break; case ('('): case ('{'): containerDepth++; break; case ('}'): case (')'): containerDepth--; if (containerDepth == 0) { if (character + 1 != string.end()) { ret.push_back(""); } } break; default: if (containerDepth == 0) { if (character + 1 != string.end()) { ret.push_back(""); } } break; } } return ret; } int convertJsonToDbus(sd_bus_message *m, const std::string &arg_type, const nlohmann::json &input_json) { int r = 0; BMCWEB_LOG_DEBUG << "Converting " << input_json.dump() << " to type: " << arg_type; const std::vector argTypes = dbusArgSplit(arg_type); // Assume a single object for now. const nlohmann::json *j = &input_json; nlohmann::json::const_iterator jIt = input_json.begin(); for (const std::string &argCode : argTypes) { // If we are decoding multiple objects, grab the pointer to the // iterator, and increment it for the next loop if (argTypes.size() > 1) { if (jIt == input_json.end()) { return -2; } j = &*jIt; jIt++; } const int64_t *intValue = j->get_ptr(); const uint64_t *uintValue = j->get_ptr(); const std::string *stringValue = j->get_ptr(); const double *doubleValue = j->get_ptr(); const bool *b = j->get_ptr(); int64_t v = 0; double d = 0.0; // Do some basic type conversions that make sense. uint can be // converted to int. int and uint can be converted to double if (uintValue != nullptr && intValue == nullptr) { v = static_cast(*uintValue); intValue = &v; } if (uintValue != nullptr && doubleValue == nullptr) { d = static_cast(*uintValue); doubleValue = &d; } if (intValue != nullptr && doubleValue == nullptr) { d = static_cast(*intValue); doubleValue = &d; } if (argCode == "s") { if (stringValue == nullptr) { return -1; } r = sd_bus_message_append_basic(m, argCode[0], (void *)stringValue->c_str()); if (r < 0) { return r; } } else if (argCode == "i") { if (intValue == nullptr) { return -1; } if ((*intValue < std::numeric_limits::lowest()) || (*intValue > std::numeric_limits::max())) { return -ERANGE; } int32_t i = static_cast(*intValue); r = sd_bus_message_append_basic(m, argCode[0], &i); if (r < 0) { return r; } } else if (argCode == "b") { // lots of ways bool could be represented here. Try them all int boolInt = false; if (intValue != nullptr) { if (*intValue == 1) { boolInt = true; } else if (*intValue == 0) { boolInt = false; } else { return -ERANGE; } } else if (b != nullptr) { boolInt = *b ? 1 : 0; } else if (stringValue != nullptr) { boolInt = boost::istarts_with(*stringValue, "t") ? 1 : 0; } else { return -1; } r = sd_bus_message_append_basic(m, argCode[0], &boolInt); if (r < 0) { return r; } } else if (argCode == "n") { if (intValue == nullptr) { return -1; } if ((*intValue < std::numeric_limits::lowest()) || (*intValue > std::numeric_limits::max())) { return -ERANGE; } int16_t n = static_cast(*intValue); r = sd_bus_message_append_basic(m, argCode[0], &n); if (r < 0) { return r; } } else if (argCode == "x") { if (intValue == nullptr) { return -1; } if ((*intValue < std::numeric_limits::lowest()) || (*intValue > std::numeric_limits::max())) { return -ERANGE; } r = sd_bus_message_append_basic(m, argCode[0], intValue); if (r < 0) { return r; } } else if (argCode == "y") { if (uintValue == nullptr) { return -1; } if ((*uintValue < std::numeric_limits::lowest()) || (*uintValue > std::numeric_limits::max())) { return -ERANGE; } uint8_t y = static_cast(*uintValue); r = sd_bus_message_append_basic(m, argCode[0], &y); } else if (argCode == "q") { if (uintValue == nullptr) { return -1; } if ((*uintValue < std::numeric_limits::lowest()) || (*uintValue > std::numeric_limits::max())) { return -ERANGE; } uint16_t q = static_cast(*uintValue); r = sd_bus_message_append_basic(m, argCode[0], &q); } else if (argCode == "u") { if (uintValue == nullptr) { return -1; } if ((*uintValue < std::numeric_limits::lowest()) || (*uintValue > std::numeric_limits::max())) { return -ERANGE; } uint32_t u = static_cast(*uintValue); r = sd_bus_message_append_basic(m, argCode[0], &u); } else if (argCode == "t") { if (uintValue == nullptr) { return -1; } if ((*uintValue < std::numeric_limits::lowest()) || (*uintValue > std::numeric_limits::max())) { return -ERANGE; } r = sd_bus_message_append_basic(m, argCode[0], uintValue); } else if (argCode == "d") { if (doubleValue == nullptr) { return -1; } if ((*doubleValue < std::numeric_limits::lowest()) || (*doubleValue > std::numeric_limits::max())) { return -ERANGE; } sd_bus_message_append_basic(m, argCode[0], doubleValue); } else if (boost::starts_with(argCode, "a")) { std::string containedType = argCode.substr(1); r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, containedType.c_str()); if (r < 0) { return r; } for (nlohmann::json::const_iterator it = j->begin(); it != j->end(); ++it) { r = convertJsonToDbus(m, containedType, *it); if (r < 0) { return r; } } sd_bus_message_close_container(m); } else if (boost::starts_with(argCode, "v")) { std::string containedType = argCode.substr(1); BMCWEB_LOG_DEBUG << "variant type: " << argCode << " appending variant of type: " << containedType; r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, containedType.c_str()); if (r < 0) { return r; } r = convertJsonToDbus(m, containedType, input_json); if (r < 0) { return r; } r = sd_bus_message_close_container(m); if (r < 0) { return r; } } else if (boost::starts_with(argCode, "(") && boost::ends_with(argCode, ")")) { std::string containedType = argCode.substr(1, argCode.size() - 1); r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, containedType.c_str()); if (r < 0) { return r; } nlohmann::json::const_iterator it = j->begin(); for (const std::string &argCode : dbusArgSplit(arg_type)) { if (it == j->end()) { return -1; } r = convertJsonToDbus(m, argCode, *it); if (r < 0) { return r; } it++; } r = sd_bus_message_close_container(m); } else if (boost::starts_with(argCode, "{") && boost::ends_with(argCode, "}")) { std::string containedType = argCode.substr(1, argCode.size() - 1); r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY, containedType.c_str()); if (r < 0) { return r; } std::vector codes = dbusArgSplit(containedType); if (codes.size() != 2) { return -1; } const std::string &key_type = codes[0]; const std::string &value_type = codes[1]; for (auto it : j->items()) { r = convertJsonToDbus(m, key_type, it.key()); if (r < 0) { return r; } r = convertJsonToDbus(m, value_type, it.value()); if (r < 0) { return r; } } r = sd_bus_message_close_container(m); } else { return -2; } if (r < 0) { return r; } if (argTypes.size() > 1) { jIt++; } } return r; } template int readMessageItem(const std::string &typeCode, sdbusplus::message::message &m, nlohmann::json &data) { T value; int r = sd_bus_message_read_basic(m.get(), typeCode.front(), &value); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_read_basic on type " << typeCode << " failed!"; return r; } data = value; return 0; } int convertDBusToJSON(const std::string &returnType, sdbusplus::message::message &m, nlohmann::json &response); int readDictEntryFromMessage(const std::string &typeCode, sdbusplus::message::message &m, nlohmann::json &object) { std::vector types = dbusArgSplit(typeCode); if (types.size() != 2) { BMCWEB_LOG_ERROR << "wrong number contained types in dictionary: " << types.size(); return -1; } int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_DICT_ENTRY, typeCode.c_str()); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_enter_container with rc " << r; return r; } nlohmann::json key; r = convertDBusToJSON(types[0], m, key); if (r < 0) { return r; } const std::string *keyPtr = key.get_ptr(); if (keyPtr == nullptr) { // json doesn't support non-string keys. If we hit this condition, // convert the result to a string so we can proceed key = key.dump(); keyPtr = key.get_ptr(); // in theory this can't fail now, but lets be paranoid about it // anyway if (keyPtr == nullptr) { return -1; } } nlohmann::json &value = object[*keyPtr]; r = convertDBusToJSON(types[1], m, value); if (r < 0) { return r; } r = sd_bus_message_exit_container(m.get()); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_exit_container failed"; return r; } return 0; } int readArrayFromMessage(const std::string &typeCode, sdbusplus::message::message &m, nlohmann::json &data) { if (typeCode.size() < 2) { BMCWEB_LOG_ERROR << "Type code " << typeCode << " too small for an array"; return -1; } std::string containedType = typeCode.substr(1); int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_ARRAY, containedType.c_str()); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_enter_container failed with rc " << r; return r; } bool dict = boost::starts_with(containedType, "{") && boost::ends_with(containedType, "}"); if (dict) { // Remove the { } containedType = containedType.substr(1, containedType.size() - 2); data = nlohmann::json::object(); } else { data = nlohmann::json::array(); } while (true) { r = sd_bus_message_at_end(m.get(), false); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_at_end failed"; return r; } if (r > 0) { break; } // Dictionaries are only ever seen in an array if (dict) { r = readDictEntryFromMessage(containedType, m, data); if (r < 0) { return r; } } else { data.push_back(nlohmann::json()); r = convertDBusToJSON(containedType, m, data.back()); if (r < 0) { return r; } } } r = sd_bus_message_exit_container(m.get()); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_exit_container failed"; return r; } return 0; } int readStructFromMessage(const std::string &typeCode, sdbusplus::message::message &m, nlohmann::json &data) { if (typeCode.size() < 3) { BMCWEB_LOG_ERROR << "Type code " << typeCode << " too small for a struct"; return -1; } std::string containedTypes = typeCode.substr(1, typeCode.size() - 2); std::vector types = dbusArgSplit(containedTypes); int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_STRUCT, containedTypes.c_str()); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_enter_container failed with rc " << r; return r; } for (const std::string &type : types) { data.push_back(nlohmann::json()); r = convertDBusToJSON(type, m, data.back()); if (r < 0) { return r; } } r = sd_bus_message_exit_container(m.get()); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_exit_container failed"; return r; } return 0; } int readVariantFromMessage(sdbusplus::message::message &m, nlohmann::json &data) { const char *containerType; int r = sd_bus_message_peek_type(m.get(), NULL, &containerType); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_peek_type failed"; return r; } r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_VARIANT, containerType); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_enter_container failed with rc " << r; return r; } r = convertDBusToJSON(containerType, m, data); if (r < 0) { return r; } r = sd_bus_message_exit_container(m.get()); if (r < 0) { BMCWEB_LOG_ERROR << "sd_bus_message_enter_container failed"; return r; } return 0; } int convertDBusToJSON(const std::string &returnType, sdbusplus::message::message &m, nlohmann::json &response) { int r = 0; const std::vector returnTypes = dbusArgSplit(returnType); for (const std::string &typeCode : returnTypes) { nlohmann::json *thisElement = &response; if (returnTypes.size() > 1) { response.push_back(nlohmann::json{}); thisElement = &response.back(); } if (typeCode == "s") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "g") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "o") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "b") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } *thisElement = static_cast(thisElement->get()); } else if (typeCode == "u") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "i") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "x") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "t") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "n") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "q") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "y") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "d") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "h") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (boost::starts_with(typeCode, "a")) { r = readArrayFromMessage(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (boost::starts_with(typeCode, "(") && boost::ends_with(typeCode, ")")) { r = readStructFromMessage(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (boost::starts_with(typeCode, "v")) { r = readVariantFromMessage(m, *thisElement); if (r < 0) { return r; } } else { BMCWEB_LOG_ERROR << "Invalid D-Bus signature type " << typeCode; return -2; } } return 0; } void handleMethodResponse(std::shared_ptr transaction, sdbusplus::message::message &m, const std::string &returnType) { nlohmann::json data; int r = convertDBusToJSON(returnType, m, data); if (r < 0) { transaction->outputFailed = true; return; } if (data.is_null()) { return; } if (transaction->methodResponse.is_null()) { transaction->methodResponse = std::move(data); return; } // If they're both dictionaries or arrays, merge into one. // Otherwise, make the results an array with every result // an entry. Could also just fail in that case, but it // seems better to get the data back somehow. if (transaction->methodResponse.is_object() && data.is_object()) { for (const auto &obj : data.items()) { // Note: Will overwrite the data for a duplicate key transaction->methodResponse.emplace(obj.key(), std::move(obj.value())); } return; } if (transaction->methodResponse.is_array() && data.is_array()) { for (auto &obj : data) { transaction->methodResponse.push_back(std::move(obj)); } return; } if (!transaction->convertedToArray) { // They are different types. May as well turn them into an array nlohmann::json j = std::move(transaction->methodResponse); transaction->methodResponse = nlohmann::json::array(); transaction->methodResponse.push_back(std::move(j)); transaction->methodResponse.push_back(std::move(data)); transaction->convertedToArray = true; } else { transaction->methodResponse.push_back(std::move(data)); } } void findActionOnInterface(std::shared_ptr transaction, const std::string &connectionName) { BMCWEB_LOG_DEBUG << "findActionOnInterface for connection " << connectionName; crow::connections::systemBus->async_method_call( [transaction, connectionName{std::string(connectionName)}]( const boost::system::error_code ec, const std::string &introspect_xml) { BMCWEB_LOG_DEBUG << "got xml:\n " << introspect_xml; if (ec) { BMCWEB_LOG_ERROR << "Introspect call failed with error: " << ec.message() << " on process: " << connectionName << "\n"; return; } tinyxml2::XMLDocument doc; doc.Parse(introspect_xml.data(), introspect_xml.size()); tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR << "XML document failed to parse " << connectionName << "\n"; return; } tinyxml2::XMLElement *interfaceNode = pRoot->FirstChildElement("interface"); while (interfaceNode != nullptr) { const char *thisInterfaceName = interfaceNode->Attribute("name"); if (thisInterfaceName != nullptr) { if (!transaction->interfaceName.empty() && (transaction->interfaceName != thisInterfaceName)) { interfaceNode = interfaceNode->NextSiblingElement("interface"); continue; } tinyxml2::XMLElement *methodNode = interfaceNode->FirstChildElement("method"); while (methodNode != nullptr) { const char *thisMethodName = methodNode->Attribute("name"); BMCWEB_LOG_DEBUG << "Found method: " << thisMethodName; if (thisMethodName != nullptr && thisMethodName == transaction->methodName) { BMCWEB_LOG_DEBUG << "Found method named " << thisMethodName << " on interface " << thisInterfaceName; sdbusplus::message::message m = crow::connections::systemBus->new_method_call( connectionName.c_str(), transaction->path.c_str(), thisInterfaceName, transaction->methodName.c_str()); tinyxml2::XMLElement *argumentNode = methodNode->FirstChildElement("arg"); std::string returnType; // Find the output type while (argumentNode != nullptr) { const char *argDirection = argumentNode->Attribute("direction"); const char *argType = argumentNode->Attribute("type"); if (argDirection != nullptr && argType != nullptr && std::string(argDirection) == "out") { returnType = argType; break; } argumentNode = argumentNode->NextSiblingElement("arg"); } nlohmann::json::const_iterator argIt = transaction->arguments.begin(); argumentNode = methodNode->FirstChildElement("arg"); while (argumentNode != nullptr) { const char *argDirection = argumentNode->Attribute("direction"); const char *argType = argumentNode->Attribute("type"); if (argDirection != nullptr && argType != nullptr && std::string(argDirection) == "in") { if (argIt == transaction->arguments.end()) { transaction->setErrorStatus( "Invalid method args"); return; } if (convertJsonToDbus(m.get(), std::string(argType), *argIt) < 0) { transaction->setErrorStatus( "Invalid method arg type"); return; } argIt++; } argumentNode = argumentNode->NextSiblingElement("arg"); } crow::connections::systemBus->async_send( m, [transaction, returnType]( boost::system::error_code ec, sdbusplus::message::message &m) { if (ec) { transaction->methodFailed = true; const sd_bus_error *e = m.get_error(); if (e) { setErrorResponse( transaction->res, boost::beast::http::status:: bad_request, e->name, e->message); } else { setErrorResponse( transaction->res, boost::beast::http::status:: bad_request, "Method call failed", methodFailedMsg); } return; } else { transaction->methodPassed = true; } handleMethodResponse(transaction, m, returnType); }); break; } methodNode = methodNode->NextSiblingElement("method"); } } interfaceNode = interfaceNode->NextSiblingElement("interface"); } }, connectionName, transaction->path, "org.freedesktop.DBus.Introspectable", "Introspect"); } void handleAction(const crow::Request &req, crow::Response &res, const std::string &objectPath, const std::string &methodName) { BMCWEB_LOG_DEBUG << "handleAction on path: " << objectPath << " and method " << methodName; nlohmann::json requestDbusData = nlohmann::json::parse(req.body, nullptr, false); if (requestDbusData.is_discarded()) { setErrorResponse(res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); res.end(); return; } nlohmann::json::iterator data = requestDbusData.find("data"); if (data == requestDbusData.end()) { setErrorResponse(res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); res.end(); return; } if (!data->is_array()) { setErrorResponse(res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); res.end(); return; } auto transaction = std::make_shared(res); transaction->path = objectPath; transaction->methodName = methodName; transaction->arguments = std::move(*data); crow::connections::systemBus->async_method_call( [transaction]( const boost::system::error_code ec, const std::vector>> &interfaceNames) { if (ec || interfaceNames.size() <= 0) { BMCWEB_LOG_ERROR << "Can't find object"; setErrorResponse(transaction->res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); return; } BMCWEB_LOG_DEBUG << "GetObject returned " << interfaceNames.size() << " object(s)"; for (const std::pair> &object : interfaceNames) { findActionOnInterface(transaction, object.first); } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject", objectPath, std::array()); } void handleDelete(const crow::Request &req, crow::Response &res, const std::string &objectPath) { BMCWEB_LOG_DEBUG << "handleDelete on path: " << objectPath; crow::connections::systemBus->async_method_call( [&res, objectPath]( const boost::system::error_code ec, const std::vector>> &interfaceNames) { if (ec || interfaceNames.size() <= 0) { BMCWEB_LOG_ERROR << "Can't find object"; setErrorResponse(res, boost::beast::http::status::method_not_allowed, methodNotAllowedDesc, methodNotAllowedMsg); res.end(); return; } auto transaction = std::make_shared(res); transaction->path = objectPath; transaction->methodName = "Delete"; transaction->interfaceName = "xyz.openbmc_project.Object.Delete"; for (const std::pair> &object : interfaceNames) { findActionOnInterface(transaction, object.first); } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject", objectPath, std::array()); } void handleList(crow::Response &res, const std::string &objectPath, int32_t depth = 0) { crow::connections::systemBus->async_method_call( [&res](const boost::system::error_code ec, std::vector &objectPaths) { if (ec) { setErrorResponse(res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); } else { res.jsonValue = {{"status", "ok"}, {"message", "200 OK"}, {"data", std::move(objectPaths)}}; } res.end(); }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", objectPath, depth, std::array()); } void handleEnumerate(crow::Response &res, const std::string &objectPath) { BMCWEB_LOG_DEBUG << "Doing enumerate on " << objectPath; auto asyncResp = std::make_shared(res); asyncResp->res.jsonValue = {{"message", "200 OK"}, {"status", "ok"}, {"data", nlohmann::json::object()}}; crow::connections::systemBus->async_method_call( [objectPath, asyncResp](const boost::system::error_code ec, GetSubTreeType &object_names) { auto transaction = std::make_shared( objectPath, asyncResp); transaction->subtree = std::make_shared(std::move(object_names)); if (ec) { BMCWEB_LOG_ERROR << "GetSubTree failed on " << transaction->objectPath; setErrorResponse(transaction->asyncResp->res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); return; } // Add the data for the path passed in to the results // as if GetSubTree returned it, and continue on enumerating getObjectAndEnumerate(transaction); }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree", objectPath, static_cast(0), std::array()); } void handleGet(crow::Response &res, std::string &objectPath, std::string &destProperty) { BMCWEB_LOG_DEBUG << "handleGet: " << objectPath << " prop:" << destProperty; std::shared_ptr propertyName = std::make_shared(std::move(destProperty)); std::shared_ptr path = std::make_shared(std::move(objectPath)); using GetObjectType = std::vector>>; crow::connections::systemBus->async_method_call( [&res, path, propertyName](const boost::system::error_code ec, const GetObjectType &object_names) { if (ec || object_names.size() <= 0) { setErrorResponse(res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); res.end(); return; } std::shared_ptr response = std::make_shared(nlohmann::json::object()); // The mapper should never give us an empty interface names // list, but check anyway for (const std::pair> connection : object_names) { const std::vector &interfaceNames = connection.second; if (interfaceNames.size() <= 0) { setErrorResponse(res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); res.end(); return; } for (const std::string &interface : interfaceNames) { sdbusplus::message::message m = crow::connections::systemBus->new_method_call( connection.first.c_str(), path->c_str(), "org.freedesktop.DBus.Properties", "GetAll"); m.append(interface); crow::connections::systemBus->async_send( m, [&res, response, propertyName](const boost::system::error_code ec, sdbusplus::message::message &msg) { if (ec) { BMCWEB_LOG_ERROR << "Bad dbus request error: " << ec; } else { nlohmann::json properties; int r = convertDBusToJSON("a{sv}", msg, properties); if (r < 0) { BMCWEB_LOG_ERROR << "convertDBusToJSON failed"; } else { for (auto &prop : properties.items()) { // if property name is empty, or // matches our search query, add it // to the response json if (propertyName->empty()) { (*response)[prop.key()] = std::move(prop.value()); } else if (prop.key() == *propertyName) { *response = std::move(prop.value()); } } } } if (response.use_count() == 1) { if (!propertyName->empty() && response->empty()) { setErrorResponse( res, boost::beast::http::status::not_found, propNotFoundDesc, notFoundMsg); } else { res.jsonValue = {{"status", "ok"}, {"message", "200 OK"}, {"data", *response}}; } res.end(); } }); } } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject", *path, std::array()); } struct AsyncPutRequest { AsyncPutRequest(crow::Response &res) : res(res) { } ~AsyncPutRequest() { if (res.jsonValue.empty()) { setErrorResponse(res, boost::beast::http::status::forbidden, forbiddenMsg, forbiddenPropDesc); } res.end(); } void setErrorStatus(const std::string &desc) { setErrorResponse(res, boost::beast::http::status::internal_server_error, desc, badReqMsg); } crow::Response &res; std::string objectPath; std::string propertyName; nlohmann::json propertyValue; }; void handlePut(const crow::Request &req, crow::Response &res, const std::string &objectPath, const std::string &destProperty) { if (destProperty.empty()) { setErrorResponse(res, boost::beast::http::status::forbidden, forbiddenResDesc, forbiddenMsg); res.end(); return; } nlohmann::json requestDbusData = nlohmann::json::parse(req.body, nullptr, false); if (requestDbusData.is_discarded()) { setErrorResponse(res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); res.end(); return; } nlohmann::json::const_iterator propertyIt = requestDbusData.find("data"); if (propertyIt == requestDbusData.end()) { setErrorResponse(res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); res.end(); return; } const nlohmann::json &propertySetValue = *propertyIt; auto transaction = std::make_shared(res); transaction->objectPath = objectPath; transaction->propertyName = destProperty; transaction->propertyValue = propertySetValue; using GetObjectType = std::vector>>; crow::connections::systemBus->async_method_call( [transaction](const boost::system::error_code ec, const GetObjectType &object_names) { if (!ec && object_names.size() <= 0) { setErrorResponse(transaction->res, boost::beast::http::status::not_found, propNotFoundDesc, notFoundMsg); return; } for (const std::pair> connection : object_names) { const std::string &connectionName = connection.first; crow::connections::systemBus->async_method_call( [connectionName{std::string(connectionName)}, transaction](const boost::system::error_code ec, const std::string &introspectXml) { if (ec) { BMCWEB_LOG_ERROR << "Introspect call failed with error: " << ec.message() << " on process: " << connectionName; transaction->setErrorStatus("Unexpected Error"); return; } tinyxml2::XMLDocument doc; doc.Parse(introspectXml.c_str()); tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR << "XML document failed to parse: " << introspectXml; transaction->setErrorStatus("Unexpected Error"); return; } tinyxml2::XMLElement *ifaceNode = pRoot->FirstChildElement("interface"); while (ifaceNode != nullptr) { const char *interfaceName = ifaceNode->Attribute("name"); BMCWEB_LOG_DEBUG << "found interface " << interfaceName; tinyxml2::XMLElement *propNode = ifaceNode->FirstChildElement("property"); while (propNode != nullptr) { const char *propertyName = propNode->Attribute("name"); BMCWEB_LOG_DEBUG << "Found property " << propertyName; if (propertyName == transaction->propertyName) { const char *argType = propNode->Attribute("type"); if (argType != nullptr) { sdbusplus::message::message m = crow::connections::systemBus ->new_method_call( connectionName.c_str(), transaction->objectPath .c_str(), "org.freedesktop.DBus." "Properties", "Set"); m.append(interfaceName, transaction->propertyName); int r = sd_bus_message_open_container( m.get(), SD_BUS_TYPE_VARIANT, argType); if (r < 0) { transaction->setErrorStatus( "Unexpected Error"); return; } r = convertJsonToDbus( m.get(), argType, transaction->propertyValue); if (r < 0) { if (r == -ERANGE) { transaction->setErrorStatus( "Provided property value " "is out of range for the " "property type"); } else { transaction->setErrorStatus( "Invalid arg type"); } return; } r = sd_bus_message_close_container( m.get()); if (r < 0) { transaction->setErrorStatus( "Unexpected Error"); return; } crow::connections::systemBus ->async_send( m, [transaction]( boost::system::error_code ec, sdbusplus::message::message &m) { BMCWEB_LOG_DEBUG << "sent"; if (ec) { const sd_bus_error *e = m.get_error(); setErrorResponse( transaction->res, boost::beast::http:: status:: forbidden, (e) ? e->name : ec.category() .name(), (e) ? e->message : ec.message()); } else { transaction->res .jsonValue = { {"status", "ok"}, {"message", "200 OK"}, {"data", nullptr}}; } }); } } propNode = propNode->NextSiblingElement("property"); } ifaceNode = ifaceNode->NextSiblingElement("interface"); } }, connectionName, transaction->objectPath, "org.freedesktop.DBus.Introspectable", "Introspect"); } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject", transaction->objectPath, std::array()); } inline void handleDBusUrl(const crow::Request &req, crow::Response &res, std::string &objectPath) { // If accessing a single attribute, fill in and update objectPath, // otherwise leave destProperty blank std::string destProperty = ""; const char *attrSeperator = "/attr/"; size_t attrPosition = objectPath.find(attrSeperator); if (attrPosition != objectPath.npos) { destProperty = objectPath.substr(attrPosition + strlen(attrSeperator), objectPath.length()); objectPath = objectPath.substr(0, attrPosition); } if (req.method() == "POST"_method) { constexpr const char *actionSeperator = "/action/"; size_t actionPosition = objectPath.find(actionSeperator); if (actionPosition != objectPath.npos) { std::string postProperty = objectPath.substr((actionPosition + strlen(actionSeperator)), objectPath.length()); objectPath = objectPath.substr(0, actionPosition); handleAction(req, res, objectPath, postProperty); return; } } else if (req.method() == "GET"_method) { if (boost::ends_with(objectPath, "/enumerate")) { objectPath.erase(objectPath.end() - sizeof("enumerate"), objectPath.end()); handleEnumerate(res, objectPath); } else if (boost::ends_with(objectPath, "/list")) { objectPath.erase(objectPath.end() - sizeof("list"), objectPath.end()); handleList(res, objectPath); } else { // Trim any trailing "/" at the end if (boost::ends_with(objectPath, "/")) { objectPath.pop_back(); handleList(res, objectPath, 1); } else { handleGet(res, objectPath, destProperty); } } return; } else if (req.method() == "PUT"_method) { handlePut(req, res, objectPath, destProperty); return; } else if (req.method() == "DELETE"_method) { handleDelete(req, res, objectPath); return; } setErrorResponse(res, boost::beast::http::status::method_not_allowed, methodNotAllowedDesc, methodNotAllowedMsg); res.end(); } template void requestRoutes(Crow &app) { BMCWEB_ROUTE(app, "/bus/") .requires({"Login"}) .methods("GET"_method)( [](const crow::Request &req, crow::Response &res) { res.jsonValue = {{"busses", {{{"name", "system"}}}}, {"status", "ok"}}; res.end(); }); BMCWEB_ROUTE(app, "/bus/system/") .requires({"Login"}) .methods("GET"_method)( [](const crow::Request &req, crow::Response &res) { auto myCallback = [&res](const boost::system::error_code ec, std::vector &names) { if (ec) { BMCWEB_LOG_ERROR << "Dbus call failed with code " << ec; res.result( boost::beast::http::status::internal_server_error); } else { std::sort(names.begin(), names.end()); res.jsonValue = {{"status", "ok"}}; auto &objectsSub = res.jsonValue["objects"]; for (auto &name : names) { objectsSub.push_back({{"name", name}}); } } res.end(); }; crow::connections::systemBus->async_method_call( std::move(myCallback), "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"); }); BMCWEB_ROUTE(app, "/list/") .requires({"Login"}) .methods("GET"_method)( [](const crow::Request &req, crow::Response &res) { handleList(res, "/"); }); BMCWEB_ROUTE(app, "/xyz/") .requires({"Login"}) .methods("GET"_method)([](const crow::Request &req, crow::Response &res, const std::string &path) { std::string objectPath = "/xyz/" + path; handleDBusUrl(req, res, objectPath); }); BMCWEB_ROUTE(app, "/xyz/") .requires({"ConfigureComponents", "ConfigureManager"}) .methods("PUT"_method, "POST"_method, "DELETE"_method)( [](const crow::Request &req, crow::Response &res, const std::string &path) { std::string objectPath = "/xyz/" + path; handleDBusUrl(req, res, objectPath); }); BMCWEB_ROUTE(app, "/org/") .requires({"Login"}) .methods("GET"_method)([](const crow::Request &req, crow::Response &res, const std::string &path) { std::string objectPath = "/org/" + path; handleDBusUrl(req, res, objectPath); }); BMCWEB_ROUTE(app, "/org/") .requires({"ConfigureComponents", "ConfigureManager"}) .methods("PUT"_method, "POST"_method, "DELETE"_method)( [](const crow::Request &req, crow::Response &res, const std::string &path) { std::string objectPath = "/org/" + path; handleDBusUrl(req, res, objectPath); }); BMCWEB_ROUTE(app, "/download/dump//") .requires({"ConfigureManager"}) .methods("GET"_method)([](const crow::Request &req, crow::Response &res, const std::string &dumpId) { std::regex validFilename("^[\\w\\- ]+(\\.?[\\w\\- ]*)$"); if (!std::regex_match(dumpId, validFilename)) { res.result(boost::beast::http::status::bad_request); res.end(); return; } std::filesystem::path loc( "/var/lib/phosphor-debug-collector/dumps"); loc /= dumpId; if (!std::filesystem::exists(loc) || !std::filesystem::is_directory(loc)) { BMCWEB_LOG_ERROR << loc << "Not found"; res.result(boost::beast::http::status::not_found); res.end(); return; } std::filesystem::directory_iterator files(loc); for (auto &file : files) { std::ifstream readFile(file.path()); if (!readFile.good()) { continue; } res.addHeader("Content-Type", "application/octet-stream"); // Assuming only one dump file will be present in the dump id // directory std::string dumpFileName = file.path().filename().string(); // Filename should be in alphanumeric, dot and underscore // Its based on phosphor-debug-collector application dumpfile // format std::regex dumpFileRegex("[a-zA-Z0-9\\._]+"); if (!std::regex_match(dumpFileName, dumpFileRegex)) { BMCWEB_LOG_ERROR << "Invalid dump filename " << dumpFileName; res.result(boost::beast::http::status::not_found); res.end(); return; } std::string contentDispositionParam = "attachment; filename=\"" + dumpFileName + "\""; res.addHeader("Content-Disposition", contentDispositionParam); res.body() = {std::istreambuf_iterator(readFile), std::istreambuf_iterator()}; res.end(); return; } res.result(boost::beast::http::status::not_found); res.end(); return; }); BMCWEB_ROUTE(app, "/bus/system//") .requires({"Login"}) .methods("GET"_method)([](const crow::Request &req, crow::Response &res, const std::string &Connection) { introspectObjects(Connection, "/", std::make_shared(res)); }); BMCWEB_ROUTE(app, "/bus/system//") .methods("GET"_method, "POST"_method)([](const crow::Request &req, crow::Response &res, const std::string &processName, const std::string &requestedPath) { std::vector strs; boost::split(strs, requestedPath, boost::is_any_of("/")); std::string objectPath; std::string interfaceName; std::string methodName; auto it = strs.begin(); if (it == strs.end()) { objectPath = "/"; } 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 specifier above, which // causes the normal trailing backslash redirector to // fail. } else if (!it->empty()) { objectPath += "/" + *it; } it++; } if (it != strs.end()) { interfaceName = *it; it++; // after interface, we might have a method name if (it != strs.end()) { methodName = *it; it++; } } if (it != strs.end()) { // if there is more levels past the method name, something // went wrong, return not found res.result(boost::beast::http::status::not_found); return; } if (interfaceName.empty()) { std::shared_ptr asyncResp = std::make_shared(res); crow::connections::systemBus->async_method_call( [asyncResp, processName, objectPath](const boost::system::error_code ec, const std::string &introspect_xml) { if (ec) { BMCWEB_LOG_ERROR << "Introspect call failed with error: " << ec.message() << " on process: " << processName << " path: " << objectPath << "\n"; return; } tinyxml2::XMLDocument doc; doc.Parse(introspect_xml.c_str()); tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR << "XML document failed to parse " << processName << " " << objectPath << "\n"; asyncResp->res.jsonValue = { {"status", "XML parse error"}}; asyncResp->res.result(boost::beast::http::status:: internal_server_error); return; } BMCWEB_LOG_DEBUG << introspect_xml; asyncResp->res.jsonValue = { {"status", "ok"}, {"bus_name", processName}, {"object_path", objectPath}}; nlohmann::json &interfacesArray = asyncResp->res.jsonValue["interfaces"]; interfacesArray = nlohmann::json::array(); tinyxml2::XMLElement *interface = pRoot->FirstChildElement("interface"); while (interface != nullptr) { const char *ifaceName = interface->Attribute("name"); if (ifaceName != nullptr) { interfacesArray.push_back( {{"name", ifaceName}}); } interface = interface->NextSiblingElement("interface"); } }, processName, objectPath, "org.freedesktop.DBus.Introspectable", "Introspect"); } else if (methodName.empty()) { std::shared_ptr asyncResp = std::make_shared(res); crow::connections::systemBus->async_method_call( [asyncResp, processName, objectPath, interfaceName](const boost::system::error_code ec, const std::string &introspect_xml) { if (ec) { BMCWEB_LOG_ERROR << "Introspect call failed with error: " << ec.message() << " on process: " << processName << " path: " << objectPath << "\n"; return; } tinyxml2::XMLDocument doc; doc.Parse(introspect_xml.data(), introspect_xml.size()); tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR << "XML document failed to parse " << processName << " " << objectPath << "\n"; asyncResp->res.result(boost::beast::http::status:: internal_server_error); return; } asyncResp->res.jsonValue = { {"status", "ok"}, {"bus_name", processName}, {"interface", interfaceName}, {"object_path", objectPath}}; nlohmann::json &methodsArray = asyncResp->res.jsonValue["methods"]; methodsArray = nlohmann::json::array(); nlohmann::json &signalsArray = asyncResp->res.jsonValue["signals"]; signalsArray = nlohmann::json::array(); nlohmann::json &propertiesObj = asyncResp->res.jsonValue["properties"]; propertiesObj = nlohmann::json::object(); // if we know we're the only call, build the // json directly tinyxml2::XMLElement *interface = pRoot->FirstChildElement("interface"); while (interface != nullptr) { const char *ifaceName = interface->Attribute("name"); if (ifaceName != nullptr && ifaceName == interfaceName) { break; } interface = interface->NextSiblingElement("interface"); } if (interface == nullptr) { // if we got to the end of the list and // never found a match, throw 404 asyncResp->res.result( boost::beast::http::status::not_found); return; } tinyxml2::XMLElement *methods = interface->FirstChildElement("method"); while (methods != nullptr) { nlohmann::json argsArray = nlohmann::json::array(); tinyxml2::XMLElement *arg = methods->FirstChildElement("arg"); while (arg != nullptr) { nlohmann::json thisArg; for (const char *fieldName : std::array{ "name", "direction", "type"}) { const char *fieldValue = arg->Attribute(fieldName); if (fieldValue != nullptr) { thisArg[fieldName] = fieldValue; } } argsArray.push_back(std::move(thisArg)); arg = arg->NextSiblingElement("arg"); } const char *name = methods->Attribute("name"); if (name != nullptr) { methodsArray.push_back( {{"name", name}, {"uri", "/bus/system/" + processName + objectPath + "/" + interfaceName + "/" + name}, {"args", argsArray}}); } methods = methods->NextSiblingElement("method"); } tinyxml2::XMLElement *signals = interface->FirstChildElement("signal"); while (signals != nullptr) { nlohmann::json argsArray = nlohmann::json::array(); tinyxml2::XMLElement *arg = signals->FirstChildElement("arg"); while (arg != nullptr) { const char *name = arg->Attribute("name"); const char *type = arg->Attribute("type"); if (name != nullptr && type != nullptr) { argsArray.push_back({ {"name", name}, {"type", type}, }); } arg = arg->NextSiblingElement("arg"); } const char *name = signals->Attribute("name"); if (name != nullptr) { signalsArray.push_back( {{"name", name}, {"args", argsArray}}); } signals = signals->NextSiblingElement("signal"); } tinyxml2::XMLElement *property = interface->FirstChildElement("property"); while (property != nullptr) { const char *name = property->Attribute("name"); const char *type = property->Attribute("type"); if (type != nullptr && name != nullptr) { sdbusplus::message::message m = crow::connections::systemBus ->new_method_call(processName.c_str(), objectPath.c_str(), "org.freedesktop." "DBus." "Properties", "Get"); m.append(interfaceName, name); nlohmann::json &propertyItem = propertiesObj[name]; crow::connections::systemBus->async_send( m, [&propertyItem, asyncResp]( boost::system::error_code &ec, sdbusplus::message::message &m) { if (ec) { return; } convertDBusToJSON("v", m, propertyItem); }); } property = property->NextSiblingElement("property"); } }, processName, objectPath, "org.freedesktop.DBus.Introspectable", "Introspect"); } else { if (req.method() != "POST"_method) { res.result(boost::beast::http::status::not_found); res.end(); return; } nlohmann::json requestDbusData = nlohmann::json::parse(req.body, nullptr, false); if (requestDbusData.is_discarded()) { res.result(boost::beast::http::status::bad_request); res.end(); return; } if (!requestDbusData.is_array()) { res.result(boost::beast::http::status::bad_request); res.end(); return; } auto transaction = std::make_shared(res); transaction->path = objectPath; transaction->methodName = methodName; transaction->arguments = std::move(requestDbusData); findActionOnInterface(transaction, processName); } }); } } // namespace openbmc_mapper } // namespace crow