xref: /openbmc/phosphor-host-ipmid/libipmid/utils.cpp (revision 195d552861198586ad0a7a2e7faf606b9128c411)
1 #include <arpa/inet.h>
2 #include <dirent.h>
3 #include <fcntl.h>
4 #include <linux/i2c-dev.h>
5 #include <linux/i2c.h>
6 #include <net/if.h>
7 #include <sys/ioctl.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10 
11 #include <ipmid/utils.hpp>
12 #include <phosphor-logging/elog-errors.hpp>
13 #include <phosphor-logging/lg2.hpp>
14 #include <sdbusplus/message/types.hpp>
15 #include <xyz/openbmc_project/Common/error.hpp>
16 
17 #include <algorithm>
18 #include <chrono>
19 
20 namespace ipmi
21 {
22 
23 using namespace phosphor::logging;
24 using namespace sdbusplus::error::xyz::openbmc_project::common;
25 
26 namespace network
27 {
28 
29 /** @brief checks if the given ip is Link Local Ip or not.
30  *  @param[in] ipaddress - IPAddress.
31  */
32 bool isLinkLocalIP(const std::string& ipaddress);
33 
34 } // namespace network
35 
36 // TODO There may be cases where an interface is implemented by multiple
37 //  objects,to handle such cases we are interested on that object
38 //  which are on interested busname.
39 //  Currently mapper doesn't give the readable busname(gives busid) so we can't
40 //  use busname to find the object,will do later once the support is there.
41 
getDbusObject(sdbusplus::bus_t & bus,const std::string & interface,const std::string & serviceRoot,const std::string & match)42 DbusObjectInfo getDbusObject(
43     sdbusplus::bus_t& bus, const std::string& interface,
44     const std::string& serviceRoot, const std::string& match)
45 {
46     std::vector<DbusInterface> interfaces;
47     interfaces.emplace_back(interface);
48 
49     ObjectTree objectTree = getSubTree(bus, interfaces, serviceRoot);
50     if (objectTree.empty())
51     {
52         lg2::error("No Object has implemented the interface: {INTERFACE}",
53                    "INTERFACE", interface);
54         elog<InternalFailure>();
55     }
56 
57     DbusObjectInfo objectInfo;
58 
59     // if match is empty then return the first object
60     if (match == "")
61     {
62         objectInfo = std::make_pair(
63             objectTree.begin()->first,
64             std::move(objectTree.begin()->second.begin()->first));
65         return objectInfo;
66     }
67 
68     // else search the match string in the object path
69     auto found = std::find_if(
70         objectTree.begin(), objectTree.end(), [&match](const auto& object) {
71             return (object.first.find(match) != std::string::npos);
72         });
73 
74     if (found == objectTree.end())
75     {
76         lg2::error("Failed to find object which matches: {MATCH}", "MATCH",
77                    match);
78         elog<InternalFailure>();
79         // elog<> throws an exception.
80     }
81 
82     return make_pair(found->first, std::move(found->second.begin()->first));
83 }
84 
getDbusProperty(sdbusplus::bus_t & bus,const std::string & service,const std::string & objPath,const std::string & interface,const std::string & property,std::chrono::microseconds timeout)85 Value getDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
86                       const std::string& objPath, const std::string& interface,
87                       const std::string& property,
88                       std::chrono::microseconds timeout)
89 {
90     Value value;
91 
92     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
93                                       PROP_INTF, METHOD_GET);
94 
95     method.append(interface, property);
96 
97     auto reply = bus.call(method, timeout.count());
98     reply.read(value);
99 
100     return value;
101 }
102 
getAllDbusProperties(sdbusplus::bus_t & bus,const std::string & service,const std::string & objPath,const std::string & interface,std::chrono::microseconds timeout)103 PropertyMap getAllDbusProperties(
104     sdbusplus::bus_t& bus, const std::string& service,
105     const std::string& objPath, const std::string& interface,
106     std::chrono::microseconds timeout)
107 {
108     PropertyMap properties;
109 
110     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
111                                       PROP_INTF, METHOD_GET_ALL);
112 
113     method.append(interface);
114 
115     auto reply = bus.call(method, timeout.count());
116     reply.read(properties);
117 
118     return properties;
119 }
120 
getManagedObjects(sdbusplus::bus_t & bus,const std::string & service,const std::string & objPath)121 ObjectValueTree getManagedObjects(sdbusplus::bus_t& bus,
122                                   const std::string& service,
123                                   const std::string& objPath)
124 {
125     ipmi::ObjectValueTree interfaces;
126 
127     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
128                                       "org.freedesktop.DBus.ObjectManager",
129                                       "GetManagedObjects");
130     auto reply = bus.call(method);
131     reply.read(interfaces);
132 
133     return interfaces;
134 }
135 
setDbusProperty(sdbusplus::bus_t & bus,const std::string & service,const std::string & objPath,const std::string & interface,const std::string & property,const Value & value,std::chrono::microseconds timeout)136 void setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
137                      const std::string& objPath, const std::string& interface,
138                      const std::string& property, const Value& value,
139                      std::chrono::microseconds timeout)
140 {
141     auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
142                                       PROP_INTF, METHOD_SET);
143 
144     method.append(interface, property, value);
145 
146     if (!bus.call(method, timeout.count()))
147     {
148         lg2::error("Failed to set {PROPERTY}, path: {PATH}, "
149                    "interface: {INTERFACE}",
150                    "PROPERTY", property, "PATH", objPath, "INTERFACE",
151                    interface);
152         elog<InternalFailure>();
153     }
154 }
155 
ServiceCache(const std::string & intf,const std::string & path)156 ServiceCache::ServiceCache(const std::string& intf, const std::string& path) :
157     intf(intf), path(path), cachedService(std::nullopt),
158     cachedBusName(std::nullopt)
159 {}
160 
ServiceCache(std::string && intf,std::string && path)161 ServiceCache::ServiceCache(std::string&& intf, std::string&& path) :
162     intf(std::move(intf)), path(std::move(path)), cachedService(std::nullopt),
163     cachedBusName(std::nullopt)
164 {}
165 
getService(sdbusplus::bus_t & bus)166 const std::string& ServiceCache::getService(sdbusplus::bus_t& bus)
167 {
168     if (!isValid(bus))
169     {
170         cachedBusName = bus.get_unique_name();
171         cachedService = ::ipmi::getService(bus, intf, path);
172     }
173 
174     if (!cachedService)
175     {
176         throw std::runtime_error("Service not cached");
177     }
178     return cachedService.value();
179 }
180 
invalidate()181 void ServiceCache::invalidate()
182 {
183     cachedBusName = std::nullopt;
184     cachedService = std::nullopt;
185 }
186 
newMethodCall(sdbusplus::bus_t & bus,const char * intf,const char * method)187 sdbusplus::message_t ServiceCache::newMethodCall(
188     sdbusplus::bus_t& bus, const char* intf, const char* method)
189 {
190     return bus.new_method_call(getService(bus).c_str(), path.c_str(), intf,
191                                method);
192 }
193 
isValid(sdbusplus::bus_t & bus) const194 bool ServiceCache::isValid(sdbusplus::bus_t& bus) const
195 {
196     return cachedService && cachedBusName == bus.get_unique_name();
197 }
198 
getService(sdbusplus::bus_t & bus,const std::string & intf,const std::string & path)199 std::string getService(sdbusplus::bus_t& bus, const std::string& intf,
200                        const std::string& path)
201 {
202     auto mapperCall =
203         bus.new_method_call("xyz.openbmc_project.ObjectMapper",
204                             "/xyz/openbmc_project/object_mapper",
205                             "xyz.openbmc_project.ObjectMapper", "GetObject");
206 
207     mapperCall.append(path);
208     mapperCall.append(std::vector<std::string>({intf}));
209 
210     auto mapperResponseMsg = bus.call(mapperCall);
211 
212     std::map<std::string, std::vector<std::string>> mapperResponse;
213     mapperResponseMsg.read(mapperResponse);
214 
215     if (mapperResponse.begin() == mapperResponse.end())
216     {
217         throw std::runtime_error("ERROR in reading the mapper response");
218     }
219 
220     return mapperResponse.begin()->first;
221 }
222 
getSubTree(sdbusplus::bus_t & bus,const InterfaceList & interfaces,const std::string & subtreePath,int32_t depth)223 ObjectTree getSubTree(sdbusplus::bus_t& bus, const InterfaceList& interfaces,
224                       const std::string& subtreePath, int32_t depth)
225 {
226     auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
227                                           MAPPER_INTF, "GetSubTree");
228 
229     mapperCall.append(subtreePath, depth, interfaces);
230 
231     auto mapperReply = bus.call(mapperCall);
232     ObjectTree objectTree;
233     mapperReply.read(objectTree);
234 
235     return objectTree;
236 }
237 
getAllDbusObjects(sdbusplus::bus_t & bus,const std::string & serviceRoot,const std::string & interface,const std::string & match)238 ipmi::ObjectTree getAllDbusObjects(
239     sdbusplus::bus_t& bus, const std::string& serviceRoot,
240     const std::string& interface, const std::string& match)
241 {
242     std::vector<std::string> interfaces;
243     interfaces.emplace_back(interface);
244 
245     ObjectTree objectTree = getSubTree(bus, interfaces, serviceRoot);
246     for (auto it = objectTree.begin(); it != objectTree.end();)
247     {
248         if (it->first.find(match) == std::string::npos)
249         {
250             it = objectTree.erase(it);
251         }
252         else
253         {
254             ++it;
255         }
256     }
257 
258     return objectTree;
259 }
260 
deleteAllDbusObjects(sdbusplus::bus_t & bus,const std::string & serviceRoot,const std::string & interface,const std::string & match)261 void deleteAllDbusObjects(sdbusplus::bus_t& bus, const std::string& serviceRoot,
262                           const std::string& interface,
263                           const std::string& match)
264 {
265     try
266     {
267         auto objectTree = getAllDbusObjects(bus, serviceRoot, interface, match);
268 
269         for (auto& object : objectTree)
270         {
271             method_no_args::callDbusMethod(bus, object.second.begin()->first,
272                                            object.first, DELETE_INTERFACE,
273                                            "Delete");
274         }
275     }
276     catch (const sdbusplus::exception_t& e)
277     {
278         lg2::info("sdbusplus exception - Unable to delete the objects, "
279                   "service: {SERVICE}, interface: {INTERFACE}, error: {ERROR}",
280                   "SERVICE", serviceRoot, "INTERFACE", interface, "ERROR", e);
281     }
282 }
283 
convertToString(const InterfaceList & interfaces)284 static inline std::string convertToString(const InterfaceList& interfaces)
285 {
286     std::string intfStr;
287     for (const auto& intf : interfaces)
288     {
289         intfStr += "," + intf;
290     }
291     return intfStr;
292 }
293 
getAllAncestors(sdbusplus::bus_t & bus,const std::string & path,InterfaceList && interfaces)294 ObjectTree getAllAncestors(sdbusplus::bus_t& bus, const std::string& path,
295                            InterfaceList&& interfaces)
296 {
297     auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
298                                           MAPPER_INTF, "GetAncestors");
299     mapperCall.append(path, interfaces);
300 
301     auto mapperReply = bus.call(mapperCall);
302     ObjectTree objectTree;
303     mapperReply.read(objectTree);
304 
305     if (objectTree.empty())
306     {
307         lg2::error("No Object has implemented the interface: {INTERFACE}, "
308                    "path: {PATH}",
309                    "INTERFACE", convertToString(interfaces), "PATH", path);
310         elog<InternalFailure>();
311     }
312 
313     return objectTree;
314 }
315 
316 namespace method_no_args
317 {
318 
callDbusMethod(sdbusplus::bus_t & bus,const std::string & service,const std::string & objPath,const std::string & interface,const std::string & method)319 void callDbusMethod(sdbusplus::bus_t& bus, const std::string& service,
320                     const std::string& objPath, const std::string& interface,
321                     const std::string& method)
322 
323 {
324     auto busMethod = bus.new_method_call(service.c_str(), objPath.c_str(),
325                                          interface.c_str(), method.c_str());
326     auto reply = bus.call(busMethod);
327 }
328 
329 } // namespace method_no_args
330 
331 /********* Begin co-routine yielding alternatives ***************/
332 
getService(Context::ptr ctx,const std::string & intf,const std::string & path,std::string & service)333 boost::system::error_code getService(Context::ptr ctx, const std::string& intf,
334                                      const std::string& path,
335                                      std::string& service)
336 {
337     boost::system::error_code ec;
338     std::map<std::string, std::vector<std::string>> mapperResponse =
339         ctx->bus->yield_method_call<decltype(mapperResponse)>(
340             ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
341             "/xyz/openbmc_project/object_mapper",
342             "xyz.openbmc_project.ObjectMapper", "GetObject", path,
343             std::vector<std::string>({intf}));
344 
345     if (!ec)
346     {
347         service = std::move(mapperResponse.begin()->first);
348     }
349     return ec;
350 }
351 
getSubTree(Context::ptr ctx,const InterfaceList & interfaces,const std::string & subtreePath,int32_t depth,ObjectTree & objectTree)352 boost::system::error_code getSubTree(
353     Context::ptr ctx, const InterfaceList& interfaces,
354     const std::string& subtreePath, int32_t depth, ObjectTree& objectTree)
355 {
356     boost::system::error_code ec;
357     objectTree = ctx->bus->yield_method_call<ObjectTree>(
358         ctx->yield, ec, MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, "GetSubTree",
359         subtreePath, depth, interfaces);
360 
361     return ec;
362 }
363 
getDbusObject(Context::ptr ctx,const std::string & interface,const std::string & subtreePath,const std::string & match,DbusObjectInfo & dbusObject)364 boost::system::error_code getDbusObject(
365     Context::ptr ctx, const std::string& interface,
366     const std::string& subtreePath, const std::string& match,
367     DbusObjectInfo& dbusObject)
368 {
369     std::vector<DbusInterface> interfaces;
370     interfaces.emplace_back(interface);
371 
372     auto depth = 0;
373     ObjectTree objectTree;
374     boost::system::error_code ec =
375         getSubTree(ctx, interfaces, subtreePath, depth, objectTree);
376 
377     if (ec)
378     {
379         return ec;
380     }
381 
382     if (objectTree.empty())
383     {
384         lg2::error("No Object has implemented the interface: {INTERFACE}, "
385                    "NetFn: {NETFN}, Cmd: {CMD}",
386                    "INTERFACE", interface, "NETFN", lg2::hex, ctx->netFn, "CMD",
387                    lg2::hex, ctx->cmd);
388         return boost::system::errc::make_error_code(
389             boost::system::errc::no_such_process);
390     }
391 
392     // if match is empty then return the first object
393     if (match == "")
394     {
395         dbusObject = std::make_pair(
396             std::move(objectTree.begin()->first),
397             std::move(objectTree.begin()->second.begin()->first));
398         return ec;
399     }
400 
401     // else search the match string in the object path
402     auto found = std::find_if(
403         objectTree.begin(), objectTree.end(), [&match](const auto& object) {
404             return (object.first.find(match) != std::string::npos);
405         });
406 
407     if (found == objectTree.end())
408     {
409         lg2::error("Failed to find object which matches: {MATCH}, "
410                    "NetFn: {NETFN}, Cmd: {CMD}",
411                    "MATCH", match, "NETFN", lg2::hex, ctx->netFn, "CMD",
412                    lg2::hex, ctx->cmd);
413         // set ec
414         return boost::system::errc::make_error_code(
415             boost::system::errc::no_such_file_or_directory);
416     }
417 
418     dbusObject = std::make_pair(std::move(found->first),
419                                 std::move(found->second.begin()->first));
420     return ec;
421 }
422 
getAllDbusProperties(Context::ptr ctx,const std::string & service,const std::string & objPath,const std::string & interface,PropertyMap & properties)423 boost::system::error_code getAllDbusProperties(
424     Context::ptr ctx, const std::string& service, const std::string& objPath,
425     const std::string& interface, PropertyMap& properties)
426 {
427     boost::system::error_code ec;
428     properties = ctx->bus->yield_method_call<PropertyMap>(
429         ctx->yield, ec, service.c_str(), objPath.c_str(), PROP_INTF,
430         METHOD_GET_ALL, interface);
431     return ec;
432 }
433 
setDbusProperty(Context::ptr ctx,const std::string & service,const std::string & objPath,const std::string & interface,const std::string & property,const Value & value)434 boost::system::error_code setDbusProperty(
435     Context::ptr ctx, const std::string& service, const std::string& objPath,
436     const std::string& interface, const std::string& property,
437     const Value& value)
438 {
439     boost::system::error_code ec;
440     ctx->bus->yield_method_call(ctx->yield, ec, service.c_str(),
441                                 objPath.c_str(), PROP_INTF, METHOD_SET,
442                                 interface, property, value);
443     return ec;
444 }
445 
getAllDbusObjects(Context::ptr ctx,const std::string & serviceRoot,const std::string & interface,const std::string & match,ObjectTree & objectTree)446 boost::system::error_code getAllDbusObjects(
447     Context::ptr ctx, const std::string& serviceRoot,
448     const std::string& interface, const std::string& match,
449     ObjectTree& objectTree)
450 {
451     std::vector<std::string> interfaces;
452     interfaces.emplace_back(interface);
453 
454     auto depth = 0;
455     boost::system::error_code ec =
456         getSubTree(ctx, interfaces, serviceRoot, depth, objectTree);
457     if (ec)
458     {
459         return ec;
460     }
461 
462     for (auto it = objectTree.begin(); it != objectTree.end();)
463     {
464         if (it->first.find(match) == std::string::npos)
465         {
466             it = objectTree.erase(it);
467         }
468         else
469         {
470             ++it;
471         }
472     }
473 
474     return ec;
475 }
476 
deleteAllDbusObjects(Context::ptr ctx,const std::string & serviceRoot,const std::string & interface,const std::string & match)477 boost::system::error_code deleteAllDbusObjects(
478     Context::ptr ctx, const std::string& serviceRoot,
479     const std::string& interface, const std::string& match)
480 {
481     ObjectTree objectTree;
482     boost::system::error_code ec =
483         getAllDbusObjects(ctx, serviceRoot, interface, match, objectTree);
484     if (ec)
485     {
486         return ec;
487     }
488 
489     for (auto& object : objectTree)
490     {
491         ctx->bus->yield_method_call(ctx->yield, ec,
492                                     object.second.begin()->first, object.first,
493                                     DELETE_INTERFACE, "Delete");
494         if (ec)
495         {
496             lg2::error("Failed to delete all objects, service: {SERVICE}, "
497                        "interface: {INTERFACE}, NetFn: {NETFN}, "
498                        "Cmd: {CMD}, Error: {ERROR}",
499                        "SERVICE", serviceRoot, "INTERFACE", interface, "NETFN",
500                        lg2::hex, ctx->netFn, "CMD", lg2::hex, ctx->cmd, "ERROR",
501                        ec.message());
502             break;
503         }
504     }
505     return ec;
506 }
507 
getManagedObjects(Context::ptr ctx,const std::string & service,const std::string & objPath,ObjectValueTree & objects)508 boost::system::error_code getManagedObjects(
509     Context::ptr ctx, const std::string& service, const std::string& objPath,
510     ObjectValueTree& objects)
511 {
512     boost::system::error_code ec;
513     objects = ctx->bus->yield_method_call<ipmi::ObjectValueTree>(
514         ctx->yield, ec, service.c_str(), objPath.c_str(),
515         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
516     return ec;
517 }
518 
getAllAncestors(Context::ptr ctx,const std::string & path,const InterfaceList & interfaces,ObjectTree & objectTree)519 boost::system::error_code getAllAncestors(
520     Context::ptr ctx, const std::string& path, const InterfaceList& interfaces,
521     ObjectTree& objectTree)
522 {
523     std::string interfaceList = convertToString(interfaces);
524 
525     boost::system::error_code ec;
526     objectTree = ctx->bus->yield_method_call<ObjectTree>(
527         ctx->yield, ec, MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
528         "GetAncestors", path, interfaceList);
529 
530     if (ec)
531     {
532         return ec;
533     }
534 
535     if (objectTree.empty())
536     {
537         lg2::error("No Object has implemented the interface: {INTERFACE}, "
538                    "path: {PATH}",
539                    "INTERFACE", interfaceList, "PATH", path);
540         elog<InternalFailure>();
541     }
542 
543     return ec;
544 }
545 
callDbusMethod(Context::ptr ctx,const std::string & service,const std::string & objPath,const std::string & interface,const std::string & method)546 boost::system::error_code callDbusMethod(
547     Context::ptr ctx, const std::string& service, const std::string& objPath,
548     const std::string& interface, const std::string& method)
549 {
550     boost::system::error_code ec;
551     ctx->bus->yield_method_call(ctx->yield, ec, service, objPath, interface,
552                                 method);
553     return ec;
554 }
555 
556 /********* End co-routine yielding alternatives ***************/
557 
i2cWriteRead(std::string i2cBus,const uint8_t targetAddr,std::vector<uint8_t> writeData,std::vector<uint8_t> & readBuf)558 ipmi::Cc i2cWriteRead(std::string i2cBus, const uint8_t targetAddr,
559                       std::vector<uint8_t> writeData,
560                       std::vector<uint8_t>& readBuf)
561 {
562     // Open the i2c device, for low-level combined data write/read
563     int i2cDev = ::open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
564     if (i2cDev < 0)
565     {
566         lg2::error("Failed to open i2c bus: {BUS}", "BUS", i2cBus);
567         return ipmi::ccInvalidFieldRequest;
568     }
569 
570     const size_t writeCount = writeData.size();
571     const size_t readCount = readBuf.size();
572     int msgCount = 0;
573     i2c_msg i2cmsg[2] = {};
574     if (writeCount)
575     {
576         // Data will be writtern to the target address
577         i2cmsg[msgCount].addr = targetAddr;
578         i2cmsg[msgCount].flags = 0x00;
579         i2cmsg[msgCount].len = writeCount;
580         i2cmsg[msgCount].buf = writeData.data();
581         msgCount++;
582     }
583     if (readCount)
584     {
585         // Data will be read into the buffer from the target address
586         i2cmsg[msgCount].addr = targetAddr;
587         i2cmsg[msgCount].flags = I2C_M_RD;
588         i2cmsg[msgCount].len = readCount;
589         i2cmsg[msgCount].buf = readBuf.data();
590         msgCount++;
591     }
592 
593     i2c_rdwr_ioctl_data msgReadWrite = {};
594     msgReadWrite.msgs = i2cmsg;
595     msgReadWrite.nmsgs = msgCount;
596 
597     // Perform the combined write/read
598     int ret = ::ioctl(i2cDev, I2C_RDWR, &msgReadWrite);
599     ::close(i2cDev);
600 
601     if (ret < 0)
602     {
603         lg2::error("I2C WR Failed! {RET}", "RET", ret);
604         return ipmi::ccUnspecifiedError;
605     }
606     if (readCount)
607     {
608         readBuf.resize(msgReadWrite.msgs[msgCount - 1].len);
609     }
610 
611     return ipmi::ccSuccess;
612 }
613 
614 } // namespace ipmi
615