blob: 512c24e096e8b7474d84d9ba5cb24fa9f14073da [file] [log] [blame]
/*
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 "app.hpp"
#include "async_resp.hpp"
#include "boost_formatters.hpp"
#include "dbus_singleton.hpp"
#include "dbus_utility.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
#include "json_formatters.hpp"
#include "logging.hpp"
#include "parsing.hpp"
#include "routing.hpp"
#include "str_utility.hpp"
#include <systemd/sd-bus-protocol.h>
#include <systemd/sd-bus.h>
#include <tinyxml2.h>
#include <boost/beast/http/status.hpp>
#include <boost/beast/http/verb.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/vector.hpp>
#include <boost/system/error_code.hpp>
#include <nlohmann/json.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/exception.hpp>
#include <sdbusplus/message.hpp>
#include <sdbusplus/message/native_types.hpp>
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <limits>
#include <map>
#include <memory>
#include <ranges>
#include <regex>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
namespace crow
{
namespace openbmc_mapper
{
const constexpr char* notFoundMsg = "404 Not Found";
const constexpr char* badReqMsg = "400 Bad Request";
const constexpr char* methodNotAllowedMsg = "405 Method Not Allowed";
const constexpr char* forbiddenMsg = "403 Forbidden";
const constexpr char* unsupportedMediaMsg = "415 Unsupported Media Type";
const constexpr char* methodFailedMsg = "500 Method Call Failed";
const constexpr char* methodOutputFailedMsg = "500 Method Output Error";
const constexpr char* notFoundDesc =
"org.freedesktop.DBus.Error.FileNotFound: path or object not found";
const constexpr char* propNotFoundDesc =
"The specified property cannot be found";
const constexpr char* noJsonDesc = "No JSON object could be decoded";
const constexpr char* invalidContentType =
"Content-type header is missing or invalid";
const constexpr char* methodNotFoundDesc =
"The specified method cannot be found";
const constexpr char* methodNotAllowedDesc = "Method not allowed";
const constexpr char* forbiddenPropDesc =
"The specified property cannot be created";
const constexpr char* forbiddenResDesc =
"The specified resource cannot be created";
inline bool validateFilename(const std::string& filename)
{
static std::regex validFilename(R"(^[\w\- ]+(\.?[\w\- ]*)$)");
return std::regex_match(filename, validFilename);
}
inline void setErrorResponse(crow::Response& res,
boost::beast::http::status result,
const std::string& desc, std::string_view msg)
{
res.result(result);
res.jsonValue["data"]["description"] = desc;
res.jsonValue["message"] = msg;
res.jsonValue["status"] = "error";
}
inline void introspectObjects(
const std::string& processName, const std::string& objectPath,
const std::shared_ptr<bmcweb::AsyncResp>& transaction)
{
if (transaction->res.jsonValue.is_null())
{
transaction->res.jsonValue["status"] = "ok";
transaction->res.jsonValue["bus_name"] = processName;
transaction->res.jsonValue["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& introspectXml) {
if (ec)
{
BMCWEB_LOG_ERROR(
"Introspect call failed with error: {} on process: {} path: {}",
ec.message(), processName, objectPath);
return;
}
nlohmann::json::object_t object;
object["path"] = objectPath;
transaction->res.jsonValue["objects"].emplace_back(
std::move(object));
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 {} {}",
processName, objectPath);
}
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");
}
inline void getPropertiesForEnumerate(
const std::string& objectPath, const std::string& service,
const std::string& interface,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
BMCWEB_LOG_DEBUG("getPropertiesForEnumerate {} {} {}", objectPath, service,
interface);
sdbusplus::asio::getAllProperties(
*crow::connections::systemBus, service, objectPath, interface,
[asyncResp, objectPath, service,
interface](const boost::system::error_code& ec,
const dbus::utility::DBusPropertiesMap& propertiesList) {
if (ec)
{
BMCWEB_LOG_ERROR(
"GetAll on path {} iface {} service {} failed with code {}",
objectPath, interface, service, 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) {
if constexpr (std::is_same_v<
std::decay_t<decltype(val)>,
sdbusplus::message::unix_fd>)
{
propertyJson = val.fd;
}
else
{
propertyJson = val;
}
},
value);
}
});
}
// Find any results that weren't picked up by ObjectManagers, to be
// called after all ObjectManagers are searched for and called.
inline void findRemainingObjectsForEnumerate(
const std::string& objectPath,
const std::shared_ptr<dbus::utility::MapperGetSubTreeResponse>& subtree,
const std::shared_ptr<bmcweb::AsyncResp>& 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 (!interface.starts_with("org.freedesktop.DBus"))
{
getPropertiesForEnumerate(path, service, interface,
asyncResp);
}
}
}
}
}
}
struct InProgressEnumerateData
{
InProgressEnumerateData(
const std::string& objectPathIn,
const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
objectPath(objectPathIn), asyncResp(asyncRespIn)
{}
~InProgressEnumerateData()
{
try
{
findRemainingObjectsForEnumerate(objectPath, subtree, asyncResp);
}
catch (...)
{
BMCWEB_LOG_CRITICAL(
"findRemainingObjectsForEnumerate threw exception");
}
}
InProgressEnumerateData(const InProgressEnumerateData&) = delete;
InProgressEnumerateData(InProgressEnumerateData&&) = delete;
InProgressEnumerateData& operator=(const InProgressEnumerateData&) = delete;
InProgressEnumerateData& operator=(InProgressEnumerateData&&) = delete;
const std::string objectPath;
std::shared_ptr<dbus::utility::MapperGetSubTreeResponse> subtree;
std::shared_ptr<bmcweb::AsyncResp> asyncResp;
};
inline void getManagedObjectsForEnumerate(
const std::string& objectName, const std::string& objectManagerPath,
const std::string& connectionName,
const std::shared_ptr<InProgressEnumerateData>& transaction)
{
BMCWEB_LOG_DEBUG(
"getManagedObjectsForEnumerate {} object_manager_path {} connection_name {}",
objectName, objectManagerPath, connectionName);
sdbusplus::message::object_path path(objectManagerPath);
dbus::utility::getManagedObjects(
connectionName, path,
[transaction, objectName,
connectionName](const boost::system::error_code& ec,
const dbus::utility::ManagedObjectType& objects) {
if (ec)
{
BMCWEB_LOG_ERROR(
"GetManagedObjects on path {} on connection {} failed with code {}",
objectName, connectionName, ec);
return;
}
nlohmann::json& dataJson =
transaction->asyncResp->res.jsonValue["data"];
for (const auto& objectPath : objects)
{
if (objectPath.first.str.starts_with(objectName))
{
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) {
if constexpr (
std::is_same_v<
std::decay_t<decltype(val)>,
sdbusplus::message::unix_fd>)
{
propertyJson = val.fd;
}
else
{
propertyJson = val;
}
},
property.second);
}
}
}
for (const auto& interface : objectPath.second)
{
if (interface.first == "org.freedesktop.DBus.ObjectManager")
{
getManagedObjectsForEnumerate(
objectPath.first.str, objectPath.first.str,
connectionName, transaction);
}
}
}
});
}
inline void findObjectManagerPathForEnumerate(
const std::string& objectName, const std::string& connectionName,
const std::shared_ptr<InProgressEnumerateData>& transaction)
{
BMCWEB_LOG_DEBUG("Finding objectmanager for path {} on connection:{}",
objectName, connectionName);
crow::connections::systemBus->async_method_call(
[transaction, objectName, connectionName](
const boost::system::error_code& ec,
const dbus::utility::MapperGetAncestorsResponse& objects) {
if (ec)
{
BMCWEB_LOG_ERROR("GetAncestors on path {} failed with code {}",
objectName, ec);
return;
}
for (const auto& pathGroup : objects)
{
for (const auto& connectionGroup : pathGroup.second)
{
if (connectionGroup.first == connectionName)
{
// Found the object manager path for this resource.
getManagedObjectsForEnumerate(
objectName, pathGroup.first, connectionName,
transaction);
return;
}
}
}
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetAncestors", objectName,
std::array<const char*, 1>{"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.
inline void getObjectAndEnumerate(
const std::shared_ptr<InProgressEnumerateData>& transaction)
{
dbus::utility::getDbusObject(
transaction->objectPath, {},
[transaction](const boost::system::error_code& ec,
const dbus::utility::MapperGetObject& objects) {
if (ec)
{
BMCWEB_LOG_ERROR("GetObject for path {} failed with code {}",
transaction->objectPath, ec);
return;
}
BMCWEB_LOG_DEBUG("GetObject for {} has {} entries",
transaction->objectPath, objects.size());
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<
std::string, std::string, std::less<>,
std::vector<std::pair<std::string, std::string>>>
connections;
for (const auto& object : *(transaction->subtree))
{
for (const auto& connection : object.second)
{
for (const auto& interface : connection.second)
{
BMCWEB_LOG_DEBUG("{} has interface {}",
connection.first, interface);
if (interface == "org.freedesktop.DBus.ObjectManager")
{
BMCWEB_LOG_DEBUG("found object manager path {}",
object.first);
connections[connection.first] = object.first;
}
}
}
}
BMCWEB_LOG_DEBUG("Got {} connections", connections.size());
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);
}
}
});
}
// Structure for storing data on an in progress action
struct InProgressActionData
{
explicit InProgressActionData(
const std::shared_ptr<bmcweb::AsyncResp>& res) : asyncResp(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 (asyncResp->res.result() == boost::beast::http::status::ok)
{
if (!methodPassed)
{
if (!methodFailed)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::not_found,
methodNotFoundDesc, notFoundMsg);
}
}
else
{
if (outputFailed)
{
setErrorResponse(
asyncResp->res,
boost::beast::http::status::internal_server_error,
"Method output failure", methodOutputFailedMsg);
}
else
{
asyncResp->res.jsonValue["status"] = "ok";
asyncResp->res.jsonValue["message"] = "200 OK";
asyncResp->res.jsonValue["data"] = methodResponse;
}
}
}
}
InProgressActionData(const InProgressActionData&) = delete;
InProgressActionData(InProgressActionData&&) = delete;
InProgressActionData& operator=(const InProgressActionData&) = delete;
InProgressActionData& operator=(InProgressActionData&&) = delete;
void setErrorStatus(const std::string& desc)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::bad_request, desc,
badReqMsg);
}
std::shared_ptr<bmcweb::AsyncResp> asyncResp;
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;
};
inline std::vector<std::string> dbusArgSplit(const std::string& string)
{
std::vector<std::string> ret;
if (string.empty())
{
return ret;
}
ret.emplace_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.emplace_back("");
}
}
break;
default:
if (containerDepth == 0)
{
if (character + 1 != string.end())
{
ret.emplace_back("");
}
}
break;
}
}
return ret;
}
inline int convertJsonToDbus(sd_bus_message* m, const std::string& argType,
const nlohmann::json& inputJson)
{
int r = 0;
BMCWEB_LOG_DEBUG("Converting {} to type: {}", inputJson, argType);
const std::vector<std::string> argTypes = dbusArgSplit(argType);
// Assume a single object for now.
const nlohmann::json* j = &inputJson;
nlohmann::json::const_iterator jIt = inputJson.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 == inputJson.end())
{
return -2;
}
j = &*jIt;
jIt++;
}
const int64_t* intValue = j->get_ptr<const int64_t*>();
const std::string* stringValue = j->get_ptr<const std::string*>();
const double* doubleValue = j->get_ptr<const double*>();
const bool* b = j->get_ptr<const bool*>();
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 (intValue == nullptr)
{
const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
if (uintValue != nullptr)
{
v = static_cast<int64_t>(*uintValue);
intValue = &v;
}
}
if (doubleValue == nullptr)
{
const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
if (uintValue != nullptr)
{
d = static_cast<double>(*uintValue);
doubleValue = &d;
}
}
if (doubleValue == nullptr)
{
if (intValue != nullptr)
{
d = static_cast<double>(*intValue);
doubleValue = &d;
}
}
if (argCode == "s")
{
if (stringValue == nullptr)
{
return -1;
}
r = sd_bus_message_append_basic(
m, argCode[0], static_cast<const void*>(stringValue->data()));
if (r < 0)
{
return r;
}
}
else if (argCode == "i")
{
if (intValue == nullptr)
{
return -1;
}
if ((*intValue < std::numeric_limits<int32_t>::lowest()) ||
(*intValue > std::numeric_limits<int32_t>::max()))
{
return -ERANGE;
}
int32_t i = static_cast<int32_t>(*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 = 0;
if (intValue != nullptr)
{
if (*intValue == 1)
{
boolInt = 1;
}
else if (*intValue == 0)
{
boolInt = 0;
}
else
{
return -ERANGE;
}
}
else if (b != nullptr)
{
boolInt = *b ? 1 : 0;
}
else if (stringValue != nullptr)
{
if (!stringValue->empty())
{
if (stringValue->front() == 't' ||
stringValue->front() == 'T')
{
boolInt = 1;
}
}
}
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<int16_t>::lowest()) ||
(*intValue > std::numeric_limits<int16_t>::max()))
{
return -ERANGE;
}
int16_t n = static_cast<int16_t>(*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;
}
r = sd_bus_message_append_basic(m, argCode[0], intValue);
if (r < 0)
{
return r;
}
}
else if (argCode == "y")
{
const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
if (uintValue == nullptr)
{
return -1;
}
if (*uintValue > std::numeric_limits<uint8_t>::max())
{
return -ERANGE;
}
uint8_t y = static_cast<uint8_t>(*uintValue);
r = sd_bus_message_append_basic(m, argCode[0], &y);
}
else if (argCode == "q")
{
const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
if (uintValue == nullptr)
{
return -1;
}
if (*uintValue > std::numeric_limits<uint16_t>::max())
{
return -ERANGE;
}
uint16_t q = static_cast<uint16_t>(*uintValue);
r = sd_bus_message_append_basic(m, argCode[0], &q);
}
else if (argCode == "u")
{
const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
if (uintValue == nullptr)
{
return -1;
}
if (*uintValue > std::numeric_limits<uint32_t>::max())
{
return -ERANGE;
}
uint32_t u = static_cast<uint32_t>(*uintValue);
r = sd_bus_message_append_basic(m, argCode[0], &u);
}
else if (argCode == "t")
{
const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
if (uintValue == nullptr)
{
return -1;
}
r = sd_bus_message_append_basic(m, argCode[0], uintValue);
}
else if (argCode == "d")
{
if (doubleValue == nullptr)
{
return -1;
}
if ((*doubleValue < std::numeric_limits<double>::lowest()) ||
(*doubleValue > std::numeric_limits<double>::max()))
{
return -ERANGE;
}
r = sd_bus_message_append_basic(m, argCode[0], doubleValue);
if (r < 0)
{
return r;
}
}
else if (argCode.starts_with("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 (const auto& it : *j)
{
r = convertJsonToDbus(m, containedType, it);
if (r < 0)
{
return r;
}
}
sd_bus_message_close_container(m);
}
else if (argCode.starts_with("v"))
{
std::string containedType = argCode.substr(1);
BMCWEB_LOG_DEBUG("variant type: {} appending variant of type: {}",
argCode, containedType);
r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
containedType.c_str());
if (r < 0)
{
return r;
}
r = convertJsonToDbus(m, containedType, inputJson);
if (r < 0)
{
return r;
}
r = sd_bus_message_close_container(m);
if (r < 0)
{
return r;
}
}
else if (argCode.starts_with("(") && argCode.ends_with(")"))
{
std::string containedType = argCode.substr(1, argCode.size() - 2);
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& argCode2 : dbusArgSplit(containedType))
{
if (it == j->end())
{
return -1;
}
r = convertJsonToDbus(m, argCode2, *it);
if (r < 0)
{
return r;
}
it++;
}
r = sd_bus_message_close_container(m);
}
else if (argCode.starts_with("{") && argCode.ends_with("}"))
{
std::string containedType = argCode.substr(1, argCode.size() - 2);
r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
containedType.c_str());
if (r < 0)
{
return r;
}
std::vector<std::string> codes = dbusArgSplit(containedType);
if (codes.size() != 2)
{
return -1;
}
const std::string& keyType = codes[0];
const std::string& valueType = codes[1];
const nlohmann::json::object_t* arr =
j->get_ptr<const nlohmann::json::object_t*>();
if (arr == nullptr)
{
return -1;
}
for (const auto& it : *arr)
{
r = convertJsonToDbus(m, keyType, it.first);
if (r < 0)
{
return r;
}
r = convertJsonToDbus(m, valueType, it.second);
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 <typename T>
int readMessageItem(const std::string& typeCode, sdbusplus::message_t& m,
nlohmann::json& data)
{
T value;
// When T == char*, this warning fires. Unclear how to resolve
// Given that sd-bus takes a void pointer to a char*, and that's
// Not something we can fix.
// NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
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 {} failed!",
typeCode);
return r;
}
data = value;
return 0;
}
int convertDBusToJSON(const std::string& returnType, sdbusplus::message_t& m,
nlohmann::json& response);
inline int readDictEntryFromMessage(const std::string& typeCode,
sdbusplus::message_t& m,
nlohmann::json& object)
{
std::vector<std::string> 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<const std::string*>();
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(2, ' ', true, nlohmann::json::error_handler_t::replace);
keyPtr = key.get_ptr<const std::string*>();
// 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;
}
inline int readArrayFromMessage(const std::string& typeCode,
sdbusplus::message_t& m, nlohmann::json& data)
{
if (typeCode.size() < 2)
{
BMCWEB_LOG_ERROR("Type code {} too small for an array", typeCode);
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 = containedType.starts_with("{") && containedType.ends_with("}");
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(), 0);
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;
}
inline int readStructFromMessage(const std::string& typeCode,
sdbusplus::message_t& m, nlohmann::json& data)
{
if (typeCode.size() < 3)
{
BMCWEB_LOG_ERROR("Type code {} too small for a struct", typeCode);
return -1;
}
std::string containedTypes = typeCode.substr(1, typeCode.size() - 2);
std::vector<std::string> 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;
}
inline int readVariantFromMessage(sdbusplus::message_t& m, nlohmann::json& data)
{
const char* containerType = nullptr;
int r = sd_bus_message_peek_type(m.get(), nullptr, &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;
}
inline int convertDBusToJSON(const std::string& returnType,
sdbusplus::message_t& m, nlohmann::json& response)
{
int r = 0;
const std::vector<std::string> 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" || typeCode == "g" || typeCode == "o")
{
r = readMessageItem<char*>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "b")
{
r = readMessageItem<int>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
*thisElement = static_cast<bool>(thisElement->get<int>());
}
else if (typeCode == "u")
{
r = readMessageItem<uint32_t>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "i")
{
r = readMessageItem<int32_t>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "x")
{
r = readMessageItem<int64_t>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "t")
{
r = readMessageItem<uint64_t>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "n")
{
r = readMessageItem<int16_t>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "q")
{
r = readMessageItem<uint16_t>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "y")
{
r = readMessageItem<uint8_t>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "d")
{
r = readMessageItem<double>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode == "h")
{
r = readMessageItem<int>(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode.starts_with("a"))
{
r = readArrayFromMessage(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode.starts_with("(") && typeCode.ends_with(")"))
{
r = readStructFromMessage(typeCode, m, *thisElement);
if (r < 0)
{
return r;
}
}
else if (typeCode.starts_with("v"))
{
r = readVariantFromMessage(m, *thisElement);
if (r < 0)
{
return r;
}
}
else
{
BMCWEB_LOG_ERROR("Invalid D-Bus signature type {}", typeCode);
return -2;
}
}
return 0;
}
inline void handleMethodResponse(
const std::shared_ptr<InProgressActionData>& transaction,
sdbusplus::message_t& 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.
nlohmann::json::object_t* dataobj =
data.get_ptr<nlohmann::json::object_t*>();
if (transaction->methodResponse.is_object() && dataobj != nullptr)
{
for (auto& obj : *dataobj)
{
// Note: Will overwrite the data for a duplicate key
transaction->methodResponse.emplace(obj.first,
std::move(obj.second));
}
return;
}
nlohmann::json::array_t* dataarr = data.get_ptr<nlohmann::json::array_t*>();
if (transaction->methodResponse.is_array() && dataarr != nullptr)
{
for (auto& obj : *dataarr)
{
transaction->methodResponse.emplace_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.emplace_back(std::move(j));
transaction->methodResponse.emplace_back(std::move(data));
transaction->convertedToArray = true;
}
else
{
transaction->methodResponse.emplace_back(std::move(data));
}
}
inline void findActionOnInterface(
const std::shared_ptr<InProgressActionData>& 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& introspectXml) {
BMCWEB_LOG_DEBUG("got xml:\n {}", introspectXml);
if (ec)
{
BMCWEB_LOG_ERROR(
"Introspect call failed with error: {} on process: {}",
ec.message(), connectionName);
return;
}
tinyxml2::XMLDocument doc;
doc.Parse(introspectXml.data(), introspectXml.size());
tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
if (pRoot == nullptr)
{
BMCWEB_LOG_ERROR("XML document failed to parse {}",
connectionName);
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 {} on interface {}",
thisMethodName, thisInterfaceName);
sdbusplus::message_t 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");
}
auto 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](
const boost::system::error_code& ec2,
sdbusplus::message_t& m2) {
if (ec2)
{
transaction->methodFailed = true;
const sd_bus_error* e = m2.get_error();
if (e != nullptr)
{
setErrorResponse(
transaction->asyncResp->res,
boost::beast::http::status::
bad_request,
e->name, e->message);
}
else
{
setErrorResponse(
transaction->asyncResp->res,
boost::beast::http::status::
bad_request,
"Method call failed",
methodFailedMsg);
}
return;
}
transaction->methodPassed = true;
handleMethodResponse(transaction, m2,
returnType);
});
break;
}
methodNode = methodNode->NextSiblingElement("method");
}
}
interfaceNode = interfaceNode->NextSiblingElement("interface");
}
},
connectionName, transaction->path,
"org.freedesktop.DBus.Introspectable", "Introspect");
}
inline void handleAction(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& objectPath,
const std::string& methodName)
{
BMCWEB_LOG_DEBUG("handleAction on path: {} and method {}", objectPath,
methodName);
nlohmann::json requestDbusData;
JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
if (ret == JsonParseResult::BadContentType)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::unsupported_media_type,
invalidContentType, unsupportedMediaMsg);
return;
}
if (ret != JsonParseResult::Success)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::bad_request, noJsonDesc,
badReqMsg);
return;
}
nlohmann::json::iterator data = requestDbusData.find("data");
if (data == requestDbusData.end())
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::bad_request, noJsonDesc,
badReqMsg);
return;
}
if (!data->is_array())
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::bad_request, noJsonDesc,
badReqMsg);
return;
}
auto transaction = std::make_shared<InProgressActionData>(asyncResp);
transaction->path = objectPath;
transaction->methodName = methodName;
transaction->arguments = std::move(*data);
dbus::utility::getDbusObject(
objectPath, {},
[transaction](
const boost::system::error_code& ec,
const std::vector<std::pair<std::string, std::vector<std::string>>>&
interfaceNames) {
if (ec || interfaceNames.empty())
{
BMCWEB_LOG_ERROR("Can't find object");
setErrorResponse(transaction->asyncResp->res,
boost::beast::http::status::not_found,
notFoundDesc, notFoundMsg);
return;
}
BMCWEB_LOG_DEBUG("GetObject returned {} object(s)",
interfaceNames.size());
for (const std::pair<std::string, std::vector<std::string>>&
object : interfaceNames)
{
findActionOnInterface(transaction, object.first);
}
});
}
inline void handleDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& objectPath)
{
BMCWEB_LOG_DEBUG("handleDelete on path: {}", objectPath);
dbus::utility::getDbusObject(
objectPath, {},
[asyncResp, objectPath](
const boost::system::error_code& ec,
const std::vector<std::pair<std::string, std::vector<std::string>>>&
interfaceNames) {
if (ec || interfaceNames.empty())
{
BMCWEB_LOG_ERROR("Can't find object");
setErrorResponse(asyncResp->res,
boost::beast::http::status::method_not_allowed,
methodNotAllowedDesc, methodNotAllowedMsg);
return;
}
auto transaction =
std::make_shared<InProgressActionData>(asyncResp);
transaction->path = objectPath;
transaction->methodName = "Delete";
transaction->interfaceName = "xyz.openbmc_project.Object.Delete";
for (const std::pair<std::string, std::vector<std::string>>&
object : interfaceNames)
{
findActionOnInterface(transaction, object.first);
}
});
}
inline void handleList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& objectPath, int32_t depth = 0)
{
dbus::utility::getSubTreePaths(
objectPath, depth, {},
[asyncResp](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse& objectPaths) {
if (ec)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::not_found,
notFoundDesc, notFoundMsg);
}
else
{
asyncResp->res.jsonValue["status"] = "ok";
asyncResp->res.jsonValue["message"] = "200 OK";
asyncResp->res.jsonValue["data"] = objectPaths;
}
});
}
inline void handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& objectPath)
{
BMCWEB_LOG_DEBUG("Doing enumerate on {}", objectPath);
asyncResp->res.jsonValue["message"] = "200 OK";
asyncResp->res.jsonValue["status"] = "ok";
asyncResp->res.jsonValue["data"] = nlohmann::json::object();
dbus::utility::getSubTree(
objectPath, 0, {},
[objectPath, asyncResp](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& objectNames) {
auto transaction = std::make_shared<InProgressEnumerateData>(
objectPath, asyncResp);
transaction->subtree =
std::make_shared<dbus::utility::MapperGetSubTreeResponse>(
objectNames);
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);
});
}
inline void handleGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
std::string& objectPath, std::string& destProperty)
{
BMCWEB_LOG_DEBUG("handleGet: {} prop:{}", objectPath, destProperty);
std::shared_ptr<std::string> propertyName =
std::make_shared<std::string>(std::move(destProperty));
std::shared_ptr<std::string> path =
std::make_shared<std::string>(std::move(objectPath));
dbus::utility::getDbusObject(
*path, {},
[asyncResp, path,
propertyName](const boost::system::error_code& ec,
const dbus::utility::MapperGetObject& objectNames) {
if (ec || objectNames.empty())
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::not_found,
notFoundDesc, notFoundMsg);
return;
}
std::shared_ptr<nlohmann::json> response =
std::make_shared<nlohmann::json>(nlohmann::json::object());
// The mapper should never give us an empty interface names
// list, but check anyway
for (const std::pair<std::string, std::vector<std::string>>&
connection : objectNames)
{
const std::vector<std::string>& interfaceNames =
connection.second;
if (interfaceNames.empty())
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::not_found,
notFoundDesc, notFoundMsg);
return;
}
for (const std::string& interface : interfaceNames)
{
sdbusplus::message_t 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, [asyncResp, response,
propertyName](const boost::system::error_code& ec2,
sdbusplus::message_t& msg) {
if (ec2)
{
BMCWEB_LOG_ERROR("Bad dbus request error: {}",
ec2);
}
else
{
nlohmann::json properties;
int r =
convertDBusToJSON("a{sv}", msg, properties);
if (r < 0)
{
BMCWEB_LOG_ERROR(
"convertDBusToJSON failed");
}
else
{
nlohmann::json::object_t* obj =
properties.get_ptr<
nlohmann::json::object_t*>();
if (obj == nullptr)
{
return;
}
for (auto& prop : *obj)
{
// if property name is empty, or
// matches our search query, add it
// to the response json
if (propertyName->empty())
{
(*response)[prop.first] =
std::move(prop.second);
}
else if (prop.first == *propertyName)
{
*response = std::move(prop.second);
}
}
}
}
if (response.use_count() == 1)
{
if (!propertyName->empty() && response->empty())
{
setErrorResponse(
asyncResp->res,
boost::beast::http::status::not_found,
propNotFoundDesc, notFoundMsg);
}
else
{
asyncResp->res.jsonValue["status"] = "ok";
asyncResp->res.jsonValue["message"] =
"200 OK";
asyncResp->res.jsonValue["data"] =
*response;
}
}
});
}
}
});
}
struct AsyncPutRequest
{
explicit AsyncPutRequest(const std::shared_ptr<bmcweb::AsyncResp>& resIn) :
asyncResp(resIn)
{}
~AsyncPutRequest()
{
if (asyncResp->res.jsonValue.empty())
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::forbidden,
forbiddenMsg, forbiddenPropDesc);
}
}
AsyncPutRequest(const AsyncPutRequest&) = delete;
AsyncPutRequest(AsyncPutRequest&&) = delete;
AsyncPutRequest& operator=(const AsyncPutRequest&) = delete;
AsyncPutRequest& operator=(AsyncPutRequest&&) = delete;
void setErrorStatus(const std::string& desc)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::internal_server_error,
desc, badReqMsg);
}
const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
std::string objectPath;
std::string propertyName;
nlohmann::json propertyValue;
};
inline void handlePut(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& objectPath,
const std::string& destProperty)
{
if (destProperty.empty())
{
setErrorResponse(asyncResp->res, boost::beast::http::status::forbidden,
forbiddenResDesc, forbiddenMsg);
return;
}
nlohmann::json requestDbusData;
JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
if (ret == JsonParseResult::BadContentType)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::unsupported_media_type,
invalidContentType, unsupportedMediaMsg);
return;
}
if (ret != JsonParseResult::Success)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::bad_request, noJsonDesc,
badReqMsg);
return;
}
auto propertyIt = requestDbusData.find("data");
if (propertyIt == requestDbusData.end())
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::bad_request, noJsonDesc,
badReqMsg);
return;
}
const nlohmann::json& propertySetValue = *propertyIt;
auto transaction = std::make_shared<AsyncPutRequest>(asyncResp);
transaction->objectPath = objectPath;
transaction->propertyName = destProperty;
transaction->propertyValue = propertySetValue;
dbus::utility::getDbusObject(
transaction->objectPath, {},
[transaction](const boost::system::error_code& ec2,
const dbus::utility::MapperGetObject& objectNames) {
if (!ec2 && objectNames.empty())
{
setErrorResponse(transaction->asyncResp->res,
boost::beast::http::status::not_found,
propNotFoundDesc, notFoundMsg);
return;
}
for (const std::pair<std::string, std::vector<std::string>>&
connection : objectNames)
{
const std::string& connectionName = connection.first;
crow::connections::systemBus->async_method_call(
[connectionName{std::string(connectionName)},
transaction](const boost::system::error_code& ec3,
const std::string& introspectXml) {
if (ec3)
{
BMCWEB_LOG_ERROR(
"Introspect call failed with error: {} on process: {}",
ec3.message(), 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");
if (propertyName == nullptr)
{
BMCWEB_LOG_DEBUG(
"Couldn't find name property");
continue;
}
BMCWEB_LOG_DEBUG("Found property {}",
propertyName);
if (propertyName == transaction->propertyName)
{
const char* argType =
propNode->Attribute("type");
if (argType != nullptr)
{
sdbusplus::message_t 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](
const boost::system::
error_code& ec,
sdbusplus::message_t& m2) {
BMCWEB_LOG_DEBUG("sent");
if (ec)
{
const sd_bus_error* e =
m2.get_error();
setErrorResponse(
transaction
->asyncResp
->res,
boost::beast::http::
status::
forbidden,
(e) != nullptr
? e->name
: ec.category()
.name(),
(e) != nullptr
? e->message
: ec.message());
}
else
{
transaction->asyncResp
->res.jsonValue
["status"] =
"ok";
transaction->asyncResp
->res.jsonValue
["message"] =
"200 OK";
transaction->asyncResp
->res
.jsonValue["data"] =
nullptr;
}
});
}
}
propNode =
propNode->NextSiblingElement("property");
}
ifaceNode =
ifaceNode->NextSiblingElement("interface");
}
},
connectionName, transaction->objectPath,
"org.freedesktop.DBus.Introspectable", "Introspect");
}
});
}
inline void handleDBusUrl(const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
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 != std::string::npos)
{
destProperty = objectPath.substr(attrPosition + strlen(attrSeperator),
objectPath.length());
objectPath.resize(attrPosition);
}
if (req.method() == boost::beast::http::verb::post)
{
constexpr const char* actionSeperator = "/action/";
size_t actionPosition = objectPath.find(actionSeperator);
if (actionPosition != std::string::npos)
{
std::string postProperty =
objectPath.substr((actionPosition + strlen(actionSeperator)),
objectPath.length());
objectPath.resize(actionPosition);
handleAction(req, asyncResp, objectPath, postProperty);
return;
}
}
else if (req.method() == boost::beast::http::verb::get)
{
if (objectPath.ends_with("/enumerate"))
{
objectPath.erase(objectPath.end() - sizeof("enumerate"),
objectPath.end());
handleEnumerate(asyncResp, objectPath);
}
else if (objectPath.ends_with("/list"))
{
objectPath.erase(objectPath.end() - sizeof("list"),
objectPath.end());
handleList(asyncResp, objectPath);
}
else
{
// Trim any trailing "/" at the end
if (objectPath.ends_with("/"))
{
objectPath.pop_back();
handleList(asyncResp, objectPath, 1);
}
else
{
handleGet(asyncResp, objectPath, destProperty);
}
}
return;
}
else if (req.method() == boost::beast::http::verb::put)
{
handlePut(req, asyncResp, objectPath, destProperty);
return;
}
else if (req.method() == boost::beast::http::verb::delete_)
{
handleDelete(asyncResp, objectPath);
return;
}
setErrorResponse(asyncResp->res,
boost::beast::http::status::method_not_allowed,
methodNotAllowedDesc, methodNotAllowedMsg);
}
inline void handleBusSystemPost(
const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& processName, const std::string& requestedPath)
{
std::vector<std::string> strs;
bmcweb::split(strs, requestedPath, '/');
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 necessary as the trailing slash gets
// parsed as part of our <path> specifier above, which
// causes the normal trailing backslash redirector to
// fail.
}
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
asyncResp->res.result(boost::beast::http::status::not_found);
return;
}
if (interfaceName.empty())
{
crow::connections::systemBus->async_method_call(
[asyncResp, processName,
objectPath](const boost::system::error_code& ec,
const std::string& introspectXml) {
if (ec)
{
BMCWEB_LOG_ERROR(
"Introspect call failed with error: {} on process: {} path: {}",
ec.message(), processName, objectPath);
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 {} {}",
processName, objectPath);
asyncResp->res.jsonValue["status"] = "XML parse error";
asyncResp->res.result(
boost::beast::http::status::internal_server_error);
return;
}
BMCWEB_LOG_DEBUG("{}", introspectXml);
asyncResp->res.jsonValue["status"] = "ok";
asyncResp->res.jsonValue["bus_name"] = processName;
asyncResp->res.jsonValue["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)
{
nlohmann::json::object_t interfaceObj;
interfaceObj["name"] = ifaceName;
interfacesArray.emplace_back(std::move(interfaceObj));
}
interface = interface->NextSiblingElement("interface");
}
},
processName, objectPath, "org.freedesktop.DBus.Introspectable",
"Introspect");
}
else if (methodName.empty())
{
crow::connections::systemBus->async_method_call(
[asyncResp, processName, objectPath,
interfaceName](const boost::system::error_code& ec,
const std::string& introspectXml) {
if (ec)
{
BMCWEB_LOG_ERROR(
"Introspect call failed with error: {} on process: {} path: {}",
ec.message(), processName, objectPath);
return;
}
tinyxml2::XMLDocument doc;
doc.Parse(introspectXml.data(), introspectXml.size());
tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
if (pRoot == nullptr)
{
BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
processName, objectPath);
asyncResp->res.result(
boost::beast::http::status::internal_server_error);
return;
}
asyncResp->res.jsonValue["status"] = "ok";
asyncResp->res.jsonValue["bus_name"] = processName;
asyncResp->res.jsonValue["interface"] = interfaceName;
asyncResp->res.jsonValue["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<const char*, 3>{
"name", "direction", "type"})
{
const char* fieldValue = arg->Attribute(fieldName);
if (fieldValue != nullptr)
{
thisArg[fieldName] = fieldValue;
}
}
argsArray.emplace_back(std::move(thisArg));
arg = arg->NextSiblingElement("arg");
}
const char* name = methods->Attribute("name");
if (name != nullptr)
{
std::string uri;
uri.reserve(14 + processName.size() +
objectPath.size() + interfaceName.size() +
strlen(name));
uri += "/bus/system/";
uri += processName;
uri += objectPath;
uri += "/";
uri += interfaceName;
uri += "/";
uri += name;
nlohmann::json::object_t object;
object["name"] = name;
object["uri"] = std::move(uri);
object["args"] = argsArray;
methodsArray.emplace_back(std::move(object));
}
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)
{
nlohmann::json::object_t params;
params["name"] = name;
params["type"] = type;
argsArray.push_back(std::move(params));
}
arg = arg->NextSiblingElement("arg");
}
const char* name = signals->Attribute("name");
if (name != nullptr)
{
nlohmann::json::object_t object;
object["name"] = name;
object["args"] = argsArray;
signalsArray.emplace_back(std::move(object));
}
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_t 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](const boost::system::error_code& ec2,
sdbusplus::message_t& msg) {
if (ec2)
{
return;
}
int r =
convertDBusToJSON("v", msg, propertyItem);
if (r < 0)
{
BMCWEB_LOG_ERROR(
"Couldn't convert vector to json");
}
});
}
property = property->NextSiblingElement("property");
}
},
processName, objectPath, "org.freedesktop.DBus.Introspectable",
"Introspect");
}
else
{
if (req.method() != boost::beast::http::verb::post)
{
asyncResp->res.result(boost::beast::http::status::not_found);
return;
}
nlohmann::json requestDbusData;
JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
if (ret == JsonParseResult::BadContentType)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::unsupported_media_type,
invalidContentType, unsupportedMediaMsg);
return;
}
if (ret != JsonParseResult::Success)
{
setErrorResponse(asyncResp->res,
boost::beast::http::status::bad_request,
noJsonDesc, badReqMsg);
return;
}
if (!requestDbusData.is_array())
{
asyncResp->res.result(boost::beast::http::status::bad_request);
return;
}
auto transaction = std::make_shared<InProgressActionData>(asyncResp);
transaction->path = objectPath;
transaction->methodName = methodName;
transaction->arguments = std::move(requestDbusData);
findActionOnInterface(transaction, processName);
}
}
inline void requestRoutes(App& app)
{
BMCWEB_ROUTE(app, "/bus/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
nlohmann::json::array_t buses;
nlohmann::json& bus = buses.emplace_back();
bus["name"] = "system";
asyncResp->res.jsonValue["busses"] = std::move(buses);
asyncResp->res.jsonValue["status"] = "ok";
});
BMCWEB_ROUTE(app, "/bus/system/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
auto myCallback = [asyncResp](
const boost::system::error_code& ec,
std::vector<std::string>& names) {
if (ec)
{
BMCWEB_LOG_ERROR("Dbus call failed with code {}", ec);
asyncResp->res.result(
boost::beast::http::status::internal_server_error);
}
else
{
std::ranges::sort(names);
asyncResp->res.jsonValue["status"] = "ok";
auto& objectsSub = asyncResp->res.jsonValue["objects"];
for (const auto& name : names)
{
nlohmann::json::object_t object;
object["name"] = name;
objectsSub.emplace_back(std::move(object));
}
}
};
crow::connections::systemBus->async_method_call(
std::move(myCallback), "org.freedesktop.DBus", "/",
"org.freedesktop.DBus", "ListNames");
});
BMCWEB_ROUTE(app, "/list/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
handleList(asyncResp, "/");
});
BMCWEB_ROUTE(app, "/xyz/<path>")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path) {
std::string objectPath = "/xyz/" + path;
handleDBusUrl(req, asyncResp, objectPath);
});
BMCWEB_ROUTE(app, "/xyz/<path>")
.privileges({{"ConfigureComponents", "ConfigureManager"}})
.methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
boost::beast::http::verb::delete_)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path) {
std::string objectPath = "/xyz/" + path;
handleDBusUrl(req, asyncResp, objectPath);
});
BMCWEB_ROUTE(app, "/org/<path>")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path) {
std::string objectPath = "/org/" + path;
handleDBusUrl(req, asyncResp, objectPath);
});
BMCWEB_ROUTE(app, "/org/<path>")
.privileges({{"ConfigureComponents", "ConfigureManager"}})
.methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
boost::beast::http::verb::delete_)(
[](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path) {
std::string objectPath = "/org/" + path;
handleDBusUrl(req, asyncResp, objectPath);
});
BMCWEB_ROUTE(app, "/download/dump/<str>/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& dumpId) {
if (!validateFilename(dumpId))
{
asyncResp->res.result(
boost::beast::http::status::bad_request);
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("{}Not found", loc.string());
asyncResp->res.result(
boost::beast::http::status::not_found);
return;
}
std::filesystem::directory_iterator files(loc);
for (const auto& file : files)
{
if (!asyncResp->res.openFile(file))
{
continue;
}
asyncResp->res.addHeader(
boost::beast::http::field::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
static std::regex dumpFileRegex("[a-zA-Z0-9\\._]+");
if (!std::regex_match(dumpFileName, dumpFileRegex))
{
BMCWEB_LOG_ERROR("Invalid dump filename {}",
dumpFileName);
asyncResp->res.result(
boost::beast::http::status::not_found);
return;
}
std::string contentDispositionParam =
"attachment; filename=\"" + dumpFileName + "\"";
asyncResp->res.addHeader(
boost::beast::http::field::content_disposition,
contentDispositionParam);
return;
}
asyncResp->res.result(boost::beast::http::status::not_found);
return;
});
BMCWEB_ROUTE(app, "/bus/system/<str>/")
.privileges({{"Login"}})
.methods(boost::beast::http::verb::get)(
[](const crow::Request&,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& connection) {
introspectObjects(connection, "/", asyncResp);
});
BMCWEB_ROUTE(app, "/bus/system/<str>/<path>")
.privileges({{"ConfigureComponents", "ConfigureManager"}})
.methods(boost::beast::http::verb::get,
boost::beast::http::verb::post)(handleBusSystemPost);
}
} // namespace openbmc_mapper
} // namespace crow