frudevice: consolidate FRU reading

Consolidate FRU reading and validation into one method shared by eeproms
and raw i2c devices.

Tested: Verified this work for devices read via i2c and via eeprom
sysfs.
Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I3144692f91d0f77c6ca3953860e0dfa9c212ccc9
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
index 492e7ff..7a46311 100644
--- a/src/FruDevice.cpp
+++ b/src/FruDevice.cpp
@@ -28,6 +28,7 @@
 #include <ctime>
 #include <filesystem>
 #include <fstream>
+#include <functional>
 #include <future>
 #include <iomanip>
 #include <iostream>
@@ -80,6 +81,91 @@
 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
 auto objServer = sdbusplus::asio::object_server(systemBus);
 
+bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData);
+
+using ReadBlockFunc = std::function<int64_t(int flag, int file, uint16_t offset,
+                                            uint8_t length, uint8_t* outBuf)>;
+
+// Read and validate FRU contents, given the flag required for raw i2c, the open
+// file handle, a read method, and a helper string for failures.
+std::vector<char> readFruContents(int flag, int file, ReadBlockFunc readBlock,
+                                  const std::string& errorHelp)
+{
+    std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+
+    if (readBlock(flag, file, 0x0, 0x8, blockData.data()) < 0)
+    {
+        std::cerr << "failed to read " << errorHelp << "\n";
+        return {};
+    }
+
+    // check the header checksum
+    if (!validateHeader(blockData))
+    {
+        if (DEBUG)
+        {
+            std::cerr << "Illegal header " << errorHelp << "\n";
+        }
+
+        return {};
+    }
+
+    std::vector<char> device;
+    device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
+
+    int fruLength = 0;
+    for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
+    {
+        // TODO: offset can be 255, device is holding "chars" that's not
+        // good.
+        int areaOffset = device[jj];
+        if (areaOffset == 0)
+        {
+            continue;
+        }
+
+        areaOffset *= 8;
+
+        if (readBlock(flag, file, static_cast<uint16_t>(areaOffset), 0x2,
+                      blockData.data()) < 0)
+        {
+            std::cerr << "failed to read " << errorHelp << "\n";
+            return {};
+        }
+
+        // Ignore data type.
+        int length = blockData[1] * 8;
+        areaOffset += length;
+        fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
+    }
+
+    // You already copied these first 8 bytes (the ipmi fru header size)
+    fruLength -= 8;
+
+    int readOffset = 8;
+
+    while (fruLength > 0)
+    {
+        int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
+
+        if (readBlock(flag, file, static_cast<uint16_t>(readOffset),
+                      static_cast<uint8_t>(requestLength),
+                      blockData.data()) < 0)
+        {
+            std::cerr << "failed to read " << errorHelp << "\n";
+            return {};
+        }
+
+        device.insert(device.end(), blockData.begin(),
+                      blockData.begin() + requestLength);
+
+        readOffset += requestLength;
+        fruLength -= requestLength;
+    }
+
+    return device;
+}
+
 // Given a bus/address, produce the path in sysfs for an eeprom.
 static std::string getEepromPath(size_t bus, size_t address)
 {
@@ -240,96 +326,19 @@
 // with some tweaks.
 static std::vector<char> processEeprom(int bus, int address)
 {
-    std::vector<char> device;
-    std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
-
     auto path = getEepromPath(bus, address);
 
     int file = open(path.c_str(), O_RDONLY);
     if (file < 0)
     {
         std::cerr << "Unable to open eeprom file: " << path << "\n";
-        return device;
+        return {};
     }
 
-    auto readBytes = readFromEeprom(0, file, 0, 0x8, blockData.data());
-    if (readBytes < 0)
-    {
-        std::cerr << "failed to read eeprom at " << bus << " address "
-                  << address << "\n";
-        close(file);
-        return device;
-    }
-
-    if (!validateHeader(blockData))
-    {
-        if (DEBUG)
-        {
-            std::cerr << "Illegal header at bus " << bus << " address "
-                      << address << "\n";
-        }
-
-        close(file);
-        return device;
-    }
-
-    // Copy the IPMI Fru Header
-    device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
-
-    int fruLength = 0;
-    for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
-    {
-        // TODO: offset can be 255, device is holding "chars" that's not good.
-        int areaOffset = device[jj];
-        if (areaOffset == 0)
-        {
-            continue;
-        }
-
-        areaOffset *= 8;
-
-        if (readFromEeprom(0, file, static_cast<uint16_t>(areaOffset), 0x2,
-                           blockData.data()) < 0)
-        {
-            std::cerr << "failed to read bus " << bus << " address " << address
-                      << "\n";
-            device.clear();
-            close(file);
-            return device;
-        }
-
-        // Ignore data type.
-        int length = blockData[1] * 8;
-        areaOffset += length;
-        fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
-    }
-
-    // You already copied these first 8 bytes (the ipmi fru header size)
-    fruLength -= 8;
-
-    int readOffset = 8;
-
-    while (fruLength > 0)
-    {
-        int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
-
-        if (readFromEeprom(0, file, static_cast<uint16_t>(readOffset),
-                           static_cast<uint8_t>(requestLength),
-                           blockData.data()) < 0)
-        {
-            std::cerr << "failed to read bus " << bus << " address " << address
-                      << "\n";
-            device.clear();
-            close(file);
-            return device;
-        }
-
-        device.insert(device.end(), blockData.begin(),
-                      blockData.begin() + requestLength);
-
-        readOffset += requestLength;
-        fruLength -= requestLength;
-    }
+    std::string errorMessage = "eeprom at " + std::to_string(bus) +
+                               " address " + std::to_string(address);
+    std::vector<char> device =
+        readFruContents(0, file, readFromEeprom, errorMessage);
 
     close(file);
     return device;
@@ -401,8 +410,6 @@
 {
 
     std::future<int> future = std::async(std::launch::async, [&]() {
-        std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
-
         // NOTE: When reading the devices raw on the bus, it can interfere with
         // the driver's ability to operate, therefore read eeproms first before
         // scanning for devices without drivers. Several experiments were run
@@ -452,79 +459,15 @@
                 continue;
             }
 
-            if (readBlockData(flag, file, 0x0, 0x8, blockData.data()) < 0)
+            std::string errorMessage =
+                "bus " + std::to_string(bus) + " address " + std::to_string(ii);
+            std::vector<char> device =
+                readFruContents(flag, file, readBlockData, errorMessage);
+            if (device.empty())
             {
-                std::cerr << "failed to read bus " << bus << " address " << ii
-                          << "\n";
                 continue;
             }
 
-            // check the header checksum
-            if (!validateHeader(blockData))
-            {
-                if (DEBUG)
-                {
-                    std::cerr << "Illegal header at bus " << bus << " address "
-                              << ii << "\n";
-                }
-                continue;
-            }
-
-            std::vector<char> device;
-            device.insert(device.end(), blockData.begin(),
-                          blockData.begin() + 8);
-
-            int fruLength = 0;
-            for (size_t jj = 1; jj <= FRU_AREAS.size(); jj++)
-            {
-                // TODO: offset can be 255, device is holding "chars" that's not
-                // good.
-                int areaOffset = device[jj];
-                if (areaOffset == 0)
-                {
-                    continue;
-                }
-
-                areaOffset *= 8;
-
-                if (readBlockData(flag, file, static_cast<uint16_t>(areaOffset),
-                                  0x2, blockData.data()) < 0)
-                {
-                    std::cerr << "failed to read bus " << bus << " address "
-                              << ii << "\n";
-                    return -1;
-                }
-
-                // Ignore data type.
-                int length = blockData[1] * 8;
-                areaOffset += length;
-                fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
-            }
-
-            // You already copied these first 8 bytes (the ipmi fru header size)
-            fruLength -= 8;
-
-            int readOffset = 8;
-
-            while (fruLength > 0)
-            {
-                int requestLength = std::min(I2C_SMBUS_BLOCK_MAX, fruLength);
-
-                if (readBlockData(flag, file, static_cast<uint16_t>(readOffset),
-                                  static_cast<uint8_t>(requestLength),
-                                  blockData.data()) < 0)
-                {
-                    std::cerr << "failed to read bus " << bus << " address "
-                              << ii << "\n";
-                    return -1;
-                }
-
-                device.insert(device.end(), blockData.begin(),
-                              blockData.begin() + requestLength);
-
-                readOffset += requestLength;
-                fruLength -= requestLength;
-            }
             devices->emplace(ii, device);
         }
         return 1;