Windows: all JNI methods verify their path arg.
Also remove the GetAttributesOfMaybeMissingFile
method because it's based on a false belief,
namely that FindFirstFile returns up-to-date
information. According to this OldNewThing post
that belief was wrong: https://blogs.msdn.microsoft.com/oldnewthing/20111226-00/?p=8813
This PR is a follow-up to https://github.com/bazelbuild/bazel/pull/7176
Closes #7178.
PiperOrigin-RevId: 230705273
diff --git a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java
index 741533e..c8243e4 100644
--- a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java
+++ b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java
@@ -104,22 +104,28 @@
String[] result = new String[] {null};
String[] error = new String[] {null};
if (nativeGetLongPath(asLongPath(path), result, error)) {
- return result[0];
+ return removeUncPrefixAndUseSlashes(result[0]);
} else {
throw new IOException(error[0]);
}
}
- /**
- * Returns a Windows-style path suitable to pass to unicode WinAPI functions.
- *
- * <p>Returns an UNC path if `path` is at least `MAX_PATH` long. If it's shorter or is already an
- * UNC path, then this method returns `path` itself.
- */
+ /** Returns a Windows-style path suitable to pass to unicode WinAPI functions. */
static String asLongPath(String path) {
- return path.length() >= MAX_PATH && !path.startsWith("\\\\?\\")
+ return !path.startsWith("\\\\?\\")
? ("\\\\?\\" + path.replace('/', '\\'))
- : path;
+ : path.replace('/', '\\');
+ }
+
+ private static String removeUncPrefixAndUseSlashes(String p) {
+ if (p.length() >= 4
+ && p.charAt(0) == '\\'
+ && (p.charAt(1) == '\\' || p.charAt(1) == '?')
+ && p.charAt(2) == '?'
+ && p.charAt(3) == '\\') {
+ p = p.substring(4);
+ }
+ return p.replace('\\', '/');
}
/**
diff --git a/src/main/native/windows/file-jni.cc b/src/main/native/windows/file-jni.cc
index 0dc4fbd..99b95d8 100644
--- a/src/main/native/windows/file-jni.cc
+++ b/src/main/native/windows/file-jni.cc
@@ -41,13 +41,14 @@
Java_com_google_devtools_build_lib_windows_jni_WindowsFileOperations_nativeIsJunction(
JNIEnv* env, jclass clazz, jstring path, jobjectArray error_msg_holder) {
std::wstring wpath(bazel::windows::GetJavaWstring(env, path));
- int result = bazel::windows::IsJunctionOrDirectorySymlink(wpath.c_str());
+ std::wstring error;
+ int result =
+ bazel::windows::IsJunctionOrDirectorySymlink(wpath.c_str(), &error);
if (result == bazel::windows::IS_JUNCTION_ERROR &&
CanReportError(env, error_msg_holder)) {
- DWORD err_code = GetLastError();
ReportLastError(
bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__,
- L"nativeIsJunction", wpath, err_code),
+ L"nativeIsJunction", wpath, error),
env, error_msg_holder);
}
return result;
@@ -60,11 +61,13 @@
std::unique_ptr<WCHAR[]> result;
std::wstring wpath(bazel::windows::GetJavaWstring(env, path));
std::wstring error(bazel::windows::GetLongPath(wpath.c_str(), &result));
- if (!error.empty() && CanReportError(env, error_msg_holder)) {
- ReportLastError(
- bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__,
- L"nativeGetLongPath", wpath, error),
- env, error_msg_holder);
+ if (!error.empty()) {
+ if (CanReportError(env, error_msg_holder)) {
+ ReportLastError(
+ bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__,
+ L"nativeGetLongPath", wpath, error),
+ env, error_msg_holder);
+ }
return JNI_FALSE;
}
env->SetObjectArrayElement(
diff --git a/src/main/native/windows/file.cc b/src/main/native/windows/file.cc
index fcfae4c..31ef954 100644
--- a/src/main/native/windows/file.cc
+++ b/src/main/native/windows/file.cc
@@ -72,30 +72,23 @@
return wstring(attr_str, 8);
}
-static DWORD GetAttributesOfMaybeMissingFile(const WCHAR* path) {
- // According to a comment in .NET CoreFX [1] (which is the only relevant
- // information we found as of 2018-07-13) GetFileAttributesW may fail with
- // ERROR_ACCESS_DENIED if the file is marked for deletion but not yet
- // actually deleted, but FindFirstFileW should succeed even then.
- //
- // [1]
- // https://github.com/dotnet/corefx/blob/f25eb288a449010574a6e95fe298f3ad880ada1e/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs#L205-L208
- WIN32_FIND_DATAW find_data;
- HANDLE find = FindFirstFileW(path, &find_data);
- if (find == INVALID_HANDLE_VALUE) {
- // The path is deleted and we couldn't create a directory there.
- // Give up.
- return INVALID_FILE_ATTRIBUTES;
+int IsJunctionOrDirectorySymlink(const WCHAR* path, wstring* error) {
+ if (!IsAbsoluteNormalizedWindowsPath(path)) {
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
+ L"IsJunctionOrDirectorySymlink", path,
+ L"expected an absolute Windows path");
+ }
+ return IS_JUNCTION_ERROR;
}
- FindClose(find);
- // The path exists, yet we cannot open it for metadata-reading. Report at
- // least the attributes, then give up.
- return find_data.dwFileAttributes;
-}
-int IsJunctionOrDirectorySymlink(const WCHAR* path) {
DWORD attrs = ::GetFileAttributesW(path);
if (attrs == INVALID_FILE_ATTRIBUTES) {
+ DWORD err = GetLastError();
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
+ L"IsJunctionOrDirectorySymlink", path, err);
+ }
return IS_JUNCTION_ERROR;
} else {
if ((attrs & FILE_ATTRIBUTE_DIRECTORY) &&
@@ -108,14 +101,20 @@
}
wstring GetLongPath(const WCHAR* path, unique_ptr<WCHAR[]>* result) {
- DWORD size = ::GetLongPathNameW(path, NULL, 0);
+ if (!IsAbsoluteNormalizedWindowsPath(path)) {
+ return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"GetLongPath", path,
+ L"expected an absolute Windows path");
+ }
+
+ std::wstring wpath(AddUncPrefixMaybe(path));
+ DWORD size = ::GetLongPathNameW(wpath.c_str(), NULL, 0);
if (size == 0) {
DWORD err_code = GetLastError();
return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"GetLongPathNameW", path,
err_code);
}
result->reset(new WCHAR[size]);
- ::GetLongPathNameW(path, result->get(), size);
+ ::GetLongPathNameW(wpath.c_str(), result->get(), size);
return L"";
}
@@ -142,6 +141,23 @@
int CreateJunction(const wstring& junction_name, const wstring& junction_target,
wstring* error) {
+ if (!IsAbsoluteNormalizedWindowsPath(junction_name)) {
+ if (error) {
+ *error = MakeErrorMessage(
+ WSTR(__FILE__), __LINE__, L"CreateJunction", junction_name,
+ L"expected an absolute Windows path for junction_name");
+ }
+ CreateJunctionResult::kError;
+ }
+ if (!IsAbsoluteNormalizedWindowsPath(junction_target)) {
+ if (error) {
+ *error = MakeErrorMessage(
+ WSTR(__FILE__), __LINE__, L"CreateJunction", junction_target,
+ L"expected an absolute Windows path for junction_target");
+ }
+ CreateJunctionResult::kError;
+ }
+
const wstring target = HasUncPrefix(junction_target.c_str())
? junction_target.substr(4)
: junction_target;
@@ -220,23 +236,11 @@
return CreateJunctionResult::kDisappeared;
}
- wstring err_str = uint32asHexString(err);
// The path seems to exist yet we cannot open it for metadata-reading.
// Report as much information as we have, then give up.
- DWORD attr = GetAttributesOfMaybeMissingFile(name.c_str());
- if (attr == INVALID_FILE_ATTRIBUTES) {
- if (error) {
- *error = MakeErrorMessage(
- WSTR(__FILE__), __LINE__, L"CreateFileW", name,
- wstring(L"err=0x") + err_str + L", invalid attributes");
- }
- } else {
- if (error) {
- *error =
- MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateFileW", name,
- wstring(L"err=0x") + err_str + L", attr=0x" +
- uint32asHexString(attr));
- }
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateFileW",
+ name, err);
}
return CreateJunctionResult::kError;
}
@@ -429,7 +433,7 @@
// The file disappeared, or one of its parent directories disappeared,
// or one of its parent directories is no longer a directory.
return DeletePathResult::kDoesNotExist;
- } else if (err != ERROR_ACCESS_DENIED) {
+ } else {
// Some unknown error occurred.
if (error) {
*error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
@@ -437,14 +441,9 @@
}
return DeletePathResult::kError;
}
-
- attr = GetAttributesOfMaybeMissingFile(wpath);
- if (attr == INVALID_FILE_ATTRIBUTES) {
- // The path is already deleted.
- return DeletePathResult::kDoesNotExist;
- }
}
+ // DeleteFileW failed with access denied, but the path exists.
if (attr & FILE_ATTRIBUTE_DIRECTORY) {
// It's a directory or a junction.
if (!RemoveDirectoryW(wpath)) {
diff --git a/src/main/native/windows/file.h b/src/main/native/windows/file.h
index 05a3274..84cbde3 100644
--- a/src/main/native/windows/file.h
+++ b/src/main/native/windows/file.h
@@ -93,7 +93,7 @@
// created using "mklink" instead of "mklink /d", as such symlinks don't
// behave the same way as directories (e.g. they can't be listed)
// - IS_JUNCTION_ERROR, if `path` doesn't exist or some error occurred
-int IsJunctionOrDirectorySymlink(const WCHAR* path);
+int IsJunctionOrDirectorySymlink(const WCHAR* path, std::wstring* error);
// Computes the long version of `path` if it has any 8dot3 style components.
// Returns the empty string upon success, or a human-readable error message upon
diff --git a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileOperationsTest.java b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileOperationsTest.java
index 2f7483a..c5a50d3 100644
--- a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileOperationsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileOperationsTest.java
@@ -90,29 +90,29 @@
testUtil.createJunctions(junctions);
- assertThat(WindowsFileOperations.isJunction(root + "/shrtpath/a")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/shrtpath/b")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/shrtpath/c")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/longlinkpath/a")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/longlinkpath/b")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/longlinkpath/c")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/longli~1/a")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/longli~1/b")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/longli~1/c")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/abbreviated/a")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/abbreviated/b")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/abbreviated/c")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/abbrev~1/a")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/abbrev~1/b")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/abbrev~1/c")).isTrue();
- assertThat(WindowsFileOperations.isJunction(root + "/control/a")).isFalse();
- assertThat(WindowsFileOperations.isJunction(root + "/control/b")).isFalse();
- assertThat(WindowsFileOperations.isJunction(root + "/control/c")).isFalse();
- assertThat(WindowsFileOperations.isJunction(root + "/shrttrgt/file1.txt")).isFalse();
- assertThat(WindowsFileOperations.isJunction(root + "/longtargetpath/file2.txt")).isFalse();
- assertThat(WindowsFileOperations.isJunction(root + "/longta~1/file2.txt")).isFalse();
+ assertThat(WindowsFileOperations.isJunction(root + "\\shrtpath\\a")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\shrtpath\\b")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\shrtpath\\c")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\longlinkpath\\a")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\longlinkpath\\b")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\longlinkpath\\c")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\longli~1\\a")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\longli~1\\b")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\longli~1\\c")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\abbreviated\\a")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\abbreviated\\b")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\abbreviated\\c")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\abbrev~1\\a")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\abbrev~1\\b")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\abbrev~1\\c")).isTrue();
+ assertThat(WindowsFileOperations.isJunction(root + "\\control\\a")).isFalse();
+ assertThat(WindowsFileOperations.isJunction(root + "\\control\\b")).isFalse();
+ assertThat(WindowsFileOperations.isJunction(root + "\\control\\c")).isFalse();
+ assertThat(WindowsFileOperations.isJunction(root + "\\shrttrgt\\file1.txt")).isFalse();
+ assertThat(WindowsFileOperations.isJunction(root + "\\longtargetpath\\file2.txt")).isFalse();
+ assertThat(WindowsFileOperations.isJunction(root + "\\longta~1\\file2.txt")).isFalse();
try {
- WindowsFileOperations.isJunction(root + "/non-existent");
+ WindowsFileOperations.isJunction(root + "\\non-existent");
fail("expected to throw");
} catch (IOException e) {
assertThat(e.getMessage()).contains("nativeIsJunction");
@@ -215,8 +215,8 @@
assertThat(helloFile.exists()).isTrue();
assertThat(new File(longPath).exists()).isTrue();
assertThat(new File(shortPath).exists()).isTrue();
- assertThat(WindowsFileOperations.getLongPath(longPath)).endsWith("will.exist\\helloworld.txt");
- assertThat(WindowsFileOperations.getLongPath(shortPath)).endsWith("will.exist\\helloworld.txt");
+ assertThat(WindowsFileOperations.getLongPath(longPath)).endsWith("will.exist/helloworld.txt");
+ assertThat(WindowsFileOperations.getLongPath(shortPath)).endsWith("will.exist/helloworld.txt");
// Delete the file and the directory, assert that long path resolution fails for them.
assertThat(helloFile.delete()).isTrue();
@@ -243,9 +243,9 @@
.toFile();
assertThat(new File(shortPath).exists()).isTrue();
assertThat(WindowsFileOperations.getLongPath(shortPath))
- .endsWith("will.exist_again\\hellowelt.txt");
+ .endsWith("will.exist_again/hellowelt.txt");
assertThat(WindowsFileOperations.getLongPath(foo + "\\will.exist_again\\hellowelt.txt"))
- .endsWith("will.exist_again\\hellowelt.txt");
+ .endsWith("will.exist_again/hellowelt.txt");
try {
WindowsFileOperations.getLongPath(longPath);
fail("expected to throw");
diff --git a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
index 89303fd..e547074 100644
--- a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
@@ -27,7 +27,6 @@
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Symlinks;
-import com.google.devtools.build.lib.windows.jni.WindowsFileOperations;
import com.google.devtools.build.lib.windows.util.WindowsTestUtil;
import java.io.File;
import java.io.IOException;
@@ -312,7 +311,7 @@
fs.createSymbolicLink(link4, fs.getPath(scratchRoot).getRelative("bar.txt").asFragment());
// Assert that link1 and link2 are true junctions and have the right contents.
for (Path p : ImmutableList.of(link1, link2)) {
- assertThat(WindowsFileOperations.isJunction(p.getPathString())).isTrue();
+ assertThat(WindowsFileSystem.isJunction(new File(p.getPathString()))).isTrue();
assertThat(p.isSymbolicLink()).isTrue();
assertThat(
Iterables.transform(
@@ -327,7 +326,7 @@
}
// Assert that link3 and link4 are copies of files.
for (Path p : ImmutableList.of(link3, link4)) {
- assertThat(WindowsFileOperations.isJunction(p.getPathString())).isFalse();
+ assertThat(WindowsFileSystem.isJunction(new File(p.getPathString()))).isFalse();
assertThat(p.isSymbolicLink()).isFalse();
assertThat(p.isFile()).isTrue();
}
diff --git a/src/test/native/windows/file_test.cc b/src/test/native/windows/file_test.cc
index 5919a37..ca7604d 100644
--- a/src/test/native/windows/file_test.cc
+++ b/src/test/native/windows/file_test.cc
@@ -81,7 +81,8 @@
wstring file1(target + L"\\foo");
EXPECT_TRUE(blaze_util::CreateDummyFile(file1));
- EXPECT_EQ(IS_JUNCTION_NO, IsJunctionOrDirectorySymlink(target.c_str()));
+ EXPECT_EQ(IS_JUNCTION_NO,
+ IsJunctionOrDirectorySymlink(target.c_str(), nullptr));
EXPECT_NE(INVALID_FILE_ATTRIBUTES, ::GetFileAttributesW(file1.c_str()));
wstring name(tmp + L"\\junc_name");
@@ -99,13 +100,13 @@
// Assert creation of the junctions.
ASSERT_EQ(IS_JUNCTION_YES,
- IsJunctionOrDirectorySymlink((name + L"1").c_str()));
+ IsJunctionOrDirectorySymlink((name + L"1").c_str(), nullptr));
ASSERT_EQ(IS_JUNCTION_YES,
- IsJunctionOrDirectorySymlink((name + L"2").c_str()));
+ IsJunctionOrDirectorySymlink((name + L"2").c_str(), nullptr));
ASSERT_EQ(IS_JUNCTION_YES,
- IsJunctionOrDirectorySymlink((name + L"3").c_str()));
+ IsJunctionOrDirectorySymlink((name + L"3").c_str(), nullptr));
ASSERT_EQ(IS_JUNCTION_YES,
- IsJunctionOrDirectorySymlink((name + L"4").c_str()));
+ IsJunctionOrDirectorySymlink((name + L"4").c_str(), nullptr));
// Assert that the file is visible under all junctions.
ASSERT_NE(INVALID_FILE_ATTRIBUTES,