regulators: Create TemporaryFile class

Create class for handling temporary files:
* Constructor creates the temporary file
* Destructor deletes the temporary file
* Provides move constructor and move assignment operator so that class
  can be stored in containers like std::vector
* Throws exception if errors occur creating or deleting the temporary file

This class is an enhanced version of the TmpFile class in the test
directory.  A future commit will delete TmpFile and modify testcases to
use TemporaryFile instead.

Tested:
* Created automated tests for most of the code in the class.
* Manually tested some error cases that were difficult to automate.

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: Ieda58f4d2738dc4f184232936bc082c0d3a93fa0
diff --git a/phosphor-regulators/src/meson.build b/phosphor-regulators/src/meson.build
index 6f067b5..2012855 100644
--- a/phosphor-regulators/src/meson.build
+++ b/phosphor-regulators/src/meson.build
@@ -15,6 +15,7 @@
     'rail.cpp',
     'sensor_monitoring.cpp',
     'system.cpp',
+    'temporary_file.cpp',
 
     'actions/if_action.cpp',
     'actions/i2c_compare_bit_action.cpp',
diff --git a/phosphor-regulators/src/temporary_file.cpp b/phosphor-regulators/src/temporary_file.cpp
new file mode 100644
index 0000000..514d818
--- /dev/null
+++ b/phosphor-regulators/src/temporary_file.cpp
@@ -0,0 +1,93 @@
+/**
+ * Copyright © 2020 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 "temporary_file.hpp"
+
+#include <errno.h>  // for errno
+#include <stdlib.h> // for mkstemp()
+#include <string.h> // for strerror()
+#include <unistd.h> // for close()
+
+#include <stdexcept>
+#include <string>
+
+namespace phosphor::power::regulators
+{
+
+TemporaryFile::TemporaryFile()
+{
+    // Build template path required by mkstemp()
+    std::string templatePath =
+        fs::temp_directory_path() / "phosphor-regulators-XXXXXX";
+
+    // Generate unique file name, create file, and open it.  The XXXXXX
+    // characters are replaced by mkstemp() to make the file name unique.
+    int fd = mkstemp(templatePath.data());
+    if (fd == -1)
+    {
+        throw std::runtime_error{
+            std::string{"Unable to create temporary file: "} + strerror(errno)};
+    }
+
+    // Store path to temporary file
+    path = templatePath;
+
+    // Close file descriptor
+    if (close(fd) == -1)
+    {
+        // Save errno value; will likely change when we delete temporary file
+        int savedErrno = errno;
+
+        // 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 close temporary file: "} +
+            strerror(savedErrno)};
+    }
+}
+
+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 phosphor::power::regulators
diff --git a/phosphor-regulators/src/temporary_file.hpp b/phosphor-regulators/src/temporary_file.hpp
new file mode 100644
index 0000000..df97ee2
--- /dev/null
+++ b/phosphor-regulators/src/temporary_file.hpp
@@ -0,0 +1,128 @@
+/**
+ * Copyright © 2020 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.
+ */
+#pragma once
+
+#include <filesystem>
+#include <utility>
+
+namespace phosphor::power::regulators
+{
+
+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().
+ *
+ * The temporary file can be deleted by calling remove().  Otherwise the file
+ * will be deleted by the destructor.
+ *
+ * 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.
+     */
+    TemporaryFile();
+
+    /**
+     * 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.
+     *
+     * Deletes the temporary file if necessary.
+     */
+    ~TemporaryFile()
+    {
+        try
+        {
+            remove();
+        }
+        catch (...)
+        {
+            // Destructors should not throw exceptions
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+  private:
+    /**
+     * Absolute path to the temporary file.
+     *
+     * Empty when file has been deleted.
+     */
+    fs::path path{};
+};
+
+} // namespace phosphor::power::regulators