From: Tudor Bosman Date: Tue, 16 Apr 2013 20:22:41 +0000 (-0700) Subject: Modernize TemporaryFile, add TemporaryDirectory X-Git-Tag: v0.22.0~1001 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=34129f8f52e1d144ae38a3ade5c4aa2365b2b0b1;p=folly.git Modernize TemporaryFile, add TemporaryDirectory Test Plan: fbconfig $(find folly -name test) && fbmake runtests_opt Reviewed By: tjackson@fb.com FB internal diff: D777186 --- diff --git a/folly/experimental/TestUtil.cpp b/folly/experimental/TestUtil.cpp index c7384fca..d68235fb 100644 --- a/folly/experimental/TestUtil.cpp +++ b/folly/experimental/TestUtil.cpp @@ -16,62 +16,56 @@ #include "folly/experimental/TestUtil.h" -#include -#include -#include -#include +#include +#include +#include -#include "folly/Format.h" +#include "folly/Conv.h" +#include "folly/Exception.h" namespace folly { namespace test { -TemporaryFile::TemporaryFile(const char* prefix, Scope scope, - bool closeOnDestruction) - : scope_(scope), - closeOnDestruction_(closeOnDestruction) { - static const char* suffix = ".XXXXXX"; // per mkstemp(3) - if (!prefix || prefix[0] == '\0') { - prefix = "temp"; +namespace { + +fs::path generateUniquePath(fs::path path, StringPiece namePrefix) { + if (path.empty()) { + path = fs::temp_directory_path(); } - const char* dir = nullptr; - if (!strchr(prefix, '/')) { - // Not a full path, try getenv("TMPDIR") or "/tmp" - dir = getenv("TMPDIR"); - if (!dir) { - dir = "/tmp"; - } - // The "!" is a placeholder to ensure that &(path[0]) is null-terminated. - // This is the only standard-compliant way to get at a null-terminated - // non-const char string inside a std::string: put the null-terminator - // yourself. - path_ = format("{}/{}{}!", dir, prefix, suffix).str(); + if (namePrefix.empty()) { + path /= fs::unique_path(); } else { - path_ = format("{}{}!", prefix, suffix).str(); + path /= fs::unique_path( + to(namePrefix, ".%%%%-%%%%-%%%%-%%%%")); } + return path; +} - // Replace the '!' with a null terminator, we'll get rid of it later - path_[path_.size() - 1] = '\0'; - - fd_ = mkstemp(&(path_[0])); - if (fd_ == -1) { - throw std::system_error(errno, std::system_category(), - format("mkstemp failed: {}", path_).str().c_str()); - } +} // namespce - DCHECK_EQ(path_[path_.size() - 1], '\0'); - path_.erase(path_.size() - 1); +TemporaryFile::TemporaryFile(StringPiece namePrefix, + fs::path dir, + Scope scope, + bool closeOnDestruction) + : scope_(scope), + closeOnDestruction_(closeOnDestruction), + fd_(-1), + path_(generateUniquePath(std::move(dir), namePrefix)) { + fd_ = open(path_.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644); + checkUnixError(fd_, "open failed"); if (scope_ == Scope::UNLINK_IMMEDIATELY) { - if (unlink(path_.c_str()) == -1) { - throw std::system_error(errno, std::system_category(), - format("unlink failed: {}", path_).str().c_str()); + boost::system::error_code ec; + fs::remove(path_, ec); + if (ec) { + LOG(WARNING) << "unlink on construction failed: " << ec; + } else { + path_.clear(); } - path_.clear(); // path no longer available or meaningful } } -const std::string& TemporaryFile::path() const { +const fs::path& TemporaryFile::path() const { CHECK(scope_ != Scope::UNLINK_IMMEDIATELY); DCHECK(!path_.empty()); return path_; @@ -87,8 +81,28 @@ TemporaryFile::~TemporaryFile() { // If we previously failed to unlink() (UNLINK_IMMEDIATELY), we'll // try again here. if (scope_ != Scope::PERMANENT && !path_.empty()) { - if (unlink(path_.c_str()) == -1) { - PLOG(ERROR) << "unlink(" << path_ << ") failed"; + boost::system::error_code ec; + fs::remove(path_, ec); + if (ec) { + LOG(WARNING) << "unlink on destruction failed: " << ec; + } + } +} + +TemporaryDirectory::TemporaryDirectory(StringPiece namePrefix, + fs::path dir, + Scope scope) + : scope_(scope), + path_(generateUniquePath(std::move(dir), namePrefix)) { + fs::create_directory(path_); +} + +TemporaryDirectory::~TemporaryDirectory() { + if (scope_ == Scope::DELETE_ON_DESTRUCTION) { + boost::system::error_code ec; + fs::remove_all(path_, ec); + if (ec) { + LOG(WARNING) << "recursive delete on destruction failed: " << ec; } } } diff --git a/folly/experimental/TestUtil.h b/folly/experimental/TestUtil.h index c0406107..06e2f048 100644 --- a/folly/experimental/TestUtil.h +++ b/folly/experimental/TestUtil.h @@ -18,6 +18,8 @@ #define FOLLY_TESTUTIL_H_ #include +#include "folly/Range.h" +#include "folly/experimental/io/FsUtil.h" namespace folly { namespace test { @@ -27,8 +29,7 @@ namespace test { * * By default, the file is created in a system-specific location (the value * of the TMPDIR environment variable, or /tmp), but you can override that - * by making "prefix" be a path (containing a '/'; use a prefix starting with - * './' to create a file in the current directory). + * with a different (non-empty) directory passed to the constructor. * * By default, the file is closed and deleted when the TemporaryFile object * is destroyed, but both these behaviors can be overridden with arguments @@ -41,19 +42,50 @@ class TemporaryFile { UNLINK_IMMEDIATELY, UNLINK_ON_DESTRUCTION }; - explicit TemporaryFile(const char* prefix=nullptr, - Scope scope=Scope::UNLINK_ON_DESTRUCTION, - bool closeOnDestruction=true); + explicit TemporaryFile(StringPiece namePrefix = StringPiece(), + fs::path dir = fs::path(), + Scope scope = Scope::UNLINK_ON_DESTRUCTION, + bool closeOnDestruction = true); ~TemporaryFile(); int fd() const { return fd_; } - const std::string& path() const; + const fs::path& path() const; private: Scope scope_; bool closeOnDestruction_; int fd_; - std::string path_; + fs::path path_; +}; + +/** + * Temporary directory. + * + * By default, the temporary directory is created in a system-specific + * location (the value of the TMPDIR environment variable, or /tmp), but you + * can override that with a non-empty directory passed to the constructor. + * + * By default, the directory is recursively deleted when the TemporaryDirectory + * object is destroyed, but that can be overridden with an argument + * to the constructor. + */ + +class TemporaryDirectory { + public: + enum class Scope { + PERMANENT, + DELETE_ON_DESTRUCTION + }; + explicit TemporaryDirectory(StringPiece namePrefix = StringPiece(), + fs::path dir = fs::path(), + Scope scope = Scope::DELETE_ON_DESTRUCTION); + ~TemporaryDirectory(); + + const fs::path& path() const { return path_; } + + private: + Scope scope_; + fs::path path_; }; } // namespace test diff --git a/folly/experimental/test/TestUtilTest.cpp b/folly/experimental/test/TestUtilTest.cpp index 27df213f..f47e1170 100644 --- a/folly/experimental/test/TestUtilTest.cpp +++ b/folly/experimental/test/TestUtilTest.cpp @@ -16,6 +16,13 @@ #include "folly/experimental/TestUtil.h" +#include +#include +#include + +#include + +#include #include #include @@ -28,7 +35,7 @@ TEST(TemporaryFile, Simple) { { TemporaryFile f; EXPECT_FALSE(f.path().empty()); - EXPECT_EQ('/', f.path()[0]); + EXPECT_TRUE(f.path().is_absolute()); fd = f.fd(); EXPECT_LE(0, fd); ssize_t r = write(fd, &c, 1); @@ -44,6 +51,55 @@ TEST(TemporaryFile, Simple) { EXPECT_EQ(EBADF, savedErrno); } +TEST(TemporaryFile, Prefix) { + TemporaryFile f("Foo"); + EXPECT_TRUE(f.path().is_absolute()); + EXPECT_TRUE(boost::algorithm::starts_with(f.path().filename().native(), + "Foo")); +} + +TEST(TemporaryFile, PathPrefix) { + TemporaryFile f("Foo", "."); + EXPECT_EQ(fs::path("."), f.path().parent_path()); + EXPECT_TRUE(boost::algorithm::starts_with(f.path().filename().native(), + "Foo")); +} + +TEST(TemporaryFile, NoSuchPath) { + EXPECT_THROW({TemporaryFile f("", "/no/such/path");}, + std::system_error); +} + +void testTemporaryDirectory(TemporaryDirectory::Scope scope) { + fs::path path; + { + TemporaryDirectory d("", "", scope); + path = d.path(); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE(path.is_absolute()); + EXPECT_TRUE(fs::exists(path)); + EXPECT_TRUE(fs::is_directory(path)); + + fs::path fp = path / "bar"; + int fd = open(fp.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644); + EXPECT_NE(fd, -1); + close(fd); + + TemporaryFile f("Foo", d.path()); + EXPECT_EQ(d.path(), f.path().parent_path()); + } + bool exists = (scope == TemporaryDirectory::Scope::PERMANENT); + EXPECT_EQ(exists, fs::exists(path)); +} + +TEST(TemporaryDirectory, Permanent) { + testTemporaryDirectory(TemporaryDirectory::Scope::PERMANENT); +} + +TEST(TemporaryDirectory, DeleteOnDestruction) { + testTemporaryDirectory(TemporaryDirectory::Scope::DELETE_ON_DESTRUCTION); +} + int main(int argc, char *argv[]) { testing::InitGoogleTest(&argc, argv); google::ParseCommandLineFlags(&argc, &argv, true);