xref: /openbmc/entity-manager/src/fru_device.cpp (revision 3ce9143f)
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 /// \file fru_device.cpp
17 
18 #include "fru_utils.hpp"
19 #include "utils.hpp"
20 
21 #include <fcntl.h>
22 #include <sys/inotify.h>
23 #include <sys/ioctl.h>
24 
25 #include <boost/algorithm/string/predicate.hpp>
26 #include <boost/asio/deadline_timer.hpp>
27 #include <boost/asio/io_service.hpp>
28 #include <boost/container/flat_map.hpp>
29 #include <nlohmann/json.hpp>
30 #include <sdbusplus/asio/connection.hpp>
31 #include <sdbusplus/asio/object_server.hpp>
32 
33 #include <array>
34 #include <cerrno>
35 #include <chrono>
36 #include <ctime>
37 #include <filesystem>
38 #include <fstream>
39 #include <functional>
40 #include <future>
41 #include <iomanip>
42 #include <iostream>
43 #include <limits>
44 #include <map>
45 #include <regex>
46 #include <set>
47 #include <sstream>
48 #include <string>
49 #include <thread>
50 #include <utility>
51 #include <variant>
52 #include <vector>
53 
54 extern "C"
55 {
56 #include <i2c/smbus.h>
57 #include <linux/i2c-dev.h>
58 }
59 
60 namespace fs = std::filesystem;
61 static constexpr bool debug = false;
62 constexpr size_t maxFruSize = 512;
63 constexpr size_t maxEepromPageIndex = 255;
64 constexpr size_t busTimeoutSeconds = 5;
65 
66 constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
67 
68 const static constexpr char* baseboardFruLocation =
69     "/etc/fru/baseboard.fru.bin";
70 
71 const static constexpr char* i2CDevLocation = "/dev";
72 
73 static std::set<size_t> busBlacklist;
74 struct FindDevicesWithCallback;
75 
76 static boost::container::flat_map<
77     std::pair<size_t, size_t>, std::shared_ptr<sdbusplus::asio::dbus_interface>>
78     foundDevices;
79 
80 static boost::container::flat_map<size_t, std::set<size_t>> failedAddresses;
81 
82 boost::asio::io_service io;
83 
84 uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
85                           std::vector<uint8_t>::const_iterator end);
86 bool updateFRUProperty(
87     const std::string& assetTag, uint32_t bus, uint32_t address,
88     const std::string& propertyName,
89     boost::container::flat_map<
90         std::pair<size_t, size_t>,
91         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
92     size_t& unknownBusObjectCount, const bool& powerIsOn,
93     sdbusplus::asio::object_server& objServer,
94     std::shared_ptr<sdbusplus::asio::connection>& systemBus);
95 
96 // Given a bus/address, produce the path in sysfs for an eeprom.
97 static std::string getEepromPath(size_t bus, size_t address)
98 {
99     std::stringstream output;
100     output << "/sys/bus/i2c/devices/" << bus << "-" << std::right
101            << std::setfill('0') << std::setw(4) << std::hex << address
102            << "/eeprom";
103     return output.str();
104 }
105 
106 static bool hasEepromFile(size_t bus, size_t address)
107 {
108     auto path = getEepromPath(bus, address);
109     try
110     {
111         return fs::exists(path);
112     }
113     catch (...)
114     {
115         return false;
116     }
117 }
118 
119 static ssize_t readFromEeprom(int flag __attribute__((unused)), int fd,
120                               uint16_t address __attribute__((unused)),
121                               off_t offset, size_t len, uint8_t* buf)
122 {
123     auto result = lseek(fd, offset, SEEK_SET);
124     if (result < 0)
125     {
126         std::cerr << "failed to seek\n";
127         return -1;
128     }
129 
130     return read(fd, buf, len);
131 }
132 
133 static int busStrToInt(const std::string& busName)
134 {
135     auto findBus = busName.rfind('-');
136     if (findBus == std::string::npos)
137     {
138         return -1;
139     }
140     return std::stoi(busName.substr(findBus + 1));
141 }
142 
143 static int getRootBus(size_t bus)
144 {
145     auto ec = std::error_code();
146     auto path = std::filesystem::read_symlink(
147         std::filesystem::path("/sys/bus/i2c/devices/i2c-" +
148                               std::to_string(bus) + "/mux_device"),
149         ec);
150     if (ec)
151     {
152         return -1;
153     }
154 
155     std::string filename = path.filename();
156     auto findBus = filename.find('-');
157     if (findBus == std::string::npos)
158     {
159         return -1;
160     }
161     return std::stoi(filename.substr(0, findBus));
162 }
163 
164 static bool isMuxBus(size_t bus)
165 {
166     return is_symlink(std::filesystem::path(
167         "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"));
168 }
169 
170 static void makeProbeInterface(size_t bus, size_t address,
171                                sdbusplus::asio::object_server& objServer)
172 {
173     if (isMuxBus(bus))
174     {
175         return; // the mux buses are random, no need to publish
176     }
177     auto [it, success] = foundDevices.emplace(
178         std::make_pair(bus, address),
179         objServer.add_interface(
180             "/xyz/openbmc_project/FruDevice/" + std::to_string(bus) + "_" +
181                 std::to_string(address),
182             "xyz.openbmc_project.Inventory.Item.I2CDevice"));
183     if (!success)
184     {
185         return; // already added
186     }
187     it->second->register_property("Bus", bus);
188     it->second->register_property("Address", address);
189     it->second->initialize();
190 }
191 
192 static int isDevice16Bit(int file)
193 {
194     // Set the higher data word address bits to 0. It's safe on 8-bit addressing
195     // EEPROMs because it doesn't write any actual data.
196     int ret = i2c_smbus_write_byte(file, 0);
197     if (ret < 0)
198     {
199         return ret;
200     }
201 
202     /* Get first byte */
203     int byte1 = i2c_smbus_read_byte_data(file, 0);
204     if (byte1 < 0)
205     {
206         return byte1;
207     }
208     /* Read 7 more bytes, it will read same first byte in case of
209      * 8 bit but it will read next byte in case of 16 bit
210      */
211     for (int i = 0; i < 7; i++)
212     {
213         int byte2 = i2c_smbus_read_byte_data(file, 0);
214         if (byte2 < 0)
215         {
216             return byte2;
217         }
218         if (byte2 != byte1)
219         {
220             return 1;
221         }
222     }
223     return 0;
224 }
225 
226 // Issue an I2C transaction to first write to_slave_buf_len bytes,then read
227 // from_slave_buf_len bytes.
228 static int i2cSmbusWriteThenRead(int file, uint16_t address,
229                                  uint8_t* toSlaveBuf, uint8_t toSlaveBufLen,
230                                  uint8_t* fromSlaveBuf, uint8_t fromSlaveBufLen)
231 {
232     if (toSlaveBuf == nullptr || toSlaveBufLen == 0 ||
233         fromSlaveBuf == nullptr || fromSlaveBufLen == 0)
234     {
235         return -1;
236     }
237 
238 #define SMBUS_IOCTL_WRITE_THEN_READ_MSG_COUNT 2
239     struct i2c_msg msgs[SMBUS_IOCTL_WRITE_THEN_READ_MSG_COUNT];
240     struct i2c_rdwr_ioctl_data rdwr;
241 
242     msgs[0].addr = address;
243     msgs[0].flags = 0;
244     msgs[0].len = toSlaveBufLen;
245     msgs[0].buf = toSlaveBuf;
246     msgs[1].addr = address;
247     msgs[1].flags = I2C_M_RD;
248     msgs[1].len = fromSlaveBufLen;
249     msgs[1].buf = fromSlaveBuf;
250 
251     rdwr.msgs = msgs;
252     rdwr.nmsgs = SMBUS_IOCTL_WRITE_THEN_READ_MSG_COUNT;
253 
254     int ret = ioctl(file, I2C_RDWR, &rdwr);
255 
256     return (ret == SMBUS_IOCTL_WRITE_THEN_READ_MSG_COUNT) ? msgs[1].len : -1;
257 }
258 
259 static ssize_t readBlockData(int flag, int file, uint16_t address, off_t offset,
260                              size_t len, uint8_t* buf)
261 {
262     if (flag == 0)
263     {
264         return i2c_smbus_read_i2c_block_data(file, static_cast<uint8_t>(offset),
265                                              len, buf);
266     }
267 
268     offset = htobe16(offset);
269     return i2cSmbusWriteThenRead(
270         file, address, reinterpret_cast<uint8_t*>(&offset), 2, buf, len);
271 }
272 
273 // TODO: This code is very similar to the non-eeprom version and can be merged
274 // with some tweaks.
275 static std::vector<uint8_t> processEeprom(int bus, int address)
276 {
277     auto path = getEepromPath(bus, address);
278 
279     int file = open(path.c_str(), O_RDONLY);
280     if (file < 0)
281     {
282         std::cerr << "Unable to open eeprom file: " << path << "\n";
283         return {};
284     }
285 
286     std::string errorMessage = "eeprom at " + std::to_string(bus) +
287                                " address " + std::to_string(address);
288     std::vector<uint8_t> device = readFRUContents(
289         0, file, static_cast<uint16_t>(address), readFromEeprom, errorMessage);
290 
291     close(file);
292     return device;
293 }
294 
295 std::set<int> findI2CEeproms(int i2cBus,
296                              const std::shared_ptr<DeviceMap>& devices)
297 {
298     std::set<int> foundList;
299 
300     std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus);
301 
302     // For each file listed under the i2c device
303     // NOTE: This should be faster than just checking for each possible address
304     // path.
305     for (const auto& p : fs::directory_iterator(path))
306     {
307         const std::string node = p.path().string();
308         std::smatch m;
309         bool found =
310             std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)"));
311 
312         if (!found)
313         {
314             continue;
315         }
316         if (m.size() != 2)
317         {
318             std::cerr << "regex didn't capture\n";
319             continue;
320         }
321 
322         std::ssub_match subMatch = m[1];
323         std::string addressString = subMatch.str();
324 
325         std::size_t ignored;
326         const int hexBase = 16;
327         int address = std::stoi(addressString, &ignored, hexBase);
328 
329         const std::string eeprom = node + "/eeprom";
330 
331         try
332         {
333             if (!fs::exists(eeprom))
334             {
335                 continue;
336             }
337         }
338         catch (...)
339         {
340             continue;
341         }
342 
343         // There is an eeprom file at this address, it may have invalid
344         // contents, but we found it.
345         foundList.insert(address);
346 
347         std::vector<uint8_t> device = processEeprom(i2cBus, address);
348         if (!device.empty())
349         {
350             devices->emplace(address, device);
351         }
352     }
353 
354     return foundList;
355 }
356 
357 int getBusFRUs(int file, int first, int last, int bus,
358                std::shared_ptr<DeviceMap> devices, const bool& powerIsOn,
359                sdbusplus::asio::object_server& objServer)
360 {
361 
362     std::future<int> future = std::async(std::launch::async, [&]() {
363         // NOTE: When reading the devices raw on the bus, it can interfere with
364         // the driver's ability to operate, therefore read eeproms first before
365         // scanning for devices without drivers. Several experiments were run
366         // and it was determined that if there were any devices on the bus
367         // before the eeprom was hit and read, the eeprom driver wouldn't open
368         // while the bus device was open. An experiment was not performed to see
369         // if this issue was resolved if the i2c bus device was closed, but
370         // hexdumps of the eeprom later were successful.
371 
372         // Scan for i2c eeproms loaded on this bus.
373         std::set<int> skipList = findI2CEeproms(bus, devices);
374         std::set<size_t>& failedItems = failedAddresses[bus];
375 
376         std::set<size_t>* rootFailures = nullptr;
377         int rootBus = getRootBus(bus);
378 
379         if (rootBus >= 0)
380         {
381             rootFailures = &(failedAddresses[rootBus]);
382         }
383 
384         constexpr int startSkipSlaveAddr = 0;
385         constexpr int endSkipSlaveAddr = 12;
386 
387         for (int ii = first; ii <= last; ii++)
388         {
389             if (skipList.find(ii) != skipList.end())
390             {
391                 continue;
392             }
393             // skipping since no device is present in this range
394             if (ii >= startSkipSlaveAddr && ii <= endSkipSlaveAddr)
395             {
396                 continue;
397             }
398             // Set slave address
399             if (ioctl(file, I2C_SLAVE, ii) < 0)
400             {
401                 std::cerr << "device at bus " << bus << " address " << ii
402                           << " busy\n";
403                 continue;
404             }
405             // probe
406             if (i2c_smbus_read_byte(file) < 0)
407             {
408                 continue;
409             }
410 
411             if (debug)
412             {
413                 std::cout << "something at bus " << bus << " addr " << ii
414                           << "\n";
415             }
416 
417             makeProbeInterface(bus, ii, objServer);
418 
419             if (failedItems.find(ii) != failedItems.end())
420             {
421                 // if we failed to read it once, unlikely we can read it later
422                 continue;
423             }
424 
425             if (rootFailures != nullptr)
426             {
427                 if (rootFailures->find(ii) != rootFailures->end())
428                 {
429                     continue;
430                 }
431             }
432 
433             /* Check for Device type if it is 8 bit or 16 bit */
434             int flag = isDevice16Bit(file);
435             if (flag < 0)
436             {
437                 std::cerr << "failed to read bus " << bus << " address " << ii
438                           << "\n";
439                 if (powerIsOn)
440                 {
441                     failedItems.insert(ii);
442                 }
443                 continue;
444             }
445 
446             std::string errorMessage =
447                 "bus " + std::to_string(bus) + " address " + std::to_string(ii);
448             std::vector<uint8_t> device =
449                 readFRUContents(flag, file, static_cast<uint16_t>(ii),
450                                 readBlockData, errorMessage);
451             if (device.empty())
452             {
453                 continue;
454             }
455 
456             devices->emplace(ii, device);
457         }
458         return 1;
459     });
460     std::future_status status =
461         future.wait_for(std::chrono::seconds(busTimeoutSeconds));
462     if (status == std::future_status::timeout)
463     {
464         std::cerr << "Error reading bus " << bus << "\n";
465         if (powerIsOn)
466         {
467             busBlacklist.insert(bus);
468         }
469         close(file);
470         return -1;
471     }
472 
473     close(file);
474     return future.get();
475 }
476 
477 void loadBlacklist(const char* path)
478 {
479     std::ifstream blacklistStream(path);
480     if (!blacklistStream.good())
481     {
482         // File is optional.
483         std::cerr << "Cannot open blacklist file.\n\n";
484         return;
485     }
486 
487     nlohmann::json data =
488         nlohmann::json::parse(blacklistStream, nullptr, false);
489     if (data.is_discarded())
490     {
491         std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
492                      "exiting\n";
493         std::exit(EXIT_FAILURE);
494     }
495 
496     // It's expected to have at least one field, "buses" that is an array of the
497     // buses by integer. Allow for future options to exclude further aspects,
498     // such as specific addresses or ranges.
499     if (data.type() != nlohmann::json::value_t::object)
500     {
501         std::cerr << "Illegal blacklist, expected to read dictionary\n";
502         std::exit(EXIT_FAILURE);
503     }
504 
505     // If buses field is missing, that's fine.
506     if (data.count("buses") == 1)
507     {
508         // Parse the buses array after a little validation.
509         auto buses = data.at("buses");
510         if (buses.type() != nlohmann::json::value_t::array)
511         {
512             // Buses field present but invalid, therefore this is an error.
513             std::cerr << "Invalid contents for blacklist buses field\n";
514             std::exit(EXIT_FAILURE);
515         }
516 
517         // Catch exception here for type mis-match.
518         try
519         {
520             for (const auto& bus : buses)
521             {
522                 busBlacklist.insert(bus.get<size_t>());
523             }
524         }
525         catch (const nlohmann::detail::type_error& e)
526         {
527             // Type mis-match is a critical error.
528             std::cerr << "Invalid bus type: " << e.what() << "\n";
529             std::exit(EXIT_FAILURE);
530         }
531     }
532 
533     return;
534 }
535 
536 static void findI2CDevices(const std::vector<fs::path>& i2cBuses,
537                            BusMap& busmap, const bool& powerIsOn,
538                            sdbusplus::asio::object_server& objServer)
539 {
540     for (auto& i2cBus : i2cBuses)
541     {
542         int bus = busStrToInt(i2cBus);
543 
544         if (bus < 0)
545         {
546             std::cerr << "Cannot translate " << i2cBus << " to int\n";
547             continue;
548         }
549         if (busBlacklist.find(bus) != busBlacklist.end())
550         {
551             continue; // skip previously failed busses
552         }
553 
554         int rootBus = getRootBus(bus);
555         if (busBlacklist.find(rootBus) != busBlacklist.end())
556         {
557             continue;
558         }
559 
560         auto file = open(i2cBus.c_str(), O_RDWR);
561         if (file < 0)
562         {
563             std::cerr << "unable to open i2c device " << i2cBus.string()
564                       << "\n";
565             continue;
566         }
567         unsigned long funcs = 0;
568 
569         if (ioctl(file, I2C_FUNCS, &funcs) < 0)
570         {
571             std::cerr
572                 << "Error: Could not get the adapter functionality matrix bus "
573                 << bus << "\n";
574             close(file);
575             continue;
576         }
577         if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE) ||
578             !(I2C_FUNC_SMBUS_READ_I2C_BLOCK))
579         {
580             std::cerr << "Error: Can't use SMBus Receive Byte command bus "
581                       << bus << "\n";
582             continue;
583         }
584         auto& device = busmap[bus];
585         device = std::make_shared<DeviceMap>();
586 
587         //  i2cdetect by default uses the range 0x03 to 0x77, as
588         //  this is  what we have tested with, use this range. Could be
589         //  changed in future.
590         if (debug)
591         {
592             std::cerr << "Scanning bus " << bus << "\n";
593         }
594 
595         // fd is closed in this function in case the bus locks up
596         getBusFRUs(file, 0x03, 0x77, bus, device, powerIsOn, objServer);
597 
598         if (debug)
599         {
600             std::cerr << "Done scanning bus " << bus << "\n";
601         }
602     }
603 }
604 
605 // this class allows an async response after all i2c devices are discovered
606 struct FindDevicesWithCallback :
607     std::enable_shared_from_this<FindDevicesWithCallback>
608 {
609     FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses,
610                             BusMap& busmap, const bool& powerIsOn,
611                             sdbusplus::asio::object_server& objServer,
612                             std::function<void(void)>&& callback) :
613         _i2cBuses(i2cBuses),
614         _busMap(busmap), _powerIsOn(powerIsOn), _objServer(objServer),
615         _callback(std::move(callback))
616     {}
617     ~FindDevicesWithCallback()
618     {
619         _callback();
620     }
621     void run()
622     {
623         findI2CDevices(_i2cBuses, _busMap, _powerIsOn, _objServer);
624     }
625 
626     const std::vector<fs::path>& _i2cBuses;
627     BusMap& _busMap;
628     const bool& _powerIsOn;
629     sdbusplus::asio::object_server& _objServer;
630     std::function<void(void)> _callback;
631 };
632 
633 void addFruObjectToDbus(
634     std::vector<uint8_t>& device,
635     boost::container::flat_map<
636         std::pair<size_t, size_t>,
637         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
638     uint32_t bus, uint32_t address, size_t& unknownBusObjectCount,
639     const bool& powerIsOn, sdbusplus::asio::object_server& objServer,
640     std::shared_ptr<sdbusplus::asio::connection>& systemBus)
641 {
642     boost::container::flat_map<std::string, std::string> formattedFRU;
643     resCodes res = formatIPMIFRU(device, formattedFRU);
644     if (res == resCodes::resErr)
645     {
646         std::cerr << "failed to parse FRU for device at bus " << bus
647                   << " address " << address << "\n";
648         return;
649     }
650     if (res == resCodes::resWarn)
651     {
652         std::cerr << "there were warnings while parsing FRU for device at bus "
653                   << bus << " address " << address << "\n";
654     }
655 
656     auto productNameFind = formattedFRU.find("BOARD_PRODUCT_NAME");
657     std::string productName;
658     // Not found under Board section or an empty string.
659     if (productNameFind == formattedFRU.end() ||
660         productNameFind->second.empty())
661     {
662         productNameFind = formattedFRU.find("PRODUCT_PRODUCT_NAME");
663     }
664     // Found under Product section and not an empty string.
665     if (productNameFind != formattedFRU.end() &&
666         !productNameFind->second.empty())
667     {
668         productName = productNameFind->second;
669         std::regex illegalObject("[^A-Za-z0-9_]");
670         productName = std::regex_replace(productName, illegalObject, "_");
671     }
672     else
673     {
674         productName = "UNKNOWN" + std::to_string(unknownBusObjectCount);
675         unknownBusObjectCount++;
676     }
677 
678     productName = "/xyz/openbmc_project/FruDevice/" + productName;
679     // avoid duplicates by checking to see if on a mux
680     if (bus > 0)
681     {
682         int highest = -1;
683         bool found = false;
684 
685         for (auto const& busIface : dbusInterfaceMap)
686         {
687             std::string path = busIface.second->get_object_path();
688             if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
689             {
690                 if (isMuxBus(bus) && bus != busIface.first.first &&
691                     address == busIface.first.second &&
692                     (getFRUInfo(static_cast<uint8_t>(busIface.first.first),
693                                 static_cast<uint8_t>(busIface.first.second)) ==
694                      getFRUInfo(static_cast<uint8_t>(bus),
695                                 static_cast<uint8_t>(address))))
696                 {
697                     // This device is already added to the lower numbered bus,
698                     // do not replicate it.
699                     return;
700                 }
701 
702                 // Check if the match named has extra information.
703                 found = true;
704                 std::smatch baseMatch;
705 
706                 bool match = std::regex_match(
707                     path, baseMatch, std::regex(productName + "_(\\d+)$"));
708                 if (match)
709                 {
710                     if (baseMatch.size() == 2)
711                     {
712                         std::ssub_match baseSubMatch = baseMatch[1];
713                         std::string base = baseSubMatch.str();
714 
715                         int value = std::stoi(base);
716                         highest = (value > highest) ? value : highest;
717                     }
718                 }
719             }
720         } // end searching objects
721 
722         if (found)
723         {
724             // We found something with the same name.  If highest was still -1,
725             // it means this new entry will be _0.
726             productName += "_";
727             productName += std::to_string(++highest);
728         }
729     }
730 
731     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
732         objServer.add_interface(productName, "xyz.openbmc_project.FruDevice");
733     dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface;
734 
735     for (auto& property : formattedFRU)
736     {
737 
738         std::regex_replace(property.second.begin(), property.second.begin(),
739                            property.second.end(), nonAsciiRegex, "_");
740         if (property.second.empty() && property.first != "PRODUCT_ASSET_TAG")
741         {
742             continue;
743         }
744         std::string key =
745             std::regex_replace(property.first, nonAsciiRegex, "_");
746 
747         if (property.first == "PRODUCT_ASSET_TAG")
748         {
749             std::string propertyName = property.first;
750             iface->register_property(
751                 key, property.second + '\0',
752                 [bus, address, propertyName, &dbusInterfaceMap,
753                  &unknownBusObjectCount, &powerIsOn, &objServer,
754                  &systemBus](const std::string& req, std::string& resp) {
755                     if (strcmp(req.c_str(), resp.c_str()) != 0)
756                     {
757                         // call the method which will update
758                         if (updateFRUProperty(req, bus, address, propertyName,
759                                               dbusInterfaceMap,
760                                               unknownBusObjectCount, powerIsOn,
761                                               objServer, systemBus))
762                         {
763                             resp = req;
764                         }
765                         else
766                         {
767                             throw std::invalid_argument(
768                                 "FRU property update failed.");
769                         }
770                     }
771                     return 1;
772                 });
773         }
774         else if (!iface->register_property(key, property.second + '\0'))
775         {
776             std::cerr << "illegal key: " << key << "\n";
777         }
778         if (debug)
779         {
780             std::cout << property.first << ": " << property.second << "\n";
781         }
782     }
783 
784     // baseboard will be 0, 0
785     iface->register_property("BUS", bus);
786     iface->register_property("ADDRESS", address);
787 
788     iface->initialize();
789 }
790 
791 static bool readBaseboardFRU(std::vector<uint8_t>& baseboardFRU)
792 {
793     // try to read baseboard fru from file
794     std::ifstream baseboardFRUFile(baseboardFruLocation, std::ios::binary);
795     if (baseboardFRUFile.good())
796     {
797         baseboardFRUFile.seekg(0, std::ios_base::end);
798         size_t fileSize = static_cast<size_t>(baseboardFRUFile.tellg());
799         baseboardFRU.resize(fileSize);
800         baseboardFRUFile.seekg(0, std::ios_base::beg);
801         baseboardFRUFile.read(reinterpret_cast<char*>(baseboardFRU.data()),
802                               fileSize);
803     }
804     else
805     {
806         return false;
807     }
808     return true;
809 }
810 
811 bool writeFRU(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru)
812 {
813     boost::container::flat_map<std::string, std::string> tmp;
814     if (fru.size() > maxFruSize)
815     {
816         std::cerr << "Invalid fru.size() during writeFRU\n";
817         return false;
818     }
819     // verify legal fru by running it through fru parsing logic
820     if (formatIPMIFRU(fru, tmp) != resCodes::resOK)
821     {
822         std::cerr << "Invalid fru format during writeFRU\n";
823         return false;
824     }
825     // baseboard fru
826     if (bus == 0 && address == 0)
827     {
828         std::ofstream file(baseboardFruLocation, std::ios_base::binary);
829         if (!file.good())
830         {
831             std::cerr << "Error opening file " << baseboardFruLocation << "\n";
832             throw DBusInternalError();
833             return false;
834         }
835         file.write(reinterpret_cast<const char*>(fru.data()), fru.size());
836         return file.good();
837     }
838 
839     if (hasEepromFile(bus, address))
840     {
841         auto path = getEepromPath(bus, address);
842         int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC);
843         if (eeprom < 0)
844         {
845             std::cerr << "unable to open i2c device " << path << "\n";
846             throw DBusInternalError();
847             return false;
848         }
849 
850         ssize_t writtenBytes = write(eeprom, fru.data(), fru.size());
851         if (writtenBytes < 0)
852         {
853             std::cerr << "unable to write to i2c device " << path << "\n";
854             close(eeprom);
855             throw DBusInternalError();
856             return false;
857         }
858 
859         close(eeprom);
860         return true;
861     }
862 
863     std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
864 
865     int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
866     if (file < 0)
867     {
868         std::cerr << "unable to open i2c device " << i2cBus << "\n";
869         throw DBusInternalError();
870         return false;
871     }
872     if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
873     {
874         std::cerr << "unable to set device address\n";
875         close(file);
876         throw DBusInternalError();
877         return false;
878     }
879 
880     constexpr const size_t retryMax = 2;
881     uint16_t index = 0;
882     size_t retries = retryMax;
883     while (index < fru.size())
884     {
885         if ((index && ((index % (maxEepromPageIndex + 1)) == 0)) &&
886             (retries == retryMax))
887         {
888             // The 4K EEPROM only uses the A2 and A1 device address bits
889             // with the third bit being a memory page address bit.
890             if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0)
891             {
892                 std::cerr << "unable to set device address\n";
893                 close(file);
894                 throw DBusInternalError();
895                 return false;
896             }
897         }
898 
899         if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index),
900                                       fru[index]) < 0)
901         {
902             if (!retries--)
903             {
904                 std::cerr << "error writing fru: " << strerror(errno) << "\n";
905                 close(file);
906                 throw DBusInternalError();
907                 return false;
908             }
909         }
910         else
911         {
912             retries = retryMax;
913             index++;
914         }
915         // most eeproms require 5-10ms between writes
916         std::this_thread::sleep_for(std::chrono::milliseconds(10));
917     }
918     close(file);
919     return true;
920 }
921 
922 void rescanOneBus(
923     BusMap& busmap, uint8_t busNum,
924     boost::container::flat_map<
925         std::pair<size_t, size_t>,
926         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
927     bool dbusCall, size_t& unknownBusObjectCount, const bool& powerIsOn,
928     sdbusplus::asio::object_server& objServer,
929     std::shared_ptr<sdbusplus::asio::connection>& systemBus)
930 {
931     for (auto& [pair, interface] : foundDevices)
932     {
933         if (pair.first == static_cast<size_t>(busNum))
934         {
935             objServer.remove_interface(interface);
936             foundDevices.erase(pair);
937         }
938     }
939 
940     fs::path busPath = fs::path("/dev/i2c-" + std::to_string(busNum));
941     if (!fs::exists(busPath))
942     {
943         if (dbusCall)
944         {
945             std::cerr << "Unable to access i2c bus " << static_cast<int>(busNum)
946                       << "\n";
947             throw std::invalid_argument("Invalid Bus.");
948         }
949         return;
950     }
951 
952     std::vector<fs::path> i2cBuses;
953     i2cBuses.emplace_back(busPath);
954 
955     auto scan = std::make_shared<FindDevicesWithCallback>(
956         i2cBuses, busmap, powerIsOn, objServer,
957         [busNum, &busmap, &dbusInterfaceMap, &unknownBusObjectCount, &powerIsOn,
958          &objServer, &systemBus]() {
959             for (auto& busIface : dbusInterfaceMap)
960             {
961                 if (busIface.first.first == static_cast<size_t>(busNum))
962                 {
963                     objServer.remove_interface(busIface.second);
964                 }
965             }
966             auto found = busmap.find(busNum);
967             if (found == busmap.end() || found->second == nullptr)
968             {
969                 return;
970             }
971             for (auto& device : *(found->second))
972             {
973                 addFruObjectToDbus(device.second, dbusInterfaceMap,
974                                    static_cast<uint32_t>(busNum), device.first,
975                                    unknownBusObjectCount, powerIsOn, objServer,
976                                    systemBus);
977             }
978         });
979     scan->run();
980 }
981 
982 void rescanBusses(
983     BusMap& busmap,
984     boost::container::flat_map<
985         std::pair<size_t, size_t>,
986         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
987     size_t& unknownBusObjectCount, const bool& powerIsOn,
988     sdbusplus::asio::object_server& objServer,
989     std::shared_ptr<sdbusplus::asio::connection>& systemBus)
990 {
991     static boost::asio::deadline_timer timer(io);
992     timer.expires_from_now(boost::posix_time::seconds(1));
993 
994     // setup an async wait in case we get flooded with requests
995     timer.async_wait([&](const boost::system::error_code&) {
996         auto devDir = fs::path("/dev/");
997         std::vector<fs::path> i2cBuses;
998 
999         boost::container::flat_map<size_t, fs::path> busPaths;
1000         if (!getI2cDevicePaths(devDir, busPaths))
1001         {
1002             std::cerr << "unable to find i2c devices\n";
1003             return;
1004         }
1005 
1006         for (const auto& busPath : busPaths)
1007         {
1008             i2cBuses.emplace_back(busPath.second);
1009         }
1010 
1011         busmap.clear();
1012         for (auto& [pair, interface] : foundDevices)
1013         {
1014             objServer.remove_interface(interface);
1015         }
1016         foundDevices.clear();
1017 
1018         auto scan = std::make_shared<FindDevicesWithCallback>(
1019             i2cBuses, busmap, powerIsOn, objServer, [&]() {
1020                 for (auto& busIface : dbusInterfaceMap)
1021                 {
1022                     objServer.remove_interface(busIface.second);
1023                 }
1024 
1025                 dbusInterfaceMap.clear();
1026                 unknownBusObjectCount = 0;
1027 
1028                 // todo, get this from a more sensable place
1029                 std::vector<uint8_t> baseboardFRU;
1030                 if (readBaseboardFRU(baseboardFRU))
1031                 {
1032                     // If no device on i2c bus 0, the insertion will happen.
1033                     auto bus0 =
1034                         busmap.try_emplace(0, std::make_shared<DeviceMap>());
1035                     bus0.first->second->emplace(0, baseboardFRU);
1036                 }
1037                 for (auto& devicemap : busmap)
1038                 {
1039                     for (auto& device : *devicemap.second)
1040                     {
1041                         addFruObjectToDbus(device.second, dbusInterfaceMap,
1042                                            devicemap.first, device.first,
1043                                            unknownBusObjectCount, powerIsOn,
1044                                            objServer, systemBus);
1045                     }
1046                 }
1047             });
1048         scan->run();
1049     });
1050 }
1051 
1052 // Details with example of Asset Tag Update
1053 // To find location of Product Info Area asset tag as per FRU specification
1054 // 1. Find product Info area starting offset (*8 - as header will be in
1055 // multiple of 8 bytes).
1056 // 2. Skip 3 bytes of product info area (like format version, area length,
1057 // and language code).
1058 // 3. Traverse manufacturer name, product name, product version, & product
1059 // serial number, by reading type/length code to reach the Asset Tag.
1060 // 4. Update the Asset Tag, reposition the product Info area in multiple of
1061 // 8 bytes. Update the Product area length and checksum.
1062 
1063 bool updateFRUProperty(
1064     const std::string& updatePropertyReq, uint32_t bus, uint32_t address,
1065     const std::string& propertyName,
1066     boost::container::flat_map<
1067         std::pair<size_t, size_t>,
1068         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
1069     size_t& unknownBusObjectCount, const bool& powerIsOn,
1070     sdbusplus::asio::object_server& objServer,
1071     std::shared_ptr<sdbusplus::asio::connection>& systemBus)
1072 {
1073     size_t updatePropertyReqLen = updatePropertyReq.length();
1074     if (updatePropertyReqLen == 1 || updatePropertyReqLen > 63)
1075     {
1076         std::cerr
1077             << "FRU field data cannot be of 1 char or more than 63 chars. "
1078                "Invalid Length "
1079             << updatePropertyReqLen << "\n";
1080         return false;
1081     }
1082 
1083     std::vector<uint8_t> fruData;
1084     try
1085     {
1086         fruData = getFRUInfo(static_cast<uint8_t>(bus),
1087                              static_cast<uint8_t>(address));
1088     }
1089     catch (const std::invalid_argument& e)
1090     {
1091         std::cerr << "Failure getting FRU Info" << e.what() << "\n";
1092         return false;
1093     }
1094 
1095     if (fruData.empty())
1096     {
1097         return false;
1098     }
1099 
1100     const std::vector<std::string>* fruAreaFieldNames;
1101 
1102     uint8_t fruAreaOffsetFieldValue = 0;
1103     size_t offset = 0;
1104     std::string areaName = propertyName.substr(0, propertyName.find('_'));
1105     std::string propertyNamePrefix = areaName + "_";
1106     auto it = std::find(fruAreaNames.begin(), fruAreaNames.end(), areaName);
1107     if (it == fruAreaNames.end())
1108     {
1109         std::cerr << "Can't parse area name for property " << propertyName
1110                   << " \n";
1111         return false;
1112     }
1113     fruAreas fruAreaToUpdate = static_cast<fruAreas>(it - fruAreaNames.begin());
1114     fruAreaOffsetFieldValue =
1115         fruData[getHeaderAreaFieldOffset(fruAreaToUpdate)];
1116     switch (fruAreaToUpdate)
1117     {
1118         case fruAreas::fruAreaChassis:
1119             offset = 3; // chassis part number offset. Skip fixed first 3 bytes
1120             fruAreaFieldNames = &chassisFruAreas;
1121             break;
1122         case fruAreas::fruAreaBoard:
1123             offset = 6; // board manufacturer offset. Skip fixed first 6 bytes
1124             fruAreaFieldNames = &boardFruAreas;
1125             break;
1126         case fruAreas::fruAreaProduct:
1127             // Manufacturer name offset. Skip fixed first 3 product fru bytes
1128             // i.e. version, area length and language code
1129             offset = 3;
1130             fruAreaFieldNames = &productFruAreas;
1131             break;
1132         default:
1133             std::cerr << "Don't know how to handle property " << propertyName
1134                       << " \n";
1135             return false;
1136     }
1137     if (fruAreaOffsetFieldValue == 0)
1138     {
1139         std::cerr << "FRU Area for " << propertyName << " not present \n";
1140         return false;
1141     }
1142 
1143     size_t fruAreaStart = fruAreaOffsetFieldValue * fruBlockSize;
1144     size_t fruAreaSize = fruData[fruAreaStart + 1] * fruBlockSize;
1145     size_t fruAreaEnd = fruAreaStart + fruAreaSize;
1146     size_t fruDataIter = fruAreaStart + offset;
1147     size_t skipToFRUUpdateField = 0;
1148     ssize_t fieldLength;
1149 
1150     bool found = false;
1151     for (auto& field : *fruAreaFieldNames)
1152     {
1153         skipToFRUUpdateField++;
1154         if (propertyName == propertyNamePrefix + field)
1155         {
1156             found = true;
1157             break;
1158         }
1159     }
1160     if (!found)
1161     {
1162         std::size_t pos = propertyName.find(fruCustomFieldName);
1163         if (pos == std::string::npos)
1164         {
1165             std::cerr << "PropertyName doesn't exist in FRU Area Vectors: "
1166                       << propertyName << "\n";
1167             return false;
1168         }
1169         std::string fieldNumStr =
1170             propertyName.substr(pos + fruCustomFieldName.length());
1171         size_t fieldNum = std::stoi(fieldNumStr);
1172         if (fieldNum == 0)
1173         {
1174             std::cerr << "PropertyName not recognized: " << propertyName
1175                       << "\n";
1176             return false;
1177         }
1178         skipToFRUUpdateField += fieldNum;
1179     }
1180 
1181     for (size_t i = 1; i < skipToFRUUpdateField; i++)
1182     {
1183         fieldLength = getFieldLength(fruData[fruDataIter]);
1184         if (fieldLength < 0)
1185         {
1186             break;
1187         }
1188         fruDataIter += 1 + fieldLength;
1189     }
1190     size_t fruUpdateFieldLoc = fruDataIter;
1191 
1192     // Push post update fru field bytes to a vector
1193     fieldLength = getFieldLength(fruData[fruUpdateFieldLoc]);
1194     if (fieldLength < 0)
1195     {
1196         std::cerr << "Property " << propertyName << " not present \n";
1197         return false;
1198     }
1199     fruDataIter += 1 + fieldLength;
1200     size_t restFRUFieldsLoc = fruDataIter;
1201     size_t endOfFieldsLoc = 0;
1202     while ((fieldLength = getFieldLength(fruData[fruDataIter])) >= 0)
1203     {
1204         if (fruDataIter >= (fruAreaStart + fruAreaSize))
1205         {
1206             fruDataIter = fruAreaStart + fruAreaSize;
1207             break;
1208         }
1209         fruDataIter += 1 + fieldLength;
1210     }
1211     endOfFieldsLoc = fruDataIter;
1212 
1213     std::vector<uint8_t> restFRUAreaFieldsData;
1214     std::copy_n(fruData.begin() + restFRUFieldsLoc,
1215                 endOfFieldsLoc - restFRUFieldsLoc + 1,
1216                 std::back_inserter(restFRUAreaFieldsData));
1217 
1218     // Push post update fru areas if any
1219     unsigned int nextFRUAreaLoc = 0;
1220     for (fruAreas nextFRUArea = fruAreas::fruAreaInternal;
1221          nextFRUArea <= fruAreas::fruAreaMultirecord; ++nextFRUArea)
1222     {
1223         unsigned int fruAreaLoc =
1224             fruData[getHeaderAreaFieldOffset(nextFRUArea)] * fruBlockSize;
1225         if ((fruAreaLoc > endOfFieldsLoc) &&
1226             ((nextFRUAreaLoc == 0) || (fruAreaLoc < nextFRUAreaLoc)))
1227         {
1228             nextFRUAreaLoc = fruAreaLoc;
1229         }
1230     }
1231     std::vector<uint8_t> restFRUAreasData;
1232     if (nextFRUAreaLoc)
1233     {
1234         std::copy_n(fruData.begin() + nextFRUAreaLoc,
1235                     fruData.size() - nextFRUAreaLoc,
1236                     std::back_inserter(restFRUAreasData));
1237     }
1238 
1239     // check FRU area size
1240     size_t fruAreaDataSize =
1241         ((fruUpdateFieldLoc - fruAreaStart + 1) + restFRUAreaFieldsData.size());
1242     size_t fruAreaAvailableSize = fruAreaSize - fruAreaDataSize;
1243     if ((updatePropertyReqLen + 1) > fruAreaAvailableSize)
1244     {
1245 
1246 #ifdef ENABLE_FRU_AREA_RESIZE
1247         size_t newFRUAreaSize = fruAreaDataSize + updatePropertyReqLen + 1;
1248         // round size to 8-byte blocks
1249         newFRUAreaSize =
1250             ((newFRUAreaSize - 1) / fruBlockSize + 1) * fruBlockSize;
1251         size_t newFRUDataSize = fruData.size() + newFRUAreaSize - fruAreaSize;
1252         fruData.resize(newFRUDataSize);
1253         fruAreaSize = newFRUAreaSize;
1254         fruAreaEnd = fruAreaStart + fruAreaSize;
1255 #else
1256         std::cerr << "FRU field length: " << updatePropertyReqLen + 1
1257                   << " should not be greater than available FRU area size: "
1258                   << fruAreaAvailableSize << "\n";
1259         return false;
1260 #endif // ENABLE_FRU_AREA_RESIZE
1261     }
1262 
1263     // write new requested property field length and data
1264     constexpr uint8_t newTypeLenMask = 0xC0;
1265     fruData[fruUpdateFieldLoc] =
1266         static_cast<uint8_t>(updatePropertyReqLen | newTypeLenMask);
1267     fruUpdateFieldLoc++;
1268     std::copy(updatePropertyReq.begin(), updatePropertyReq.end(),
1269               fruData.begin() + fruUpdateFieldLoc);
1270 
1271     // Copy remaining data to main fru area - post updated fru field vector
1272     restFRUFieldsLoc = fruUpdateFieldLoc + updatePropertyReqLen;
1273     size_t fruAreaDataEnd = restFRUFieldsLoc + restFRUAreaFieldsData.size();
1274     std::copy(restFRUAreaFieldsData.begin(), restFRUAreaFieldsData.end(),
1275               fruData.begin() + restFRUFieldsLoc);
1276 
1277     // Update final fru with new fru area length and checksum
1278     unsigned int nextFRUAreaNewLoc = updateFRUAreaLenAndChecksum(
1279         fruData, fruAreaStart, fruAreaDataEnd, fruAreaEnd);
1280 
1281 #ifdef ENABLE_FRU_AREA_RESIZE
1282     ++nextFRUAreaNewLoc;
1283     ssize_t nextFRUAreaOffsetDiff =
1284         (nextFRUAreaNewLoc - nextFRUAreaLoc) / fruBlockSize;
1285     // Append rest FRU Areas if size changed and there were other sections after
1286     // updated one
1287     if (nextFRUAreaOffsetDiff && nextFRUAreaLoc)
1288     {
1289         std::copy(restFRUAreasData.begin(), restFRUAreasData.end(),
1290                   fruData.begin() + nextFRUAreaNewLoc);
1291         // Update Common Header
1292         for (int fruArea = fruAreaInternal; fruArea <= fruAreaMultirecord;
1293              fruArea++)
1294         {
1295             unsigned int fruAreaOffsetField = getHeaderAreaFieldOffset(fruArea);
1296             size_t curFRUAreaOffset = fruData[fruAreaOffsetField];
1297             if (curFRUAreaOffset > fruAreaOffsetFieldValue)
1298             {
1299                 fruData[fruAreaOffsetField] = static_cast<int8_t>(
1300                     curFRUAreaOffset + nextFRUAreaOffsetDiff);
1301             }
1302         }
1303         // Calculate new checksum
1304         std::vector<uint8_t> headerFRUData;
1305         std::copy_n(fruData.begin(), 7, std::back_inserter(headerFRUData));
1306         size_t checksumVal = calculateChecksum(headerFRUData);
1307         fruData[7] = static_cast<uint8_t>(checksumVal);
1308         // fill zeros if FRU Area size decreased
1309         if (nextFRUAreaOffsetDiff < 0)
1310         {
1311             std::fill(fruData.begin() + nextFRUAreaNewLoc +
1312                           restFRUAreasData.size(),
1313                       fruData.end(), 0);
1314         }
1315     }
1316 #else
1317     // this is to avoid "unused variable" warning
1318     (void)nextFRUAreaNewLoc;
1319 #endif // ENABLE_FRU_AREA_RESIZE
1320     if (fruData.empty())
1321     {
1322         return false;
1323     }
1324 
1325     if (!writeFRU(static_cast<uint8_t>(bus), static_cast<uint8_t>(address),
1326                   fruData))
1327     {
1328         return false;
1329     }
1330 
1331     // Rescan the bus so that GetRawFru dbus-call fetches updated values
1332     rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1333                  objServer, systemBus);
1334     return true;
1335 }
1336 
1337 int main()
1338 {
1339     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1340     sdbusplus::asio::object_server objServer(systemBus);
1341 
1342     static size_t unknownBusObjectCount = 0;
1343     static bool powerIsOn = false;
1344     auto devDir = fs::path("/dev/");
1345     auto matchString = std::string(R"(i2c-\d+$)");
1346     std::vector<fs::path> i2cBuses;
1347 
1348     if (!findFiles(devDir, matchString, i2cBuses))
1349     {
1350         std::cerr << "unable to find i2c devices\n";
1351         return 1;
1352     }
1353 
1354     // check for and load blacklist with initial buses.
1355     loadBlacklist(blacklistPath);
1356 
1357     systemBus->request_name("xyz.openbmc_project.FruDevice");
1358 
1359     // this is a map with keys of pair(bus number, address) and values of
1360     // the object on dbus
1361     boost::container::flat_map<std::pair<size_t, size_t>,
1362                                std::shared_ptr<sdbusplus::asio::dbus_interface>>
1363         dbusInterfaceMap;
1364 
1365     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1366         objServer.add_interface("/xyz/openbmc_project/FruDevice",
1367                                 "xyz.openbmc_project.FruDeviceManager");
1368 
1369     iface->register_method("ReScan", [&]() {
1370         rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1371                      objServer, systemBus);
1372     });
1373 
1374     iface->register_method("ReScanBus", [&](uint8_t bus) {
1375         rescanOneBus(busMap, bus, dbusInterfaceMap, true, unknownBusObjectCount,
1376                      powerIsOn, objServer, systemBus);
1377     });
1378 
1379     iface->register_method("GetRawFru", getFRUInfo);
1380 
1381     iface->register_method("WriteFru", [&](const uint8_t bus,
1382                                            const uint8_t address,
1383                                            const std::vector<uint8_t>& data) {
1384         if (!writeFRU(bus, address, data))
1385         {
1386             throw std::invalid_argument("Invalid Arguments.");
1387             return;
1388         }
1389         // schedule rescan on success
1390         rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1391                      objServer, systemBus);
1392     });
1393     iface->initialize();
1394 
1395     std::function<void(sdbusplus::message::message & message)> eventHandler =
1396         [&](sdbusplus::message::message& message) {
1397             std::string objectName;
1398             boost::container::flat_map<
1399                 std::string,
1400                 std::variant<std::string, bool, int64_t, uint64_t, double>>
1401                 values;
1402             message.read(objectName, values);
1403             auto findState = values.find("CurrentHostState");
1404             if (findState != values.end())
1405             {
1406                 powerIsOn = boost::ends_with(
1407                     std::get<std::string>(findState->second), "Running");
1408             }
1409 
1410             if (powerIsOn)
1411             {
1412                 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount,
1413                              powerIsOn, objServer, systemBus);
1414             }
1415         };
1416 
1417     sdbusplus::bus::match::match powerMatch = sdbusplus::bus::match::match(
1418         static_cast<sdbusplus::bus::bus&>(*systemBus),
1419         "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/"
1420         "openbmc_project/state/"
1421         "host0',arg0='xyz.openbmc_project.State.Host'",
1422         eventHandler);
1423 
1424     int fd = inotify_init();
1425     inotify_add_watch(fd, i2CDevLocation, IN_CREATE | IN_MOVED_TO | IN_DELETE);
1426     std::array<char, 4096> readBuffer;
1427     // monitor for new i2c devices
1428     boost::asio::posix::stream_descriptor dirWatch(io, fd);
1429     std::function<void(const boost::system::error_code, std::size_t)>
1430         watchI2cBusses = [&](const boost::system::error_code& ec,
1431                              std::size_t bytesTransferred) {
1432             if (ec)
1433             {
1434                 std::cout << "Callback Error " << ec << "\n";
1435                 return;
1436             }
1437             size_t index = 0;
1438             while ((index + sizeof(inotify_event)) <= bytesTransferred)
1439             {
1440                 const inotify_event* iEvent =
1441                     reinterpret_cast<const inotify_event*>(&readBuffer[index]);
1442                 switch (iEvent->mask)
1443                 {
1444                     case IN_CREATE:
1445                     case IN_MOVED_TO:
1446                     case IN_DELETE:
1447                         std::string name(iEvent->name);
1448                         if (boost::starts_with(name, "i2c"))
1449                         {
1450                             int bus = busStrToInt(name);
1451                             if (bus < 0)
1452                             {
1453                                 std::cerr << "Could not parse bus " << name
1454                                           << "\n";
1455                                 continue;
1456                             }
1457                             rescanOneBus(busMap, static_cast<uint8_t>(bus),
1458                                          dbusInterfaceMap, false,
1459                                          unknownBusObjectCount, powerIsOn,
1460                                          objServer, systemBus);
1461                         }
1462                 }
1463                 index += sizeof(inotify_event) + iEvent->len;
1464             }
1465 
1466             dirWatch.async_read_some(boost::asio::buffer(readBuffer),
1467                                      watchI2cBusses);
1468         };
1469 
1470     dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses);
1471     // run the initial scan
1472     rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn,
1473                  objServer, systemBus);
1474 
1475     io.run();
1476     return 0;
1477 }
1478