diff --git a/include/llvm/Support/FileSystem.h b/include/llvm/Support/FileSystem.h index 960853569f5..46bb3d68ba9 100644 --- a/include/llvm/Support/FileSystem.h +++ b/include/llvm/Support/FileSystem.h @@ -390,19 +390,21 @@ error_code symlink_status(const Twine &path, file_status &result); /// /// Generates a unique path suitable for a temporary file and then opens it as a /// file. The name is based on \a model with '%' replaced by a random char in -/// [0-9a-f]. +/// [0-9a-f]. If \a model is not an absolute path, a suitable temporary +/// directory will be prepended. /// /// This is an atomic operation. Either the file is created and opened, or the /// file system is left untouched. /// -/// clang-%%-%%-%%-%%-%%.s => /clang-a0-b1-c2-d3-e4.s +/// clang-%%-%%-%%-%%-%%.s => /tmp/clang-a0-b1-c2-d3-e4.s /// /// @param model Name to base unique path off of. -/// @param result Set to the opened file. -/// @results errc::success if result has been successfully set, otherwise a -/// platform specific error_code. -/// @see temp_directory_path -error_code unique_file(const Twine &model, void* i_have_not_decided_the_ty_yet); +/// @param result_fs Set to the opened file's file descriptor. +/// @param result_path Set to the opened file's absolute path. +/// @results errc::success if result_{fd,path} have been successfully set, +/// otherwise a platform specific error_code. +error_code unique_file(const Twine &model, int &result_fd, + SmallVectorImpl &result_path); /// @brief Canonicalize path. /// diff --git a/lib/Support/Unix/PathV2.inc b/lib/Support/Unix/PathV2.inc index 0fa4b87cff2..1368a82658f 100644 --- a/lib/Support/Unix/PathV2.inc +++ b/lib/Support/Unix/PathV2.inc @@ -23,10 +23,12 @@ #if HAVE_FCNTL_H #include #endif -#if HAVE_SYS_TYPES_H -#include +#if HAVE_STDIO_H +#include #endif +using namespace llvm; + namespace { struct AutoFD { int FileDescriptor; @@ -45,6 +47,24 @@ namespace { operator int() const {return FileDescriptor;} }; + + error_code TempDir(SmallVectorImpl &result) { + // FIXME: Don't use TMPDIR if program is SUID or SGID enabled. + const char *dir = 0; + (dir = std::getenv("TMPDIR" )) || + (dir = std::getenv("TMP" )) || + (dir = std::getenv("TEMP" )) || + (dir = std::getenv("TEMPDIR")) || +#ifdef P_tmpdir + (dir = P_tmpdir) || +#endif + (dir = "/tmp"); + + result.set_size(0); + StringRef d(dir); + result.append(d.begin(), d.end()); + return make_error_code(errc::success); + } } namespace llvm { @@ -126,6 +146,113 @@ error_code copy_file(const Twine &from, const Twine &to, copy_option copt) { return make_error_code(errc::success); } +error_code exists(const Twine &path, bool &result) { + SmallString<128> path_storage; + StringRef p = path.toNullTerminatedStringRef(path_storage); + + struct stat status; + if (::stat(p.begin(), &status) == -1) { + if (errno != ENOENT) + return error_code(errno, system_category()); + result = false; + } else + result = true; + + return make_error_code(errc::success); +} + +error_code unique_file(const Twine &model, int &result_fd, + SmallVectorImpl &result_path) { + SmallString<128> Model; + model.toVector(Model); + // Null terminate. + Model.c_str(); + + // Make model absolute by prepending a temp directory if it's not already. + bool absolute; + if (error_code ec = path::is_absolute(Twine(Model), absolute)) return ec; + if (!absolute) { + SmallString<128> TDir; + if (error_code ec = TempDir(TDir)) return ec; + if (error_code ec = path::append(TDir, Twine(Model))) return ec; + Model.swap(TDir); + } + + // Replace '%' with random chars. From here on, DO NOT modify model. It may be + // needed if the randomly chosen path already exists. + SmallString<128> RandomPath; + RandomPath.reserve(Model.size() + 1); + ::srand(::time(NULL)); + +retry_random_path: + // This is opened here instead of above to make it easier to track when to + // close it. Collisions should be rare enough for the possible extra syscalls + // not to matter. + FILE *RandomSource = ::fopen("/dev/urandom", "r"); + RandomPath.set_size(0); + for (SmallVectorImpl::const_iterator i = Model.begin(), + e = Model.end(); i != e; ++i) { + if (*i == '%') { + char val = 0; + if (RandomSource) + val = fgetc(RandomSource); + else + val = ::rand(); + RandomPath.push_back("0123456789abcdef"[val & 15]); + } else + RandomPath.push_back(*i); + } + + if (RandomSource) + ::fclose(RandomSource); + + // Try to open + create the file. +rety_open_create: + int RandomFD = ::open(RandomPath.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); + if (RandomFD == -1) { + // If the file existed, try again, otherwise, error. + if (errno == EEXIST) + goto retry_random_path; + // The path prefix doesn't exist. + if (errno == ENOENT) { + StringRef p(RandomPath.begin(), RandomPath.size()); + SmallString<64> dir_to_create; + for (path::const_iterator i = path::begin(p), + e = --path::end(p); i != e; ++i) { + if (error_code ec = path::append(dir_to_create, *i)) return ec; + bool Exists; + if (error_code ec = exists(Twine(dir_to_create), Exists)) return ec; + if (!Exists) { + // Don't try to create network paths. + if (i->size() > 2 && (*i)[0] == '/' && + (*i)[1] == '/' && + (*i)[2] != '/') + return error_code(ENOENT, system_category()); + if (::mkdir(dir_to_create.c_str(), 0700) == -1) + return error_code(errno, system_category()); + } + } + goto rety_open_create; + } + return error_code(errno, system_category()); + } + + // Make the path absolute. + char real_path_buff[PATH_MAX + 1]; + if (realpath(RandomPath.c_str(), real_path_buff) == NULL) { + ::close(RandomFD); + ::unlink(RandomPath.c_str()); + return error_code(errno, system_category()); + } + + result_path.set_size(0); + StringRef d(real_path_buff); + result_path.append(d.begin(), d.end()); + + result_fd = RandomFD; + return make_error_code(errc::success); +} + } // end namespace fs } // end namespace sys } // end namespace llvm diff --git a/lib/Support/Windows/PathV2.inc b/lib/Support/Windows/PathV2.inc index b1f8ae00d3d..909deb0418c 100644 --- a/lib/Support/Windows/PathV2.inc +++ b/lib/Support/Windows/PathV2.inc @@ -17,6 +17,8 @@ //===----------------------------------------------------------------------===// #include "Windows.h" +#include +#include using namespace llvm; @@ -46,6 +48,62 @@ namespace { return make_error_code(errc::success); } + + error_code UTF16ToUTF8(const wchar_t *utf16, size_t utf16_len, + SmallVectorImpl &utf8) { + // Get length. + int len = ::WideCharToMultiByte(CP_UTF8, NULL, + utf16, utf16_len, + utf8.begin(), 0, + NULL, NULL); + + if (len == 0) + return make_error_code(windows_error(::GetLastError())); + + utf8.reserve(len); + utf8.set_size(len); + + // Now do the actual conversion. + len = ::WideCharToMultiByte(CP_UTF8, NULL, + utf16, utf16_len, + utf8.data(), utf8.size(), + NULL, NULL); + + if (len == 0) + return make_error_code(windows_error(::GetLastError())); + + // Make utf8 null terminated. + utf8.push_back(0); + utf8.pop_back(); + + return make_error_code(errc::success); + } + + error_code TempDir(SmallVectorImpl &result) { + retry_temp_dir: + DWORD len = ::GetTempPathW(result.capacity(), result.begin()); + + if (len == 0) + return make_error_code(windows_error(::GetLastError())); + + if (len > result.capacity()) { + result.reserve(len); + goto retry_temp_dir; + } + + result.set_size(len); + return make_error_code(errc::success); + } + + struct AutoCryptoProvider { + HCRYPTPROV CryptoProvider; + + ~AutoCryptoProvider() { + ::CryptReleaseContext(CryptoProvider, 0); + } + + operator HCRYPTPROV() const {return CryptoProvider;} + }; } namespace llvm { @@ -123,6 +181,152 @@ error_code copy_file(const Twine &from, const Twine &to, copy_option copt) { return make_error_code(errc::success); } +error_code exists(const Twine &path, bool &result) { + SmallString<128> path_storage; + SmallVector path_utf16; + + if (error_code ec = UTF8ToUTF16(path.toStringRef(path_storage), + path_utf16)) + return ec; + + DWORD attributes = ::GetFileAttributesW(path_utf16.begin()); + + if (attributes == INVALID_FILE_ATTRIBUTES) { + // See if the file didn't actually exist. + error_code ec = make_error_code(windows_error(::GetLastError())); + if (ec != error_code(windows_error::file_not_found) && + ec != error_code(windows_error::path_not_found)) + return ec; + result = false; + } else + result = true; + return make_error_code(errc::success); +} + +error_code unique_file(const Twine &model, int &result_fd, + SmallVectorImpl &result_path) { + // Use result_path as temp storage. + result_path.set_size(0); + StringRef m = model.toStringRef(result_path); + + SmallVector model_utf16; + if (error_code ec = UTF8ToUTF16(m, model_utf16)) return ec; + + // Make model absolute by prepending a temp directory if it's not already. + bool absolute; + if (error_code ec = path::is_absolute(m, absolute)) return ec; + + if (!absolute) { + SmallVector temp_dir; + if (error_code ec = TempDir(temp_dir)) return ec; + // Handle c: by removing it. + if (model_utf16.size() > 2 && model_utf16[1] == L':') { + model_utf16.erase(model_utf16.begin(), model_utf16.begin() + 2); + } + model_utf16.insert(model_utf16.begin(), temp_dir.begin(), temp_dir.end()); + } + + // Replace '%' with random chars. From here on, DO NOT modify model. It may be + // needed if the randomly chosen path already exists. + SmallVector random_path_utf16; + + // Get a Crypto Provider for CryptGenRandom. + AutoCryptoProvider CryptoProvider; + BOOL success = ::CryptAcquireContextW(&CryptoProvider.CryptoProvider, + NULL, + NULL, + PROV_RSA_FULL, + NULL); + if (!success) + return make_error_code(windows_error(::GetLastError())); + +retry_random_path: + random_path_utf16.set_size(0); + for (SmallVectorImpl::const_iterator i = model_utf16.begin(), + e = model_utf16.end(); + i != e; ++i) { + if (*i == L'%') { + BYTE val = 0; + if (!::CryptGenRandom(CryptoProvider, 1, &val)) + return make_error_code(windows_error(::GetLastError())); + random_path_utf16.push_back("0123456789abcdef"[val & 15]); + } + else + random_path_utf16.push_back(*i); + } + // Make random_path_utf16 null terminated. + random_path_utf16.push_back(0); + random_path_utf16.pop_back(); + + // Try to create + open the path. +retry_create_file: + HANDLE TempFileHandle = ::CreateFileW(random_path_utf16.begin(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + // Return ERROR_FILE_EXISTS if the file + // already exists. + CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY, + NULL); + if (TempFileHandle == INVALID_HANDLE_VALUE) { + // If the file existed, try again, otherwise, error. + error_code ec = make_error_code(windows_error(::GetLastError())); + if (ec == error_code(windows_error::file_exists)) + goto retry_random_path; + // Check for non-existing parent directories. + if (ec == error_code(windows_error::path_not_found)) { + // Create the directories using result_path as temp storage. + if (error_code ec = UTF16ToUTF8(random_path_utf16.begin(), + random_path_utf16.size(), result_path)) + return ec; + StringRef p(result_path.begin(), result_path.size()); + SmallString<64> dir_to_create; + for (path::const_iterator i = path::begin(p), + e = --path::end(p); i != e; ++i) { + if (error_code ec = path::append(dir_to_create, *i)) return ec; + bool Exists; + if (error_code ec = exists(Twine(dir_to_create), Exists)) return ec; + if (!Exists) { + // If c: doesn't exist, bail. + if (i->endswith(":")) + return ec; + + SmallVector dir_to_create_utf16; + if (error_code ec = UTF8ToUTF16(dir_to_create, dir_to_create_utf16)) + return ec; + + // Create the directory. + if (!::CreateDirectoryW(dir_to_create_utf16.begin(), NULL)) + return make_error_code(windows_error(::GetLastError())); + } + } + goto retry_create_file; + } + return ec; + } + + // Set result_path to the utf-8 representation of the path. + if (error_code ec = UTF16ToUTF8(random_path_utf16.begin(), + random_path_utf16.size(), result_path)) { + ::CloseHandle(TempFileHandle); + ::DeleteFileW(random_path_utf16.begin()); + return ec; + } + + // Convert the Windows API file handle into a C-runtime handle. + int fd = ::_open_osfhandle(intptr_t(TempFileHandle), 0); + if (fd == -1) { + ::CloseHandle(TempFileHandle); + ::DeleteFileW(random_path_utf16.begin()); + // MSDN doesn't say anything about _open_osfhandle setting errno or + // GetLastError(), so just return invalid_handle. + return make_error_code(windows_error::invalid_handle); + } + + result_fd = fd; + return make_error_code(errc::success); +} } // end namespace fs } // end namespace sys } // end namespace llvm diff --git a/lib/Support/Windows/system_error.inc b/lib/Support/Windows/system_error.inc index 8dc4799e7cf..73304d517fa 100644 --- a/lib/Support/Windows/system_error.inc +++ b/lib/Support/Windows/system_error.inc @@ -96,6 +96,7 @@ _system_error_category::default_error_condition(int ev) const { MAP_ERR_TO_COND(ERROR_OPERATION_ABORTED, operation_canceled); MAP_ERR_TO_COND(ERROR_OUTOFMEMORY, not_enough_memory); MAP_ERR_TO_COND(ERROR_PATH_NOT_FOUND, no_such_file_or_directory); + MAP_ERR_TO_COND(ERROR_BAD_NETPATH, no_such_file_or_directory); MAP_ERR_TO_COND(ERROR_READ_FAULT, io_error); MAP_ERR_TO_COND(ERROR_RETRY, resource_unavailable_try_again); MAP_ERR_TO_COND(ERROR_SEEK, io_error); diff --git a/unittests/Support/Path.cpp b/unittests/Support/Path.cpp index a3d96ecf340..ecf818b5d45 100644 --- a/unittests/Support/Path.cpp +++ b/unittests/Support/Path.cpp @@ -7,11 +7,13 @@ // //===----------------------------------------------------------------------===// +#include "llvm/Support/FileSystem.h" #include "llvm/Support/PathV2.h" #include "gtest/gtest.h" using namespace llvm; +using namespace llvm::sys; #define TEST_OUT(func, result) outs() << " " #func ": " << result << '\n'; @@ -131,6 +133,22 @@ TEST(Support, Path) { outs().flush(); } + + int FileDescriptor; + SmallString<64> TempPath; + if (error_code ec = sys::fs::unique_file("%%-%%-%%-%%.temp", + FileDescriptor, TempPath)) + ASSERT_FALSE(ec.message().c_str()); + + bool TempFileExists; + ASSERT_FALSE(sys::fs::exists(Twine(TempPath), TempFileExists)); + EXPECT_TRUE(TempFileExists); + + ::close(FileDescriptor); + ::remove(TempPath.begin()); + + ASSERT_FALSE(fs::exists(Twine(TempPath), TempFileExists)); + EXPECT_FALSE(TempFileExists); } } // anonymous namespace