flash-ipmi: implement flashStartTransfer

Change-Id: I207f350dad3d73dacf1216e7742637531b50ea1a
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/configure.ac b/configure.ac
index 0fe7382..dd05a59 100644
--- a/configure.ac
+++ b/configure.ac
@@ -68,6 +68,10 @@
     AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
 )
 
+AC_ARG_VAR(STAGING_PATH, [The staging path for the flash image.])
+AS_IF([test "x$STAGING_PATH" == "x"], [STAGING_PATH="/run/initramfs/bmc-image"])
+AC_DEFINE_UNQUOTED([STAGING_PATH], ["$STAGING_PATH"], [The staging path for the flash image.])
+
 # Create configured output
 AC_CONFIG_FILES([Makefile test/Makefile])
 AC_OUTPUT
diff --git a/flash-ipmi.cpp b/flash-ipmi.cpp
index 4588b5b..9a0566c 100644
--- a/flash-ipmi.cpp
+++ b/flash-ipmi.cpp
@@ -16,24 +16,78 @@
 
 #include "flash-ipmi.hpp"
 
+#include <cstdio>
+#include <phosphor-logging/log.hpp>
+
+using namespace phosphor::logging;
+
+namespace
+{
+
+/**
+ * Close a file pointer and set to null.
+ *
+ * @param[in] fd a pointer to your file handle.
+ */
+void closeFile(std::FILE** fd)
+{
+    if (!fd)
+    {
+        return;
+    }
+
+    if (*fd)
+    {
+        std::fclose(*fd);
+        (*fd) = nullptr;
+    }
+}
+}
+
+void FlashUpdate::closeEverything()
+{
+    closeFile(&flashFd);
+}
+
+FlashUpdate::~FlashUpdate()
+{
+    /* Close without deleting.  This object can only be destroyed if the ipmi
+     * daemon unloads it, by closing down.  In this event, we want the verified
+     * file to live on.
+     */
+    closeEverything();
+}
+
 void FlashUpdate::abortEverything()
 {
+    closeEverything();
+
+    /* TODO: And now delete everything */
     return;
 }
 
 bool FlashUpdate::openEverything()
 {
+    flashFd = std::fopen(tmpPath.c_str(), "w");
+    if (flashFd == nullptr)
+    {
+        log<level::INFO>("Unable to open staging path",
+                         entry("PATH=%s", tmpPath.c_str()));
+        return false;
+    }
+
     return true;
 }
 
 /* Prepare to receive a BMC image and then a signature. */
-bool FlashUpdate::start(uint32_t)
+bool FlashUpdate::start(uint32_t length)
 {
-    /* TODO: Validate request->length */
-
     /* Close out and delete everything. */
     abortEverything();
 
+    /* TODO: Validate request->length */
+    flashLength = length;
+
     /* Start over! */
     return openEverything();
 }
diff --git a/flash-ipmi.hpp b/flash-ipmi.hpp
index 5e5fb22..7b60b05 100644
--- a/flash-ipmi.hpp
+++ b/flash-ipmi.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <string>
 #include <vector>
 
 #include "host-ipmid/ipmid-api.h"
@@ -148,8 +149,10 @@
 class FlashUpdate : public UpdateInterface
 {
   public:
-    FlashUpdate() = default;
-    ~FlashUpdate() = default;
+    FlashUpdate(const std::string& stagingPath) :
+        flashLength(0), flashFd(nullptr), tmpPath(stagingPath){};
+    ~FlashUpdate();
+
     FlashUpdate(const FlashUpdate&) = default;
     FlashUpdate& operator=(const FlashUpdate&) = default;
     FlashUpdate(FlashUpdate&&) = default;
@@ -168,6 +171,11 @@
 
   private:
     /**
+     * Tries to close out everything.
+     */
+    void closeEverything();
+
+    /**
      * Tries to close out and delete anything staged.
      */
     void abortEverything();
@@ -178,4 +186,13 @@
      * @return false on failure.
      */
     bool openEverything();
+
+    /* The length of the flash image in bytes. */
+    uint32_t flashLength;
+
+    /* The file handle to the flash staging file. */
+    std::FILE* flashFd;
+
+    /* Where the bytes are written before verification. */
+    const std::string tmpPath;
 };
diff --git a/main.cpp b/main.cpp
index 0c65f85..e623c13 100644
--- a/main.cpp
+++ b/main.cpp
@@ -19,9 +19,12 @@
 #include "host-ipmid/ipmid-api.h"
 #include "host-ipmid/oemrouter.hpp"
 
+#include "config.h"
 #include "flash-ipmi.hpp"
 #include "ipmi.hpp"
 
+static constexpr auto stagingPath = STAGING_PATH;
+
 /* TODO: Once OEM IPMI number placement is settled, point to that. */
 namespace oem
 {
@@ -81,7 +84,7 @@
 
 void setupGlobalOemFlashControl()
 {
-    flashUpdateSingleton = std::make_unique<FlashUpdate>();
+    flashUpdateSingleton = std::make_unique<FlashUpdate>(stagingPath);
 
 #ifdef ENABLE_GOOGLE
     oem::Router* router = oem::mutableRouter();
diff --git a/test/Makefile.am b/test/Makefile.am
index c02ebb0..245b251 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -19,7 +19,8 @@
 	ipmi_startverify_unittest \
 	ipmi_abort_unittest \
 	ipmi_validate_unittest \
-	ipmi_command_unittest
+	ipmi_command_unittest \
+	flash_start_unittest
 
 TESTS = $(check_PROGRAMS)
 
@@ -52,3 +53,6 @@
 
 ipmi_command_unittest_SOURCES = ipmi_command_unittest.cpp
 ipmi_command_unittest_LDADD = $(top_builddir)/ipmi.o
+
+flash_start_unittest_SOURCES = flash_start_unittest.cpp
+flash_start_unittest_LDADD = $(top_builddir)/flash-ipmi.o $(SDBUSPLUS_LIBS)
diff --git a/test/flash_start_unittest.cpp b/test/flash_start_unittest.cpp
new file mode 100644
index 0000000..b287561
--- /dev/null
+++ b/test/flash_start_unittest.cpp
@@ -0,0 +1,23 @@
+#include "flash-ipmi.hpp"
+
+#include <cstdio>
+#include <gtest/gtest.h>
+#include <string>
+
+#define THIRTYTWO_MIB 33554432
+
+TEST(FlashIpmiStartTest, VerifiesFieldsAndAction)
+{
+    // The interface does not currently support failure injection, so let's
+    // simply verify it does what we think it should.
+
+    std::string name = std::tmpnam(nullptr);
+
+    FlashUpdate updater(name);
+    updater.start(THIRTYTWO_MIB);
+
+    auto file = std::fopen(name.c_str(), "r");
+    EXPECT_TRUE(file);
+    std::fclose(file);
+    (void)std::remove(name.c_str());
+}