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
diff --git a/phosphor-regulators/test/meson.build b/phosphor-regulators/test/meson.build
index 8bf6a5c..511695a 100644
--- a/phosphor-regulators/test/meson.build
+++ b/phosphor-regulators/test/meson.build
@@ -20,6 +20,7 @@
'rule_tests.cpp',
'sensor_monitoring_tests.cpp',
'system_tests.cpp',
+ 'temporary_file_tests.cpp',
'write_verification_error_tests.cpp',
'actions/action_environment_tests.cpp',
diff --git a/phosphor-regulators/test/temporary_file_tests.cpp b/phosphor-regulators/test/temporary_file_tests.cpp
new file mode 100644
index 0000000..dafdcf3
--- /dev/null
+++ b/phosphor-regulators/test/temporary_file_tests.cpp
@@ -0,0 +1,314 @@
+/**
+ * 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 <filesystem>
+#include <fstream>
+#include <string>
+#include <utility>
+
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::regulators;
+namespace fs = std::filesystem;
+
+/**
+ * Modify the specified temporary file so that fs::remove() fails with an
+ * exception.
+ *
+ * @param path path to the temporary file
+ */
+void makeFileUnRemovable(const fs::path& path)
+{
+ // Delete temporary file. Note that this is not sufficient to cause
+ // fs::remove() to throw an exception.
+ fs::remove(path);
+
+ // Create a directory at the temporary 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"};
+}
+
+/**
+ * Modify the specified temporary file so that fs::remove() can successfully
+ * delete it.
+ *
+ * Undo the modifications from an earlier call to makeFileUnRemovable().
+ *
+ * @param path path to the temporary file
+ */
+void makeFileRemovable(const fs::path& path)
+{
+ // makeFileUnRemovable() creates a directory at the temporary file path.
+ // Remove the directory and all of its contents.
+ fs::remove_all(path);
+
+ // Re-create the temporary file
+ std::ofstream file{path};
+}
+
+TEST(TemporaryFileTests, DefaultConstructor)
+{
+ TemporaryFile file{};
+
+ fs::path path = file.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-regulators-";
+ EXPECT_EQ(fileName.compare(0, namePrefix.size(), namePrefix), 0);
+}
+
+TEST(TemporaryFileTests, MoveConstructor)
+{
+ // Create first TemporaryFile object and verify temporary file exists
+ TemporaryFile file1{};
+ EXPECT_FALSE(file1.getPath().empty());
+ EXPECT_TRUE(fs::exists(file1.getPath()));
+
+ // Save path to temporary file
+ fs::path path = file1.getPath();
+
+ // Create second TemporaryFile object by moving first object
+ TemporaryFile file2{std::move(file1)};
+
+ // Verify first object now has an empty path
+ EXPECT_TRUE(file1.getPath().empty());
+
+ // Verify second object now owns same temporary file and file exists
+ EXPECT_EQ(file2.getPath(), path);
+ EXPECT_TRUE(fs::exists(file2.getPath()));
+}
+
+TEST(TemporaryFileTests, MoveAssignmentOperator)
+{
+ // Test where works: TemporaryFile object is moved
+ {
+ // Create first TemporaryFile object and verify temporary file exists
+ TemporaryFile file1{};
+ EXPECT_FALSE(file1.getPath().empty());
+ EXPECT_TRUE(fs::exists(file1.getPath()));
+
+ // Save path to first temporary file
+ fs::path path1 = file1.getPath();
+
+ // Create second TemporaryFile object and verify temporary file exists
+ TemporaryFile file2{};
+ EXPECT_FALSE(file2.getPath().empty());
+ EXPECT_TRUE(fs::exists(file2.getPath()));
+
+ // Save path to second temporary file
+ fs::path path2 = file2.getPath();
+
+ // Verify temporary files are different
+ EXPECT_NE(path1, path2);
+
+ // Move first object into the second
+ file2 = std::move(file1);
+
+ // Verify first object now has an empty path
+ EXPECT_TRUE(file1.getPath().empty());
+
+ // Verify second object now owns first temporary file and file exists
+ EXPECT_EQ(file2.getPath(), path1);
+ EXPECT_TRUE(fs::exists(path1));
+
+ // Verify second temporary file was deleted
+ EXPECT_FALSE(fs::exists(path2));
+ }
+
+ // Test where does nothing: TemporaryFile object is moved into itself
+ {
+ // Create TemporaryFile object and verify temporary file exists
+ TemporaryFile file{};
+ EXPECT_FALSE(file.getPath().empty());
+ EXPECT_TRUE(fs::exists(file.getPath()));
+
+ // Save path to temporary file
+ fs::path path = file.getPath();
+
+ // Try to move object into itself; should do nothing
+ file = std::move(file);
+
+ // Verify object still owns same temporary file and file exists
+ EXPECT_EQ(file.getPath(), path);
+ EXPECT_TRUE(fs::exists(path));
+ }
+
+ // Test where fails: Cannot delete temporary file
+ {
+ // Create first TemporaryFile object and verify temporary file exists
+ TemporaryFile file1{};
+ EXPECT_FALSE(file1.getPath().empty());
+ EXPECT_TRUE(fs::exists(file1.getPath()));
+
+ // Save path to first temporary file
+ fs::path path1 = file1.getPath();
+
+ // Create second TemporaryFile object and verify temporary file exists
+ TemporaryFile file2{};
+ EXPECT_FALSE(file2.getPath().empty());
+ EXPECT_TRUE(fs::exists(file2.getPath()));
+
+ // Save path to second temporary file
+ fs::path path2 = file2.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
+ file2 = std::move(file1);
+ 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(file1.getPath(), path1);
+ EXPECT_TRUE(fs::exists(path1));
+
+ // Verify second object has not changed and second temporary file exists
+ EXPECT_EQ(file2.getPath(), path2);
+ EXPECT_TRUE(fs::exists(path2));
+
+ // Make second temporary file removeable so destructor can delete it
+ makeFileRemovable(path2);
+ }
+}
+
+TEST(TemporaryFileTests, Destructor)
+{
+ // Test where works: Temporary file is deleted
+ {
+ fs::path path{};
+ {
+ TemporaryFile file{};
+ path = file.getPath();
+ EXPECT_TRUE(fs::exists(path));
+ }
+ EXPECT_FALSE(fs::exists(path));
+ }
+
+ // Test where works: Temporary file was already deleted
+ {
+ fs::path path{};
+ {
+ TemporaryFile file{};
+ 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{};
+ 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(TemporaryFileTests, Remove)
+{
+ // Test where works
+ {
+ // Create TemporaryFile object and verify temporary file exists
+ TemporaryFile file{};
+ EXPECT_FALSE(file.getPath().empty());
+ EXPECT_TRUE(fs::exists(file.getPath()));
+
+ // Save path to temporary file
+ fs::path path = file.getPath();
+
+ // Delete temporary file
+ file.remove();
+
+ // Verify path is cleared and file does not exist
+ EXPECT_TRUE(file.getPath().empty());
+ EXPECT_FALSE(fs::exists(path));
+
+ // Delete temporary file again; should do nothing
+ file.remove();
+ EXPECT_TRUE(file.getPath().empty());
+ EXPECT_FALSE(fs::exists(path));
+ }
+
+ // Test where fails
+ {
+ // Create TemporaryFile object and verify temporary file exists
+ TemporaryFile file{};
+ EXPECT_FALSE(file.getPath().empty());
+ EXPECT_TRUE(fs::exists(file.getPath()));
+
+ // Make file unremovable
+ makeFileUnRemovable(file.getPath());
+
+ try
+ {
+ // Try to delete temporary file; should fail with exception
+ file.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(file.getPath());
+ }
+}
+
+TEST(TemporaryFileTests, GetPath)
+{
+ TemporaryFile file{};
+ EXPECT_FALSE(file.getPath().empty());
+ EXPECT_EQ(file.getPath().parent_path(), "/tmp");
+ EXPECT_TRUE(fs::exists(file.getPath()));
+}