Enhance sys_info and postcode for multi-host

This patch enhances multi-host platform support in the OCP debug card
display (sys_info and postcode pages) and OEM command modules.
It replaces hard-coded D-Bus paths and single-interface networkqueries
with dynamic lookups based on the current host selector position.
 -Sys_Info page now retrieves the correct FRU path.
 -POSTCODE page dynamically calls Boot.PostCode<N> service/object for
  each host slot.
 -Network information Network information now supports fetching from
  eth1 when eth0 is unavailable, preventing missing data or crashes.

Motivation:
Original implementation used hardcoded D-Bus paths such as PostCode0
and only checked eth0 for network-related data. On multi-host platforms,
each host has a separate PostCode service, and FRU/FW data may differ
or be present in alternate paths. Moreover, some systems expose IP/MAC
data on eth1, causing failures when only eth0 is queried. These issue
led to incomplete data being shown or crashes in the sys_info display.

Design:
- Introduced logic to dynamically determine the current host position
 and fetch the corresponding PostCode service (PostCode0 ~ PostCode7).
- Enhance `getNetworkData` to check both eth0 and eth1 for IP and MAC
 addresses, and avoid crashes if a value is not found.
- Updated FRU retrieval to support multiple motherboard paths and
 fallback paths, using filtered D-Bus object mapping
 via interface matching and predicate.
- Ensured all std::string results are checked before accessing to
 prevent null dereferencing and crashes.

Change-Id: I0c88eb58c3b416437a02994c4c202d5c2269d32a
Signed-off-by: Liora Guo <liora.guo.wiwynn@gmail.com>
diff --git a/include/usb-dbg.hpp b/include/usb-dbg.hpp
index 1344150..0d7f2ef 100644
--- a/include/usb-dbg.hpp
+++ b/include/usb-dbg.hpp
@@ -60,6 +60,9 @@
 static constexpr size_t FRAME_PAGE_BUF_SIZE = 256;
 
 #define FRU_ALL 0
+#define BOOT_POSTCODE_SERVICE "xyz.openbmc_project.State.Boot.PostCode"
+#define BOOT_POSTCODE_OBJECTPATH "/xyz/openbmc_project/State/Boot/PostCode"
+#define BOOT_POSTCODE_INTERFACE "xyz.openbmc_project.State.Boot.PostCode"
 
 static constexpr auto DEBUG_GPIO_KEY = "GpioDesc";
 static constexpr auto GPIO_ARRAY_SIZE = 4;
diff --git a/src/oemcommands.cpp b/src/oemcommands.cpp
index e5cb0f5..99b28c2 100644
--- a/src/oemcommands.cpp
+++ b/src/oemcommands.cpp
@@ -33,8 +33,10 @@
 #include <array>
 #include <cstring>
 #include <fstream>
+#include <functional>
 #include <iomanip>
 #include <iostream>
+#include <optional>
 #include <regex>
 #include <sstream>
 #include <string>
@@ -432,71 +434,94 @@
     return 0;
 }
 
+static std::optional<std::string> findIpAddress(
+    sdbusplus::bus_t& bus, const std::string& protocol,
+    const std::vector<std::string>& ifaces)
+{
+    for (auto& dev : ifaces)
+    {
+        try
+        {
+            auto ipObjectInfo =
+                ipmi::network::getIPObject(bus, ipmi::network::IP_INTERFACE,
+                                           ipmi::network::ROOT, protocol, dev);
+
+            auto props = ipmi::getAllDbusProperties(
+                bus, ipObjectInfo.second, ipObjectInfo.first,
+                ipmi::network::IP_INTERFACE);
+
+            auto addr = std::get<std::string>(props.at("Address"));
+            if (!addr.empty())
+                return addr;
+        }
+        catch (const sdbusplus::exception::SdBusError&)
+        {}
+    }
+    return std::nullopt;
+}
+static std::optional<std::string> findMacAddress(
+    sdbusplus::bus_t& bus, const std::vector<std::string>& ifaces)
+{
+    for (auto& dev : ifaces)
+    {
+        try
+        {
+            auto [path, obj] = ipmi::getDbusObject(
+                bus, ipmi::network::MAC_INTERFACE, ipmi::network::ROOT, dev);
+
+            auto var = ipmi::getDbusProperty(
+                bus, obj, path, ipmi::network::MAC_INTERFACE, "MACAddress");
+
+            auto mac = std::get<std::string>(var);
+            if (!mac.empty())
+                return mac;
+        }
+        catch (const sdbusplus::exception::SdBusError&)
+        {}
+    }
+    return std::nullopt;
+}
+
 ipmi_ret_t getNetworkData(uint8_t lan_param, char* data)
 {
-    ipmi_ret_t rc = ipmi::ccSuccess;
-    sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+    static const std::vector<std::string> ifaces{"eth0", "eth1"};
 
-    const std::string ethdevice = "eth0";
+    sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+    ipmi_ret_t rc = IPMI_CC_OK;
+    std::optional<std::string> result;
 
     switch (static_cast<LanParam>(lan_param))
     {
         case LanParam::IP:
-        {
-            std::string ipaddress;
-            auto ipObjectInfo = ipmi::network::getIPObject(
-                bus, ipmi::network::IP_INTERFACE, ipmi::network::ROOT,
-                ipmi::network::IPV4_PROTOCOL, ethdevice);
-
-            auto properties = ipmi::getAllDbusProperties(
-                bus, ipObjectInfo.second, ipObjectInfo.first,
-                ipmi::network::IP_INTERFACE);
-
-            ipaddress = std::get<std::string>(properties["Address"]);
-
-            std::strcpy(data, ipaddress.c_str());
-        }
-        break;
+            result = findIpAddress(bus, ipmi::network::IPV4_PROTOCOL, ifaces);
+            lg2::info("Found IP Address: {ADDR}", "ADDR",
+                      result.value_or("Not Found"));
+            break;
 
         case LanParam::IPV6:
-        {
-            std::string ipaddress;
-            auto ipObjectInfo = ipmi::network::getIPObject(
-                bus, ipmi::network::IP_INTERFACE, ipmi::network::ROOT,
-                ipmi::network::IPV6_PROTOCOL, ethdevice);
-
-            auto properties = ipmi::getAllDbusProperties(
-                bus, ipObjectInfo.second, ipObjectInfo.first,
-                ipmi::network::IP_INTERFACE);
-
-            ipaddress = std::get<std::string>(properties["Address"]);
-
-            std::strcpy(data, ipaddress.c_str());
-        }
-        break;
+            result = findIpAddress(bus, ipmi::network::IPV6_PROTOCOL, ifaces);
+            lg2::info("Found IPv6 Address: {ADDR}", "ADDR",
+                      result.value_or("Not Found"));
+            break;
 
         case LanParam::MAC:
-        {
-            std::string macAddress;
-            auto macObjectInfo =
-                ipmi::getDbusObject(bus, ipmi::network::MAC_INTERFACE,
-                                    ipmi::network::ROOT, ethdevice);
-
-            auto variant = ipmi::getDbusProperty(
-                bus, macObjectInfo.second, macObjectInfo.first,
-                ipmi::network::MAC_INTERFACE, "MACAddress");
-
-            macAddress = std::get<std::string>(variant);
-
-            sscanf(macAddress.c_str(), ipmi::network::MAC_ADDRESS_FORMAT,
-                   (data), (data + 1), (data + 2), (data + 3), (data + 4),
-                   (data + 5));
-            std::strcpy(data, macAddress.c_str());
-        }
-        break;
+            result = findMacAddress(bus, ifaces);
+            lg2::info("Found MAC Address: {ADDR}", "ADDR",
+                      result.value_or("Not Found"));
+            break;
 
         default:
-            rc = ipmi::ccParmOutOfRange;
+            return IPMI_CC_PARM_OUT_OF_RANGE;
+    }
+
+    if (!result.has_value())
+    {
+        rc = IPMI_CC_UNSPECIFIED_ERROR;
+        data[0] = '\0';
+    }
+    else
+    {
+        std::strcpy(data, result->c_str());
     }
     return rc;
 }
@@ -514,38 +539,78 @@
     }
     return platform;
 }
-
-// return "" equals failed
-std::string getMotherBoardFruPath()
+static std::optional<std::string> findFruPathByInterface(
+    sdbusplus::bus_t& dbus, const std::string& interfaceName,
+    std::function<bool(const std::string&)> predicate)
 {
+    const int depth = 0;
     std::vector<std::string> paths;
-    static constexpr const auto depth = 0;
-    sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
 
     auto mapperCall = dbus.new_method_call(
         "xyz.openbmc_project.ObjectMapper",
         "/xyz/openbmc_project/object_mapper",
         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths");
-    static constexpr auto interface = {
-        "xyz.openbmc_project.Inventory.Item.Board.Motherboard"};
+    mapperCall.append("/xyz/openbmc_project/inventory/", depth,
+                      std::array<const char*, 1>{interfaceName.c_str()});
 
-    mapperCall.append("/xyz/openbmc_project/inventory/", depth, interface);
     try
     {
         auto reply = dbus.call(mapperCall);
         reply.read(paths);
     }
-    catch (sdbusplus::exception_t& e)
+    catch (const sdbusplus::exception_t& e)
     {
         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
-        return "";
+        return std::nullopt;
     }
 
     for (const auto& path : paths)
     {
-        return path;
+        if (predicate(path))
+        {
+            return path;
+        }
+    }
+    return std::nullopt;
+}
+
+// return "" equals failed
+std::string getMotherBoardFruPath()
+{
+    sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
+
+    bool platform = isMultiHostPlatform();
+    size_t hostPosition;
+    if (platform)
+        getSelectorPosition(hostPosition);
+
+    if (hostPosition == 0)
+    {
+        if (auto path = findFruPathByInterface(
+                dbus, "xyz.openbmc_project.Inventory.Item.Bmc",
+                [](const std::string&) { return true; }))
+        {
+            lg2::info("[1] Found Motherboard: {PATH}", "PATH", *path);
+            return *path;
+        }
     }
 
+    size_t targetIndex = (hostPosition > 0 ? hostPosition - 1 : 0);
+    auto predNth = [targetIndex,
+                    counter = size_t{0}](const std::string& /*path*/) mutable {
+        return (counter++ == targetIndex);
+    };
+
+    if (auto path = findFruPathByInterface(
+            dbus, "xyz.openbmc_project.Inventory.Item.Board.Motherboard",
+            predNth))
+    {
+        lg2::info("[2] Found Motherboard: {PATH}", "PATH", *path);
+        return *path;
+    }
+
+    phosphor::logging::log<phosphor::logging::level::ERR>(
+        "getMotherBoardFruPath: no valid FRU path found");
     return "";
 }
 
diff --git a/src/usb-dbg.cpp b/src/usb-dbg.cpp
index 70b69a0..57c3339 100644
--- a/src/usb-dbg.cpp
+++ b/src/usb-dbg.cpp
@@ -650,19 +650,25 @@
         }
 
         getMaxHostPosition(maxPosition);
+        std::string data;
         if (hostPosition == BMC_POSITION || hostInstances == "0")
         {
-            std::string data = "FRU:" + getMotherBoardFruName();
-            frame_info.append(data);
+            data = "FRU:" + getMotherBoardFruName();
         }
         else if (hostPosition != BMC_POSITION && hostPosition <= maxPosition)
         {
-            std::string data = "FRU:slot" + std::to_string(hostPosition);
-            frame_info.append(data);
+            if (getMotherBoardFruName() != "")
+            {
+                data = "FRU:" + getMotherBoardFruName();
+            }
+            else
+            {
+                data = "FRU:slot" + std::to_string(hostPosition);
+            }
         }
+        frame_info.append(data);
 
         // FRU
-        std::string data;
         frame_info.append("SN:");
         if (getFruData(data, serialName) != 0)
         {
@@ -783,6 +789,8 @@
 {
     // up to 70 codes can be displayed on 10 pages
     static constexpr size_t maxPostcodes = 70;
+    bool platform = isMultiHostPlatform();
+    size_t hostPosition = 0;
 
     if (page == 1)
     {
@@ -791,18 +799,24 @@
         snprintf(frame_postcode.title, 32, "POST CODE");
         frame_postcode.max_page = 10;
 
+        if (platform)
+            getSelectorPosition(hostPosition);
+
         // Synchronously get D-Bus connection
         auto bus = sdbusplus::bus::new_default();
+        std::string serviceName =
+            BOOT_POSTCODE_SERVICE + std::to_string(hostPosition);
+        std::string objectPath =
+            BOOT_POSTCODE_OBJECTPATH + std::to_string(hostPosition);
 
         // Build D-Bus method call
         auto method = bus.new_method_call(
-            "xyz.openbmc_project.State.Boot.PostCode0",  // Target service name
-            "/xyz/openbmc_project/State/Boot/PostCode0", // Object path
-            "xyz.openbmc_project.State.Boot.PostCode",   // Interface name
-            "GetPostCodes");                             // Method name
+            serviceName.c_str(),     // Target service name
+            objectPath.c_str(),      // Object path
+            BOOT_POSTCODE_INTERFACE, // Interface name
+            "GetPostCodes");         // Method name
 
-        method.append(uint16_t(1)); // Add method parameter, assuming it's page
-
+        method.append(uint16_t(1));  // Add method parameter, assuming it's pag
         try
         {
             auto reply = bus.call(method); // Send synchronous method call