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