Create class for finding compatible system types

Create class for finding the list of compatible system types for the
current system.

Use the new D-Bus interface
xyz.openbmc_project.Inventory.Decorator.Compatible to find the system
types.

Tested:
* See test plan at
  https://gist.github.com/smccarney/7bfc20b258cd6ccfba908730102a32dd

Change-Id: I47063642a601991aac8e63f39d8d1f29c4896db0
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/compatible_system_types_finder.cpp b/compatible_system_types_finder.cpp
new file mode 100644
index 0000000..4306942
--- /dev/null
+++ b/compatible_system_types_finder.cpp
@@ -0,0 +1,87 @@
+/**
+ * Copyright © 2024 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compatible_system_types_finder.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <algorithm>
+#include <exception>
+#include <format>
+#include <ranges>
+#include <regex>
+#include <stdexcept>
+#include <utility>
+#include <variant>
+
+namespace phosphor::power::util
+{
+
+const std::string compatibleInterfaceService =
+    "xyz.openbmc_project.EntityManager";
+
+const std::string compatibleInterface =
+    "xyz.openbmc_project.Inventory.Decorator.Compatible";
+
+const std::string namesProperty = "Names";
+
+CompatibleSystemTypesFinder::CompatibleSystemTypesFinder(sdbusplus::bus_t& bus,
+                                                         Callback callback) :
+    callback{std::move(callback)},
+    interfaceFinder{
+        bus, compatibleInterfaceService,
+        std::vector<std::string>{compatibleInterface},
+        std::bind_front(&CompatibleSystemTypesFinder::interfaceFoundCallback,
+                        this)}
+{}
+
+void CompatibleSystemTypesFinder::interfaceFoundCallback(
+    [[maybe_unused]] const std::string& path,
+    [[maybe_unused]] const std::string& interface,
+    const DbusPropertyMap& properties)
+{
+    try
+    {
+        // Get the property containing the list of compatible names
+        auto it = properties.find(namesProperty);
+        if (it == properties.end())
+        {
+            throw std::runtime_error{
+                std::format("{} property not found", namesProperty)};
+        }
+        auto names = std::get<std::vector<std::string>>(it->second);
+        if (!names.empty())
+        {
+            // If all the compatible names are system or chassis types
+            std::regex pattern{"\\.(system|chassis)\\.", std::regex::icase};
+            if (std::ranges::all_of(names, [&pattern](auto&& name) {
+                return std::regex_search(name, pattern);
+            }))
+            {
+                // Call callback with compatible system type names
+                callback(names);
+            }
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Unable to obtain properties of Compatible interface: {ERROR}",
+            "ERROR", e);
+    }
+}
+
+} // namespace phosphor::power::util
diff --git a/compatible_system_types_finder.hpp b/compatible_system_types_finder.hpp
new file mode 100644
index 0000000..e365679
--- /dev/null
+++ b/compatible_system_types_finder.hpp
@@ -0,0 +1,104 @@
+/**
+ * Copyright © 2024 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "dbus_interfaces_finder.hpp"
+#include "utility.hpp"
+
+#include <sdbusplus/bus.hpp>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace phosphor::power::util
+{
+
+/**
+ * @class CompatibleSystemTypesFinder
+ *
+ * Class that finds the compatible system types for the current system.
+ *
+ * The compatible system types are in a list ordered from most to least
+ * specific.
+ *
+ * Example:
+ *   - com.acme.Hardware.Chassis.Model.MegaServer4CPU
+ *   - com.acme.Hardware.Chassis.Model.MegaServer
+ *   - com.acme.Hardware.Chassis.Model.Server
+ *
+ * When a list of compatible system types is found, the callback function
+ * specified in the constructor is called.  This function will be called
+ * multiple times if multiple lists of compatible system types are found.
+ */
+class CompatibleSystemTypesFinder
+{
+  public:
+    // Specify which compiler-generated methods we want
+    CompatibleSystemTypesFinder() = delete;
+    CompatibleSystemTypesFinder(const CompatibleSystemTypesFinder&) = delete;
+    CompatibleSystemTypesFinder(CompatibleSystemTypesFinder&&) = delete;
+    CompatibleSystemTypesFinder&
+        operator=(const CompatibleSystemTypesFinder&) = delete;
+    CompatibleSystemTypesFinder&
+        operator=(CompatibleSystemTypesFinder&&) = delete;
+    ~CompatibleSystemTypesFinder() = default;
+
+    /**
+     * Callback function that is called when a list of compatible system types
+     * is found.
+     *
+     * @param compatibleSystemTypes Compatible system types for the current
+     *                              system ordered from most to least specific
+     */
+    using Callback = std::function<void(
+        const std::vector<std::string>& compatibleSystemTypes)>;
+
+    /**
+     * Constructor.
+     *
+     * @param bus D-Bus bus object
+     * @param callback Callback function that is called each time a list of
+     *                 compatible system types is found
+     */
+    explicit CompatibleSystemTypesFinder(sdbusplus::bus_t& bus,
+                                         Callback callback);
+
+    /**
+     * Callback function that is called when a Compatible interface is found.
+     *
+     * @param path D-Bus object path that implements the interface
+     * @param interface D-Bus interface that was found
+     * @param properties Properties of the D-Bus interface
+     */
+    void interfaceFoundCallback(const std::string& path,
+                                const std::string& interface,
+                                const DbusPropertyMap& properties);
+
+  private:
+    /**
+     * Callback function that is called each time a list of compatible system
+     * types is found.
+     */
+    Callback callback;
+
+    /**
+     * Class used to find instances of the D-Bus Compatible interface.
+     */
+    DBusInterfacesFinder interfaceFinder;
+};
+
+} // namespace phosphor::power::util
diff --git a/meson.build b/meson.build
index bc0f16f..eef72a3 100644
--- a/meson.build
+++ b/meson.build
@@ -154,6 +154,7 @@
     'power',
     error_cpp,
     error_hpp,
+    'compatible_system_types_finder.cpp',
     'dbus_interfaces_finder.cpp',
     'gpio.cpp',
     'pmbus.cpp',
diff --git a/utility.hpp b/utility.hpp
index ad6be99..e721b9b 100644
--- a/utility.hpp
+++ b/utility.hpp
@@ -31,7 +31,8 @@
 using DbusSubtree =
     std::map<DbusPath, std::map<DbusService, DbusInterfaceList>>;
 using DbusVariant =
-    std::variant<bool, uint64_t, std::string, std::vector<uint64_t>>;
+    std::variant<bool, uint64_t, std::string, std::vector<uint64_t>,
+                 std::vector<std::string>>;
 using DbusPropertyMap = std::map<DbusProperty, DbusVariant>;
 /**
  * @brief Get the service name from the mapper for the