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