Get PSU version from vendor specific tool

The code was getting the version from PSU inventory object.
This commit changes the behavior to use a vendor-specific tool to get
the version directly, where the tool is expected to be configured during
build time.

Add an example get_version app that shows the expected behavior of the
tool:
* It accepts an argument of PSU inventory object;
* It outputs the version to stdout.

Tested: 1. Put and configure to use the example get_version on witherspoon,
        verify that PSU software objects are created with the version
        returned by the exmaple tool.
        2. With the Witherspoon specific tool in
        https://gerrit.openbmc-project.xyz/c/openbmc/witherspoon-pfault-analysis/+/24811,
        verify the version is correctly got from the PSU inventory path
        and the software objects are created.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I5195cb6fc8998a76b09abcfe0b107364cb180c01
diff --git a/meson.build b/meson.build
index c56d09d..31bffe1 100644
--- a/meson.build
+++ b/meson.build
@@ -34,6 +34,7 @@
 cdata.set_quoted('SOFTWARE_OBJPATH', get_option('SOFTWARE_OBJPATH'))
 cdata.set_quoted('MANIFEST_FILE', get_option('MANIFEST_FILE'))
 cdata.set_quoted('PSU_INVENTORY_PATH_BASE', get_option('PSU_INVENTORY_PATH_BASE'))
+cdata.set_quoted('PSU_VERSION_UTIL', get_option('PSU_VERSION_UTIL'))
 
 phosphor_dbus_interfaces = dependency('phosphor-dbus-interfaces')
 phosphor_logging = dependency('phosphor-logging')
@@ -45,6 +46,11 @@
 subdir('src')
 
 build_tests = get_option('tests')
+build_examples = get_option('examples')
+
+if build_examples
+  subdir('vendor-example')
+endif
 
 if not build_tests.disabled()
   subdir('test')
diff --git a/meson_options.txt b/meson_options.txt
index e5297cf..1509ac2 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,5 +1,6 @@
 option('tests', type: 'feature', description: 'Build tests')
 option('oe-sdk', type: 'feature', description: 'Enable OE SDK')
+option('examples', type: 'boolean', value: true, description: 'Build vendor-example')
 
 option('MANIFEST_FILE',
        type: 'string',
@@ -15,3 +16,14 @@
        type: 'string',
        value: '/xyz/openbmc_project/inventory/system',
        description: 'The base path for PSU inventory')
+
+
+# The PSU_VERSION_UTIL specifies an executable that accepts the PSU
+# inventory path as input, and output the version string, e.g
+#   psutils get-version /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
+# or in vendor-example
+#   get-version <some-psu-path>
+option('PSU_VERSION_UTIL',
+       type: 'string',
+       value: '/usr/bin/psutils --getversion',
+       description: 'The command and arguments to get PSU version')
diff --git a/src/item_updater.cpp b/src/item_updater.cpp
index 89bd388..cf691d7 100644
--- a/src/item_updater.cpp
+++ b/src/item_updater.cpp
@@ -356,12 +356,10 @@
     auto paths = utils::getPSUInventoryPath(bus);
     for (const auto& p : paths)
     {
-        // Assume the same service implement both Version and Item interface
-        auto service = utils::getService(bus, p.c_str(), VERSION_IFACE);
-        auto version = utils::getProperty<std::string>(
-            bus, service.c_str(), p.c_str(), VERSION_IFACE, VERSION);
+        auto service = utils::getService(bus, p.c_str(), ITEM_IFACE);
         auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(),
                                                 ITEM_IFACE, PRESENT);
+        auto version = utils::getVersion(p);
         if (present && !version.empty())
         {
             createPsuObject(p, version);
diff --git a/src/utils.cpp b/src/utils.cpp
index 5e92f4a..7a968b7 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -6,6 +6,7 @@
 
 #include <fstream>
 #include <phosphor-logging/log.hpp>
+#include <sstream>
 
 using namespace phosphor::logging;
 
@@ -19,6 +20,39 @@
 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
 } // namespace
 
+namespace internal
+{
+template <typename... Ts>
+std::string concat_string(Ts const&... ts)
+{
+    std::stringstream s;
+    ((s << ts << " "), ...) << std::endl;
+    return s.str();
+}
+
+// Helper function to run command
+// Returns return code and the stdout
+template <typename... Ts>
+std::pair<int, std::string> exec(Ts const&... ts)
+{
+    std::array<char, 512> buffer;
+    std::string cmd = concat_string(ts...);
+    std::stringstream result;
+    int rc;
+    FILE* pipe = popen(cmd.c_str(), "r");
+    if (!pipe)
+    {
+        throw std::runtime_error("popen() failed!");
+    }
+    while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
+    {
+        result << buffer.data();
+    }
+    rc = pclose(pipe);
+    return {rc, result.str()};
+}
+
+} // namespace internal
 const UtilsInterface& getUtils()
 {
     static Utils utils;
@@ -98,6 +132,15 @@
     return (hexId.substr(0, 8));
 }
 
+std::string Utils::getVersion(const std::string& inventoryPath) const
+{
+    // Invoke vendor-specify tool to get the version string, e.g.
+    //   psutils get-version
+    //   /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
+    auto [rc, r] = internal::exec(PSU_VERSION_UTIL, inventoryPath);
+    return (rc == 0) ? r : "";
+}
+
 any Utils::getPropertyImpl(sdbusplus::bus::bus& bus, const char* service,
                            const char* path, const char* interface,
                            const char* propertyName) const
diff --git a/src/utils.hpp b/src/utils.hpp
index dd1317f..86ee5dd 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -63,6 +63,14 @@
  */
 std::string getVersionId(const std::string& version);
 
+/** @brief Get version of PSU specified by the inventory path
+ *
+ * @param[in] inventoryPath - The PSU inventory object path
+ *
+ * @return The version string, or empry string if it fails to get the version
+ */
+std::string getVersion(const std::string& inventoryPath);
+
 /**
  * @brief The interface for utils
  */
@@ -82,6 +90,8 @@
 
     virtual std::string getVersionId(const std::string& version) const = 0;
 
+    virtual std::string getVersion(const std::string& inventoryPath) const = 0;
+
     virtual any getPropertyImpl(sdbusplus::bus::bus& bus, const char* service,
                                 const char* path, const char* interface,
                                 const char* propertyName) const = 0;
@@ -109,6 +119,8 @@
 
     std::string getVersionId(const std::string& version) const override;
 
+    std::string getVersion(const std::string& inventoryPath) const override;
+
     any getPropertyImpl(sdbusplus::bus::bus& bus, const char* service,
                         const char* path, const char* interface,
                         const char* propertyName) const override;
@@ -130,6 +142,11 @@
     return getUtils().getVersionId(version);
 }
 
+inline std::string getVersion(const std::string& inventoryPath)
+{
+    return getUtils().getVersion(inventoryPath);
+}
+
 template <typename T>
 T getProperty(sdbusplus::bus::bus& bus, const char* service, const char* path,
               const char* interface, const char* propertyName)
diff --git a/test/mocked_utils.hpp b/test/mocked_utils.hpp
index e8a8f34..88fdcf2 100644
--- a/test/mocked_utils.hpp
+++ b/test/mocked_utils.hpp
@@ -19,6 +19,9 @@
 
     MOCK_CONST_METHOD1(getVersionId, std::string(const std::string& version));
 
+    MOCK_CONST_METHOD1(getVersion,
+                       std::string(const std::string& psuInventoryPath));
+
     MOCK_CONST_METHOD5(getPropertyImpl,
                        any(sdbusplus::bus::bus& bus, const char* service,
                            const char* path, const char* interface,
diff --git a/test/test_item_updater.cpp b/test/test_item_updater.cpp
index b362803..0ae9c76 100644
--- a/test/test_item_updater.cpp
+++ b/test/test_item_updater.cpp
@@ -69,9 +69,8 @@
         .WillOnce(Return(std::vector<std::string>({psuPath})));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
         .WillOnce(Return(service));
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
-                                             _, StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(std::string(version)))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
+        .WillOnce(Return(std::string(version)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
                                              _, StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(false)))); // not present
@@ -96,9 +95,8 @@
         .WillOnce(Return(std::vector<std::string>({psuPath})));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
         .WillOnce(Return(service));
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
-                                             _, StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(std::string(version)))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
+        .WillOnce(Return(std::string(version)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
                                              _, StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
@@ -129,15 +127,13 @@
         .WillOnce(Return(service));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu1), _))
         .WillOnce(Return(service));
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
-                                             StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(version0))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psu0)))
+        .WillOnce(Return(std::string(version0)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
                                              StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
-                                             StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(version1))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psu1)))
+        .WillOnce(Return(std::string(version1)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
                                              StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
@@ -177,15 +173,13 @@
         .WillOnce(Return(service));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu1), _))
         .WillOnce(Return(service));
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
-                                             StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(version0))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psu0)))
+        .WillOnce(Return(std::string(version0)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
                                              StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
-                                             StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(version1))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psu1)))
+        .WillOnce(Return(std::string(version1)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
                                              StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
@@ -225,9 +219,8 @@
         .WillOnce(Return(std::vector<std::string>({psuPath})));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
         .WillOnce(Return(service));
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
-                                             _, StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(std::string(version)))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
+        .WillOnce(Return(std::string(version)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
                                              _, StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
@@ -262,9 +255,8 @@
         .WillOnce(Return(std::vector<std::string>({psuPath})));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
         .WillOnce(Return(service));
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
-                                             _, StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(std::string(version)))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
+        .WillOnce(Return(std::string(version)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
                                              _, StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(false)))); // not present
@@ -296,9 +288,8 @@
         .WillOnce(Return(std::vector<std::string>({psuPath})));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
         .WillOnce(Return(service));
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
-                                             _, StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(std::string(version)))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
+        .WillOnce(Return(std::string(version)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
                                              _, StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
@@ -349,15 +340,13 @@
         .WillOnce(Return(service));
     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu1), _))
         .WillOnce(Return(service));
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
-                                             StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(version0))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psu0)))
+        .WillOnce(Return(std::string(version0)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
                                              StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
-    EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
-                                             StrEq(VERSION)))
-        .WillOnce(Return(any(PropertyType(version1))));
+    EXPECT_CALL(mockedUtils, getVersion(StrEq(psu1)))
+        .WillOnce(Return(std::string(version1)));
     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
                                              StrEq(PRESENT)))
         .WillOnce(Return(any(PropertyType(true)))); // present
diff --git a/vendor-example/get_version.cpp b/vendor-example/get_version.cpp
new file mode 100644
index 0000000..c0e3033
--- /dev/null
+++ b/vendor-example/get_version.cpp
@@ -0,0 +1,25 @@
+#include <cstdio>
+#include <string>
+
+// Get the version string for a PSU and output to stdout
+// In this example, it just returns the last 8 bytes as the version
+constexpr int NUM_OF_BYTES = 8;
+
+int main(int argc, char** argv)
+{
+    if (argc != 2)
+    {
+        printf("Usage: %s <psu-inventory-path>\n", argv[0]);
+        return 1;
+    }
+
+    std::string psu = argv[1];
+    if (psu.size() < NUM_OF_BYTES)
+    {
+        psu.append(NUM_OF_BYTES - psu.size(), '0'); //"0", 8 - psu.size());
+    }
+
+    printf("%s", psu.substr(psu.size() - NUM_OF_BYTES).c_str());
+
+    return 0;
+}
diff --git a/vendor-example/meson.build b/vendor-example/meson.build
new file mode 100644
index 0000000..73e30c2
--- /dev/null
+++ b/vendor-example/meson.build
@@ -0,0 +1,26 @@
+oe_sdk = get_option('oe-sdk')
+if oe_sdk.enabled()
+  # Setup OE SYSROOT
+  OECORE_TARGET_SYSROOT = run_command('sh', '-c', 'echo $OECORE_TARGET_SYSROOT').stdout().strip()
+  if OECORE_TARGET_SYSROOT == ''
+      error('Unable to get $OECORE_TARGET_SYSROOT, check your environment.')
+  endif
+  message('OE_SYSROOT: ' + OECORE_TARGET_SYSROOT)
+  rpath = ':'.join([OECORE_TARGET_SYSROOT + '/lib', OECORE_TARGET_SYSROOT + '/usr/lib'])
+  ld_so = run_command('sh', '-c', 'find ' + OECORE_TARGET_SYSROOT + '/lib/ld-*.so | sort -r -n | head -n1').stdout().strip()
+  dynamic_linker = ['-Wl,-dynamic-linker,' + ld_so]
+else
+  dynamic_linker = []
+endif
+
+examples = [
+  'get_version',
+]
+
+foreach example : examples
+  executable(example, example + '.cpp',
+             implicit_include_directories: false,
+             link_args: dynamic_linker,
+             build_rpath: oe_sdk.enabled() ? rpath : '')
+endforeach
+