1 /*
2 Copyright (c) 2018 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 #pragma once
18 #include "app.hpp"
19 #include "async_resp.hpp"
20 #include "boost_formatters.hpp"
21 #include "dbus_singleton.hpp"
22 #include "dbus_utility.hpp"
23 #include "http_request.hpp"
24 #include "http_response.hpp"
25 #include "json_formatters.hpp"
26 #include "logging.hpp"
27 #include "parsing.hpp"
28 #include "routing.hpp"
29 #include "str_utility.hpp"
30
31 #include <systemd/sd-bus-protocol.h>
32 #include <systemd/sd-bus.h>
33 #include <tinyxml2.h>
34
35 #include <boost/beast/http/status.hpp>
36 #include <boost/beast/http/verb.hpp>
37 #include <boost/container/flat_map.hpp>
38 #include <boost/container/vector.hpp>
39 #include <boost/system/error_code.hpp>
40 #include <nlohmann/json.hpp>
41 #include <sdbusplus/asio/connection.hpp>
42 #include <sdbusplus/asio/property.hpp>
43 #include <sdbusplus/exception.hpp>
44 #include <sdbusplus/message.hpp>
45 #include <sdbusplus/message/native_types.hpp>
46
47 #include <algorithm>
48 #include <array>
49 #include <cerrno>
50 #include <cstdint>
51 #include <cstring>
52 #include <filesystem>
53 #include <fstream>
54 #include <functional>
55 #include <initializer_list>
56 #include <iterator>
57 #include <limits>
58 #include <map>
59 #include <memory>
60 #include <ranges>
61 #include <regex>
62 #include <string>
63 #include <string_view>
64 #include <type_traits>
65 #include <utility>
66 #include <variant>
67 #include <vector>
68
69 namespace crow
70 {
71 namespace openbmc_mapper
72 {
73 const constexpr char* notFoundMsg = "404 Not Found";
74 const constexpr char* badReqMsg = "400 Bad Request";
75 const constexpr char* methodNotAllowedMsg = "405 Method Not Allowed";
76 const constexpr char* forbiddenMsg = "403 Forbidden";
77 const constexpr char* unsupportedMediaMsg = "415 Unsupported Media Type";
78 const constexpr char* methodFailedMsg = "500 Method Call Failed";
79 const constexpr char* methodOutputFailedMsg = "500 Method Output Error";
80 const constexpr char* notFoundDesc =
81 "org.freedesktop.DBus.Error.FileNotFound: path or object not found";
82 const constexpr char* propNotFoundDesc =
83 "The specified property cannot be found";
84 const constexpr char* noJsonDesc = "No JSON object could be decoded";
85 const constexpr char* invalidContentType =
86 "Content-type header is missing or invalid";
87 const constexpr char* methodNotFoundDesc =
88 "The specified method cannot be found";
89 const constexpr char* methodNotAllowedDesc = "Method not allowed";
90 const constexpr char* forbiddenPropDesc =
91 "The specified property cannot be created";
92 const constexpr char* forbiddenResDesc =
93 "The specified resource cannot be created";
94
validateFilename(const std::string & filename)95 inline bool validateFilename(const std::string& filename)
96 {
97 static std::regex validFilename(R"(^[\w\- ]+(\.?[\w\- ]*)$)");
98
99 return std::regex_match(filename, validFilename);
100 }
101
setErrorResponse(crow::Response & res,boost::beast::http::status result,const std::string & desc,std::string_view msg)102 inline void setErrorResponse(crow::Response& res,
103 boost::beast::http::status result,
104 const std::string& desc, std::string_view msg)
105 {
106 res.result(result);
107 res.jsonValue["data"]["description"] = desc;
108 res.jsonValue["message"] = msg;
109 res.jsonValue["status"] = "error";
110 }
111
introspectObjects(const std::string & processName,const std::string & objectPath,const std::shared_ptr<bmcweb::AsyncResp> & transaction)112 inline void introspectObjects(
113 const std::string& processName, const std::string& objectPath,
114 const std::shared_ptr<bmcweb::AsyncResp>& transaction)
115 {
116 if (transaction->res.jsonValue.is_null())
117 {
118 transaction->res.jsonValue["status"] = "ok";
119 transaction->res.jsonValue["bus_name"] = processName;
120 transaction->res.jsonValue["objects"] = nlohmann::json::array();
121 }
122
123 crow::connections::systemBus->async_method_call(
124 [transaction, processName{std::string(processName)},
125 objectPath{std::string(objectPath)}](
126 const boost::system::error_code& ec,
127 const std::string& introspectXml) {
128 if (ec)
129 {
130 BMCWEB_LOG_ERROR(
131 "Introspect call failed with error: {} on process: {} path: {}",
132 ec.message(), processName, objectPath);
133 return;
134 }
135 nlohmann::json::object_t object;
136 object["path"] = objectPath;
137
138 transaction->res.jsonValue["objects"].emplace_back(
139 std::move(object));
140
141 tinyxml2::XMLDocument doc;
142
143 doc.Parse(introspectXml.c_str());
144 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
145 if (pRoot == nullptr)
146 {
147 BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
148 processName, objectPath);
149 }
150 else
151 {
152 tinyxml2::XMLElement* node = pRoot->FirstChildElement("node");
153 while (node != nullptr)
154 {
155 const char* childPath = node->Attribute("name");
156 if (childPath != nullptr)
157 {
158 std::string newpath;
159 if (objectPath != "/")
160 {
161 newpath += objectPath;
162 }
163 newpath += std::string("/") + childPath;
164 // introspect the subobjects as well
165 introspectObjects(processName, newpath, transaction);
166 }
167
168 node = node->NextSiblingElement("node");
169 }
170 }
171 },
172 processName, objectPath, "org.freedesktop.DBus.Introspectable",
173 "Introspect");
174 }
175
getPropertiesForEnumerate(const std::string & objectPath,const std::string & service,const std::string & interface,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)176 inline void getPropertiesForEnumerate(
177 const std::string& objectPath, const std::string& service,
178 const std::string& interface,
179 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
180 {
181 BMCWEB_LOG_DEBUG("getPropertiesForEnumerate {} {} {}", objectPath, service,
182 interface);
183
184 sdbusplus::asio::getAllProperties(
185 *crow::connections::systemBus, service, objectPath, interface,
186 [asyncResp, objectPath, service,
187 interface](const boost::system::error_code& ec,
188 const dbus::utility::DBusPropertiesMap& propertiesList) {
189 if (ec)
190 {
191 BMCWEB_LOG_ERROR(
192 "GetAll on path {} iface {} service {} failed with code {}",
193 objectPath, interface, service, ec);
194 return;
195 }
196
197 nlohmann::json& dataJson = asyncResp->res.jsonValue["data"];
198 nlohmann::json& objectJson = dataJson[objectPath];
199 if (objectJson.is_null())
200 {
201 objectJson = nlohmann::json::object();
202 }
203
204 for (const auto& [name, value] : propertiesList)
205 {
206 nlohmann::json& propertyJson = objectJson[name];
207 std::visit(
208 [&propertyJson](auto&& val) {
209 if constexpr (std::is_same_v<
210 std::decay_t<decltype(val)>,
211 sdbusplus::message::unix_fd>)
212 {
213 propertyJson = val.fd;
214 }
215 else
216 {
217 propertyJson = val;
218 }
219 },
220 value);
221 }
222 });
223 }
224
225 // Find any results that weren't picked up by ObjectManagers, to be
226 // called after all ObjectManagers are searched for and called.
findRemainingObjectsForEnumerate(const std::string & objectPath,const std::shared_ptr<dbus::utility::MapperGetSubTreeResponse> & subtree,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)227 inline void findRemainingObjectsForEnumerate(
228 const std::string& objectPath,
229 const std::shared_ptr<dbus::utility::MapperGetSubTreeResponse>& subtree,
230 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
231 {
232 BMCWEB_LOG_DEBUG("findRemainingObjectsForEnumerate");
233 const nlohmann::json& dataJson = asyncResp->res.jsonValue["data"];
234
235 for (const auto& [path, interface_map] : *subtree)
236 {
237 if (path == objectPath)
238 {
239 // An enumerate does not return the target path's properties
240 continue;
241 }
242 if (dataJson.find(path) == dataJson.end())
243 {
244 for (const auto& [service, interfaces] : interface_map)
245 {
246 for (const auto& interface : interfaces)
247 {
248 if (!interface.starts_with("org.freedesktop.DBus"))
249 {
250 getPropertiesForEnumerate(path, service, interface,
251 asyncResp);
252 }
253 }
254 }
255 }
256 }
257 }
258
259 struct InProgressEnumerateData
260 {
InProgressEnumerateDatacrow::openbmc_mapper::InProgressEnumerateData261 InProgressEnumerateData(
262 const std::string& objectPathIn,
263 const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
264 objectPath(objectPathIn), asyncResp(asyncRespIn)
265 {}
266
~InProgressEnumerateDatacrow::openbmc_mapper::InProgressEnumerateData267 ~InProgressEnumerateData()
268 {
269 try
270 {
271 findRemainingObjectsForEnumerate(objectPath, subtree, asyncResp);
272 }
273 catch (...)
274 {
275 BMCWEB_LOG_CRITICAL(
276 "findRemainingObjectsForEnumerate threw exception");
277 }
278 }
279
280 InProgressEnumerateData(const InProgressEnumerateData&) = delete;
281 InProgressEnumerateData(InProgressEnumerateData&&) = delete;
282 InProgressEnumerateData& operator=(const InProgressEnumerateData&) = delete;
283 InProgressEnumerateData& operator=(InProgressEnumerateData&&) = delete;
284 const std::string objectPath;
285 std::shared_ptr<dbus::utility::MapperGetSubTreeResponse> subtree;
286 std::shared_ptr<bmcweb::AsyncResp> asyncResp;
287 };
288
getManagedObjectsForEnumerate(const std::string & objectName,const std::string & objectManagerPath,const std::string & connectionName,const std::shared_ptr<InProgressEnumerateData> & transaction)289 inline void getManagedObjectsForEnumerate(
290 const std::string& objectName, const std::string& objectManagerPath,
291 const std::string& connectionName,
292 const std::shared_ptr<InProgressEnumerateData>& transaction)
293 {
294 BMCWEB_LOG_DEBUG(
295 "getManagedObjectsForEnumerate {} object_manager_path {} connection_name {}",
296 objectName, objectManagerPath, connectionName);
297 sdbusplus::message::object_path path(objectManagerPath);
298 dbus::utility::getManagedObjects(
299 connectionName, path,
300 [transaction, objectName,
301 connectionName](const boost::system::error_code& ec,
302 const dbus::utility::ManagedObjectType& objects) {
303 if (ec)
304 {
305 BMCWEB_LOG_ERROR(
306 "GetManagedObjects on path {} on connection {} failed with code {}",
307 objectName, connectionName, ec);
308 return;
309 }
310
311 nlohmann::json& dataJson =
312 transaction->asyncResp->res.jsonValue["data"];
313
314 for (const auto& objectPath : objects)
315 {
316 if (objectPath.first.str.starts_with(objectName))
317 {
318 BMCWEB_LOG_DEBUG("Reading object {}", objectPath.first.str);
319 nlohmann::json& objectJson = dataJson[objectPath.first.str];
320 if (objectJson.is_null())
321 {
322 objectJson = nlohmann::json::object();
323 }
324 for (const auto& interface : objectPath.second)
325 {
326 for (const auto& property : interface.second)
327 {
328 nlohmann::json& propertyJson =
329 objectJson[property.first];
330 std::visit(
331 [&propertyJson](auto&& val) {
332 if constexpr (
333 std::is_same_v<
334 std::decay_t<decltype(val)>,
335 sdbusplus::message::unix_fd>)
336 {
337 propertyJson = val.fd;
338 }
339 else
340 {
341 propertyJson = val;
342 }
343 },
344 property.second);
345 }
346 }
347 }
348 for (const auto& interface : objectPath.second)
349 {
350 if (interface.first == "org.freedesktop.DBus.ObjectManager")
351 {
352 getManagedObjectsForEnumerate(
353 objectPath.first.str, objectPath.first.str,
354 connectionName, transaction);
355 }
356 }
357 }
358 });
359 }
360
findObjectManagerPathForEnumerate(const std::string & objectName,const std::string & connectionName,const std::shared_ptr<InProgressEnumerateData> & transaction)361 inline void findObjectManagerPathForEnumerate(
362 const std::string& objectName, const std::string& connectionName,
363 const std::shared_ptr<InProgressEnumerateData>& transaction)
364 {
365 BMCWEB_LOG_DEBUG("Finding objectmanager for path {} on connection:{}",
366 objectName, connectionName);
367 crow::connections::systemBus->async_method_call(
368 [transaction, objectName, connectionName](
369 const boost::system::error_code& ec,
370 const dbus::utility::MapperGetAncestorsResponse& objects) {
371 if (ec)
372 {
373 BMCWEB_LOG_ERROR("GetAncestors on path {} failed with code {}",
374 objectName, ec);
375 return;
376 }
377
378 for (const auto& pathGroup : objects)
379 {
380 for (const auto& connectionGroup : pathGroup.second)
381 {
382 if (connectionGroup.first == connectionName)
383 {
384 // Found the object manager path for this resource.
385 getManagedObjectsForEnumerate(
386 objectName, pathGroup.first, connectionName,
387 transaction);
388 return;
389 }
390 }
391 }
392 },
393 "xyz.openbmc_project.ObjectMapper",
394 "/xyz/openbmc_project/object_mapper",
395 "xyz.openbmc_project.ObjectMapper", "GetAncestors", objectName,
396 std::array<const char*, 1>{"org.freedesktop.DBus.ObjectManager"});
397 }
398
399 // Uses GetObject to add the object info about the target /enumerate path to
400 // the results of GetSubTree, as GetSubTree will not return info for the
401 // target path, and then continues on enumerating the rest of the tree.
getObjectAndEnumerate(const std::shared_ptr<InProgressEnumerateData> & transaction)402 inline void getObjectAndEnumerate(
403 const std::shared_ptr<InProgressEnumerateData>& transaction)
404 {
405 dbus::utility::getDbusObject(
406 transaction->objectPath, {},
407 [transaction](const boost::system::error_code& ec,
408 const dbus::utility::MapperGetObject& objects) {
409 if (ec)
410 {
411 BMCWEB_LOG_ERROR("GetObject for path {} failed with code {}",
412 transaction->objectPath, ec);
413 return;
414 }
415
416 BMCWEB_LOG_DEBUG("GetObject for {} has {} entries",
417 transaction->objectPath, objects.size());
418 if (!objects.empty())
419 {
420 transaction->subtree->emplace_back(transaction->objectPath,
421 objects);
422 }
423
424 // Map indicating connection name, and the path where the object
425 // manager exists
426 boost::container::flat_map<
427 std::string, std::string, std::less<>,
428 std::vector<std::pair<std::string, std::string>>>
429 connections;
430
431 for (const auto& object : *(transaction->subtree))
432 {
433 for (const auto& connection : object.second)
434 {
435 for (const auto& interface : connection.second)
436 {
437 BMCWEB_LOG_DEBUG("{} has interface {}",
438 connection.first, interface);
439 if (interface == "org.freedesktop.DBus.ObjectManager")
440 {
441 BMCWEB_LOG_DEBUG("found object manager path {}",
442 object.first);
443 connections[connection.first] = object.first;
444 }
445 }
446 }
447 }
448 BMCWEB_LOG_DEBUG("Got {} connections", connections.size());
449
450 for (const auto& connection : connections)
451 {
452 // If we already know where the object manager is, we don't
453 // need to search for it, we can call directly in to
454 // getManagedObjects
455 if (!connection.second.empty())
456 {
457 getManagedObjectsForEnumerate(
458 transaction->objectPath, connection.second,
459 connection.first, transaction);
460 }
461 else
462 {
463 // otherwise we need to find the object manager path
464 // before we can continue
465 findObjectManagerPathForEnumerate(
466 transaction->objectPath, connection.first, transaction);
467 }
468 }
469 });
470 }
471
472 // Structure for storing data on an in progress action
473 struct InProgressActionData
474 {
InProgressActionDatacrow::openbmc_mapper::InProgressActionData475 explicit InProgressActionData(
476 const std::shared_ptr<bmcweb::AsyncResp>& res) : asyncResp(res)
477 {}
~InProgressActionDatacrow::openbmc_mapper::InProgressActionData478 ~InProgressActionData()
479 {
480 // Methods could have been called across different owners
481 // and interfaces, where some calls failed and some passed.
482 //
483 // The rules for this are:
484 // * if no method was called - error
485 // * if a method failed and none passed - error
486 // (converse: if at least one method passed - OK)
487 // * for the method output:
488 // * if output processing didn't fail, return the data
489
490 // Only deal with method returns if nothing failed earlier
491 if (asyncResp->res.result() == boost::beast::http::status::ok)
492 {
493 if (!methodPassed)
494 {
495 if (!methodFailed)
496 {
497 setErrorResponse(asyncResp->res,
498 boost::beast::http::status::not_found,
499 methodNotFoundDesc, notFoundMsg);
500 }
501 }
502 else
503 {
504 if (outputFailed)
505 {
506 setErrorResponse(
507 asyncResp->res,
508 boost::beast::http::status::internal_server_error,
509 "Method output failure", methodOutputFailedMsg);
510 }
511 else
512 {
513 asyncResp->res.jsonValue["status"] = "ok";
514 asyncResp->res.jsonValue["message"] = "200 OK";
515 asyncResp->res.jsonValue["data"] = methodResponse;
516 }
517 }
518 }
519 }
520 InProgressActionData(const InProgressActionData&) = delete;
521 InProgressActionData(InProgressActionData&&) = delete;
522 InProgressActionData& operator=(const InProgressActionData&) = delete;
523 InProgressActionData& operator=(InProgressActionData&&) = delete;
524
setErrorStatuscrow::openbmc_mapper::InProgressActionData525 void setErrorStatus(const std::string& desc)
526 {
527 setErrorResponse(asyncResp->res,
528 boost::beast::http::status::bad_request, desc,
529 badReqMsg);
530 }
531 std::shared_ptr<bmcweb::AsyncResp> asyncResp;
532 std::string path;
533 std::string methodName;
534 std::string interfaceName;
535 bool methodPassed = false;
536 bool methodFailed = false;
537 bool outputFailed = false;
538 bool convertedToArray = false;
539 nlohmann::json methodResponse;
540 nlohmann::json arguments;
541 };
542
dbusArgSplit(const std::string & string)543 inline std::vector<std::string> dbusArgSplit(const std::string& string)
544 {
545 std::vector<std::string> ret;
546 if (string.empty())
547 {
548 return ret;
549 }
550 ret.emplace_back("");
551 int containerDepth = 0;
552
553 for (std::string::const_iterator character = string.begin();
554 character != string.end(); character++)
555 {
556 ret.back() += *character;
557 switch (*character)
558 {
559 case ('a'):
560 break;
561 case ('('):
562 case ('{'):
563 containerDepth++;
564 break;
565 case ('}'):
566 case (')'):
567 containerDepth--;
568 if (containerDepth == 0)
569 {
570 if (character + 1 != string.end())
571 {
572 ret.emplace_back("");
573 }
574 }
575 break;
576 default:
577 if (containerDepth == 0)
578 {
579 if (character + 1 != string.end())
580 {
581 ret.emplace_back("");
582 }
583 }
584 break;
585 }
586 }
587
588 return ret;
589 }
590
convertJsonToDbus(sd_bus_message * m,const std::string & argType,const nlohmann::json & inputJson)591 inline int convertJsonToDbus(sd_bus_message* m, const std::string& argType,
592 const nlohmann::json& inputJson)
593 {
594 int r = 0;
595 BMCWEB_LOG_DEBUG("Converting {} to type: {}", inputJson, argType);
596 const std::vector<std::string> argTypes = dbusArgSplit(argType);
597
598 // Assume a single object for now.
599 const nlohmann::json* j = &inputJson;
600 nlohmann::json::const_iterator jIt = inputJson.begin();
601
602 for (const std::string& argCode : argTypes)
603 {
604 // If we are decoding multiple objects, grab the pointer to the
605 // iterator, and increment it for the next loop
606 if (argTypes.size() > 1)
607 {
608 if (jIt == inputJson.end())
609 {
610 return -2;
611 }
612 j = &*jIt;
613 jIt++;
614 }
615 const int64_t* intValue = j->get_ptr<const int64_t*>();
616 const std::string* stringValue = j->get_ptr<const std::string*>();
617 const double* doubleValue = j->get_ptr<const double*>();
618 const bool* b = j->get_ptr<const bool*>();
619 int64_t v = 0;
620 double d = 0.0;
621
622 // Do some basic type conversions that make sense. uint can be
623 // converted to int. int and uint can be converted to double
624 if (intValue == nullptr)
625 {
626 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
627 if (uintValue != nullptr)
628 {
629 v = static_cast<int64_t>(*uintValue);
630 intValue = &v;
631 }
632 }
633 if (doubleValue == nullptr)
634 {
635 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
636 if (uintValue != nullptr)
637 {
638 d = static_cast<double>(*uintValue);
639 doubleValue = &d;
640 }
641 }
642 if (doubleValue == nullptr)
643 {
644 if (intValue != nullptr)
645 {
646 d = static_cast<double>(*intValue);
647 doubleValue = &d;
648 }
649 }
650
651 if (argCode == "s")
652 {
653 if (stringValue == nullptr)
654 {
655 return -1;
656 }
657 r = sd_bus_message_append_basic(
658 m, argCode[0], static_cast<const void*>(stringValue->data()));
659 if (r < 0)
660 {
661 return r;
662 }
663 }
664 else if (argCode == "i")
665 {
666 if (intValue == nullptr)
667 {
668 return -1;
669 }
670 if ((*intValue < std::numeric_limits<int32_t>::lowest()) ||
671 (*intValue > std::numeric_limits<int32_t>::max()))
672 {
673 return -ERANGE;
674 }
675 int32_t i = static_cast<int32_t>(*intValue);
676 r = sd_bus_message_append_basic(m, argCode[0], &i);
677 if (r < 0)
678 {
679 return r;
680 }
681 }
682 else if (argCode == "b")
683 {
684 // lots of ways bool could be represented here. Try them all
685 int boolInt = 0;
686 if (intValue != nullptr)
687 {
688 if (*intValue == 1)
689 {
690 boolInt = 1;
691 }
692 else if (*intValue == 0)
693 {
694 boolInt = 0;
695 }
696 else
697 {
698 return -ERANGE;
699 }
700 }
701 else if (b != nullptr)
702 {
703 boolInt = *b ? 1 : 0;
704 }
705 else if (stringValue != nullptr)
706 {
707 if (!stringValue->empty())
708 {
709 if (stringValue->front() == 't' ||
710 stringValue->front() == 'T')
711 {
712 boolInt = 1;
713 }
714 }
715 }
716 else
717 {
718 return -1;
719 }
720 r = sd_bus_message_append_basic(m, argCode[0], &boolInt);
721 if (r < 0)
722 {
723 return r;
724 }
725 }
726 else if (argCode == "n")
727 {
728 if (intValue == nullptr)
729 {
730 return -1;
731 }
732 if ((*intValue < std::numeric_limits<int16_t>::lowest()) ||
733 (*intValue > std::numeric_limits<int16_t>::max()))
734 {
735 return -ERANGE;
736 }
737 int16_t n = static_cast<int16_t>(*intValue);
738 r = sd_bus_message_append_basic(m, argCode[0], &n);
739 if (r < 0)
740 {
741 return r;
742 }
743 }
744 else if (argCode == "x")
745 {
746 if (intValue == nullptr)
747 {
748 return -1;
749 }
750 r = sd_bus_message_append_basic(m, argCode[0], intValue);
751 if (r < 0)
752 {
753 return r;
754 }
755 }
756 else if (argCode == "y")
757 {
758 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
759 if (uintValue == nullptr)
760 {
761 return -1;
762 }
763 if (*uintValue > std::numeric_limits<uint8_t>::max())
764 {
765 return -ERANGE;
766 }
767 uint8_t y = static_cast<uint8_t>(*uintValue);
768 r = sd_bus_message_append_basic(m, argCode[0], &y);
769 }
770 else if (argCode == "q")
771 {
772 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
773 if (uintValue == nullptr)
774 {
775 return -1;
776 }
777 if (*uintValue > std::numeric_limits<uint16_t>::max())
778 {
779 return -ERANGE;
780 }
781 uint16_t q = static_cast<uint16_t>(*uintValue);
782 r = sd_bus_message_append_basic(m, argCode[0], &q);
783 }
784 else if (argCode == "u")
785 {
786 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
787 if (uintValue == nullptr)
788 {
789 return -1;
790 }
791 if (*uintValue > std::numeric_limits<uint32_t>::max())
792 {
793 return -ERANGE;
794 }
795 uint32_t u = static_cast<uint32_t>(*uintValue);
796 r = sd_bus_message_append_basic(m, argCode[0], &u);
797 }
798 else if (argCode == "t")
799 {
800 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
801 if (uintValue == nullptr)
802 {
803 return -1;
804 }
805 r = sd_bus_message_append_basic(m, argCode[0], uintValue);
806 }
807 else if (argCode == "d")
808 {
809 if (doubleValue == nullptr)
810 {
811 return -1;
812 }
813 if ((*doubleValue < std::numeric_limits<double>::lowest()) ||
814 (*doubleValue > std::numeric_limits<double>::max()))
815 {
816 return -ERANGE;
817 }
818 r = sd_bus_message_append_basic(m, argCode[0], doubleValue);
819 if (r < 0)
820 {
821 return r;
822 }
823 }
824 else if (argCode.starts_with("a"))
825 {
826 std::string containedType = argCode.substr(1);
827 r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
828 containedType.c_str());
829 if (r < 0)
830 {
831 return r;
832 }
833
834 for (const auto& it : *j)
835 {
836 r = convertJsonToDbus(m, containedType, it);
837 if (r < 0)
838 {
839 return r;
840 }
841 }
842 sd_bus_message_close_container(m);
843 }
844 else if (argCode.starts_with("v"))
845 {
846 std::string containedType = argCode.substr(1);
847 BMCWEB_LOG_DEBUG("variant type: {} appending variant of type: {}",
848 argCode, containedType);
849 r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
850 containedType.c_str());
851 if (r < 0)
852 {
853 return r;
854 }
855
856 r = convertJsonToDbus(m, containedType, inputJson);
857 if (r < 0)
858 {
859 return r;
860 }
861
862 r = sd_bus_message_close_container(m);
863 if (r < 0)
864 {
865 return r;
866 }
867 }
868 else if (argCode.starts_with("(") && argCode.ends_with(")"))
869 {
870 std::string containedType = argCode.substr(1, argCode.size() - 2);
871 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
872 containedType.c_str());
873 if (r < 0)
874 {
875 return r;
876 }
877
878 nlohmann::json::const_iterator it = j->begin();
879 for (const std::string& argCode2 : dbusArgSplit(containedType))
880 {
881 if (it == j->end())
882 {
883 return -1;
884 }
885 r = convertJsonToDbus(m, argCode2, *it);
886 if (r < 0)
887 {
888 return r;
889 }
890 it++;
891 }
892 r = sd_bus_message_close_container(m);
893 }
894 else if (argCode.starts_with("{") && argCode.ends_with("}"))
895 {
896 std::string containedType = argCode.substr(1, argCode.size() - 2);
897 r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
898 containedType.c_str());
899 if (r < 0)
900 {
901 return r;
902 }
903
904 std::vector<std::string> codes = dbusArgSplit(containedType);
905 if (codes.size() != 2)
906 {
907 return -1;
908 }
909 const std::string& keyType = codes[0];
910 const std::string& valueType = codes[1];
911 const nlohmann::json::object_t* arr =
912 j->get_ptr<const nlohmann::json::object_t*>();
913 if (arr == nullptr)
914 {
915 return -1;
916 }
917 for (const auto& it : *arr)
918 {
919 r = convertJsonToDbus(m, keyType, it.first);
920 if (r < 0)
921 {
922 return r;
923 }
924
925 r = convertJsonToDbus(m, valueType, it.second);
926 if (r < 0)
927 {
928 return r;
929 }
930 }
931 r = sd_bus_message_close_container(m);
932 }
933 else
934 {
935 return -2;
936 }
937 if (r < 0)
938 {
939 return r;
940 }
941
942 if (argTypes.size() > 1)
943 {
944 jIt++;
945 }
946 }
947
948 return r;
949 }
950
951 template <typename T>
readMessageItem(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)952 int readMessageItem(const std::string& typeCode, sdbusplus::message_t& m,
953 nlohmann::json& data)
954 {
955 T value;
956 // When T == char*, this warning fires. Unclear how to resolve
957 // Given that sd-bus takes a void pointer to a char*, and that's
958 // Not something we can fix.
959 // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
960 int r = sd_bus_message_read_basic(m.get(), typeCode.front(), &value);
961 if (r < 0)
962 {
963 BMCWEB_LOG_ERROR("sd_bus_message_read_basic on type {} failed!",
964 typeCode);
965 return r;
966 }
967
968 data = value;
969 return 0;
970 }
971
972 int convertDBusToJSON(const std::string& returnType, sdbusplus::message_t& m,
973 nlohmann::json& response);
974
readDictEntryFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & object)975 inline int readDictEntryFromMessage(const std::string& typeCode,
976 sdbusplus::message_t& m,
977 nlohmann::json& object)
978 {
979 std::vector<std::string> types = dbusArgSplit(typeCode);
980 if (types.size() != 2)
981 {
982 BMCWEB_LOG_ERROR("wrong number contained types in dictionary: {}",
983 types.size());
984 return -1;
985 }
986
987 int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_DICT_ENTRY,
988 typeCode.c_str());
989 if (r < 0)
990 {
991 BMCWEB_LOG_ERROR("sd_bus_message_enter_container with rc {}", r);
992 return r;
993 }
994
995 nlohmann::json key;
996 r = convertDBusToJSON(types[0], m, key);
997 if (r < 0)
998 {
999 return r;
1000 }
1001
1002 const std::string* keyPtr = key.get_ptr<const std::string*>();
1003 if (keyPtr == nullptr)
1004 {
1005 // json doesn't support non-string keys. If we hit this condition,
1006 // convert the result to a string so we can proceed
1007 key = key.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
1008 keyPtr = key.get_ptr<const std::string*>();
1009 // in theory this can't fail now, but lets be paranoid about it
1010 // anyway
1011 if (keyPtr == nullptr)
1012 {
1013 return -1;
1014 }
1015 }
1016 nlohmann::json& value = object[*keyPtr];
1017
1018 r = convertDBusToJSON(types[1], m, value);
1019 if (r < 0)
1020 {
1021 return r;
1022 }
1023
1024 r = sd_bus_message_exit_container(m.get());
1025 if (r < 0)
1026 {
1027 BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
1028 return r;
1029 }
1030
1031 return 0;
1032 }
1033
readArrayFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)1034 inline int readArrayFromMessage(const std::string& typeCode,
1035 sdbusplus::message_t& m, nlohmann::json& data)
1036 {
1037 if (typeCode.size() < 2)
1038 {
1039 BMCWEB_LOG_ERROR("Type code {} too small for an array", typeCode);
1040 return -1;
1041 }
1042
1043 std::string containedType = typeCode.substr(1);
1044
1045 int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_ARRAY,
1046 containedType.c_str());
1047 if (r < 0)
1048 {
1049 BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1050 return r;
1051 }
1052
1053 bool dict = containedType.starts_with("{") && containedType.ends_with("}");
1054
1055 if (dict)
1056 {
1057 // Remove the { }
1058 containedType = containedType.substr(1, containedType.size() - 2);
1059 data = nlohmann::json::object();
1060 }
1061 else
1062 {
1063 data = nlohmann::json::array();
1064 }
1065
1066 while (true)
1067 {
1068 r = sd_bus_message_at_end(m.get(), 0);
1069 if (r < 0)
1070 {
1071 BMCWEB_LOG_ERROR("sd_bus_message_at_end failed");
1072 return r;
1073 }
1074
1075 if (r > 0)
1076 {
1077 break;
1078 }
1079
1080 // Dictionaries are only ever seen in an array
1081 if (dict)
1082 {
1083 r = readDictEntryFromMessage(containedType, m, data);
1084 if (r < 0)
1085 {
1086 return r;
1087 }
1088 }
1089 else
1090 {
1091 data.push_back(nlohmann::json());
1092
1093 r = convertDBusToJSON(containedType, m, data.back());
1094 if (r < 0)
1095 {
1096 return r;
1097 }
1098 }
1099 }
1100
1101 r = sd_bus_message_exit_container(m.get());
1102 if (r < 0)
1103 {
1104 BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
1105 return r;
1106 }
1107
1108 return 0;
1109 }
1110
readStructFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)1111 inline int readStructFromMessage(const std::string& typeCode,
1112 sdbusplus::message_t& m, nlohmann::json& data)
1113 {
1114 if (typeCode.size() < 3)
1115 {
1116 BMCWEB_LOG_ERROR("Type code {} too small for a struct", typeCode);
1117 return -1;
1118 }
1119
1120 std::string containedTypes = typeCode.substr(1, typeCode.size() - 2);
1121 std::vector<std::string> types = dbusArgSplit(containedTypes);
1122
1123 int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_STRUCT,
1124 containedTypes.c_str());
1125 if (r < 0)
1126 {
1127 BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1128 return r;
1129 }
1130
1131 for (const std::string& type : types)
1132 {
1133 data.push_back(nlohmann::json());
1134 r = convertDBusToJSON(type, m, data.back());
1135 if (r < 0)
1136 {
1137 return r;
1138 }
1139 }
1140
1141 r = sd_bus_message_exit_container(m.get());
1142 if (r < 0)
1143 {
1144 BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
1145 return r;
1146 }
1147 return 0;
1148 }
1149
readVariantFromMessage(sdbusplus::message_t & m,nlohmann::json & data)1150 inline int readVariantFromMessage(sdbusplus::message_t& m, nlohmann::json& data)
1151 {
1152 const char* containerType = nullptr;
1153 int r = sd_bus_message_peek_type(m.get(), nullptr, &containerType);
1154 if (r < 0)
1155 {
1156 BMCWEB_LOG_ERROR("sd_bus_message_peek_type failed");
1157 return r;
1158 }
1159
1160 r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_VARIANT,
1161 containerType);
1162 if (r < 0)
1163 {
1164 BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1165 return r;
1166 }
1167
1168 r = convertDBusToJSON(containerType, m, data);
1169 if (r < 0)
1170 {
1171 return r;
1172 }
1173
1174 r = sd_bus_message_exit_container(m.get());
1175 if (r < 0)
1176 {
1177 BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed");
1178 return r;
1179 }
1180
1181 return 0;
1182 }
1183
convertDBusToJSON(const std::string & returnType,sdbusplus::message_t & m,nlohmann::json & response)1184 inline int convertDBusToJSON(const std::string& returnType,
1185 sdbusplus::message_t& m, nlohmann::json& response)
1186 {
1187 int r = 0;
1188 const std::vector<std::string> returnTypes = dbusArgSplit(returnType);
1189
1190 for (const std::string& typeCode : returnTypes)
1191 {
1192 nlohmann::json* thisElement = &response;
1193 if (returnTypes.size() > 1)
1194 {
1195 response.push_back(nlohmann::json{});
1196 thisElement = &response.back();
1197 }
1198
1199 if (typeCode == "s" || typeCode == "g" || typeCode == "o")
1200 {
1201 r = readMessageItem<char*>(typeCode, m, *thisElement);
1202 if (r < 0)
1203 {
1204 return r;
1205 }
1206 }
1207 else if (typeCode == "b")
1208 {
1209 r = readMessageItem<int>(typeCode, m, *thisElement);
1210 if (r < 0)
1211 {
1212 return r;
1213 }
1214
1215 *thisElement = static_cast<bool>(thisElement->get<int>());
1216 }
1217 else if (typeCode == "u")
1218 {
1219 r = readMessageItem<uint32_t>(typeCode, m, *thisElement);
1220 if (r < 0)
1221 {
1222 return r;
1223 }
1224 }
1225 else if (typeCode == "i")
1226 {
1227 r = readMessageItem<int32_t>(typeCode, m, *thisElement);
1228 if (r < 0)
1229 {
1230 return r;
1231 }
1232 }
1233 else if (typeCode == "x")
1234 {
1235 r = readMessageItem<int64_t>(typeCode, m, *thisElement);
1236 if (r < 0)
1237 {
1238 return r;
1239 }
1240 }
1241 else if (typeCode == "t")
1242 {
1243 r = readMessageItem<uint64_t>(typeCode, m, *thisElement);
1244 if (r < 0)
1245 {
1246 return r;
1247 }
1248 }
1249 else if (typeCode == "n")
1250 {
1251 r = readMessageItem<int16_t>(typeCode, m, *thisElement);
1252 if (r < 0)
1253 {
1254 return r;
1255 }
1256 }
1257 else if (typeCode == "q")
1258 {
1259 r = readMessageItem<uint16_t>(typeCode, m, *thisElement);
1260 if (r < 0)
1261 {
1262 return r;
1263 }
1264 }
1265 else if (typeCode == "y")
1266 {
1267 r = readMessageItem<uint8_t>(typeCode, m, *thisElement);
1268 if (r < 0)
1269 {
1270 return r;
1271 }
1272 }
1273 else if (typeCode == "d")
1274 {
1275 r = readMessageItem<double>(typeCode, m, *thisElement);
1276 if (r < 0)
1277 {
1278 return r;
1279 }
1280 }
1281 else if (typeCode == "h")
1282 {
1283 r = readMessageItem<int>(typeCode, m, *thisElement);
1284 if (r < 0)
1285 {
1286 return r;
1287 }
1288 }
1289 else if (typeCode.starts_with("a"))
1290 {
1291 r = readArrayFromMessage(typeCode, m, *thisElement);
1292 if (r < 0)
1293 {
1294 return r;
1295 }
1296 }
1297 else if (typeCode.starts_with("(") && typeCode.ends_with(")"))
1298 {
1299 r = readStructFromMessage(typeCode, m, *thisElement);
1300 if (r < 0)
1301 {
1302 return r;
1303 }
1304 }
1305 else if (typeCode.starts_with("v"))
1306 {
1307 r = readVariantFromMessage(m, *thisElement);
1308 if (r < 0)
1309 {
1310 return r;
1311 }
1312 }
1313 else
1314 {
1315 BMCWEB_LOG_ERROR("Invalid D-Bus signature type {}", typeCode);
1316 return -2;
1317 }
1318 }
1319
1320 return 0;
1321 }
1322
handleMethodResponse(const std::shared_ptr<InProgressActionData> & transaction,sdbusplus::message_t & m,const std::string & returnType)1323 inline void handleMethodResponse(
1324 const std::shared_ptr<InProgressActionData>& transaction,
1325 sdbusplus::message_t& m, const std::string& returnType)
1326 {
1327 nlohmann::json data;
1328
1329 int r = convertDBusToJSON(returnType, m, data);
1330 if (r < 0)
1331 {
1332 transaction->outputFailed = true;
1333 return;
1334 }
1335
1336 if (data.is_null())
1337 {
1338 return;
1339 }
1340
1341 if (transaction->methodResponse.is_null())
1342 {
1343 transaction->methodResponse = std::move(data);
1344 return;
1345 }
1346
1347 // If they're both dictionaries or arrays, merge into one.
1348 // Otherwise, make the results an array with every result
1349 // an entry. Could also just fail in that case, but it
1350 // seems better to get the data back somehow.
1351 nlohmann::json::object_t* dataobj =
1352 data.get_ptr<nlohmann::json::object_t*>();
1353 if (transaction->methodResponse.is_object() && dataobj != nullptr)
1354 {
1355 for (auto& obj : *dataobj)
1356 {
1357 // Note: Will overwrite the data for a duplicate key
1358 transaction->methodResponse.emplace(obj.first,
1359 std::move(obj.second));
1360 }
1361 return;
1362 }
1363
1364 nlohmann::json::array_t* dataarr = data.get_ptr<nlohmann::json::array_t*>();
1365 if (transaction->methodResponse.is_array() && dataarr != nullptr)
1366 {
1367 for (auto& obj : *dataarr)
1368 {
1369 transaction->methodResponse.emplace_back(std::move(obj));
1370 }
1371 return;
1372 }
1373
1374 if (!transaction->convertedToArray)
1375 {
1376 // They are different types. May as well turn them into an array
1377 nlohmann::json j = std::move(transaction->methodResponse);
1378 transaction->methodResponse = nlohmann::json::array();
1379 transaction->methodResponse.emplace_back(std::move(j));
1380 transaction->methodResponse.emplace_back(std::move(data));
1381 transaction->convertedToArray = true;
1382 }
1383 else
1384 {
1385 transaction->methodResponse.emplace_back(std::move(data));
1386 }
1387 }
1388
findActionOnInterface(const std::shared_ptr<InProgressActionData> & transaction,const std::string & connectionName)1389 inline void findActionOnInterface(
1390 const std::shared_ptr<InProgressActionData>& transaction,
1391 const std::string& connectionName)
1392 {
1393 BMCWEB_LOG_DEBUG("findActionOnInterface for connection {}", connectionName);
1394 crow::connections::systemBus->async_method_call(
1395 [transaction, connectionName{std::string(connectionName)}](
1396 const boost::system::error_code& ec,
1397 const std::string& introspectXml) {
1398 BMCWEB_LOG_DEBUG("got xml:\n {}", introspectXml);
1399 if (ec)
1400 {
1401 BMCWEB_LOG_ERROR(
1402 "Introspect call failed with error: {} on process: {}",
1403 ec.message(), connectionName);
1404 return;
1405 }
1406 tinyxml2::XMLDocument doc;
1407
1408 doc.Parse(introspectXml.data(), introspectXml.size());
1409 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
1410 if (pRoot == nullptr)
1411 {
1412 BMCWEB_LOG_ERROR("XML document failed to parse {}",
1413 connectionName);
1414 return;
1415 }
1416 tinyxml2::XMLElement* interfaceNode =
1417 pRoot->FirstChildElement("interface");
1418 while (interfaceNode != nullptr)
1419 {
1420 const char* thisInterfaceName =
1421 interfaceNode->Attribute("name");
1422 if (thisInterfaceName != nullptr)
1423 {
1424 if (!transaction->interfaceName.empty() &&
1425 (transaction->interfaceName != thisInterfaceName))
1426 {
1427 interfaceNode =
1428 interfaceNode->NextSiblingElement("interface");
1429 continue;
1430 }
1431
1432 tinyxml2::XMLElement* methodNode =
1433 interfaceNode->FirstChildElement("method");
1434 while (methodNode != nullptr)
1435 {
1436 const char* thisMethodName =
1437 methodNode->Attribute("name");
1438 BMCWEB_LOG_DEBUG("Found method: {}", thisMethodName);
1439 if (thisMethodName != nullptr &&
1440 thisMethodName == transaction->methodName)
1441 {
1442 BMCWEB_LOG_DEBUG(
1443 "Found method named {} on interface {}",
1444 thisMethodName, thisInterfaceName);
1445 sdbusplus::message_t m =
1446 crow::connections::systemBus->new_method_call(
1447 connectionName.c_str(),
1448 transaction->path.c_str(),
1449 thisInterfaceName,
1450 transaction->methodName.c_str());
1451
1452 tinyxml2::XMLElement* argumentNode =
1453 methodNode->FirstChildElement("arg");
1454
1455 std::string returnType;
1456
1457 // Find the output type
1458 while (argumentNode != nullptr)
1459 {
1460 const char* argDirection =
1461 argumentNode->Attribute("direction");
1462 const char* argType =
1463 argumentNode->Attribute("type");
1464 if (argDirection != nullptr &&
1465 argType != nullptr &&
1466 std::string(argDirection) == "out")
1467 {
1468 returnType = argType;
1469 break;
1470 }
1471 argumentNode =
1472 argumentNode->NextSiblingElement("arg");
1473 }
1474
1475 auto argIt = transaction->arguments.begin();
1476
1477 argumentNode = methodNode->FirstChildElement("arg");
1478
1479 while (argumentNode != nullptr)
1480 {
1481 const char* argDirection =
1482 argumentNode->Attribute("direction");
1483 const char* argType =
1484 argumentNode->Attribute("type");
1485 if (argDirection != nullptr &&
1486 argType != nullptr &&
1487 std::string(argDirection) == "in")
1488 {
1489 if (argIt == transaction->arguments.end())
1490 {
1491 transaction->setErrorStatus(
1492 "Invalid method args");
1493 return;
1494 }
1495 if (convertJsonToDbus(m.get(),
1496 std::string(argType),
1497 *argIt) < 0)
1498 {
1499 transaction->setErrorStatus(
1500 "Invalid method arg type");
1501 return;
1502 }
1503
1504 argIt++;
1505 }
1506 argumentNode =
1507 argumentNode->NextSiblingElement("arg");
1508 }
1509
1510 crow::connections::systemBus->async_send(
1511 m, [transaction, returnType](
1512 const boost::system::error_code& ec2,
1513 sdbusplus::message_t& m2) {
1514 if (ec2)
1515 {
1516 transaction->methodFailed = true;
1517 const sd_bus_error* e = m2.get_error();
1518
1519 if (e != nullptr)
1520 {
1521 setErrorResponse(
1522 transaction->asyncResp->res,
1523 boost::beast::http::status::
1524 bad_request,
1525 e->name, e->message);
1526 }
1527 else
1528 {
1529 setErrorResponse(
1530 transaction->asyncResp->res,
1531 boost::beast::http::status::
1532 bad_request,
1533 "Method call failed",
1534 methodFailedMsg);
1535 }
1536 return;
1537 }
1538 transaction->methodPassed = true;
1539
1540 handleMethodResponse(transaction, m2,
1541 returnType);
1542 });
1543 break;
1544 }
1545 methodNode = methodNode->NextSiblingElement("method");
1546 }
1547 }
1548 interfaceNode = interfaceNode->NextSiblingElement("interface");
1549 }
1550 },
1551 connectionName, transaction->path,
1552 "org.freedesktop.DBus.Introspectable", "Introspect");
1553 }
1554
handleAction(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,const std::string & methodName)1555 inline void handleAction(const crow::Request& req,
1556 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1557 const std::string& objectPath,
1558 const std::string& methodName)
1559 {
1560 BMCWEB_LOG_DEBUG("handleAction on path: {} and method {}", objectPath,
1561 methodName);
1562 nlohmann::json requestDbusData;
1563
1564 JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
1565 if (ret == JsonParseResult::BadContentType)
1566 {
1567 setErrorResponse(asyncResp->res,
1568 boost::beast::http::status::unsupported_media_type,
1569 invalidContentType, unsupportedMediaMsg);
1570 return;
1571 }
1572 if (ret != JsonParseResult::Success)
1573 {
1574 setErrorResponse(asyncResp->res,
1575 boost::beast::http::status::bad_request, noJsonDesc,
1576 badReqMsg);
1577 return;
1578 }
1579 nlohmann::json::iterator data = requestDbusData.find("data");
1580 if (data == requestDbusData.end())
1581 {
1582 setErrorResponse(asyncResp->res,
1583 boost::beast::http::status::bad_request, noJsonDesc,
1584 badReqMsg);
1585 return;
1586 }
1587
1588 if (!data->is_array())
1589 {
1590 setErrorResponse(asyncResp->res,
1591 boost::beast::http::status::bad_request, noJsonDesc,
1592 badReqMsg);
1593 return;
1594 }
1595 auto transaction = std::make_shared<InProgressActionData>(asyncResp);
1596
1597 transaction->path = objectPath;
1598 transaction->methodName = methodName;
1599 transaction->arguments = std::move(*data);
1600 dbus::utility::getDbusObject(
1601 objectPath, {},
1602 [transaction](
1603 const boost::system::error_code& ec,
1604 const std::vector<std::pair<std::string, std::vector<std::string>>>&
1605 interfaceNames) {
1606 if (ec || interfaceNames.empty())
1607 {
1608 BMCWEB_LOG_ERROR("Can't find object");
1609 setErrorResponse(transaction->asyncResp->res,
1610 boost::beast::http::status::not_found,
1611 notFoundDesc, notFoundMsg);
1612 return;
1613 }
1614
1615 BMCWEB_LOG_DEBUG("GetObject returned {} object(s)",
1616 interfaceNames.size());
1617
1618 for (const std::pair<std::string, std::vector<std::string>>&
1619 object : interfaceNames)
1620 {
1621 findActionOnInterface(transaction, object.first);
1622 }
1623 });
1624 }
1625
handleDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath)1626 inline void handleDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1627 const std::string& objectPath)
1628 {
1629 BMCWEB_LOG_DEBUG("handleDelete on path: {}", objectPath);
1630
1631 dbus::utility::getDbusObject(
1632 objectPath, {},
1633 [asyncResp, objectPath](
1634 const boost::system::error_code& ec,
1635 const std::vector<std::pair<std::string, std::vector<std::string>>>&
1636 interfaceNames) {
1637 if (ec || interfaceNames.empty())
1638 {
1639 BMCWEB_LOG_ERROR("Can't find object");
1640 setErrorResponse(asyncResp->res,
1641 boost::beast::http::status::method_not_allowed,
1642 methodNotAllowedDesc, methodNotAllowedMsg);
1643 return;
1644 }
1645
1646 auto transaction =
1647 std::make_shared<InProgressActionData>(asyncResp);
1648 transaction->path = objectPath;
1649 transaction->methodName = "Delete";
1650 transaction->interfaceName = "xyz.openbmc_project.Object.Delete";
1651
1652 for (const std::pair<std::string, std::vector<std::string>>&
1653 object : interfaceNames)
1654 {
1655 findActionOnInterface(transaction, object.first);
1656 }
1657 });
1658 }
1659
handleList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,int32_t depth=0)1660 inline void handleList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1661 const std::string& objectPath, int32_t depth = 0)
1662 {
1663 dbus::utility::getSubTreePaths(
1664 objectPath, depth, {},
1665 [asyncResp](
1666 const boost::system::error_code& ec,
1667 const dbus::utility::MapperGetSubTreePathsResponse& objectPaths) {
1668 if (ec)
1669 {
1670 setErrorResponse(asyncResp->res,
1671 boost::beast::http::status::not_found,
1672 notFoundDesc, notFoundMsg);
1673 }
1674 else
1675 {
1676 asyncResp->res.jsonValue["status"] = "ok";
1677 asyncResp->res.jsonValue["message"] = "200 OK";
1678 asyncResp->res.jsonValue["data"] = objectPaths;
1679 }
1680 });
1681 }
1682
handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath)1683 inline void handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1684 const std::string& objectPath)
1685 {
1686 BMCWEB_LOG_DEBUG("Doing enumerate on {}", objectPath);
1687
1688 asyncResp->res.jsonValue["message"] = "200 OK";
1689 asyncResp->res.jsonValue["status"] = "ok";
1690 asyncResp->res.jsonValue["data"] = nlohmann::json::object();
1691
1692 dbus::utility::getSubTree(
1693 objectPath, 0, {},
1694 [objectPath, asyncResp](
1695 const boost::system::error_code& ec,
1696 const dbus::utility::MapperGetSubTreeResponse& objectNames) {
1697 auto transaction = std::make_shared<InProgressEnumerateData>(
1698 objectPath, asyncResp);
1699
1700 transaction->subtree =
1701 std::make_shared<dbus::utility::MapperGetSubTreeResponse>(
1702 objectNames);
1703
1704 if (ec)
1705 {
1706 BMCWEB_LOG_ERROR("GetSubTree failed on {}",
1707 transaction->objectPath);
1708 setErrorResponse(transaction->asyncResp->res,
1709 boost::beast::http::status::not_found,
1710 notFoundDesc, notFoundMsg);
1711 return;
1712 }
1713
1714 // Add the data for the path passed in to the results
1715 // as if GetSubTree returned it, and continue on enumerating
1716 getObjectAndEnumerate(transaction);
1717 });
1718 }
1719
handleGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string & objectPath,std::string & destProperty)1720 inline void handleGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1721 std::string& objectPath, std::string& destProperty)
1722 {
1723 BMCWEB_LOG_DEBUG("handleGet: {} prop:{}", objectPath, destProperty);
1724 std::shared_ptr<std::string> propertyName =
1725 std::make_shared<std::string>(std::move(destProperty));
1726
1727 std::shared_ptr<std::string> path =
1728 std::make_shared<std::string>(std::move(objectPath));
1729
1730 dbus::utility::getDbusObject(
1731 *path, {},
1732 [asyncResp, path,
1733 propertyName](const boost::system::error_code& ec,
1734 const dbus::utility::MapperGetObject& objectNames) {
1735 if (ec || objectNames.empty())
1736 {
1737 setErrorResponse(asyncResp->res,
1738 boost::beast::http::status::not_found,
1739 notFoundDesc, notFoundMsg);
1740 return;
1741 }
1742 std::shared_ptr<nlohmann::json> response =
1743 std::make_shared<nlohmann::json>(nlohmann::json::object());
1744 // The mapper should never give us an empty interface names
1745 // list, but check anyway
1746 for (const std::pair<std::string, std::vector<std::string>>&
1747 connection : objectNames)
1748 {
1749 const std::vector<std::string>& interfaceNames =
1750 connection.second;
1751
1752 if (interfaceNames.empty())
1753 {
1754 setErrorResponse(asyncResp->res,
1755 boost::beast::http::status::not_found,
1756 notFoundDesc, notFoundMsg);
1757 return;
1758 }
1759
1760 for (const std::string& interface : interfaceNames)
1761 {
1762 sdbusplus::message_t m =
1763 crow::connections::systemBus->new_method_call(
1764 connection.first.c_str(), path->c_str(),
1765 "org.freedesktop.DBus.Properties", "GetAll");
1766 m.append(interface);
1767 crow::connections::systemBus->async_send(
1768 m, [asyncResp, response,
1769 propertyName](const boost::system::error_code& ec2,
1770 sdbusplus::message_t& msg) {
1771 if (ec2)
1772 {
1773 BMCWEB_LOG_ERROR("Bad dbus request error: {}",
1774 ec2);
1775 }
1776 else
1777 {
1778 nlohmann::json properties;
1779 int r =
1780 convertDBusToJSON("a{sv}", msg, properties);
1781 if (r < 0)
1782 {
1783 BMCWEB_LOG_ERROR(
1784 "convertDBusToJSON failed");
1785 }
1786 else
1787 {
1788 nlohmann::json::object_t* obj =
1789 properties.get_ptr<
1790 nlohmann::json::object_t*>();
1791 if (obj == nullptr)
1792 {
1793 return;
1794 }
1795 for (auto& prop : *obj)
1796 {
1797 // if property name is empty, or
1798 // matches our search query, add it
1799 // to the response json
1800
1801 if (propertyName->empty())
1802 {
1803 (*response)[prop.first] =
1804 std::move(prop.second);
1805 }
1806 else if (prop.first == *propertyName)
1807 {
1808 *response = std::move(prop.second);
1809 }
1810 }
1811 }
1812 }
1813 if (response.use_count() == 1)
1814 {
1815 if (!propertyName->empty() && response->empty())
1816 {
1817 setErrorResponse(
1818 asyncResp->res,
1819 boost::beast::http::status::not_found,
1820 propNotFoundDesc, notFoundMsg);
1821 }
1822 else
1823 {
1824 asyncResp->res.jsonValue["status"] = "ok";
1825 asyncResp->res.jsonValue["message"] =
1826 "200 OK";
1827 asyncResp->res.jsonValue["data"] =
1828 *response;
1829 }
1830 }
1831 });
1832 }
1833 }
1834 });
1835 }
1836
1837 struct AsyncPutRequest
1838 {
AsyncPutRequestcrow::openbmc_mapper::AsyncPutRequest1839 explicit AsyncPutRequest(const std::shared_ptr<bmcweb::AsyncResp>& resIn) :
1840 asyncResp(resIn)
1841 {}
~AsyncPutRequestcrow::openbmc_mapper::AsyncPutRequest1842 ~AsyncPutRequest()
1843 {
1844 if (asyncResp->res.jsonValue.empty())
1845 {
1846 setErrorResponse(asyncResp->res,
1847 boost::beast::http::status::forbidden,
1848 forbiddenMsg, forbiddenPropDesc);
1849 }
1850 }
1851
1852 AsyncPutRequest(const AsyncPutRequest&) = delete;
1853 AsyncPutRequest(AsyncPutRequest&&) = delete;
1854 AsyncPutRequest& operator=(const AsyncPutRequest&) = delete;
1855 AsyncPutRequest& operator=(AsyncPutRequest&&) = delete;
1856
setErrorStatuscrow::openbmc_mapper::AsyncPutRequest1857 void setErrorStatus(const std::string& desc)
1858 {
1859 setErrorResponse(asyncResp->res,
1860 boost::beast::http::status::internal_server_error,
1861 desc, badReqMsg);
1862 }
1863
1864 const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1865 std::string objectPath;
1866 std::string propertyName;
1867 nlohmann::json propertyValue;
1868 };
1869
handlePut(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,const std::string & destProperty)1870 inline void handlePut(const crow::Request& req,
1871 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1872 const std::string& objectPath,
1873 const std::string& destProperty)
1874 {
1875 if (destProperty.empty())
1876 {
1877 setErrorResponse(asyncResp->res, boost::beast::http::status::forbidden,
1878 forbiddenResDesc, forbiddenMsg);
1879 return;
1880 }
1881 nlohmann::json requestDbusData;
1882
1883 JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
1884 if (ret == JsonParseResult::BadContentType)
1885 {
1886 setErrorResponse(asyncResp->res,
1887 boost::beast::http::status::unsupported_media_type,
1888 invalidContentType, unsupportedMediaMsg);
1889 return;
1890 }
1891
1892 if (ret != JsonParseResult::Success)
1893 {
1894 setErrorResponse(asyncResp->res,
1895 boost::beast::http::status::bad_request, noJsonDesc,
1896 badReqMsg);
1897 return;
1898 }
1899
1900 auto propertyIt = requestDbusData.find("data");
1901 if (propertyIt == requestDbusData.end())
1902 {
1903 setErrorResponse(asyncResp->res,
1904 boost::beast::http::status::bad_request, noJsonDesc,
1905 badReqMsg);
1906 return;
1907 }
1908 const nlohmann::json& propertySetValue = *propertyIt;
1909 auto transaction = std::make_shared<AsyncPutRequest>(asyncResp);
1910 transaction->objectPath = objectPath;
1911 transaction->propertyName = destProperty;
1912 transaction->propertyValue = propertySetValue;
1913
1914 dbus::utility::getDbusObject(
1915 transaction->objectPath, {},
1916 [transaction](const boost::system::error_code& ec2,
1917 const dbus::utility::MapperGetObject& objectNames) {
1918 if (!ec2 && objectNames.empty())
1919 {
1920 setErrorResponse(transaction->asyncResp->res,
1921 boost::beast::http::status::not_found,
1922 propNotFoundDesc, notFoundMsg);
1923 return;
1924 }
1925
1926 for (const std::pair<std::string, std::vector<std::string>>&
1927 connection : objectNames)
1928 {
1929 const std::string& connectionName = connection.first;
1930
1931 crow::connections::systemBus->async_method_call(
1932 [connectionName{std::string(connectionName)},
1933 transaction](const boost::system::error_code& ec3,
1934 const std::string& introspectXml) {
1935 if (ec3)
1936 {
1937 BMCWEB_LOG_ERROR(
1938 "Introspect call failed with error: {} on process: {}",
1939 ec3.message(), connectionName);
1940 transaction->setErrorStatus("Unexpected Error");
1941 return;
1942 }
1943 tinyxml2::XMLDocument doc;
1944
1945 doc.Parse(introspectXml.c_str());
1946 tinyxml2::XMLNode* pRoot =
1947 doc.FirstChildElement("node");
1948 if (pRoot == nullptr)
1949 {
1950 BMCWEB_LOG_ERROR("XML document failed to parse: {}",
1951 introspectXml);
1952 transaction->setErrorStatus("Unexpected Error");
1953 return;
1954 }
1955 tinyxml2::XMLElement* ifaceNode =
1956 pRoot->FirstChildElement("interface");
1957 while (ifaceNode != nullptr)
1958 {
1959 const char* interfaceName =
1960 ifaceNode->Attribute("name");
1961 BMCWEB_LOG_DEBUG("found interface {}",
1962 interfaceName);
1963 tinyxml2::XMLElement* propNode =
1964 ifaceNode->FirstChildElement("property");
1965 while (propNode != nullptr)
1966 {
1967 const char* propertyName =
1968 propNode->Attribute("name");
1969 if (propertyName == nullptr)
1970 {
1971 BMCWEB_LOG_DEBUG(
1972 "Couldn't find name property");
1973 continue;
1974 }
1975 BMCWEB_LOG_DEBUG("Found property {}",
1976 propertyName);
1977 if (propertyName == transaction->propertyName)
1978 {
1979 const char* argType =
1980 propNode->Attribute("type");
1981 if (argType != nullptr)
1982 {
1983 sdbusplus::message_t m =
1984 crow::connections::systemBus
1985 ->new_method_call(
1986 connectionName.c_str(),
1987 transaction->objectPath
1988 .c_str(),
1989 "org.freedesktop.DBus."
1990 "Properties",
1991 "Set");
1992 m.append(interfaceName,
1993 transaction->propertyName);
1994 int r = sd_bus_message_open_container(
1995 m.get(), SD_BUS_TYPE_VARIANT,
1996 argType);
1997 if (r < 0)
1998 {
1999 transaction->setErrorStatus(
2000 "Unexpected Error");
2001 return;
2002 }
2003 r = convertJsonToDbus(
2004 m.get(), argType,
2005 transaction->propertyValue);
2006 if (r < 0)
2007 {
2008 if (r == -ERANGE)
2009 {
2010 transaction->setErrorStatus(
2011 "Provided property value "
2012 "is out of range for the "
2013 "property type");
2014 }
2015 else
2016 {
2017 transaction->setErrorStatus(
2018 "Invalid arg type");
2019 }
2020 return;
2021 }
2022 r = sd_bus_message_close_container(
2023 m.get());
2024 if (r < 0)
2025 {
2026 transaction->setErrorStatus(
2027 "Unexpected Error");
2028 return;
2029 }
2030 crow::connections::systemBus
2031 ->async_send(
2032 m,
2033 [transaction](
2034 const boost::system::
2035 error_code& ec,
2036 sdbusplus::message_t& m2) {
2037 BMCWEB_LOG_DEBUG("sent");
2038 if (ec)
2039 {
2040 const sd_bus_error* e =
2041 m2.get_error();
2042 setErrorResponse(
2043 transaction
2044 ->asyncResp
2045 ->res,
2046 boost::beast::http::
2047 status::
2048 forbidden,
2049 (e) != nullptr
2050 ? e->name
2051 : ec.category()
2052 .name(),
2053 (e) != nullptr
2054 ? e->message
2055 : ec.message());
2056 }
2057 else
2058 {
2059 transaction->asyncResp
2060 ->res.jsonValue
2061 ["status"] =
2062 "ok";
2063 transaction->asyncResp
2064 ->res.jsonValue
2065 ["message"] =
2066 "200 OK";
2067 transaction->asyncResp
2068 ->res
2069 .jsonValue["data"] =
2070 nullptr;
2071 }
2072 });
2073 }
2074 }
2075 propNode =
2076 propNode->NextSiblingElement("property");
2077 }
2078 ifaceNode =
2079 ifaceNode->NextSiblingElement("interface");
2080 }
2081 },
2082 connectionName, transaction->objectPath,
2083 "org.freedesktop.DBus.Introspectable", "Introspect");
2084 }
2085 });
2086 }
2087
handleDBusUrl(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string & objectPath)2088 inline void handleDBusUrl(const crow::Request& req,
2089 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2090 std::string& objectPath)
2091 {
2092 // If accessing a single attribute, fill in and update objectPath,
2093 // otherwise leave destProperty blank
2094 std::string destProperty;
2095 const char* attrSeperator = "/attr/";
2096 size_t attrPosition = objectPath.find(attrSeperator);
2097 if (attrPosition != std::string::npos)
2098 {
2099 destProperty = objectPath.substr(attrPosition + strlen(attrSeperator),
2100 objectPath.length());
2101 objectPath.resize(attrPosition);
2102 }
2103
2104 if (req.method() == boost::beast::http::verb::post)
2105 {
2106 constexpr const char* actionSeperator = "/action/";
2107 size_t actionPosition = objectPath.find(actionSeperator);
2108 if (actionPosition != std::string::npos)
2109 {
2110 std::string postProperty =
2111 objectPath.substr((actionPosition + strlen(actionSeperator)),
2112 objectPath.length());
2113 objectPath.resize(actionPosition);
2114 handleAction(req, asyncResp, objectPath, postProperty);
2115 return;
2116 }
2117 }
2118 else if (req.method() == boost::beast::http::verb::get)
2119 {
2120 if (objectPath.ends_with("/enumerate"))
2121 {
2122 objectPath.erase(objectPath.end() - sizeof("enumerate"),
2123 objectPath.end());
2124 handleEnumerate(asyncResp, objectPath);
2125 }
2126 else if (objectPath.ends_with("/list"))
2127 {
2128 objectPath.erase(objectPath.end() - sizeof("list"),
2129 objectPath.end());
2130 handleList(asyncResp, objectPath);
2131 }
2132 else
2133 {
2134 // Trim any trailing "/" at the end
2135 if (objectPath.ends_with("/"))
2136 {
2137 objectPath.pop_back();
2138 handleList(asyncResp, objectPath, 1);
2139 }
2140 else
2141 {
2142 handleGet(asyncResp, objectPath, destProperty);
2143 }
2144 }
2145 return;
2146 }
2147 else if (req.method() == boost::beast::http::verb::put)
2148 {
2149 handlePut(req, asyncResp, objectPath, destProperty);
2150 return;
2151 }
2152 else if (req.method() == boost::beast::http::verb::delete_)
2153 {
2154 handleDelete(asyncResp, objectPath);
2155 return;
2156 }
2157
2158 setErrorResponse(asyncResp->res,
2159 boost::beast::http::status::method_not_allowed,
2160 methodNotAllowedDesc, methodNotAllowedMsg);
2161 }
2162
handleBusSystemPost(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & processName,const std::string & requestedPath)2163 inline void handleBusSystemPost(
2164 const crow::Request& req,
2165 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2166 const std::string& processName, const std::string& requestedPath)
2167 {
2168 std::vector<std::string> strs;
2169
2170 bmcweb::split(strs, requestedPath, '/');
2171 std::string objectPath;
2172 std::string interfaceName;
2173 std::string methodName;
2174 auto it = strs.begin();
2175 if (it == strs.end())
2176 {
2177 objectPath = "/";
2178 }
2179 while (it != strs.end())
2180 {
2181 // Check if segment contains ".". If it does, it must be an
2182 // interface
2183 if (it->find(".") != std::string::npos)
2184 {
2185 break;
2186 // This check is necessary as the trailing slash gets
2187 // parsed as part of our <path> specifier above, which
2188 // causes the normal trailing backslash redirector to
2189 // fail.
2190 }
2191 if (!it->empty())
2192 {
2193 objectPath += "/" + *it;
2194 }
2195 it++;
2196 }
2197 if (it != strs.end())
2198 {
2199 interfaceName = *it;
2200 it++;
2201
2202 // after interface, we might have a method name
2203 if (it != strs.end())
2204 {
2205 methodName = *it;
2206 it++;
2207 }
2208 }
2209 if (it != strs.end())
2210 {
2211 // if there is more levels past the method name, something
2212 // went wrong, return not found
2213 asyncResp->res.result(boost::beast::http::status::not_found);
2214 return;
2215 }
2216 if (interfaceName.empty())
2217 {
2218 crow::connections::systemBus->async_method_call(
2219 [asyncResp, processName,
2220 objectPath](const boost::system::error_code& ec,
2221 const std::string& introspectXml) {
2222 if (ec)
2223 {
2224 BMCWEB_LOG_ERROR(
2225 "Introspect call failed with error: {} on process: {} path: {}",
2226 ec.message(), processName, objectPath);
2227 return;
2228 }
2229 tinyxml2::XMLDocument doc;
2230
2231 doc.Parse(introspectXml.c_str());
2232 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
2233 if (pRoot == nullptr)
2234 {
2235 BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
2236 processName, objectPath);
2237 asyncResp->res.jsonValue["status"] = "XML parse error";
2238 asyncResp->res.result(
2239 boost::beast::http::status::internal_server_error);
2240 return;
2241 }
2242
2243 BMCWEB_LOG_DEBUG("{}", introspectXml);
2244 asyncResp->res.jsonValue["status"] = "ok";
2245 asyncResp->res.jsonValue["bus_name"] = processName;
2246 asyncResp->res.jsonValue["object_path"] = objectPath;
2247
2248 nlohmann::json& interfacesArray =
2249 asyncResp->res.jsonValue["interfaces"];
2250 interfacesArray = nlohmann::json::array();
2251 tinyxml2::XMLElement* interface =
2252 pRoot->FirstChildElement("interface");
2253
2254 while (interface != nullptr)
2255 {
2256 const char* ifaceName = interface->Attribute("name");
2257 if (ifaceName != nullptr)
2258 {
2259 nlohmann::json::object_t interfaceObj;
2260 interfaceObj["name"] = ifaceName;
2261 interfacesArray.emplace_back(std::move(interfaceObj));
2262 }
2263
2264 interface = interface->NextSiblingElement("interface");
2265 }
2266 },
2267 processName, objectPath, "org.freedesktop.DBus.Introspectable",
2268 "Introspect");
2269 }
2270 else if (methodName.empty())
2271 {
2272 crow::connections::systemBus->async_method_call(
2273 [asyncResp, processName, objectPath,
2274 interfaceName](const boost::system::error_code& ec,
2275 const std::string& introspectXml) {
2276 if (ec)
2277 {
2278 BMCWEB_LOG_ERROR(
2279 "Introspect call failed with error: {} on process: {} path: {}",
2280 ec.message(), processName, objectPath);
2281 return;
2282 }
2283 tinyxml2::XMLDocument doc;
2284
2285 doc.Parse(introspectXml.data(), introspectXml.size());
2286 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
2287 if (pRoot == nullptr)
2288 {
2289 BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
2290 processName, objectPath);
2291 asyncResp->res.result(
2292 boost::beast::http::status::internal_server_error);
2293 return;
2294 }
2295
2296 asyncResp->res.jsonValue["status"] = "ok";
2297 asyncResp->res.jsonValue["bus_name"] = processName;
2298 asyncResp->res.jsonValue["interface"] = interfaceName;
2299 asyncResp->res.jsonValue["object_path"] = objectPath;
2300
2301 nlohmann::json& methodsArray =
2302 asyncResp->res.jsonValue["methods"];
2303 methodsArray = nlohmann::json::array();
2304
2305 nlohmann::json& signalsArray =
2306 asyncResp->res.jsonValue["signals"];
2307 signalsArray = nlohmann::json::array();
2308
2309 nlohmann::json& propertiesObj =
2310 asyncResp->res.jsonValue["properties"];
2311 propertiesObj = nlohmann::json::object();
2312
2313 // if we know we're the only call, build the
2314 // json directly
2315 tinyxml2::XMLElement* interface =
2316 pRoot->FirstChildElement("interface");
2317 while (interface != nullptr)
2318 {
2319 const char* ifaceName = interface->Attribute("name");
2320
2321 if (ifaceName != nullptr && ifaceName == interfaceName)
2322 {
2323 break;
2324 }
2325
2326 interface = interface->NextSiblingElement("interface");
2327 }
2328 if (interface == nullptr)
2329 {
2330 // if we got to the end of the list and
2331 // never found a match, throw 404
2332 asyncResp->res.result(
2333 boost::beast::http::status::not_found);
2334 return;
2335 }
2336
2337 tinyxml2::XMLElement* methods =
2338 interface->FirstChildElement("method");
2339 while (methods != nullptr)
2340 {
2341 nlohmann::json argsArray = nlohmann::json::array();
2342 tinyxml2::XMLElement* arg =
2343 methods->FirstChildElement("arg");
2344 while (arg != nullptr)
2345 {
2346 nlohmann::json thisArg;
2347 for (const char* fieldName : std::array<const char*, 3>{
2348 "name", "direction", "type"})
2349 {
2350 const char* fieldValue = arg->Attribute(fieldName);
2351 if (fieldValue != nullptr)
2352 {
2353 thisArg[fieldName] = fieldValue;
2354 }
2355 }
2356 argsArray.emplace_back(std::move(thisArg));
2357 arg = arg->NextSiblingElement("arg");
2358 }
2359
2360 const char* name = methods->Attribute("name");
2361 if (name != nullptr)
2362 {
2363 std::string uri;
2364 uri.reserve(14 + processName.size() +
2365 objectPath.size() + interfaceName.size() +
2366 strlen(name));
2367 uri += "/bus/system/";
2368 uri += processName;
2369 uri += objectPath;
2370 uri += "/";
2371 uri += interfaceName;
2372 uri += "/";
2373 uri += name;
2374
2375 nlohmann::json::object_t object;
2376 object["name"] = name;
2377 object["uri"] = std::move(uri);
2378 object["args"] = argsArray;
2379
2380 methodsArray.emplace_back(std::move(object));
2381 }
2382 methods = methods->NextSiblingElement("method");
2383 }
2384 tinyxml2::XMLElement* signals =
2385 interface->FirstChildElement("signal");
2386 while (signals != nullptr)
2387 {
2388 nlohmann::json argsArray = nlohmann::json::array();
2389
2390 tinyxml2::XMLElement* arg =
2391 signals->FirstChildElement("arg");
2392 while (arg != nullptr)
2393 {
2394 const char* name = arg->Attribute("name");
2395 const char* type = arg->Attribute("type");
2396 if (name != nullptr && type != nullptr)
2397 {
2398 nlohmann::json::object_t params;
2399 params["name"] = name;
2400 params["type"] = type;
2401 argsArray.push_back(std::move(params));
2402 }
2403 arg = arg->NextSiblingElement("arg");
2404 }
2405 const char* name = signals->Attribute("name");
2406 if (name != nullptr)
2407 {
2408 nlohmann::json::object_t object;
2409 object["name"] = name;
2410 object["args"] = argsArray;
2411 signalsArray.emplace_back(std::move(object));
2412 }
2413
2414 signals = signals->NextSiblingElement("signal");
2415 }
2416
2417 tinyxml2::XMLElement* property =
2418 interface->FirstChildElement("property");
2419 while (property != nullptr)
2420 {
2421 const char* name = property->Attribute("name");
2422 const char* type = property->Attribute("type");
2423 if (type != nullptr && name != nullptr)
2424 {
2425 sdbusplus::message_t m =
2426 crow::connections::systemBus->new_method_call(
2427 processName.c_str(), objectPath.c_str(),
2428 "org.freedesktop."
2429 "DBus."
2430 "Properties",
2431 "Get");
2432 m.append(interfaceName, name);
2433 nlohmann::json& propertyItem = propertiesObj[name];
2434 crow::connections::systemBus->async_send(
2435 m, [&propertyItem,
2436 asyncResp](const boost::system::error_code& ec2,
2437 sdbusplus::message_t& msg) {
2438 if (ec2)
2439 {
2440 return;
2441 }
2442
2443 int r =
2444 convertDBusToJSON("v", msg, propertyItem);
2445 if (r < 0)
2446 {
2447 BMCWEB_LOG_ERROR(
2448 "Couldn't convert vector to json");
2449 }
2450 });
2451 }
2452 property = property->NextSiblingElement("property");
2453 }
2454 },
2455 processName, objectPath, "org.freedesktop.DBus.Introspectable",
2456 "Introspect");
2457 }
2458 else
2459 {
2460 if (req.method() != boost::beast::http::verb::post)
2461 {
2462 asyncResp->res.result(boost::beast::http::status::not_found);
2463 return;
2464 }
2465
2466 nlohmann::json requestDbusData;
2467 JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
2468 if (ret == JsonParseResult::BadContentType)
2469 {
2470 setErrorResponse(asyncResp->res,
2471 boost::beast::http::status::unsupported_media_type,
2472 invalidContentType, unsupportedMediaMsg);
2473 return;
2474 }
2475 if (ret != JsonParseResult::Success)
2476 {
2477 setErrorResponse(asyncResp->res,
2478 boost::beast::http::status::bad_request,
2479 noJsonDesc, badReqMsg);
2480 return;
2481 }
2482
2483 if (!requestDbusData.is_array())
2484 {
2485 asyncResp->res.result(boost::beast::http::status::bad_request);
2486 return;
2487 }
2488 auto transaction = std::make_shared<InProgressActionData>(asyncResp);
2489
2490 transaction->path = objectPath;
2491 transaction->methodName = methodName;
2492 transaction->arguments = std::move(requestDbusData);
2493
2494 findActionOnInterface(transaction, processName);
2495 }
2496 }
2497
requestRoutes(App & app)2498 inline void requestRoutes(App& app)
2499 {
2500 BMCWEB_ROUTE(app, "/bus/")
2501 .privileges({{"Login"}})
2502 .methods(boost::beast::http::verb::get)(
2503 [](const crow::Request&,
2504 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2505 nlohmann::json::array_t buses;
2506 nlohmann::json& bus = buses.emplace_back();
2507 bus["name"] = "system";
2508 asyncResp->res.jsonValue["busses"] = std::move(buses);
2509 asyncResp->res.jsonValue["status"] = "ok";
2510 });
2511
2512 BMCWEB_ROUTE(app, "/bus/system/")
2513 .privileges({{"Login"}})
2514 .methods(boost::beast::http::verb::get)(
2515 [](const crow::Request&,
2516 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2517 auto myCallback = [asyncResp](
2518 const boost::system::error_code& ec,
2519 std::vector<std::string>& names) {
2520 if (ec)
2521 {
2522 BMCWEB_LOG_ERROR("Dbus call failed with code {}", ec);
2523 asyncResp->res.result(
2524 boost::beast::http::status::internal_server_error);
2525 }
2526 else
2527 {
2528 std::ranges::sort(names);
2529 asyncResp->res.jsonValue["status"] = "ok";
2530 auto& objectsSub = asyncResp->res.jsonValue["objects"];
2531 for (const auto& name : names)
2532 {
2533 nlohmann::json::object_t object;
2534 object["name"] = name;
2535 objectsSub.emplace_back(std::move(object));
2536 }
2537 }
2538 };
2539 crow::connections::systemBus->async_method_call(
2540 std::move(myCallback), "org.freedesktop.DBus", "/",
2541 "org.freedesktop.DBus", "ListNames");
2542 });
2543
2544 BMCWEB_ROUTE(app, "/list/")
2545 .privileges({{"Login"}})
2546 .methods(boost::beast::http::verb::get)(
2547 [](const crow::Request&,
2548 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2549 handleList(asyncResp, "/");
2550 });
2551
2552 BMCWEB_ROUTE(app, "/xyz/<path>")
2553 .privileges({{"Login"}})
2554 .methods(boost::beast::http::verb::get)(
2555 [](const crow::Request& req,
2556 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2557 const std::string& path) {
2558 std::string objectPath = "/xyz/" + path;
2559 handleDBusUrl(req, asyncResp, objectPath);
2560 });
2561
2562 BMCWEB_ROUTE(app, "/xyz/<path>")
2563 .privileges({{"ConfigureComponents", "ConfigureManager"}})
2564 .methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
2565 boost::beast::http::verb::delete_)(
2566 [](const crow::Request& req,
2567 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2568 const std::string& path) {
2569 std::string objectPath = "/xyz/" + path;
2570 handleDBusUrl(req, asyncResp, objectPath);
2571 });
2572
2573 BMCWEB_ROUTE(app, "/org/<path>")
2574 .privileges({{"Login"}})
2575 .methods(boost::beast::http::verb::get)(
2576 [](const crow::Request& req,
2577 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2578 const std::string& path) {
2579 std::string objectPath = "/org/" + path;
2580 handleDBusUrl(req, asyncResp, objectPath);
2581 });
2582
2583 BMCWEB_ROUTE(app, "/org/<path>")
2584 .privileges({{"ConfigureComponents", "ConfigureManager"}})
2585 .methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
2586 boost::beast::http::verb::delete_)(
2587 [](const crow::Request& req,
2588 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2589 const std::string& path) {
2590 std::string objectPath = "/org/" + path;
2591 handleDBusUrl(req, asyncResp, objectPath);
2592 });
2593
2594 BMCWEB_ROUTE(app, "/download/dump/<str>/")
2595 .privileges({{"ConfigureManager"}})
2596 .methods(boost::beast::http::verb::get)(
2597 [](const crow::Request&,
2598 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2599 const std::string& dumpId) {
2600 if (!validateFilename(dumpId))
2601 {
2602 asyncResp->res.result(
2603 boost::beast::http::status::bad_request);
2604 return;
2605 }
2606 std::filesystem::path loc(
2607 "/var/lib/phosphor-debug-collector/dumps");
2608
2609 loc /= dumpId;
2610
2611 if (!std::filesystem::exists(loc) ||
2612 !std::filesystem::is_directory(loc))
2613 {
2614 BMCWEB_LOG_ERROR("{}Not found", loc.string());
2615 asyncResp->res.result(
2616 boost::beast::http::status::not_found);
2617 return;
2618 }
2619 std::filesystem::directory_iterator files(loc);
2620
2621 for (const auto& file : files)
2622 {
2623 if (asyncResp->res.openFile(file) !=
2624 crow::OpenCode::Success)
2625 {
2626 continue;
2627 }
2628
2629 asyncResp->res.addHeader(
2630 boost::beast::http::field::content_type,
2631 "application/octet-stream");
2632
2633 // Assuming only one dump file will be present in the dump
2634 // id directory
2635 std::string dumpFileName = file.path().filename().string();
2636
2637 // Filename should be in alphanumeric, dot and underscore
2638 // Its based on phosphor-debug-collector application
2639 // dumpfile format
2640 static std::regex dumpFileRegex("[a-zA-Z0-9\\._]+");
2641 if (!std::regex_match(dumpFileName, dumpFileRegex))
2642 {
2643 BMCWEB_LOG_ERROR("Invalid dump filename {}",
2644 dumpFileName);
2645 asyncResp->res.result(
2646 boost::beast::http::status::not_found);
2647 return;
2648 }
2649 std::string contentDispositionParam =
2650 "attachment; filename=\"" + dumpFileName + "\"";
2651
2652 asyncResp->res.addHeader(
2653 boost::beast::http::field::content_disposition,
2654 contentDispositionParam);
2655
2656 return;
2657 }
2658 asyncResp->res.result(boost::beast::http::status::not_found);
2659 return;
2660 });
2661
2662 BMCWEB_ROUTE(app, "/bus/system/<str>/")
2663 .privileges({{"Login"}})
2664
2665 .methods(boost::beast::http::verb::get)(
2666 [](const crow::Request&,
2667 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2668 const std::string& connection) {
2669 introspectObjects(connection, "/", asyncResp);
2670 });
2671
2672 BMCWEB_ROUTE(app, "/bus/system/<str>/<path>")
2673 .privileges({{"ConfigureComponents", "ConfigureManager"}})
2674 .methods(boost::beast::http::verb::get,
2675 boost::beast::http::verb::post)(handleBusSystemPost);
2676 }
2677 } // namespace openbmc_mapper
2678 } // namespace crow
2679