Index: llvm/include/llvm/Support/FileSystem.h =================================================================== --- llvm/include/llvm/Support/FileSystem.h +++ llvm/include/llvm/Support/FileSystem.h @@ -339,6 +339,13 @@ /// 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. +std::error_code real_path(const Twine &path, SmallVectorImpl &output); + /// @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) { @@ -793,6 +834,28 @@ return std::error_code(); } +std::error_code real_path(const Twine &path, SmallVectorImpl &dest) { + int fd; + dest.clear(); + SmallString<64> Storage; + auto S = path.toNullTerminatedStringRef(Storage); + if (S.empty()) + return std::error_code(); + std::error_code EC; + if (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 { @@ -803,7 +866,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 @@ -787,6 +787,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; @@ -818,20 +852,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(); @@ -929,6 +951,18 @@ return windows::UTF16ToUTF8(TempPath.data(), CharCount, ResultPath); } + +std::error_code real_path(const Twine &path, SmallVectorImpl &dest) { + 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 {