diff --git a/src/utils/tmpfile.h b/src/utils/tmpfile.h index fbb66ffa8f..b24fa517b0 100644 --- a/src/utils/tmpfile.h +++ b/src/utils/tmpfile.h @@ -28,7 +28,9 @@ class TmpFile { /// Constructor. /// Creates a new temporary file which can be written to. /// The temporary file will be automatically deleted on destruction. - TmpFile(); + /// @param extension optional file extension to use with the file. The file + /// have no extension by default. + explicit TmpFile(std::string extension = ""); /// Destructor. /// Deletes the temporary file. diff --git a/src/utils/tmpfile_other.cc b/src/utils/tmpfile_other.cc index 8a902386e9..8ee60e6e00 100644 --- a/src/utils/tmpfile_other.cc +++ b/src/utils/tmpfile_other.cc @@ -17,7 +17,7 @@ namespace tint { namespace utils { -TmpFile::TmpFile() = default; +TmpFile::TmpFile(std::string) = default; TmpFile::~TmpFile() = default; diff --git a/src/utils/tmpfile_posix.cc b/src/utils/tmpfile_posix.cc index 5bf78045d1..fac18474c1 100644 --- a/src/utils/tmpfile_posix.cc +++ b/src/utils/tmpfile_posix.cc @@ -15,15 +15,25 @@ #include "src/utils/tmpfile.h" #include +#include + +#include "src/debug.h" namespace tint { namespace utils { namespace { -std::string TmpFilePath() { - char name[] = "tint_XXXXXX"; - int file = mkstemp(name); +std::string TmpFilePath(std::string ext) { + // mkstemps requires an `int` for the file extension name but STL represents + // size_t. Pre-C++20 there the behavior for unsigned-to-signed conversion + // (when the source value exceeds the representable range) is implementation + // defined. While such a large file extension is unlikely in practice, we + // enforce this here at runtime. + TINT_ASSERT(ext.length() <= + static_cast(std::numeric_limits::max())); + std::string name = "tint_XXXXXX" + ext; + int file = mkstemps(&name[0], static_cast(ext.length())); if (file != -1) { close(file); return name; @@ -33,7 +43,8 @@ std::string TmpFilePath() { } // namespace -TmpFile::TmpFile() : path_(TmpFilePath()) {} +TmpFile::TmpFile(std::string extension) + : path_(TmpFilePath(std::move(extension))) {} TmpFile::~TmpFile() { if (!path_.empty()) { diff --git a/src/utils/tmpfile_test.cc b/src/utils/tmpfile_test.cc index 44b2f72ed7..5416da5607 100644 --- a/src/utils/tmpfile_test.cc +++ b/src/utils/tmpfile_test.cc @@ -66,6 +66,25 @@ TEST(TmpFileTest, WriteReadAppendDelete) { ASSERT_FALSE(file); } +TEST(TmpFileTest, FileExtension) { + const std::string kExt = ".foo"; + std::string path; + { + TmpFile tmp(kExt); + if (!tmp) { + GTEST_SKIP() << "Unable create a temporary file"; + } + path = tmp.Path(); + } + + ASSERT_GT(path.length(), kExt.length()); + EXPECT_EQ(kExt, path.substr(path.length() - kExt.length())); + + // Check the file has been deleted when it fell out of scope + std::ifstream file(path); + ASSERT_FALSE(file); +} + } // namespace } // namespace utils } // namespace tint diff --git a/src/utils/tmpfile_windows.cc b/src/utils/tmpfile_windows.cc index 23fb4b3221..764f2ad5dc 100644 --- a/src/utils/tmpfile_windows.cc +++ b/src/utils/tmpfile_windows.cc @@ -32,7 +32,7 @@ std::string TmpFilePath() { } // namespace -TmpFile::TmpFile() : path_(TmpFilePath()) {} +TmpFile::TmpFile(std::string ext) : path_(TmpFilePath() + ext) {} TmpFile::~TmpFile() { if (!path_.empty()) {