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