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