PEL: Create TemporaryFile class

Added support for creating temporary file
 - Constructor creates the temporary file with data
 - Destructor doesn't deletes file due to PEL function
   related requirements.
 - Provided remove function to delete the file.

Tested: Added test cases for TemporaryFile class and verified
   test_openpower_pels_temporary_file  OK  0.03s

Signed-off-by: Jayanth Othayoth <ojayanth@in.ibm.com>
Change-Id: I5295998d746ef1228512545f0a19faa1f94260d8
diff --git a/extensions/openpower-pels/meson.build b/extensions/openpower-pels/meson.build
index 068f870..fc8362a 100644
--- a/extensions/openpower-pels/meson.build
+++ b/extensions/openpower-pels/meson.build
@@ -30,6 +30,7 @@
 endif
 
 extra_sources = []
+
 build_phal = get_option('phal').enabled()
 
 if build_phal
@@ -64,6 +65,7 @@
     'service_indicators.cpp',
     'severity.cpp',
     'user_header.cpp',
+    'temporary_file.cpp',
     extra_sources,
 )
 
diff --git a/extensions/openpower-pels/temporary_file.cpp b/extensions/openpower-pels/temporary_file.cpp
new file mode 100644
index 0000000..5c52ccc
--- /dev/null
+++ b/extensions/openpower-pels/temporary_file.cpp
@@ -0,0 +1,79 @@
+#include "temporary_file.hpp"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <stdexcept>
+
+namespace openpower
+{
+namespace pels
+{
+namespace util
+{
+
+TemporaryFile::TemporaryFile(const char* data, const uint32_t len)
+{
+    // Build template path required by mkstemp()
+    std::string templatePath =
+        fs::temp_directory_path() / "phosphor-logging-XXXXXX";
+
+    // Generate unique file name, create file, and open it.  The XXXXXX
+    // characters are replaced by mkstemp() to make the file name unique.
+    fd = mkostemp(templatePath.data(), O_RDWR);
+    if (fd == -1)
+    {
+        throw std::runtime_error{
+            std::string{"Unable to create temporary file: "} + strerror(errno)};
+    }
+
+    // Update file with input Buffer data
+    auto rc = write(fd, data, len);
+    if (rc == -1)
+    {
+        // Delete temporary file.  The destructor won't be called because the
+        // exception below causes this constructor to exit without completing.
+        remove();
+        throw std::runtime_error{std::string{"Unable to update file: "} +
+                                 strerror(errno)};
+    }
+
+    // Store path to temporary file
+    path = templatePath;
+}
+
+TemporaryFile& TemporaryFile::operator=(TemporaryFile&& file)
+{
+    // Verify not assigning object to itself (a = std::move(a))
+    if (this != &file)
+    {
+        // Delete temporary file owned by this object
+        remove();
+
+        // Move temporary file path from other object, transferring ownership
+        path = std::move(file.path);
+
+        // Clear path in other object; after move path is in unspecified state
+        file.path.clear();
+    }
+    return *this;
+}
+
+void TemporaryFile::remove()
+{
+    if (!path.empty())
+    {
+        // Delete temporary file from file system
+        fs::remove(path);
+
+        // Clear path to indicate file has been deleted
+        path.clear();
+    }
+}
+
+} // namespace util
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/temporary_file.hpp b/extensions/openpower-pels/temporary_file.hpp
new file mode 100644
index 0000000..76c9e6a
--- /dev/null
+++ b/extensions/openpower-pels/temporary_file.hpp
@@ -0,0 +1,120 @@
+#pragma once
+
+#include <filesystem>
+#include <utility>
+
+namespace openpower
+{
+namespace pels
+{
+namespace util
+{
+
+namespace fs = std::filesystem;
+
+/**
+ * @class TemporaryFile
+ *
+ * A temporary file in the file system.
+ *
+ * The temporary file is created by the constructor.  The absolute path to the
+ * file can be obtained using getPath().
+ *
+ * Note: Callers responsibility to delete the file after usage.
+ *       The temporary file can be deleted by calling remove().
+ *
+ * TemporaryFile objects cannot be copied, but they can be moved.  This enables
+ * them to be stored in containers like std::vector.
+ */
+class TemporaryFile
+{
+  public:
+    // Specify which compiler-generated methods we want
+    TemporaryFile(const TemporaryFile&) = delete;
+    TemporaryFile& operator=(const TemporaryFile&) = delete;
+
+    /**
+     * Constructor.
+     *
+     * Creates a temporary file in the temporary directory (normally /tmp).
+     *
+     * Throws an exception if the file cannot be created.
+     *
+     *  @param data - data buffer
+     *  @param len - length of the data buffer
+     */
+    TemporaryFile(const char* data, const uint32_t len);
+
+    /**
+     * Move constructor.
+     *
+     * Transfers ownership of a temporary file.
+     *
+     * @param file TemporaryFile object being moved
+     */
+    TemporaryFile(TemporaryFile&& file) : path{std::move(file.path)}
+    {
+        // Clear path in other object; after move path is in unspecified state
+        file.path.clear();
+    }
+
+    /**
+     * Move assignment operator.
+     *
+     * Deletes the temporary file owned by this object.  Then transfers
+     * ownership of the temporary file owned by the other object.
+     *
+     * Throws an exception if an error occurs during the deletion.
+     *
+     * @param file TemporaryFile object being moved
+     */
+    TemporaryFile& operator=(TemporaryFile&& file);
+
+    /**
+     * Destructor.
+     */
+    ~TemporaryFile()
+    {
+    }
+
+    /**
+     * Deletes the temporary file.
+     *
+     * Does nothing if the file has already been deleted.
+     *
+     * Throws an exception if an error occurs during the deletion.
+     */
+    void remove();
+
+    /**
+     * Returns the absolute path to the temporary file.
+     *
+     * Returns an empty path if the file has been deleted.
+     *
+     * @return temporary file path
+     */
+    const fs::path& getPath() const
+    {
+        return path;
+    }
+
+    int getFd() const
+    {
+        return fd;
+    }
+
+  private:
+    /**
+     * Absolute path to the temporary file.
+     */
+    fs::path path{};
+
+    /**
+     * File descriptor of the temporary file.
+     */
+    int fd;
+};
+
+} // namespace util
+} // namespace pels
+} // namespace openpower
diff --git a/test/openpower-pels/meson.build b/test/openpower-pels/meson.build
index 0c7567e..ebfd608 100644
--- a/test/openpower-pels/meson.build
+++ b/test/openpower-pels/meson.build
@@ -53,6 +53,11 @@
     'stream': {},
     'user_data': {},
     'user_header': {},
+    'temporary_file': {
+         'sources': [
+            '../../extensions/openpower-pels/temporary_file.cpp',
+        ],
+    },
 }
 
 # Build a common shared library for all openpower tests of all the widely
diff --git a/test/openpower-pels/temporary_file_test.cpp b/test/openpower-pels/temporary_file_test.cpp
new file mode 100644
index 0000000..4fb22cc
--- /dev/null
+++ b/test/openpower-pels/temporary_file_test.cpp
@@ -0,0 +1,353 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * 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 "extensions/openpower-pels/temporary_file.hpp"
+
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <utility>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels::util;
+namespace fs = std::filesystem;
+
+/**
+ * Modify the specified file so that fs::remove() can successfully delete it.
+ *
+ * Undo the modifications from an earlier call to makeFileUnRemovable().
+ *
+ * @param path path to the file
+ */
+inline void makeFileRemovable(const fs::path& path)
+{
+    // makeFileUnRemovable() creates a directory at the file path.  Remove the
+    // directory and all of its contents.
+    fs::remove_all(path);
+
+    // Rename the file back to the original path to restore its contents
+    fs::path savePath{path.native() + ".save"};
+    fs::rename(savePath, path);
+}
+
+/**
+ * Modify the specified file so that fs::remove() fails with an exception.
+ *
+ * The file will be renamed and can be restored by calling makeFileRemovable().
+ *
+ * @param path path to the file
+ */
+inline void makeFileUnRemovable(const fs::path& path)
+{
+    // Rename the file to save its contents
+    fs::path savePath{path.native() + ".save"};
+    fs::rename(path, savePath);
+
+    // Create a directory at the original file path
+    fs::create_directory(path);
+
+    // Create a file within the directory.  fs::remove() will throw an exception
+    // if the path is a non-empty directory.
+    std::ofstream childFile{path / "childFile"};
+}
+
+class TemporaryFileTests : public ::testing::Test
+{
+  protected:
+    void SetUp() override
+    {
+        // Create temporary file with some data
+        std::string buf{"FFDCDATA"};
+        uint32_t size = buf.size();
+        tmpFile = new TemporaryFile(buf.c_str(), size);
+
+        // Create temporary file with no data
+        std::string noData{""};
+        tmpFileNoData = new TemporaryFile(noData.c_str(), 0);
+    }
+
+    void TearDown() override
+    {
+        std::filesystem::remove_all(tmpFile->getPath());
+        delete tmpFile;
+
+        std::filesystem::remove_all(tmpFileNoData->getPath());
+        delete tmpFileNoData;
+    }
+
+    // temporary file with Data
+    TemporaryFile* tmpFile;
+
+    // temporary file with no data
+    TemporaryFile* tmpFileNoData;
+};
+
+TEST_F(TemporaryFileTests, DefaultConstructor)
+{
+    fs::path path = tmpFile->getPath();
+    EXPECT_FALSE(path.empty());
+    EXPECT_TRUE(fs::exists(path));
+    EXPECT_TRUE(fs::is_regular_file(path));
+
+    fs::path parentDir = path.parent_path();
+    EXPECT_EQ(parentDir, "/tmp");
+
+    std::string fileName = path.filename();
+    std::string namePrefix = "phosphor-logging-";
+    EXPECT_EQ(fileName.compare(0, namePrefix.size(), namePrefix), 0);
+}
+
+TEST_F(TemporaryFileTests, DefaultConstructorNoData)
+{
+    fs::path path = tmpFileNoData->getPath();
+    EXPECT_FALSE(path.empty());
+    EXPECT_TRUE(fs::exists(path));
+    EXPECT_TRUE(fs::is_regular_file(path));
+
+    fs::path parentDir = path.parent_path();
+    EXPECT_EQ(parentDir, "/tmp");
+
+    std::string fileName = path.filename();
+    std::string namePrefix = "phosphor-logging-";
+    EXPECT_EQ(fileName.compare(0, namePrefix.size(), namePrefix), 0);
+}
+
+TEST_F(TemporaryFileTests, MoveConstructor)
+{
+    // verify temporary file exists
+    EXPECT_FALSE(tmpFile->getPath().empty());
+    EXPECT_TRUE(fs::exists(tmpFile->getPath()));
+
+    // Save path to temporary file
+    fs::path path = tmpFile->getPath();
+
+    // Create second TemporaryFile object by moving first object
+    TemporaryFile file{std::move(*tmpFile)};
+
+    // Verify first object now has an empty path
+    EXPECT_TRUE(tmpFile->getPath().empty());
+
+    // Verify second object now owns same temporary file and file exists
+    EXPECT_EQ(file.getPath(), path);
+    EXPECT_TRUE(fs::exists(file.getPath()));
+
+    // Delete file
+    std::filesystem::remove_all(file.getPath());
+}
+
+TEST_F(TemporaryFileTests, MoveAssignmentOperatorTest1)
+{
+    // Test where works: TemporaryFile object is moved
+    // verify temporary file exists
+    EXPECT_FALSE(tmpFile->getPath().empty());
+    EXPECT_TRUE(fs::exists(tmpFile->getPath()));
+
+    // Save path to first temporary file
+    fs::path path1 = tmpFile->getPath();
+
+    // Verify second temporary file exists
+    EXPECT_FALSE(tmpFileNoData->getPath().empty());
+    EXPECT_TRUE(fs::exists(tmpFileNoData->getPath()));
+
+    // Save path to second temporary file
+    fs::path path2 = tmpFileNoData->getPath();
+
+    // Verify temporary files are different
+    EXPECT_NE(path1, path2);
+
+    // Move first object into the second
+    *tmpFileNoData = std::move(*tmpFile);
+
+    // Verify first object now has an empty path
+    EXPECT_TRUE(tmpFile->getPath().empty());
+
+    // Verify second object now owns first temporary file and file exists
+    EXPECT_EQ(tmpFileNoData->getPath(), path1);
+    EXPECT_TRUE(fs::exists(path1));
+
+    // Verify second temporary file was deleted
+    EXPECT_FALSE(fs::exists(path2));
+}
+
+TEST_F(TemporaryFileTests, MoveAssignmentOperatorTest2)
+{
+    // Test where does nothing: TemporaryFile object is moved into itself
+    // Verify temporary file exists
+    EXPECT_FALSE(tmpFile->getPath().empty());
+    EXPECT_TRUE(fs::exists(tmpFile->getPath()));
+
+    // Save path to temporary file
+    fs::path path = tmpFile->getPath();
+
+    // Try to move object into itself; should do nothing
+    tmpFile = std::move(tmpFile);
+
+    // Verify object still owns same temporary file and file exists
+    EXPECT_EQ(tmpFile->getPath(), path);
+    EXPECT_TRUE(fs::exists(path));
+}
+
+TEST_F(TemporaryFileTests, MoveAssignmentOperatorTest3)
+{
+    // Test where fails: Cannot delete temporary file
+    // Verify temporary file exists
+    EXPECT_FALSE(tmpFile->getPath().empty());
+    EXPECT_TRUE(fs::exists(tmpFile->getPath()));
+
+    // Save path to first temporary file
+    fs::path path1 = tmpFile->getPath();
+
+    // Verify temporary file exists
+    EXPECT_FALSE(tmpFileNoData->getPath().empty());
+    EXPECT_TRUE(fs::exists(tmpFile->getPath()));
+
+    // Save path to second temporary file
+    fs::path path2 = tmpFileNoData->getPath();
+
+    // Verify temporary files are different
+    EXPECT_NE(path1, path2);
+
+    // Make second temporary file unremoveable
+    makeFileUnRemovable(path2);
+
+    try
+    {
+        // Try to move first object into the second; should throw exception
+        *tmpFileNoData = std::move(*tmpFile);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::exception& e)
+    {
+        // This is expected.  Exception message will vary.
+    }
+
+    // Verify first object has not changed and first temporary file exists
+    EXPECT_EQ(tmpFile->getPath(), path1);
+    EXPECT_TRUE(fs::exists(path1));
+
+    // Verify second object has not changed and second temporary file exists
+    EXPECT_EQ(tmpFileNoData->getPath(), path2);
+    EXPECT_TRUE(fs::exists(path2));
+
+    // Make second temporary file removeable so destructor can delete it
+    makeFileRemovable(path2);
+}
+
+TEST_F(TemporaryFileTests, Destructor)
+{
+    // Test where works: Temporary file is not deleted
+    {
+        fs::path path{};
+        {
+            TemporaryFile file("", 0);
+            path = file.getPath();
+            EXPECT_TRUE(fs::exists(path));
+        }
+        EXPECT_TRUE(fs::exists(path));
+        fs::remove(path);
+    }
+
+    // Test where works: Temporary file was already deleted
+    {
+        fs::path path{};
+        {
+            TemporaryFile file("", 0);
+            path = file.getPath();
+            EXPECT_TRUE(fs::exists(path));
+            file.remove();
+            EXPECT_FALSE(fs::exists(path));
+        }
+        EXPECT_FALSE(fs::exists(path));
+    }
+
+    // Test where fails: Cannot delete temporary file: No exception thrown
+    {
+        fs::path path{};
+        try
+        {
+            TemporaryFile file("", 0);
+            path = file.getPath();
+            EXPECT_TRUE(fs::exists(path));
+            makeFileUnRemovable(path);
+        }
+        catch (...)
+        {
+            ADD_FAILURE() << "Should not have caught exception.";
+        }
+
+        // Temporary file should still exist
+        EXPECT_TRUE(fs::exists(path));
+
+        // Make file removable and delete it
+        makeFileRemovable(path);
+        fs::remove(path);
+    }
+}
+
+TEST_F(TemporaryFileTests, RemoveTest1)
+{
+    // Test where works
+    // Vverify temporary file exists
+    EXPECT_FALSE(tmpFile->getPath().empty());
+    EXPECT_TRUE(fs::exists(tmpFile->getPath()));
+
+    // Save path to temporary file
+    fs::path path = tmpFile->getPath();
+
+    // Delete temporary file
+    tmpFile->remove();
+
+    // Verify path is cleared and file does not exist
+    EXPECT_TRUE(tmpFile->getPath().empty());
+    EXPECT_FALSE(fs::exists(path));
+
+    // Delete temporary file again; should do nothing
+    tmpFile->remove();
+    EXPECT_TRUE(tmpFile->getPath().empty());
+    EXPECT_FALSE(fs::exists(path));
+}
+
+TEST_F(TemporaryFileTests, RemoveTest2)
+{
+    // Test where fails
+    // Create TemporaryFile object and verify temporary file exists
+    EXPECT_FALSE(tmpFile->getPath().empty());
+    EXPECT_TRUE(fs::exists(tmpFile->getPath()));
+
+    // Make file unremovable
+    makeFileUnRemovable(tmpFile->getPath());
+
+    try
+    {
+        // Try to delete temporary file; should fail with exception
+        tmpFile->remove();
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::exception& e)
+    {
+        // This is expected.  Exception message will vary.
+    }
+
+    // Make file removable again so it will be deleted by the destructor
+    makeFileRemovable(tmpFile->getPath());
+}
+
+TEST_F(TemporaryFileTests, GetPath)
+{
+    EXPECT_FALSE(tmpFile->getPath().empty());
+    EXPECT_EQ(tmpFile->getPath().parent_path(), "/tmp");
+    EXPECT_TRUE(fs::exists(tmpFile->getPath()));
+}