Add zero write and verify

This code implements the zero verify and zero write dbus interface. The
goal is to fill the whole block device with zeros, then check to make
sure the operation worked correctly.

Tested:
$ systemctl stop emmc.service
$ ./eStoraged -b /dev/mmcblk0 &
$ time busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Erase s
xyz.openbmc_project.Inventory.Item.Volume.EraseMethod.ZeroOverWrite --timeout=1200
Erasing encrypted eMMC                                                                                                                                                   <6> Starting erase

real    5m59.695s
user    0m0.000s
sys     0m0.030s

root@ytbaz20-nfd01:~/jebr# hexdump /dev/mmcblk0
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
$ time busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Erase s
xyz.openbmc_project.Inventory.Item.Volume.EraseMethod.ZeroVerify  --timeout=1200
Erasing encrypted eMMC
<6> Starting erase

real    5m46.920s
user    0m0.010s
sys     0m0.010s
$ echo "not zero" > /dev/mmcblk0
$ time busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Erase s
xyz.openbmc_project.Inventory.Item.Volume.EraseMethod.ZeroVerify  --timeout=1200
Erasing encrypted eMMC
<6> Starting erase
<3> Estoraged erase zeros block is not zero
Call failed: The operation failed internally.

real    0m0.022s
user    0m0.000s
sys     0m0.020s

Change-Id: Ie78ad427de1aa75472fc7ddd72d094866fe14b66
Signed-off-by: John Edward Broadbent <jebr@google.com>
diff --git a/include/zero.hpp b/include/zero.hpp
new file mode 100644
index 0000000..b6ca93f
--- /dev/null
+++ b/include/zero.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "erase.hpp"
+
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/managed.hpp>
+
+namespace estoraged
+{
+
+using stdplus::fd::ManagedFd;
+
+class Zero : public Erase
+{
+  public:
+    /** @brief Creates a zero erase object.
+     *
+     *  @param[in] inDevPath - the linux device path for the block device.
+     */
+    Zero(std::string_view inDevPath) : Erase(inDevPath)
+    {}
+
+    /** @brief writes zero to the drive
+     * and throws errors accordingly.
+     *
+     *  @param[in] bytes - Size of the block device
+     *  @param[in] managedFd - the file descriptor for the open drive
+     */
+    void writeZero(uint64_t driveSize, ManagedFd& fd);
+
+    /** @brief verifies the  uncompressible random pattern is on the drive
+     * and throws errors accordingly.
+     *
+     *  @param[in] bytes - Size of the block device
+     *  @param[in] managedFd - the file descriptor for the open drive
+     */
+    void verifyZero(uint64_t driveSize, ManagedFd& fd);
+
+  private:
+    /* @brief the size of the blocks in bytes used for write and verify.
+     * 32768 was also tested. It had almost identical performance.
+     */
+    static constexpr size_t blockSize = 4096;
+};
+
+} // namespace estoraged
diff --git a/src/erase/meson.build b/src/erase/meson.build
index 7dbd727..2265c1a 100644
--- a/src/erase/meson.build
+++ b/src/erase/meson.build
@@ -3,6 +3,7 @@
   'verifyDriveGeometry.cpp',
   'pattern.cpp',
   'erase.cpp',
+  'zero.cpp',
   include_directories : eStoraged_headers,
   implicit_include_directories: false,
   dependencies: [
diff --git a/src/erase/zero.cpp b/src/erase/zero.cpp
new file mode 100644
index 0000000..accb8c8
--- /dev/null
+++ b/src/erase/zero.cpp
@@ -0,0 +1,78 @@
+#include "zero.hpp"
+
+#include "erase.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <array>
+#include <span>
+
+namespace estoraged
+{
+
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using stdplus::fd::ManagedFd;
+
+void Zero::writeZero(const uint64_t driveSize, ManagedFd& fd)
+{
+
+    uint64_t currentIndex = 0;
+    const std::array<const std::byte, blockSize> blockOfZeros{};
+
+    while (currentIndex < driveSize)
+    {
+        uint32_t writeSize = currentIndex + blockSize < driveSize
+                                 ? blockSize
+                                 : driveSize - currentIndex;
+        try
+        {
+            fd.write({blockOfZeros.data(), writeSize});
+        }
+        catch (...)
+        {
+            lg2::error("Estoraged erase zeros unable to write size",
+                       "REDFISH_MESSAGE_ID",
+                       std::string("eStorageD.1.0.EraseFailure"));
+            throw InternalFailure();
+        }
+        currentIndex += writeSize;
+    }
+}
+
+void Zero::verifyZero(uint64_t driveSize, ManagedFd& fd)
+{
+    uint64_t currentIndex = 0;
+    std::array<std::byte, blockSize> readArr;
+    const std::array<const std::byte, blockSize> blockOfZeros{};
+
+    while (currentIndex < driveSize)
+    {
+        uint32_t readSize = currentIndex + blockSize < driveSize
+                                ? blockSize
+                                : driveSize - currentIndex;
+        try
+        {
+            fd.read({readArr.data(), readSize});
+        }
+        catch (...)
+        {
+            lg2::error("Estoraged erase zeros block unable to read size",
+                       "REDFISH_MESSAGE_ID",
+                       std::string("eStorageD.1.0.EraseFailure"));
+            throw InternalFailure();
+        }
+        if (memcmp(readArr.data(), blockOfZeros.data(), readSize))
+        {
+            lg2::error("Estoraged erase zeros block is not zero",
+                       "REDFISH_MESSAGE_ID",
+                       std::string("eStorageD.1.0.EraseFailure"));
+            throw InternalFailure();
+        }
+        currentIndex += readSize;
+    }
+}
+
+} // namespace estoraged
diff --git a/src/estoraged.cpp b/src/estoraged.cpp
index 55a08a4..cb8f0fa 100644
--- a/src/estoraged.cpp
+++ b/src/estoraged.cpp
@@ -4,6 +4,7 @@
 #include "cryptsetupInterface.hpp"
 #include "pattern.hpp"
 #include "verifyDriveGeometry.hpp"
+#include "zero.hpp"
 
 #include <libcryptsetup.h>
 #include <openssl/rand.h>
@@ -93,10 +94,18 @@
         }
         case EraseMethod::ZeroOverWrite:
         {
+            Zero myZero(devPath);
+            ManagedFd drivefd =
+                stdplus::fd::open(devPath, stdplus::fd::OpenAccess::WriteOnly);
+            myZero.writeZero(myZero.findSizeOfBlockDevice(), drivefd);
             break;
         }
         case EraseMethod::ZeroVerify:
         {
+            Zero myZero(devPath);
+            ManagedFd drivefd =
+                stdplus::fd::open(devPath, stdplus::fd::OpenAccess::ReadOnly);
+            myZero.verifyZero(myZero.findSizeOfBlockDevice(), drivefd);
             break;
         }
         case EraseMethod::SecuredLocked:
diff --git a/src/test/erase/zero_test.cpp b/src/test/erase/zero_test.cpp
new file mode 100644
index 0000000..b3b91ec
--- /dev/null
+++ b/src/test/erase/zero_test.cpp
@@ -0,0 +1,99 @@
+#include "estoraged_conf.hpp"
+#include "zero.hpp"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <system_error>
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using estoraged::Zero;
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using stdplus::fd::ManagedFd;
+
+namespace estoraged_test
+{
+
+TEST(Zeros, zeroPass)
+{
+    uint64_t size = 4096;
+    int fds[2];
+    Zero pass("fileName");
+    if (pipe(fds))
+    {
+        FAIL();
+    }
+    ManagedFd writeFd(std::move(fds[1]));
+    EXPECT_NO_THROW(pass.writeZero(size, writeFd));
+    ManagedFd verifyFd(std::move(fds[0]));
+    EXPECT_NO_THROW(pass.verifyZero(size, verifyFd));
+}
+
+/* This test that pattern writes the correct number of bytes even if
+ * size of the drive is not divisable by the block size
+ */
+TEST(Zeros, notDivisible)
+{
+    uint64_t size = 4097;
+    // 4097 is not divisible by the block size, and we expect no errors
+    int fds[2];
+    Zero pass("fileName");
+    if (pipe(fds))
+    {
+        FAIL();
+    }
+    ManagedFd writeFd(std::move(fds[1]));
+    EXPECT_NO_THROW(pass.writeZero(size, writeFd));
+    ManagedFd verifyFd(std::move(fds[0]));
+    EXPECT_NO_THROW(pass.verifyZero(size, verifyFd));
+}
+
+TEST(Zeros, notZeroStart)
+{
+    uint64_t size = 4096;
+    int fds[2];
+    Zero pass("fileName");
+    if (pipe(fds))
+    {
+        FAIL();
+    }
+    int dummyValue = 88;
+    if (::write(fds[1], &dummyValue, sizeof(dummyValue)) != sizeof(dummyValue))
+    {
+        FAIL();
+    }
+    ManagedFd writeFd(std::move(fds[1]));
+    EXPECT_NO_THROW(pass.writeZero(size - sizeof(dummyValue), writeFd));
+    ManagedFd verifyFd(std::move(fds[0]));
+    EXPECT_THROW(pass.verifyZero(size, verifyFd), InternalFailure);
+}
+
+TEST(Zeros, notZeroEnd)
+{
+    uint64_t size = 4096;
+    int fds[2];
+    Zero pass("fileName");
+    if (pipe(fds))
+    {
+        FAIL();
+    }
+    int dummyValue = 88;
+    int tmpFd = fds[1];
+    ManagedFd writeFd(std::move(tmpFd));
+    EXPECT_NO_THROW(pass.writeZero(size - sizeof(dummyValue), writeFd));
+    if (::write(fds[1], &dummyValue, sizeof(dummyValue)) != sizeof(dummyValue))
+    {
+        FAIL();
+    }
+    ManagedFd verifyFd(std::move(fds[0]));
+    EXPECT_THROW(pass.verifyZero(size, verifyFd), InternalFailure);
+}
+
+} // namespace estoraged_test
diff --git a/src/test/meson.build b/src/test/meson.build
index 0373840..6d6880d 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -4,6 +4,7 @@
 tests = [
   'erase/verifyGeometry_test',
   'erase/pattern_test',
+  'erase/zero_test',
   'estoraged_test',
 ]