Index: llvm/include/llvm/Support/FileSystem.h =================================================================== --- llvm/include/llvm/Support/FileSystem.h +++ llvm/include/llvm/Support/FileSystem.h @@ -339,6 +339,16 @@ /// specific error_code. std::error_code create_hard_link(const Twine &to, const Twine &from); +/// @brief Collapse all . and .. patterns, resolve all symlinks, and replace +/// special folders (e.g. ~ with their real path). +/// +/// @param path The path to resolve. +/// @param output The location to store the resolved path. +/// @param expand_tilde If true, expands ~ expressions according to posix +/// rules. +std::error_code real_path(const Twine &path, SmallVectorImpl &output, + bool expand_tilde = false); + /// @brief Get the current path. /// /// @param result Holds the current path on return. Index: llvm/lib/Support/Unix/Path.inc =================================================================== --- llvm/lib/Support/Unix/Path.inc +++ llvm/lib/Support/Unix/Path.inc @@ -48,6 +48,8 @@ # endif #endif +#include + #ifdef __APPLE__ #include #include @@ -475,6 +477,45 @@ return std::error_code(); } +static void expand_tilde_expr(const Twine &Path, SmallVectorImpl &Dest) { + SmallString<64> ResultStorage; + StringRef P = Path.toNullTerminatedStringRef(ResultStorage); + Dest.clear(); + if (P.empty() || !P.startswith("~")) { + Dest.append(P.begin(), P.end()); + return; + } + + // LLVM's normal path functions don't treat ~ as a directory name, so + // root_name() / relative_name() don't work, we have to do the split manually. + StringRef Root, Relative; + std::tie(Root, Relative) = P.split('/'); + if (Root.size() == 1) { + // A path of ~/ resolves to the current user's home dir. + llvm::sys::path::home_directory(Dest); + } else { + // This is a string of the form ~username/, look up this user's entry in the + // password database. + struct passwd *Entry = nullptr; + if (P.size() == Root.size()) { + // We're already null terminated. + Entry = ::getpwnam(Root.drop_front().data()); + } else { + std::string User = Root.drop_front().str(); + Entry = ::getpwnam(User.c_str()); + } + + if (!Entry) { + // Unable to look up the entry, just return back the original path. + Dest.append(P.begin(), P.end()); + return; + } + Dest.append(Entry->pw_dir, Entry->pw_dir + strlen(Entry->pw_dir)); + } + + llvm::sys::path::append(Dest, Relative); +} + static std::error_code fillStatus(int StatRet, const struct stat &Status, file_status &Result) { if (StatRet != 0) { @@ -836,6 +877,29 @@ return std::error_code(); } +std::error_code real_path(const Twine &path, SmallVectorImpl &dest, + bool expand_tilde) { + int fd; + dest.clear(); + SmallString<64> Storage; + auto S = path.toNullTerminatedStringRef(Storage); + if (S.empty()) + return std::error_code(); + std::error_code EC; + if (expand_tilde && (S[0] == '~')) { + // open() doesn't accept tilde expressions, they are expanded by the shell. + // So expand then first before trying to resolve the file. + expand_tilde_expr(path, dest); + EC = openFileForRead(dest, fd, &dest); + } else + EC = openFileForRead(path, fd, &dest); + + if (EC) + return EC; + ::close(fd); + return std::error_code(); +} + } // end namespace fs namespace path { @@ -846,7 +910,6 @@ result.append(RequestedDir, RequestedDir + strlen(RequestedDir)); return true; } - return false; } Index: llvm/lib/Support/Windows/Path.inc =================================================================== --- llvm/lib/Support/Windows/Path.inc +++ llvm/lib/Support/Windows/Path.inc @@ -780,6 +780,40 @@ return std::error_code(); } +static void realPathFromHandle(HANDLE H, SmallVectorImpl &RealPath) { + RealPath.clear(); + wchar_t RealPathUTF16[MAX_PATH]; + DWORD CountChars = ::GetFinalPathNameByHandleW(H, RealPathUTF16, MAX_PATH, + FILE_NAME_NORMALIZED); + if (CountChars > 0 && CountChars < MAX_PATH) { + // Convert the result from UTF-16 to UTF-8. + SmallString RealPathUTF8; + if (!UTF16ToUTF8(RealPathUTF16, CountChars, RealPathUTF8)) { + StringRef Ref(RealPathUTF8); + Ref.consume_front(R"(\\?\)"); + RealPath.append(Ref.begin(), Ref.end()); + } + } +} + +static std::error_code directoryRealPath(const Twine &Name, + SmallVectorImpl &RealPath) { + SmallVector PathUTF16; + + if (std::error_code EC = widenPath(Name, PathUTF16)) + return EC; + + HANDLE H = + ::CreateFileW(PathUTF16.begin(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (H == INVALID_HANDLE_VALUE) + return mapWindowsError(GetLastError()); + realPathFromHandle(H, RealPath); + ::CloseHandle(H); + return std::error_code(); +} + std::error_code openFileForRead(const Twine &Name, int &ResultFD, SmallVectorImpl *RealPath) { SmallVector PathUTF16; @@ -811,20 +845,8 @@ } // Fetch the real name of the file, if the user asked - if (RealPath) { - RealPath->clear(); - wchar_t RealPathUTF16[MAX_PATH]; - DWORD CountChars = - ::GetFinalPathNameByHandleW(H, RealPathUTF16, MAX_PATH, - FILE_NAME_NORMALIZED); - if (CountChars > 0 && CountChars < MAX_PATH) { - // Convert the result from UTF-16 to UTF-8. - SmallString RealPathUTF8; - if (!UTF16ToUTF8(RealPathUTF16, CountChars, RealPathUTF8)) - RealPath->append(RealPathUTF8.data(), - RealPathUTF8.data() + strlen(RealPathUTF8.data())); - } - } + if (RealPath) + realPathFromHandle(H, *RealPath); ResultFD = FD; return std::error_code(); @@ -949,6 +971,51 @@ return std::error_code(); } +static void expand_tilde_expr(const Twine &Path, SmallVectorImpl &Dest) { + if (Path.isTriviallyEmpty()) + return; + + SmallString<128> Storage; + StringRef S = Path.toNullTerminatedStringRef(Storage); + // Path does not begin with a tilde expression. + if (S.empty() || S[0] != '~') + return; + + StringRef Extra; + if (S.size() >= 2) { + // This is probably a ~username/ expression. Don't support this on Windows. + if (S[1] != '/' && S[1] != '\\') + return; + Extra = S.drop_front(2); + } + + path::home_directory(Dest); + if (!Extra.empty()) + path::append(Dest, Extra); +} + +std::error_code real_path(const Twine &path, SmallVectorImpl &dest, + bool expand_tilde) { + dest.clear(); + if (path.isTriviallyEmpty()) + return std::error_code(); + + if (expand_tilde) { + SmallString<128> Temp; + expand_tilde_expr(path, Temp); + return real_path(Temp, dest, false); + } + + if (is_directory(path)) + return directoryRealPath(path, dest); + + int fd; + if (std::error_code EC = llvm::sys::fs::openFileForRead(path, fd, &dest)) + return EC; + ::close(fd); + return std::error_code(); +} + } // end namespace fs namespace path { Index: llvm/unittests/Support/Path.cpp =================================================================== --- llvm/unittests/Support/Path.cpp +++ llvm/unittests/Support/Path.cpp @@ -499,6 +499,39 @@ ASSERT_NO_ERROR(fs::remove(TempPath)); } +TEST_F(FileSystemTest, RealPath) { + ASSERT_NO_ERROR( + fs::create_directories(Twine(TestDirectory) + "/test1/test2/test3")); + ASSERT_TRUE(fs::exists(Twine(TestDirectory) + "/test1/test2/test3")); + + SmallString<64> RealBase; + SmallString<64> Expected; + SmallString<64> Actual; + + // TestDirectory itself might be under a symlink or have been specified with + // a different case than the existing temp directory. In such cases real_path + // on the concatenated path will differ in the TestDirectory portion from + // how we specified it. Make sure to compare against the real_path of the + // TestDirectory, and not just the value of TestDirectory. + ASSERT_NO_ERROR(fs::real_path(TestDirectory, RealBase)); + path::native(Twine(RealBase) + "/test1/test2", Expected); + + ASSERT_NO_ERROR(fs::real_path( + Twine(TestDirectory) + "/././test1/../test1/test2/./test3/..", Actual)); + + EXPECT_EQ(Expected, Actual); + + SmallString<64> HomeDir; + ASSERT_TRUE(llvm::sys::path::home_directory(HomeDir)); + ASSERT_NO_ERROR(fs::real_path(HomeDir, Expected)); + ASSERT_NO_ERROR(fs::real_path("~", Actual, true)); + EXPECT_EQ(Expected, Actual); + ASSERT_NO_ERROR(fs::real_path("~/", Actual, true)); + EXPECT_EQ(Expected, Actual); + + ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/test1")); +} + TEST_F(FileSystemTest, TempFiles) { // Create a temp file. int FileDescriptor;