bmc: add cleanup blob handler

Add a cleanup blob handler, such that there is a new blob id present
named "/flash/cleanup" that will delete temporary files.  This blob
handler expects a client to open/commit/close the blob.  This blob
handler will delete files that are specified as temporary.  The host
client may use this to clean up artifacts on verification or update
failure.

This can be extended later to handle calling a service or doing anything
else to cleanup.  The cleanup handler will be added if
--enable-cleanup-delete.  The recipe will automatically add this blob
handler if that configure variable is set.

Tested: Not tested on real hardware.
Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I4502b2613e38f0a947d7235d084287376c6b0ce1
diff --git a/Makefile.am b/Makefile.am
index 6e03b0a..a341233 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -94,3 +94,7 @@
 if BUILD_HOST_TOOL
 SUBDIRS += tools
 endif
+
+if BUILD_CLEANUP_DELETE
+SUBDIRS += cleanup
+endif
diff --git a/cleanup/Makefile.am b/cleanup/Makefile.am
new file mode 100644
index 0000000..3623f7f
--- /dev/null
+++ b/cleanup/Makefile.am
@@ -0,0 +1,26 @@
+ACLOCAL_AMFLAGS = -I m4
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+noinst_LTLIBRARIES = libfirmwarecleanupblob_common.la
+libfirmwarecleanupblob_common_la_SOURCES = \
+	cleanup.cpp \
+	fs.cpp \
+	$(top_srcdir)/util.cpp
+libfirmwarecleanupblob_common_la_CXXFLAGS = \
+	-flto
+libfirmwarecleanupblob_common_la_LDFLAGS = \
+	-lstdc++fs
+
+libfirmwarecleanupblobdir = ${libdir}/ipmid-providers
+libfirmwarecleanupblob_LTLIBRARIES = libfirmwarecleanupblob.la
+libfirmwarecleanupblob_la_SOURCES = main.cpp
+libfirmwarecleanupblob_la_LIBADD = libfirmwarecleanupblob_common.la
+libfirmwarecleanupblob_la_LDFLAGS = \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	-version-info 0:0:0 -shared
+libfirmwarecleanupblob_la_CXXFLAGS = \
+	-I$(top_srcdir)/ \
+	$(PHOSPHOR_LOGGING_CFLAGS) \
+	-flto
+
+SUBDIRS = . test
diff --git a/cleanup/cleanup.cpp b/cleanup/cleanup.cpp
new file mode 100644
index 0000000..7ab7ee4
--- /dev/null
+++ b/cleanup/cleanup.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cleanup.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+#include <filesystem>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+std::unique_ptr<blobs::GenericBlobInterface>
+    FileCleanupHandler::CreateCleanupHandler(
+        const std::string& blobId, const std::vector<std::string>& files)
+{
+    return std::make_unique<FileCleanupHandler>(blobId, files);
+}
+
+bool FileCleanupHandler::canHandleBlob(const std::string& path)
+{
+    return (path == supported);
+}
+
+std::vector<std::string> FileCleanupHandler::getBlobIds()
+{
+    return {supported};
+}
+
+bool FileCleanupHandler::commit(uint16_t session,
+                                const std::vector<uint8_t>& data)
+{
+    namespace fs = std::filesystem;
+
+    for (const auto& file : files)
+    {
+        helper->remove(file);
+    }
+
+    return true;
+}
+
+} // namespace ipmi_flash
diff --git a/cleanup/cleanup.hpp b/cleanup/cleanup.hpp
new file mode 100644
index 0000000..7c528af
--- /dev/null
+++ b/cleanup/cleanup.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "fs.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+
+class FileCleanupHandler : public blobs::GenericBlobInterface
+{
+  public:
+    static std::unique_ptr<blobs::GenericBlobInterface>
+        CreateCleanupHandler(const std::string& blobId,
+                             const std::vector<std::string>& files);
+
+    FileCleanupHandler(const std::string& blobId,
+                       const std::vector<std::string>& files,
+                       const FileSystemInterface* helper = &fileSystemHelper) :
+        supported(blobId),
+        files(files), helper(helper)
+    {
+    }
+
+    ~FileCleanupHandler() = default;
+    FileCleanupHandler(const FileCleanupHandler&) = default;
+    FileCleanupHandler& operator=(const FileCleanupHandler&) = default;
+    FileCleanupHandler(FileCleanupHandler&&) = default;
+    FileCleanupHandler& operator=(FileCleanupHandler&&) = default;
+
+    bool canHandleBlob(const std::string& path) override;
+    std::vector<std::string> getBlobIds() override;
+    bool commit(uint16_t session, const std::vector<uint8_t>& data) override;
+
+    /* These methods return true without doing anything. */
+    bool open(uint16_t session, uint16_t flags,
+              const std::string& path) override
+    {
+        return true;
+    }
+    bool close(uint16_t session) override
+    {
+        return true;
+    }
+    bool expire(uint16_t session) override
+    {
+        return true;
+    }
+
+    /* These methods are unsupported. */
+    bool deleteBlob(const std::string& path) override
+    {
+        return false;
+    }
+    bool stat(const std::string& path, blobs::BlobMeta* meta) override
+    {
+        return false;
+    }
+    std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+                              uint32_t requestedSize) override
+    {
+        return {};
+    }
+    bool write(uint16_t session, uint32_t offset,
+               const std::vector<uint8_t>& data) override
+    {
+        return false;
+    }
+    bool writeMeta(uint16_t session, uint32_t offset,
+                   const std::vector<uint8_t>& data) override
+    {
+        return false;
+    }
+    bool stat(uint16_t session, blobs::BlobMeta* meta) override
+    {
+        return false;
+    }
+
+  private:
+    std::string supported;
+    std::vector<std::string> files;
+    const FileSystemInterface* helper;
+};
+
+} // namespace ipmi_flash
diff --git a/cleanup/fs.cpp b/cleanup/fs.cpp
new file mode 100644
index 0000000..b12d78f
--- /dev/null
+++ b/cleanup/fs.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "fs.hpp"
+
+#include <filesystem>
+#include <string>
+
+namespace ipmi_flash
+{
+
+void FileSystem::remove(const std::string& path) const
+{
+    namespace fs = std::filesystem;
+
+    try
+    {
+        (void)fs::remove(path);
+    }
+    catch (...)
+    {
+        return;
+    }
+
+    return;
+}
+
+FileSystem fileSystemHelper;
+
+} // namespace ipmi_flash
diff --git a/cleanup/fs.hpp b/cleanup/fs.hpp
new file mode 100644
index 0000000..c206721
--- /dev/null
+++ b/cleanup/fs.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <string>
+
+namespace ipmi_flash
+{
+
+class FileSystemInterface
+{
+  public:
+    virtual ~FileSystemInterface() = default;
+
+    virtual void remove(const std::string& path) const = 0;
+};
+
+class FileSystem : public FileSystemInterface
+{
+  public:
+    FileSystem() = default;
+
+    void remove(const std::string& path) const override;
+};
+
+extern FileSystem fileSystemHelper;
+
+} // namespace ipmi_flash
diff --git a/cleanup/main.cpp b/cleanup/main.cpp
new file mode 100644
index 0000000..74612f6
--- /dev/null
+++ b/cleanup/main.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "cleanup.hpp"
+#include "util.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+#include <memory>
+#include <phosphor-logging/log.hpp>
+#include <string>
+#include <vector>
+
+namespace ipmi_flash
+{
+std::vector<std::string> files = {STATIC_HANDLER_STAGED_NAME,
+                                  TARBALL_STAGED_NAME, HASH_FILENAME,
+                                  VERIFY_STATUS_FILENAME};
+}
+
+extern "C" std::unique_ptr<blobs::GenericBlobInterface> createHandler()
+{
+    using namespace phosphor::logging;
+
+    auto handler = ipmi_flash::FileCleanupHandler::CreateCleanupHandler(
+        ipmi_flash::cleanupBlobId, ipmi_flash::files);
+
+    if (!handler)
+    {
+        log<level::ERR>("Unable to create FileCleanupHandle for Firmware");
+        return nullptr;
+    }
+
+    return handler;
+}
diff --git a/cleanup/test/Makefile.am b/cleanup/test/Makefile.am
new file mode 100644
index 0000000..4c347ba
--- /dev/null
+++ b/cleanup/test/Makefile.am
@@ -0,0 +1,18 @@
+AM_CPPFLAGS = -I$(top_srcdir)/ \
+	-I$(top_srcdir)/cleanup/ \
+	$(GTEST_CFLAGS) \
+	$(GMOCK_CFLAGS)
+AM_CXXFLAGS = \
+	$(PHOSPHOR_LOGGING_CFLAGS)
+AM_LDFLAGS = \
+	$(GTEST_LIBS) \
+	$(GMOCK_LIBS) \
+	-lgmock_main \
+	$(OESDK_TESTCASE_FLAGS) \
+	$(PHOSPHOR_LOGGING_LIBS)
+
+check_PROGRAMS = cleanup_handler_unittest
+TESTS = $(check_PROGRAMS)
+
+cleanup_handler_unittest_SOURCES = cleanup_handler_unittest.cpp
+cleanup_handler_unittest_LDADD = $(top_builddir)/cleanup/libfirmwarecleanupblob_common.la
diff --git a/cleanup/test/cleanup_handler_unittest.cpp b/cleanup/test/cleanup_handler_unittest.cpp
new file mode 100644
index 0000000..5191234
--- /dev/null
+++ b/cleanup/test/cleanup_handler_unittest.cpp
@@ -0,0 +1,42 @@
+#include "cleanup.hpp"
+#include "filesystem_mock.hpp"
+#include "util.hpp"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace ipmi_flash
+{
+namespace
+{
+
+using ::testing::Return;
+using ::testing::UnorderedElementsAreArray;
+
+class CleanupHandlerTest : public ::testing::Test
+{
+  protected:
+    std::vector<std::string> blobs = {"abcd", "efgh"};
+    FileSystemMock mock;
+    FileCleanupHandler handler{cleanupBlobId, blobs, &mock};
+};
+
+TEST_F(CleanupHandlerTest, GetBlobListReturnsExpectedList)
+{
+    EXPECT_TRUE(handler.canHandleBlob(cleanupBlobId));
+    EXPECT_THAT(handler.getBlobIds(),
+                UnorderedElementsAreArray({cleanupBlobId}));
+}
+
+TEST_F(CleanupHandlerTest, CommitShouldDeleteFiles)
+{
+    EXPECT_CALL(mock, remove("abcd")).WillOnce(Return());
+    EXPECT_CALL(mock, remove("efgh")).WillOnce(Return());
+
+    EXPECT_TRUE(handler.commit(1, {}));
+}
+
+} // namespace
+} // namespace ipmi_flash
diff --git a/cleanup/test/filesystem_mock.hpp b/cleanup/test/filesystem_mock.hpp
new file mode 100644
index 0000000..76a45c1
--- /dev/null
+++ b/cleanup/test/filesystem_mock.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "fs.hpp"
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+namespace ipmi_flash
+{
+
+class FileSystemMock : public FileSystemInterface
+{
+  public:
+    MOCK_CONST_METHOD1(remove, void(const std::string&));
+};
+} // namespace ipmi_flash
diff --git a/configure.ac b/configure.ac
index b0b24aa..53815f5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -32,6 +32,9 @@
 AS_IF([test "x$enable_tests" = "xyes"], [enable_aspeed_lpc=yes])
 AS_IF([test "x$enable_tests" = "xyes"], [enable_nuvoton_lpc=yes])
 
+# If building tests, enable building all cleanup handler options.
+AS_IF([test "x$enable_tests" = "xyes"], [enable_cleanup_delete=yes])
+
 # Checks for programs.
 AC_PROG_CXX
 AM_PROG_AR
@@ -90,6 +93,12 @@
     AX_APPEND_COMPILE_FLAGS([-DENABLE_REBOOT_UPDATE], [CXXFLAGS])
 ])
 
+# Enable the cleanup handle that deletes the temporary files.
+AC_ARG_ENABLE([cleanup-delete],
+    AS_HELP_STRING([--enable-cleanup-delete],
+                   [Enable use of the delete files cleanup mechanism.]))
+AM_CONDITIONAL([BUILD_CLEANUP_DELETE], [test "x$enable_cleanup_delete" = "xyes"])
+
 # Enable static layout for firmware image staging.
 AC_ARG_ENABLE([static-layout],
     AS_HELP_STRING([--enable-static-layout],
@@ -321,5 +330,5 @@
 )
 
 # Create configured output
-AC_CONFIG_FILES([Makefile test/Makefile tools/Makefile])
+AC_CONFIG_FILES([Makefile test/Makefile tools/Makefile cleanup/Makefile cleanup/test/Makefile])
 AC_OUTPUT
diff --git a/firmware_handler.cpp b/firmware_handler.cpp
index f54ba25..e879529 100644
--- a/firmware_handler.cpp
+++ b/firmware_handler.cpp
@@ -21,6 +21,7 @@
 #include "util.hpp"
 
 #include <algorithm>
+#include <blobs-ipmid/blobs.hpp>
 #include <cstdint>
 #include <cstring>
 #include <fstream>
diff --git a/firmware_handler.hpp b/firmware_handler.hpp
index de20200..0d86ef0 100644
--- a/firmware_handler.hpp
+++ b/firmware_handler.hpp
@@ -100,11 +100,12 @@
      * @param[in] verification - pointer to object for triggering verification
      * @param[in] update - point to object for triggering the update
      */
-    static std::unique_ptr<GenericBlobInterface> CreateFirmwareBlobHandler(
-        const std::vector<HandlerPack>& firmwares,
-        const std::vector<DataHandlerPack>& transports,
-        std::unique_ptr<TriggerableActionInterface> verification,
-        std::unique_ptr<TriggerableActionInterface> update);
+    static std::unique_ptr<blobs::GenericBlobInterface>
+        CreateFirmwareBlobHandler(
+            const std::vector<HandlerPack>& firmwares,
+            const std::vector<DataHandlerPack>& transports,
+            std::unique_ptr<TriggerableActionInterface> verification,
+            std::unique_ptr<TriggerableActionInterface> update);
 
     /**
      * Create a FirmwareBlobHandler.
diff --git a/util.cpp b/util.cpp
index be18d72..421fbaa 100644
--- a/util.cpp
+++ b/util.cpp
@@ -26,5 +26,6 @@
 const std::string activeHashBlobId = "/flash/active/hash";
 const std::string staticLayoutBlobId = "/flash/image";
 const std::string ubiTarballBlobId = "/flash/tarball";
+const std::string cleanupBlobId = "/flash/cleanup";
 
 } // namespace ipmi_flash
diff --git a/util.hpp b/util.hpp
index fd3a818..b791995 100644
--- a/util.hpp
+++ b/util.hpp
@@ -12,5 +12,6 @@
 extern const std::string activeHashBlobId;
 extern const std::string staticLayoutBlobId;
 extern const std::string ubiTarballBlobId;
+extern const std::string cleanupBlobId;
 
 } // namespace ipmi_flash