frudevice: add i2c bus blacklist json

The fru-device daemon contained a black list internally of buses to not
rescan in the future.  This list was generated automatically by talking
to each bus.  On a system where scanning the devices on a specific bus
can cause problems, extend that blacklist to be something that can be
specified for a machine.

This adds a file blacklist.json, which currently contains one field.  An
array of bus integers named "buses."  This file can be overwritten by a
platform recipe to drop in a list of buses to avoid scanning.

Tested: Verified that an empty, but valid json file (the default file
has no effect).
Tested: Verified that the array with an invalid type of entry is caught,
reported and the program exited.
Tested: Verified that the array with three valid entries prevents the
daemon from scanning the devices on those buses.

Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I1c426ebed2f7ac073ee45ed8b205e4a176a519ab
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 497f886..c57956c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -133,6 +133,7 @@
     add_dependencies (entity-manager nlohmann-json)
     add_dependencies (entity-manager sdbusplus-project)
     add_dependencies (entity-manager valijson)
+    add_dependencies (fru-device nlohmann-json)
     add_dependencies (fru-device valijson)
     add_dependencies (fru-device sdbusplus-project)
     add_dependencies (valijson nlohmann-json)
@@ -147,8 +148,10 @@
 set (PACKAGE_DIR /usr/share/entity-manager/)
 target_compile_definitions (entity-manager PRIVATE PACKAGE_DIR="${PACKAGE_DIR}"
                             -DBOOST_ASIO_DISABLE_THREADS)
+target_compile_definitions (fru-device PRIVATE PACKAGE_DIR="${PACKAGE_DIR}")
 install (TARGETS fru-device entity-manager DESTINATION bin)
 install (DIRECTORY configurations DESTINATION ${PACKAGE_DIR})
 install (DIRECTORY overlay_templates DESTINATION ${PACKAGE_DIR})
 install (DIRECTORY schemas DESTINATION ${PACKAGE_DIR}/configurations)
 install (FILES ${SERVICE_FILES} DESTINATION /lib/systemd/system/)
+install (FILES blacklist.json DESTINATION ${PACKAGE_DIR})
diff --git a/blacklist.json b/blacklist.json
new file mode 100644
index 0000000..f112a7b
--- /dev/null
+++ b/blacklist.json
@@ -0,0 +1,3 @@
+{
+    "buses": []
+}
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
index 0f1350c..0ef0e94 100644
--- a/src/FruDevice.cpp
+++ b/src/FruDevice.cpp
@@ -27,9 +27,11 @@
 #include <fstream>
 #include <future>
 #include <iostream>
+#include <nlohmann/json.hpp>
 #include <regex>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
+#include <string>
 #include <thread>
 #include <variant>
 
@@ -45,6 +47,8 @@
 constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
 constexpr size_t busTimeoutSeconds = 5;
 
+constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
+
 const static constexpr char* BASEBOARD_FRU_LOCATION =
     "/etc/fru/baseboard.fru.bin";
 
@@ -275,6 +279,69 @@
     return future.get();
 }
 
+void loadBlacklist(const char* path)
+{
+    std::ifstream blacklistStream(path);
+    if (!blacklistStream.good())
+    {
+        // File is optional.
+        std::cerr << "Cannot open blacklist file.\n\n";
+        return;
+    }
+
+    nlohmann::json data =
+        nlohmann::json::parse(blacklistStream, nullptr, false);
+    if (data.is_discarded())
+    {
+        std::cerr << "Illegal blacklist file detected, cannot validate JSON, "
+                     "exiting\n";
+        std::exit(EXIT_FAILURE);
+        return;
+    }
+
+    // It's expected to have at least one field, "buses" that is an array of the
+    // buses by integer. Allow for future options to exclude further aspects,
+    // such as specific addresses or ranges.
+    if (data.type() != nlohmann::json::value_t::object)
+    {
+        std::cerr << "Illegal blacklist, expected to read dictionary\n";
+        std::exit(EXIT_FAILURE);
+        return;
+    }
+
+    // If buses field is missing, that's fine.
+    if (data.count("buses") == 1)
+    {
+        // Parse the buses array after a little validation.
+        auto buses = data.at("buses");
+        if (buses.type() != nlohmann::json::value_t::array)
+        {
+            // Buses field present but invalid, therefore this is an error.
+            std::cerr << "Invalid contents for blacklist buses field\n";
+            std::exit(EXIT_FAILURE);
+            return;
+        }
+
+        // Catch exception here for type mis-match.
+        try
+        {
+            for (const auto& bus : buses)
+            {
+                busBlacklist.insert(bus.get<size_t>());
+            }
+        }
+        catch (const nlohmann::detail::type_error& e)
+        {
+            // Type mis-match is a critical error.
+            std::cerr << "Invalid bus type: " << e.what() << "\n";
+            std::exit(EXIT_FAILURE);
+            return;
+        }
+    }
+
+    return;
+}
+
 static void FindI2CDevices(const std::vector<fs::path>& i2cBuses,
                            BusMap& busmap)
 {
@@ -818,6 +885,9 @@
         return 1;
     }
 
+    // check for and load blacklist with initial buses.
+    loadBlacklist(blacklistPath);
+
     boost::asio::io_service io;
     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
     auto objServer = sdbusplus::asio::object_server(systemBus);