Support/FileSystem: Add unique_file and exists implementations.
authorMichael J. Spencer <bigcheesegs@gmail.com>
Fri, 3 Dec 2010 01:21:28 +0000 (01:21 +0000)
committerMichael J. Spencer <bigcheesegs@gmail.com>
Fri, 3 Dec 2010 01:21:28 +0000 (01:21 +0000)
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@120776 91177308-0d34-0410-b5e6-96231b3b80d8

include/llvm/Support/FileSystem.h
lib/Support/Unix/PathV2.inc
lib/Support/Windows/PathV2.inc
lib/Support/Windows/system_error.inc
unittests/Support/Path.cpp

index 960853569f5118f994fbdae676413d41fc8a9f47..46bb3d68ba90a5ea8819a642aee17d858d63308a 100644 (file)
@@ -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 => <current-directory>/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<char> &result_path);
 
 /// @brief Canonicalize path.
 ///
index 0fa4b87cff2e2fc75e1c13ede489239c7787b98c..1368a82658faae9ef098d5bf62e79cec889b0862 100644 (file)
 #if HAVE_FCNTL_H
 #include <fcntl.h>
 #endif
-#if HAVE_SYS_TYPES_H
-#include <sys/types.h>
+#if HAVE_STDIO_H
+#include <stdio.h>
 #endif
 
+using namespace llvm;
+
 namespace {
   struct AutoFD {
     int FileDescriptor;
@@ -45,6 +47,24 @@ namespace {
 
     operator int() const {return FileDescriptor;}
   };
+
+  error_code TempDir(SmallVectorImpl<char> &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<char> &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<char>::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
index b1f8ae00d3d197316acc140c2f42bfcd133bf733..909deb0418c9e2ad04f040f3fb07c1e421c7f8ce 100644 (file)
@@ -17,6 +17,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "Windows.h"
+#include <WinCrypt.h>
+#include <io.h>
 
 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<char> &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<wchar_t> &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<wchar_t, 128> 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<char> &result_path) {
+  // Use result_path as temp storage.
+  result_path.set_size(0);
+  StringRef m = model.toStringRef(result_path);
+
+  SmallVector<wchar_t, 128> 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<wchar_t, 64> 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<wchar_t, 128> 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<wchar_t>::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<wchar_t, 64> 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
index 8dc4799e7cf05c52685dd9b7538463d7b7e7f684..73304d517fa91c73ce05f982971035eed0e5a5cb 100644 (file)
@@ -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);
index a3d96ecf340b6719807900bb7417fc2eb1dee57a..ecf818b5d458d38b366731e70adf54d32e5cc2f2 100644 (file)
@@ -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