Export the part number and serial number

This commit exposes the eMMC's part name and serial number over the
appropriate D-Bus interface, so that it can be exposed over Redfish in
bmcweb.

Tested:
$ busctl introspect xyz.openbmc_project.eStoraged \
  /xyz/openbmc_project/inventory/storage/mmcblk0
  ...
  xyz.openbmc_project.Inventory.Decorator.Asset    interface -         -                                        -
.PartNumber                                      property  s         "ABCDEF"                                 emits-change
.SerialNumber                                    property  s         "123456abcd"                             emits-change
  ...
$ wget -qO- http://localhost:80/redfish/v1/Chassis/DC_SCM/Drives/mmcblk0
{
  "@odata.id": "/redfish/v1/Chassis/DC_SCM/Drives/mmcblk0",
  "@odata.type": "#Drive.v1_7_0.Drive",
  "CapacityBytes": 15634268160,
  "Id": "mmcblk0",
  "Links": {
    "Chassis": {
      "@odata.id": "/redfish/v1/Chassis/DC_SCM"
    }
  },
  "Name": "Name",
  "PartNumber": "ABCDEF",
  "PhysicalLocation": {
    "PartLocation": {
      "LocationType": "Embedded"
    }
  },
  "SerialNumber": "123456abcd",
  "Status": {
    "State": "Enabled"
  }
}

Signed-off-by: John Wedig <johnwedig@google.com>
Change-Id: I1d17f08b99907620b5f2c73fdaeacc84950ce64e
diff --git a/include/estoraged.hpp b/include/estoraged.hpp
index c7f0706..c938c33 100644
--- a/include/estoraged.hpp
+++ b/include/estoraged.hpp
@@ -41,6 +41,8 @@
      *  @param[in] luksName - name for the LUKS container
      *  @param[in] size - size of the drive in bytes
      *  @param[in] lifeTime - percent of lifetime remaining for a drive
+     *  @param[in] partNumber - part number for the storage device
+     *  @param[in] serialNumber - serial number for the storage device
      *  @param[in] cryptInterface - (optional) pointer to CryptsetupInterface
      *    object
      *  @param[in] fsInterface - (optional) pointer to FilesystemInterface
@@ -49,6 +51,7 @@
     EStoraged(sdbusplus::asio::object_server& server,
               const std::string& configPath, const std::string& devPath,
               const std::string& luksName, uint64_t size, uint8_t lifeTime,
+              const std::string& partNumber, const std::string& serialNumber,
               std::unique_ptr<CryptsetupInterface> cryptInterface =
                   std::make_unique<Cryptsetup>(),
               std::unique_ptr<FilesystemInterface> fsInterface =
@@ -135,6 +138,9 @@
     /** @brief D-Bus interface for the location of the drive. */
     std::shared_ptr<sdbusplus::asio::dbus_interface> embeddedLocationInterface;
 
+    /** @brief D-Bus interface for the asset information. */
+    std::shared_ptr<sdbusplus::asio::dbus_interface> assetInterface;
+
     /** @brief Association between chassis and drive. */
     std::shared_ptr<sdbusplus::asio::dbus_interface> association;
 
diff --git a/include/util.hpp b/include/util.hpp
index 2834950..a6af6ed 100644
--- a/include/util.hpp
+++ b/include/util.hpp
@@ -21,6 +21,18 @@
  */
 uint8_t findPredictedMediaLifeLeftPercent(const std::string& sysfsPath);
 
+/** @brief Get the part number (aka part name) for the storage device
+ *  @param[in] sysfsPath - The path to the linux sysfs interface.
+ *  @return part name as a string (or "unknown" if it couldn't be retrieved)
+ */
+std::string getPartNumber(const std::filesystem::path& sysfsPath);
+
+/** @brief Get the serial number for the storage device
+ *  @param[in] sysfsPath - The path to the linux sysfs interface.
+ *  @return serial name as a string (or "unknown" if it couldn't be retrieved)
+ */
+std::string getSerialNumber(const std::filesystem::path& sysfsPath);
+
 /** @brief Look for the device described by the provided StorageData.
  *  @details Currently, this function assumes that there's only one eMMC.
  *    When we need to support multiple eMMCs, we will put more information in
diff --git a/src/estoraged.cpp b/src/estoraged.cpp
index 9c4fba9..0b93b3b 100644
--- a/src/estoraged.cpp
+++ b/src/estoraged.cpp
@@ -35,7 +35,8 @@
 EStoraged::EStoraged(sdbusplus::asio::object_server& server,
                      const std::string& configPath, const std::string& devPath,
                      const std::string& luksName, uint64_t size,
-                     uint8_t lifeTime,
+                     uint8_t lifeTime, const std::string& partNumber,
+                     const std::string& serialNumber,
                      std::unique_ptr<CryptsetupInterface> cryptInterface,
                      std::unique_ptr<FilesystemInterface> fsInterface) :
     devPath(devPath),
@@ -102,9 +103,16 @@
     embeddedLocationInterface = objectServer.add_interface(
         objectPath, "xyz.openbmc_project.Inventory.Connector.Embedded");
 
+    /* Add Asset interface. */
+    assetInterface = objectServer.add_interface(
+        objectPath, "xyz.openbmc_project.Inventory.Decorator.Asset");
+    assetInterface->register_property("PartNumber", partNumber);
+    assetInterface->register_property("SerialNumber", serialNumber);
+
     volumeInterface->initialize();
     driveInterface->initialize();
     embeddedLocationInterface->initialize();
+    assetInterface->initialize();
 
     /* Set up the association between chassis and drive. */
     association = objectServer.add_interface(
@@ -122,6 +130,7 @@
     objectServer.remove_interface(volumeInterface);
     objectServer.remove_interface(driveInterface);
     objectServer.remove_interface(embeddedLocationInterface);
+    objectServer.remove_interface(assetInterface);
     objectServer.remove_interface(association);
 }
 
diff --git a/src/main.cpp b/src/main.cpp
index 380d17d..2f93899 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -94,9 +94,14 @@
                 uint8_t lifeleft =
                     estoraged::util::findPredictedMediaLifeLeftPercent(
                         sysfsDir);
+                std::string partNumber =
+                    estoraged::util::getPartNumber(sysfsDir);
+                std::string serialNumber =
+                    estoraged::util::getSerialNumber(sysfsDir);
                 /* Create the storage object. */
                 storageObjects[path] = std::make_unique<estoraged::EStoraged>(
-                    objectServer, path, deviceFile, luksName, size, lifeleft);
+                    objectServer, path, deviceFile, luksName, size, lifeleft,
+                    partNumber, serialNumber);
                 lg2::info("Created eStoraged object for path {PATH}", "PATH",
                           path, "REDFISH_MESSAGE_ID",
                           std::string("OpenBMC.0.1.CreateStorageObjects"));
diff --git a/src/test/estoraged_test.cpp b/src/test/estoraged_test.cpp
index 89581ab..0bbb32b 100644
--- a/src/test/estoraged_test.cpp
+++ b/src/test/estoraged_test.cpp
@@ -42,6 +42,8 @@
         "/xyz/openbmc_project/inventory/system/board/test_board/test_emmc";
     const uint64_t testSize = 24;
     const uint8_t testLifeTime = 25;
+    const std::string testPartNumber = "12345678";
+    const std::string testSerialNumber = "ABCDEF1234";
     std::ofstream testFile;
     std::string passwordString;
     std::vector<uint8_t> password;
@@ -82,7 +84,8 @@
 
         esObject = std::make_unique<estoraged::EStoraged>(
             *objectServer, testConfigPath, testFileName, testLuksDevName,
-            testSize, testLifeTime, std::move(cryptIface), std::move(fsIface));
+            testSize, testLifeTime, testPartNumber, testSerialNumber,
+            std::move(cryptIface), std::move(fsIface));
     }
 
     void TearDown() override
diff --git a/src/test/util_test.cpp b/src/test/util_test.cpp
index 9059d76..5f16068 100644
--- a/src/test/util_test.cpp
+++ b/src/test/util_test.cpp
@@ -13,6 +13,8 @@
 namespace estoraged_test
 {
 using estoraged::util::findPredictedMediaLifeLeftPercent;
+using estoraged::util::getPartNumber;
+using estoraged::util::getSerialNumber;
 
 TEST(utilTest, passFindPredictedMediaLife)
 {
@@ -24,6 +26,7 @@
     testFile << "0x07 0x04";
     testFile.close();
     EXPECT_EQ(findPredictedMediaLifeLeftPercent(prefixName), 40);
+    EXPECT_TRUE(std::filesystem::remove(testFileName));
 }
 
 TEST(utilTest, estimatesSame)
@@ -38,6 +41,7 @@
     testFile.close();
 
     EXPECT_EQ(findPredictedMediaLifeLeftPercent(prefixName), 70);
+    EXPECT_TRUE(std::filesystem::remove(testFileName));
 }
 
 TEST(utilTest, estimatesNotAvailable)
@@ -51,6 +55,49 @@
     testFile.close();
 
     EXPECT_EQ(findPredictedMediaLifeLeftPercent(prefixName), 255);
+    EXPECT_TRUE(std::filesystem::remove(testFileName));
+}
+
+TEST(utilTest, getPartNumberFail)
+{
+    std::string prefixName = ".";
+    std::string testFileName = prefixName + "/name";
+    /* The part name file won't exist for this test. */
+    EXPECT_EQ(getPartNumber(prefixName), "unknown");
+}
+
+TEST(utilTest, getPartNumberPass)
+{
+    std::string prefixName = ".";
+    std::string testFileName = prefixName + "/name";
+    std::ofstream testFile;
+    testFile.open(testFileName,
+                  std::ios::out | std::ios::binary | std::ios::trunc);
+    testFile << "ABCD1234";
+    testFile.close();
+    EXPECT_EQ(getPartNumber(prefixName), "ABCD1234");
+    EXPECT_TRUE(std::filesystem::remove(testFileName));
+}
+
+TEST(utilTest, getSerialNumberFail)
+{
+    std::string prefixName = ".";
+    std::string testFileName = prefixName + "/serial";
+    /* The serial number file won't exist for this test. */
+    EXPECT_EQ(getSerialNumber(prefixName), "unknown");
+}
+
+TEST(utilTest, getSerialNumberPass)
+{
+    std::string prefixName = ".";
+    std::string testFileName = prefixName + "/serial";
+    std::ofstream testFile;
+    testFile.open(testFileName,
+                  std::ios::out | std::ios::binary | std::ios::trunc);
+    testFile << "0x12345678";
+    testFile.close();
+    EXPECT_EQ(getSerialNumber(prefixName), "0x12345678");
+    EXPECT_TRUE(std::filesystem::remove(testFileName));
 }
 
 /* Test case where we successfully find the device file. */
diff --git a/src/util.cpp b/src/util.cpp
index 811cbf8..9952bca 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -92,6 +92,56 @@
     return static_cast<uint8_t>(11 - maxLifeUsed) * 10;
 }
 
+std::string getPartNumber(const std::filesystem::path& sysfsPath)
+{
+    std::ifstream partNameFile;
+    std::string partName;
+    try
+    {
+        std::filesystem::path namePath(sysfsPath);
+        namePath /= "name";
+        partNameFile.open(namePath, std::ios_base::in);
+        partNameFile >> partName;
+    }
+    catch (...)
+    {
+        lg2::error("Unable to read sysfs", "REDFISH_MESSAGE_ID",
+                   std::string("OpenBMC.0.1.PartNumberFailure"));
+    }
+    partNameFile.close();
+    if (partName.empty())
+    {
+        partName = "unknown";
+    }
+
+    return partName;
+}
+
+std::string getSerialNumber(const std::filesystem::path& sysfsPath)
+{
+    std::ifstream serialNumberFile;
+    std::string serialNumber;
+    try
+    {
+        std::filesystem::path serialPath(sysfsPath);
+        serialPath /= "serial";
+        serialNumberFile.open(serialPath, std::ios_base::in);
+        serialNumberFile >> serialNumber;
+    }
+    catch (...)
+    {
+        lg2::error("Unable to read sysfs", "REDFISH_MESSAGE_ID",
+                   std::string("OpenBMC.0.1.SerialNumberFailure"));
+    }
+    serialNumberFile.close();
+    if (serialNumber.empty())
+    {
+        serialNumber = "unknown";
+    }
+
+    return serialNumber;
+}
+
 bool findDevice(const StorageData& data, const std::filesystem::path& searchDir,
                 std::filesystem::path& deviceFile,
                 std::filesystem::path& sysfsDir, std::string& luksName)