Skip to content

Commit

Permalink
Bazel client, Windows: fix AsShortWindowsPath
Browse files Browse the repository at this point in the history
This method now works for non-existent paths too.

See #2107

--
PiperOrigin-RevId: 149284633
MOS_MIGRATED_REVID=149284633
  • Loading branch information
laszlocsomor authored and vladmos committed Mar 6, 2017
1 parent bfd9aa3 commit 2b01b5d
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 17 deletions.
4 changes: 3 additions & 1 deletion src/main/cpp/util/file_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ void ForEachDirectoryEntry(const std::string &path,
bool AsWindowsPathWithUncPrefix(const std::string &path, std::wstring *wpath);

// Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path.
// Result will never have a UNC prefix.
// Result will never have a UNC prefix, nor a trailing "/" or "\".
// Works also for non-existent paths; shortens as much of them as it can.
// Also works for non-existent drives.
bool AsShortWindowsPath(const std::string &path, std::string *result);
#endif // defined(COMPILER_MSVC) || defined(__CYGWIN__)

Expand Down
63 changes: 48 additions & 15 deletions src/main/cpp/util/file_windows.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ static void AddUncPrefixMaybe(wstring* path) {
}
}

static const wchar_t* RemoveUncPrefixMaybe(const wchar_t* ptr) {
return ptr + (windows_util::HasUncPrefix(ptr) ? 4 : 0);
}

class WindowsPipe : public IPipe {
public:
WindowsPipe(const HANDLE& read_handle, const HANDLE& write_handle)
Expand Down Expand Up @@ -505,24 +509,57 @@ bool AsShortWindowsPath(const string& path, string* result) {

result->clear();
wstring wpath;
wstring wsuffix;
if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
return false;
}
DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
if (size == 0) {
return false;
// GetShortPathNameW can fail if `wpath` does not exist. This is expected
// when we are about to create a file at that path, so instead of failing,
// walk up in the path until we find a prefix that exists and can be
// shortened, or is a root directory. Save the non-existent tail in
// `wsuffix`, we'll add it back later.
std::vector<wstring> segments;
while (size == 0 && !IsRootDirectoryW(wpath)) {
pair<wstring, wstring> split = SplitPathW(wpath);
wpath = split.first;
segments.push_back(split.second);
size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
}

// Join all segments.
std::wostringstream builder;
bool first = true;
for (std::vector<wstring>::const_reverse_iterator& it = segments.crbegin();
it != segments.crend(); ++it) {
if (!first || !IsRootDirectoryW(wpath)) {
builder << L'\\' << *it;
} else {
builder << *it;
}
first = false;
}
wsuffix = builder.str();
}

unique_ptr<WCHAR[]> wshort(new WCHAR[size]); // size includes null-terminator
if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) {
pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
"AsShortWindowsPath(%s): GetShortPathNameW(%S) failed, err=%d",
path.c_str(), wpath.c_str(), GetLastError());
wstring wresult;
if (IsRootDirectoryW(wpath)) {
// Strip the UNC prefix from `wpath`, and the leading "\" from `wsuffix`.
wresult = wstring(RemoveUncPrefixMaybe(wpath.c_str())) + wsuffix;
} else {
unique_ptr<WCHAR[]> wshort(
new WCHAR[size]); // size includes null-terminator
if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) {
pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
"AsShortWindowsPath(%s): GetShortPathNameW(%S) failed, err=%d",
path.c_str(), wpath.c_str(), GetLastError());
}
// GetShortPathNameW may preserve the UNC prefix in the result, so strip it.
wresult = wstring(RemoveUncPrefixMaybe(wshort.get())) + wsuffix;
}
// GetShortPathNameW may preserve the UNC prefix in the result, so strip it.
WCHAR* result_ptr = wshort.get() + (HasUncPrefix(wshort.get()) ? 4 : 0);

result->assign(WstringToCstring(result_ptr).get());
result->assign(WstringToCstring(wresult.c_str()).get());
ToLower(result);
return true;
}
Expand Down Expand Up @@ -911,9 +948,7 @@ string MakeCanonical(const char* path) {
size_t size = wcslen(long_realpath.get()) -
(windows_util::HasUncPrefix(long_realpath.get()) ? 4 : 0);
unique_ptr<WCHAR[]> lcase_realpath(new WCHAR[size + 1]);
const WCHAR* p_from =
long_realpath.get() +
(windows_util::HasUncPrefix(long_realpath.get()) ? 4 : 0);
const WCHAR* p_from = RemoveUncPrefixMaybe(long_realpath.get());
WCHAR* p_to = lcase_realpath.get();
while (size-- > 0) {
*p_to++ = towlower(*p_from++);
Expand Down Expand Up @@ -1093,9 +1128,7 @@ static unique_ptr<WCHAR[]> GetCwdW() {
}

string GetCwd() {
unique_ptr<WCHAR[]> cwd(GetCwdW());
return string(
WstringToCstring(cwd.get() + (HasUncPrefix(cwd.get()) ? 4 : 0)).get());
return string(WstringToCstring(RemoveUncPrefixMaybe(GetCwdW().get())).get());
}

bool ChangeDirectory(const string& path) {
Expand Down
29 changes: 28 additions & 1 deletion src/test/cpp/util/file_windows_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -243,19 +243,46 @@ TEST_F(FileWindowsTest, TestAsShortWindowsPath) {
ASSERT_TRUE(AsShortWindowsPath("nul", &actual));
ASSERT_EQ(string("NUL"), actual);

ASSERT_TRUE(AsShortWindowsPath("C://", &actual));
ASSERT_EQ(string("c:\\"), actual);
ASSERT_TRUE(AsShortWindowsPath("/C//", &actual));
ASSERT_EQ(string("c:\\"), actual);

// The A drive usually doesn't exist but AsShortWindowsPath should still work.
// Here we even have multiple trailing slashes, that should be handled too.
ASSERT_TRUE(AsShortWindowsPath("A://", &actual));
ASSERT_EQ(string("a:\\"), actual);
ASSERT_TRUE(AsShortWindowsPath("/A//", &actual));
ASSERT_EQ(string("a:\\"), actual);

// Assert that we can shorten the TEST_TMPDIR.
string tmpdir;
GET_TEST_TMPDIR(tmpdir);
string short_tmpdir;
ASSERT_TRUE(AsShortWindowsPath(tmpdir, &short_tmpdir));
ASSERT_LT(0, short_tmpdir.size());
ASSERT_TRUE(PathExists(short_tmpdir));

// Assert that a trailing "/" doesn't change the shortening logic and it will
// be stripped from the result.
ASSERT_TRUE(AsShortWindowsPath(tmpdir + "/", &actual));
ASSERT_EQ(actual, short_tmpdir);
ASSERT_NE(actual.back(), '/');
ASSERT_NE(actual.back(), '\\');

// Assert shortening another long path, and that the result is lowercased.
string dirname(JoinPath(short_tmpdir, "LONGpathNAME"));
ASSERT_EQ(0, mkdir(dirname.c_str()));
ASSERT_TRUE(PathExists(dirname));

ASSERT_TRUE(AsShortWindowsPath(dirname, &actual));
ASSERT_EQ(short_tmpdir + "\\longpa~1", actual);

// Assert shortening non-existent paths.
ASSERT_TRUE(AsShortWindowsPath(JoinPath(tmpdir, "NonExistent/FOO"), &actual));
ASSERT_EQ(short_tmpdir + "\\nonexistent\\foo", actual);
// Assert shortening non-existent root paths.
ASSERT_TRUE(AsShortWindowsPath("/c/NonExistent/FOO", &actual));
ASSERT_EQ("c:\\nonexistent\\foo", actual);
}

TEST_F(FileWindowsTest, TestMsysRootRetrieval) {
Expand Down

0 comments on commit 2b01b5d

Please sign in to comment.