Add lifetime property for drives interface

This will set the lifetime property when the eStoraged object is
created. This change does not expect the lifetime to change.

Tested:
busctl introspect  xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/inventory/storage/mmcblk0
NAME                                      TYPE      SIGNATURE RESULT/VALUE FLAGS
org.freedesktop.DBus.Introspectable       interface -         -            -
.Introspect                               method    -         s            -
org.freedesktop.DBus.Peer                 interface -         -            -
.GetMachineId                             method    -         s            -
.Ping                                     method    -         -            -
org.freedesktop.DBus.Properties           interface -         -            -
.Get                                      method    ss        v            -
.GetAll                                   method    s         a{sv}        -
.Set                                      method    ssv       -            -
.PropertiesChanged                        signal    sa{sv}as  -            -
xyz.openbmc_project.Inventory.Item.Drive  interface -         -            -
.Capacity                                 property  t         (top secret) emits-change
.PredictedMediaLifeLeftPercent            property  y         100          emits-change
xyz.openbmc_project.Inventory.Item.Volume interface -         -            -
.ChangePassword                           method    ayay      -            -
.Erase                                    method    s         -            -
.FormatLuks                               method    ays       -            -
.Lock                                     method    -         -            -
.Unlock                                   method    ay        -            -

Signed-off-by: John Edward Broadbent <jebr@google.com>
Change-Id: Ifbbed7d81c55e3edbe519c2b1048b5d1731fbb0e
diff --git a/include/estoraged.hpp b/include/estoraged.hpp
index aa89bee..c160fb1 100644
--- a/include/estoraged.hpp
+++ b/include/estoraged.hpp
@@ -2,6 +2,7 @@
 
 #include "cryptsetupInterface.hpp"
 #include "filesystemInterface.hpp"
+#include "util.hpp"
 
 #include <libcryptsetup.h>
 
@@ -36,6 +37,8 @@
      *  @param[in] server - sdbusplus asio object server
      *  @param[in] devPath - path to device file, e.g. /dev/mmcblk0
      *  @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] cryptInterface - (optional) pointer to CryptsetupInterface
      *    object
      *  @param[in] fsInterface - (optional) pointer to FilesystemInterface
@@ -43,7 +46,7 @@
      */
     EStoraged(sdbusplus::asio::object_server& server,
               const std::string& devPath, const std::string& luksName,
-              uint64_t size,
+              uint64_t size, uint8_t lifeTime,
               std::unique_ptr<CryptsetupInterface> cryptInterface =
                   std::make_unique<Cryptsetup>(),
               std::unique_ptr<FilesystemInterface> fsInterface =
diff --git a/include/pattern.hpp b/include/pattern.hpp
index 4def784..691cb16 100644
--- a/include/pattern.hpp
+++ b/include/pattern.hpp
@@ -30,7 +30,7 @@
      */
     void writePattern()
     {
-        writePattern(util::Util::findSizeOfBlockDevice(devPath));
+        writePattern(util::findSizeOfBlockDevice(devPath));
     }
 
     void writePattern(uint64_t driveSize);
@@ -43,7 +43,7 @@
 
     void verifyPattern()
     {
-        verifyPattern(util::Util::findSizeOfBlockDevice(devPath));
+        verifyPattern(util::findSizeOfBlockDevice(devPath));
     }
     void verifyPattern(uint64_t driveSize);
 };
diff --git a/include/sanitize.hpp b/include/sanitize.hpp
index 4ab73bf..7ec5a4a 100644
--- a/include/sanitize.hpp
+++ b/include/sanitize.hpp
@@ -96,7 +96,7 @@
      */
     void doSanitize()
     {
-        doSanitize(util::Util::findSizeOfBlockDevice(devPath));
+        doSanitize(util::findSizeOfBlockDevice(devPath));
     }
 
   private:
diff --git a/include/util.hpp b/include/util.hpp
index 1d706ce..b0f4970 100644
--- a/include/util.hpp
+++ b/include/util.hpp
@@ -1,20 +1,22 @@
 #pragma once
-#include <string_view>
+#include <string>
 
 namespace estoraged
 {
 namespace util
 {
 
-class Util
-{
-  public:
-    /** @brief finds the size of the linux block device in bytes
-     *  @param[in] devpath - the name of the linux block device
-     *  @return size of a block device using the devPath
-     */
-    static uint64_t findSizeOfBlockDevice(const std::string& devPath);
-};
+/** @brief finds the size of the linux block device in bytes
+ *  @param[in] devpath - the name of the linux block device
+ *  @return size of a block device using the devPath
+ */
+uint64_t findSizeOfBlockDevice(const std::string& devPath);
+
+/** @brief finds the predicted life left for a eMMC device
+ *  @param[in] sysfsPath - The path to the linux sysfs interface
+ *  @return the life remaing for the emmc, as a percentage.
+ */
+uint8_t findPredictedMediaLifeLeftPercent(const std::string& sysfsPath);
 
 } // namespace util
 
diff --git a/include/verifyDriveGeometry.hpp b/include/verifyDriveGeometry.hpp
index ea54998..ee8b3b7 100644
--- a/include/verifyDriveGeometry.hpp
+++ b/include/verifyDriveGeometry.hpp
@@ -25,7 +25,7 @@
      */
     void geometryOkay()
     {
-        geometryOkay(util::Util::findSizeOfBlockDevice(devPath));
+        geometryOkay(util::findSizeOfBlockDevice(devPath));
     }
     void geometryOkay(uint64_t bytes);
 };
diff --git a/include/zero.hpp b/include/zero.hpp
index 00c2b38..562b8ce 100644
--- a/include/zero.hpp
+++ b/include/zero.hpp
@@ -31,7 +31,7 @@
      */
     void writeZero()
     {
-        writeZero(util::Util::findSizeOfBlockDevice(devPath));
+        writeZero(util::findSizeOfBlockDevice(devPath));
     }
 
     /** @brief verifies the  uncompressible random pattern is on the drive
@@ -45,7 +45,7 @@
      */
     void verifyZero()
     {
-        verifyZero(util::Util::findSizeOfBlockDevice(devPath));
+        verifyZero(util::findSizeOfBlockDevice(devPath));
     }
 
   private:
diff --git a/src/estoraged.cpp b/src/estoraged.cpp
index 6225f91..9646139 100644
--- a/src/estoraged.cpp
+++ b/src/estoraged.cpp
@@ -33,7 +33,7 @@
 
 EStoraged::EStoraged(sdbusplus::asio::object_server& server,
                      const std::string& devPath, const std::string& luksName,
-                     uint64_t size,
+                     uint64_t size, uint8_t lifeTime,
                      std::unique_ptr<CryptsetupInterface> cryptInterface,
                      std::unique_ptr<FilesystemInterface> fsInterface) :
     devPath(devPath),
@@ -77,6 +77,8 @@
     driveInterface = objectServer.add_interface(
         path, "xyz.openbmc_project.Inventory.Item.Drive");
     driveInterface->register_property("Capacity", size);
+    driveInterface->register_property("PredictedMediaLifeLeftPercent",
+                                      lifeTime);
 
     volumeInterface->initialize();
     driveInterface->initialize();
diff --git a/src/main.cpp b/src/main.cpp
index aea7904..ed360fb 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -23,15 +23,20 @@
            "  -b <blockDevice>          The phyical encrypted device\n"
            "                            If omitted, default is /dev/mmcblk0.\n"
            "  -c <containerName>        The LUKS container name to be created\n"
-           "                            If omitted, default is luks-<devName>";
+           "                            If omitted, default is luks-<devName>"
+           "  -s <sysfsDevice>          The interface to kernel data\n"
+           "                            structures dealing with this drive.\n"
+           "                            If omitted, default is\n"
+           "                            /sys/block/mmcblk0/device/\n";
 }
 
 int main(int argc, char** argv)
 {
     std::string physicalBlockDev = "/dev/mmcblk0";
+    std::string sysfsDev = "/sys/block/mmcblk0/device";
     std::string containerBlockDev;
     int opt = 0;
-    while ((opt = getopt(argc, argv, "b:c:")) != -1)
+    while ((opt = getopt(argc, argv, "b:c:s:")) != -1)
     {
         switch (opt)
         {
@@ -41,6 +46,9 @@
             case 'c':
                 containerBlockDev = optarg;
                 break;
+            case 's':
+                sysfsDev = optarg;
+                break;
             default:
                 usage(*argv);
                 exit(EXIT_FAILURE);
@@ -68,7 +76,8 @@
 
         estoraged::EStoraged esObject{
             server, physicalBlockDev, containerBlockDev,
-            estoraged::util::Util::findSizeOfBlockDevice(physicalBlockDev)};
+            estoraged::util::findSizeOfBlockDevice(physicalBlockDev),
+            estoraged::util::findPredictedMediaLifeLeftPercent(sysfsDev)};
         lg2::info("Storage management service is running", "REDFISH_MESSAGE_ID",
                   std::string("OpenBMC.1.0.ServiceStarted"));
 
diff --git a/src/test/estoraged_test.cpp b/src/test/estoraged_test.cpp
index 05f872f..b98144e 100644
--- a/src/test/estoraged_test.cpp
+++ b/src/test/estoraged_test.cpp
@@ -39,6 +39,7 @@
     const char* testFileName = "testfile";
     const char* testLuksDevName = "testfile_luksDev";
     const uint64_t testSize = 24;
+    const uint8_t testLifeTime = 25;
     std::ofstream testFile;
     const char* testPath = "/test/openbmc_project/storage/test_dev";
     const char* estoragedInterface =
@@ -83,7 +84,7 @@
 
         esObject = std::make_unique<estoraged::EStoraged>(
             *objectServer, testFileName, testLuksDevName, testSize,
-            std::move(cryptIface), std::move(fsIface));
+            testLifeTime, std::move(cryptIface), std::move(fsIface));
     }
 
     void TearDown() override
diff --git a/src/test/meson.build b/src/test/meson.build
index 29cdda4..180d9d5 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -8,6 +8,7 @@
   'erase/crypto_test',
   'erase/sanitize_test',
   'estoraged_test',
+  'util_test',
 ]
 
 test_eStoraged_headers = include_directories('include')
diff --git a/src/test/util_test.cpp b/src/test/util_test.cpp
new file mode 100644
index 0000000..8f53510
--- /dev/null
+++ b/src/test/util_test.cpp
@@ -0,0 +1,52 @@
+#include <util.hpp>
+
+#include <fstream>
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace estoraged_test
+{
+using estoraged::util::findPredictedMediaLifeLeftPercent;
+
+TEST(utilTest, passFindPredictedMediaLife)
+{
+    std::string prefixName = ".";
+    std::string testFileName = prefixName + "/life_time";
+    std::ofstream testFile;
+    testFile.open(testFileName,
+                  std::ios::out | std::ios::binary | std::ios::trunc);
+    testFile << "0x07 0x04";
+    testFile.close();
+    EXPECT_EQ(findPredictedMediaLifeLeftPercent(prefixName), 40);
+}
+
+TEST(utilTest, estimatesSame)
+{
+
+    std::string prefixName = ".";
+    std::string testFileName = prefixName + "/life_time";
+    std::ofstream testFile;
+    testFile.open(testFileName,
+                  std::ios::out | std::ios::binary | std::ios::trunc);
+    testFile << "0x04 0x04";
+    testFile.close();
+
+    EXPECT_EQ(findPredictedMediaLifeLeftPercent(prefixName), 70);
+}
+
+TEST(utilTest, estimatesNotAvailable)
+{
+
+    std::string prefixName = ".";
+    std::string testFileName = prefixName + "/life_time";
+    std::ofstream testFile;
+    testFile.open(testFileName,
+                  std::ios::out | std::ios::binary | std::ios::trunc);
+    testFile.close();
+
+    EXPECT_EQ(findPredictedMediaLifeLeftPercent(prefixName), 255);
+}
+
+} // namespace estoraged_test
diff --git a/src/util.cpp b/src/util.cpp
index 7200d0a..acab3d8 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -8,7 +8,9 @@
 #include <stdplus/handle/managed.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
 
-#include <string_view>
+#include <fstream>
+#include <iostream>
+#include <string>
 
 namespace estoraged
 {
@@ -17,7 +19,7 @@
 using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
 using ::stdplus::fd::ManagedFd;
 
-uint64_t Util::findSizeOfBlockDevice(const std::string& devPath)
+uint64_t findSizeOfBlockDevice(const std::string& devPath)
 {
     ManagedFd fd;
     uint64_t bytes = 0;
@@ -38,6 +40,55 @@
     return bytes;
 }
 
+uint8_t findPredictedMediaLifeLeftPercent(const std::string& sysfsPath)
+{
+    // The eMMC spec defines two estimates for the life span of the device
+    // in the extended CSD field 269 and 268, named estimate A and estimate B.
+    // Linux exposes the values in the /life_time node.
+    // estimate A is for A type memory
+    // estimate B is for B type memory
+    //
+    // the estimate are encoded as such
+    // 0x01 <=>  0% - 10% device life time used
+    // 0x02 <=>  10% -20% device life time used
+    // ...
+    // 0x0A <=>  90% - 100% device life time used
+    // 0x0B <=> Exceeded its maximum estimated device life time
+
+    uint16_t estA = 0, estB = 0;
+    std::ifstream lifeTimeFile;
+    try
+    {
+        lifeTimeFile.open(sysfsPath + "/life_time", std::ios_base::in);
+        lifeTimeFile >> std::hex >> estA;
+        lifeTimeFile >> std::hex >> estB;
+        if ((estA == 0) || (estA > 11) || (estB == 0) || (estB > 11))
+        {
+            throw InternalFailure();
+        }
+    }
+    catch (...)
+    {
+        lg2::error("Unable to read sysfs", "REDFISH_MESSAGE_ID",
+                   std::string("OpenBMC.0.1.DriveEraseFailure"));
+        lifeTimeFile.close();
+        return 255;
+    }
+    lifeTimeFile.close();
+    // we are returning lowest LifeLeftPercent
+    uint8_t maxLifeUsed = 0;
+    if (estA > estB)
+    {
+        maxLifeUsed = estA;
+    }
+    else
+    {
+        maxLifeUsed = estB;
+    }
+
+    return static_cast<uint8_t>(11 - maxLifeUsed) * 10;
+}
+
 } // namespace util
 
 } // namespace estoraged