From: Paul Robinson Date: Thu, 13 Nov 2014 00:12:14 +0000 (+0000) Subject: Improve long path name support on Windows. X-Git-Url: http://plrg.eecs.uci.edu/git/?p=oota-llvm.git;a=commitdiff_plain;h=038e20451d144ddb117a0dfe8ff220c8b26f4a7a;hp=2217648f91a3ec227f249b98c0517a77ec6921d7 Improve long path name support on Windows. Windows normally limits the length of an absolute path name to 260 characters; directories can have lower limits. These limits increase to about 32K if you use absolute paths with the special '\\?\' prefix. Teach Support\Windows\Path.inc to use that prefix as needed. TODO: Other parts of Support could also learn to use this prefix. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@221841 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Support/Windows/Path.inc b/lib/Support/Windows/Path.inc index d90d408e661..140aadcfe66 100644 --- a/lib/Support/Windows/Path.inc +++ b/lib/Support/Windows/Path.inc @@ -59,6 +59,59 @@ static bool is_separator(const wchar_t value) { } } +// Convert a UTF-8 path to UTF-16. Also, if the absolute equivalent of the +// path is longer than CreateDirectory can tolerate, make it absolute and +// prefixed by '\\?\'. +static std::error_code widenPath(const Twine &Path8, + SmallVectorImpl &Path16) { + const size_t MaxDirLen = MAX_PATH - 12; // Must leave room for 8.3 filename. + + // Several operations would convert Path8 to SmallString; more efficient to + // do it once up front. + SmallString<128> Path8Str; + Path8.toVector(Path8Str); + + // If we made this path absolute, how much longer would it get? + size_t CurPathLen; + if (llvm::sys::path::is_absolute(Twine(Path8Str))) + CurPathLen = 0; // No contribution from current_path needed. + else { + CurPathLen = ::GetCurrentDirectoryW(0, NULL); + if (CurPathLen == 0) + return windows_error(::GetLastError()); + } + + // Would the absolute path be longer than our limit? + if ((Path8Str.size() + CurPathLen) >= MaxDirLen && + !Path8Str.startswith("\\\\?\\")) { + SmallString<2*MAX_PATH> FullPath("\\\\?\\"); + if (CurPathLen) { + SmallString<80> CurPath; + if (std::error_code EC = llvm::sys::fs::current_path(CurPath)) + return EC; + FullPath.append(CurPath); + } + // Traverse the requested path, canonicalizing . and .. as we go (because + // the \\?\ prefix is documented to treat them as real components). + // The iterators don't report separators and append() always attaches + // preferred_separator so we don't need to call native() on the result. + for (llvm::sys::path::const_iterator I = llvm::sys::path::begin(Path8Str), + E = llvm::sys::path::end(Path8Str); + I != E; ++I) { + if (I->size() == 1 && *I == ".") + continue; + if (I->size() == 2 && *I == "..") + llvm::sys::path::remove_filename(FullPath); + else + llvm::sys::path::append(FullPath, *I); + } + return UTF8ToUTF16(FullPath, Path16); + } + + // Just use the caller's original path. + return UTF8ToUTF16(Path8Str, Path16); +} + namespace llvm { namespace sys { namespace fs { @@ -130,11 +183,9 @@ std::error_code current_path(SmallVectorImpl &result) { } std::error_code create_directory(const Twine &path, bool IgnoreExisting) { - SmallString<128> path_storage; SmallVector path_utf16; - if (std::error_code ec = - UTF8ToUTF16(path.toStringRef(path_storage), path_utf16)) + if (std::error_code ec = widenPath(path, path_utf16)) return ec; if (!::CreateDirectoryW(path_utf16.begin(), NULL)) { @@ -148,18 +199,12 @@ std::error_code create_directory(const Twine &path, bool IgnoreExisting) { // We can't use symbolic links for windows. std::error_code create_link(const Twine &to, const Twine &from) { - // Get arguments. - SmallString<128> from_storage; - SmallString<128> to_storage; - StringRef f = from.toStringRef(from_storage); - StringRef t = to.toStringRef(to_storage); - // Convert to utf-16. SmallVector wide_from; SmallVector wide_to; - if (std::error_code ec = UTF8ToUTF16(f, wide_from)) + if (std::error_code ec = widenPath(from, wide_from)) return ec; - if (std::error_code ec = UTF8ToUTF16(t, wide_to)) + if (std::error_code ec = widenPath(to, wide_to)) return ec; if (!::CreateHardLinkW(wide_from.begin(), wide_to.begin(), NULL)) @@ -169,7 +214,6 @@ std::error_code create_link(const Twine &to, const Twine &from) { } std::error_code remove(const Twine &path, bool IgnoreNonExisting) { - SmallString<128> path_storage; SmallVector path_utf16; file_status ST; @@ -179,8 +223,7 @@ std::error_code remove(const Twine &path, bool IgnoreNonExisting) { return std::error_code(); } - if (std::error_code ec = - UTF8ToUTF16(path.toStringRef(path_storage), path_utf16)) + if (std::error_code ec = widenPath(path, path_utf16)) return ec; if (ST.type() == file_type::directory_file) { @@ -200,18 +243,12 @@ std::error_code remove(const Twine &path, bool IgnoreNonExisting) { } std::error_code rename(const Twine &from, const Twine &to) { - // Get arguments. - SmallString<128> from_storage; - SmallString<128> to_storage; - StringRef f = from.toStringRef(from_storage); - StringRef t = to.toStringRef(to_storage); - // Convert to utf-16. SmallVector wide_from; SmallVector wide_to; - if (std::error_code ec = UTF8ToUTF16(f, wide_from)) + if (std::error_code ec = widenPath(from, wide_from)) return ec; - if (std::error_code ec = UTF8ToUTF16(t, wide_to)) + if (std::error_code ec = widenPath(to, wide_to)) return ec; std::error_code ec = std::error_code(); @@ -232,11 +269,9 @@ std::error_code rename(const Twine &from, const Twine &to) { } std::error_code resize_file(const Twine &path, uint64_t size) { - SmallString<128> path_storage; SmallVector path_utf16; - if (std::error_code ec = - UTF8ToUTF16(path.toStringRef(path_storage), path_utf16)) + if (std::error_code ec = widenPath(path, path_utf16)) return ec; int fd = ::_wopen(path_utf16.begin(), O_BINARY | _O_RDWR, S_IWRITE); @@ -252,11 +287,9 @@ std::error_code resize_file(const Twine &path, uint64_t size) { } std::error_code access(const Twine &Path, AccessMode Mode) { - SmallString<128> PathStorage; SmallVector PathUtf16; - if (std::error_code EC = - UTF8ToUTF16(Path.toStringRef(PathStorage), PathUtf16)) + if (std::error_code EC = widenPath(Path, PathUtf16)) return EC; DWORD Attributes = ::GetFileAttributesW(PathUtf16.begin()); @@ -382,7 +415,7 @@ std::error_code status(const Twine &path, file_status &result) { return std::error_code(); } - if (std::error_code ec = UTF8ToUTF16(path8, path_utf16)) + if (std::error_code ec = widenPath(path8, path_utf16)) return ec; DWORD attr = ::GetFileAttributesW(path_utf16.begin()); @@ -525,11 +558,10 @@ mapped_file_region::mapped_file_region(const Twine &path, , FileDescriptor() , FileHandle(INVALID_HANDLE_VALUE) , FileMappingHandle() { - SmallString<128> path_storage; SmallVector path_utf16; // Convert path to UTF-16. - if ((ec = UTF8ToUTF16(path.toStringRef(path_storage), path_utf16))) + if (ec = widenPath(path, path_utf16)) return; // Get file handle for creating a file mapping. @@ -635,7 +667,7 @@ std::error_code detail::directory_iterator_construct(detail::DirIterState &it, StringRef path){ SmallVector path_utf16; - if (std::error_code ec = UTF8ToUTF16(path, path_utf16)) + if (std::error_code ec = widenPath(path, path_utf16)) return ec; // Convert path to the format that Windows is happy with. @@ -718,11 +750,9 @@ std::error_code detail::directory_iterator_increment(detail::DirIterState &it) { } std::error_code openFileForRead(const Twine &Name, int &ResultFD) { - SmallString<128> PathStorage; SmallVector PathUTF16; - if (std::error_code EC = - UTF8ToUTF16(Name.toStringRef(PathStorage), PathUTF16)) + if (std::error_code EC = widenPath(Name, PathUTF16)) return EC; HANDLE H = ::CreateFileW(PathUTF16.begin(), GENERIC_READ, @@ -757,11 +787,9 @@ std::error_code openFileForWrite(const Twine &Name, int &ResultFD, assert((!(Flags & sys::fs::F_Excl) || !(Flags & sys::fs::F_Append)) && "Cannot specify both 'excl' and 'append' file creation flags!"); - SmallString<128> PathStorage; SmallVector PathUTF16; - if (std::error_code EC = - UTF8ToUTF16(Name.toStringRef(PathStorage), PathUTF16)) + if (std::error_code EC = widenPath(Name, PathUTF16)) return EC; DWORD CreationDisposition; diff --git a/unittests/Support/Path.cpp b/unittests/Support/Path.cpp index ae4c9eb207f..d7c216a5777 100644 --- a/unittests/Support/Path.cpp +++ b/unittests/Support/Path.cpp @@ -16,6 +16,7 @@ #include "gtest/gtest.h" #ifdef LLVM_ON_WIN32 +#include #include #endif @@ -261,7 +262,7 @@ TEST(Support, HomeDirectory) { class FileSystemTest : public testing::Test { protected: /// Unique temporary directory in which all created filesystem entities must - /// be placed. It is recursively removed at the end of each test. + /// be placed. It is removed at the end of each test (must be empty). SmallString<128> TestDirectory; virtual void SetUp() { @@ -397,7 +398,15 @@ TEST_F(FileSystemTest, TempFiles) { "abcdefghijklmnopqrstuvwxyz3abcdefghijklmnopqrstuvwxyz2" "abcdefghijklmnopqrstuvwxyz1abcdefghijklmnopqrstuvwxyz0"; EXPECT_EQ(fs::createUniqueFile(Twine(Path270), FileDescriptor, TempPath), - errc::no_such_file_or_directory); + errc::invalid_argument); + // Relative path < 247 chars, no problem. + const char *Path216 = + "abcdefghijklmnopqrstuvwxyz7abcdefghijklmnopqrstuvwxyz6" + "abcdefghijklmnopqrstuvwxyz5abcdefghijklmnopqrstuvwxyz4" + "abcdefghijklmnopqrstuvwxyz3abcdefghijklmnopqrstuvwxyz2" + "abcdefghijklmnopqrstuvwxyz1abcdefghijklmnopqrstuvwxyz0"; + ASSERT_NO_ERROR(fs::createTemporaryFile(Twine(Path216), "", TempPath)); + ASSERT_NO_ERROR(fs::remove(Twine(TempPath))); #endif } @@ -407,6 +416,54 @@ TEST_F(FileSystemTest, CreateDir) { ASSERT_EQ(fs::create_directory(Twine(TestDirectory) + "foo", false), errc::file_exists); ASSERT_NO_ERROR(fs::remove(Twine(TestDirectory) + "foo")); + +#ifdef LLVM_ON_WIN32 + // Prove that create_directories() can handle a pathname > 248 characters, + // which is the documented limit for CreateDirectory(). + // (248 is MAX_PATH subtracting room for an 8.3 filename.) + // Generate a directory path guaranteed to fall into that range. + size_t TmpLen = TestDirectory.size(); + const char *OneDir = "\\123456789"; + size_t OneDirLen = strlen(OneDir); + ASSERT_LT(OneDirLen, 12); + size_t NLevels = ((248 - TmpLen) / OneDirLen) + 1; + SmallString<260> LongDir(TestDirectory); + for (size_t I = 0; I < NLevels; ++I) + LongDir.append(OneDir); + ASSERT_NO_ERROR(fs::create_directories(Twine(LongDir))); + ASSERT_NO_ERROR(fs::create_directories(Twine(LongDir))); + ASSERT_EQ(fs::create_directories(Twine(LongDir), false), + errc::file_exists); + // Tidy up, "recursively" removing the directories. + StringRef ThisDir(LongDir); + for (size_t J = 0; J < NLevels; ++J) { + ASSERT_NO_ERROR(fs::remove(ThisDir)); + ThisDir = path::parent_path(ThisDir); + } + + // Similarly for a relative pathname. Need to set the current directory to + // TestDirectory so that the one we create ends up in the right place. + char PreviousDir[260]; + size_t PreviousDirLen = ::GetCurrentDirectoryA(260, PreviousDir); + ASSERT_GT(PreviousDirLen, 0); + ASSERT_LT(PreviousDirLen, 260); + ASSERT_NE(::SetCurrentDirectoryA(TestDirectory.c_str()), 0); + LongDir.clear(); + // Generate a relative directory name with absolute length > 248. + size_t LongDirLen = 249 - TestDirectory.size(); + LongDir.assign(LongDirLen, 'a'); + ASSERT_NO_ERROR(fs::create_directory(Twine(LongDir))); + // While we're here, prove that .. and . handling works in these long paths. + const char *DotDotDirs = "\\..\\.\\b"; + LongDir.append(DotDotDirs); + ASSERT_NO_ERROR(fs::create_directory(Twine("b"))); + ASSERT_EQ(fs::create_directory(Twine(LongDir), false), errc::file_exists); + // And clean up. + ASSERT_NO_ERROR(fs::remove(Twine("b"))); + ASSERT_NO_ERROR(fs::remove( + Twine(LongDir.substr(0, LongDir.size() - strlen(DotDotDirs))))); + ASSERT_NE(::SetCurrentDirectoryA(PreviousDir), 0); +#endif } TEST_F(FileSystemTest, DirectoryIteration) {