Skip to content

Commit 763f1d9

Browse files
fmeumcopybara-github
authored andcommitted
Use ctime in file digest cache key
File digests are now additionally keyed by ctime for supported file system implementations. Since Bazel has a non-zero default for `--cache_computed_file_digests`, this may be required for correctness in cases where different files have identical mtime and inode number. For example, this can happen on Linux when files are extracted from a tar file with fixed mtime and are then replaced with `mv`, which preserves inodes. Since Java (N)IO doesn't have support for reading file ctimes on Windows, a new method backed by a native implementation is added to `WindowsFileOperation`. Adding a call to this function to `stat` uncovered previously silent bugs where Unix-style `PathFragment`s were created on Windows: 1. Bzlmod's `createLocalRepoSpec` did not correctly extract the path from a registry's `file://` URI on Windows. 2. `--package_path` isn't usable with absolute paths on Windows as it splits on `:`. Since the flag is deprecated, this commit fixes the tests rather than the implementation. Fixes #14723 Closes #18003. PiperOrigin-RevId: 524297459 Change-Id: I96bfc0210e2f71bf8603c7b7cc5eb06a04048c85
1 parent 7146086 commit 763f1d9

File tree

15 files changed

+284
-11
lines changed

15 files changed

+284
-11
lines changed

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ java_library(
7777
"//src/main/java/com/google/devtools/build/lib/bazel/repository/downloader",
7878
"//src/main/java/com/google/devtools/build/lib/cmdline",
7979
"//src/main/java/com/google/devtools/build/lib/events",
80+
"//src/main/java/com/google/devtools/build/lib/util:os",
8081
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
8182
"//third_party:gson",
8283
"//third_party:guava",

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.devtools.build.lib.bazel.repository.downloader.DownloadManager;
2525
import com.google.devtools.build.lib.cmdline.RepositoryName;
2626
import com.google.devtools.build.lib.events.ExtendedEventHandler;
27+
import com.google.devtools.build.lib.util.OS;
2728
import com.google.devtools.build.lib.vfs.PathFragment;
2829
import com.google.gson.FieldNamingPolicy;
2930
import com.google.gson.Gson;
@@ -176,7 +177,15 @@ private RepoSpec createLocalPathRepoSpec(
176177
path = moduleBase + "/" + path;
177178
if (!PathFragment.isAbsolute(moduleBase)) {
178179
if (uri.getScheme().equals("file")) {
179-
path = uri.getPath() + "/" + path;
180+
if (uri.getPath().isEmpty() || !uri.getPath().startsWith("/")) {
181+
throw new IOException(
182+
String.format(
183+
"Provided non absolute local registry path for module %s: %s",
184+
key, uri.getPath()));
185+
}
186+
// Unix: file:///tmp --> /tmp
187+
// Windows: file:///C:/tmp --> C:/tmp
188+
path = uri.getPath().substring(OS.getCurrent() == OS.WINDOWS ? 1 : 0) + "/" + path;
180189
} else {
181190
throw new IOException(String.format("Provided non local registry for module %s", key));
182191
}

src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,10 @@ private static FileArtifactValue fileArtifactValueFromStat(
692692
}
693693

694694
private void setPathPermissionsIfFile(Path path) throws IOException {
695-
if (path.isFile(Symlinks.NOFOLLOW)) {
695+
FileStatus stat = path.statIfFound(Symlinks.NOFOLLOW);
696+
if (stat != null
697+
&& stat.isFile()
698+
&& stat.getPermissions() != outputPermissions.getPermissionsMode()) {
696699
setPathPermissions(path);
697700
}
698701
}

src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ public long getNodeId() {
120120
return status.getInodeNumber();
121121
}
122122

123-
int getPermissions() {
123+
@Override
124+
public int getPermissions() {
124125
return status.getPermissions();
125126
}
126127

src/main/java/com/google/devtools/build/lib/vfs/DigestUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ private static class CacheKey {
4646
/** File system identifier of the file (typically the inode number). */
4747
private final long nodeId;
4848

49+
/** Last change time of the file. */
50+
private final long changeTime;
51+
4952
/** Last modification time of the file. */
5053
private final long modifiedTime;
5154

@@ -62,6 +65,7 @@ private static class CacheKey {
6265
public CacheKey(Path path, FileStatus status) throws IOException {
6366
this.path = path.asFragment();
6467
this.nodeId = status.getNodeId();
68+
this.changeTime = status.getLastChangeTime();
6569
this.modifiedTime = status.getLastModifiedTime();
6670
this.size = status.getSize();
6771
}
@@ -76,6 +80,7 @@ public boolean equals(Object object) {
7680
CacheKey key = (CacheKey) object;
7781
return path.equals(key.path)
7882
&& nodeId == key.nodeId
83+
&& changeTime == key.changeTime
7984
&& modifiedTime == key.modifiedTime
8085
&& size == key.size;
8186
}
@@ -86,6 +91,7 @@ public int hashCode() {
8691
int result = 17;
8792
result = 31 * result + path.hashCode();
8893
result = 31 * result + Longs.hashCode(nodeId);
94+
result = 31 * result + Longs.hashCode(changeTime);
8995
result = 31 * result + Longs.hashCode(modifiedTime);
9096
result = 31 * result + Longs.hashCode(size);
9197
return result;

src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,16 @@ public interface FileStatus {
8585
* ought to cause the node ID of b to change, but appending / modifying b should not.
8686
*/
8787
long getNodeId() throws IOException;
88+
89+
/**
90+
* Returns the file's permissions in POSIX format (e.g. 0755) if possible without performing
91+
* additional IO, otherwise (or if unsupported by the file system) returns -1.
92+
*
93+
* <p>If accurate group and other permissions aren't available, the returned value should attempt
94+
* to mimic a umask of 022 (i.e. read and execute permissions extend to group and other, write
95+
* does not).
96+
*/
97+
default int getPermissions() {
98+
return -1;
99+
}
88100
}

src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ public long getNodeId() {
120120
return System.identityHashCode(this);
121121
}
122122

123+
@Override
124+
public final int getPermissions() {
125+
int permissions = 0;
126+
// Emulate the default umask of 022.
127+
if (isReadable) {
128+
permissions |= 0444;
129+
}
130+
if (isWritable) {
131+
permissions |= 0200;
132+
}
133+
if (isExecutable) {
134+
permissions |= 0111;
135+
}
136+
return permissions;
137+
}
138+
123139
@Override
124140
public final InMemoryContentInfo inode() {
125141
return this;

src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
package com.google.devtools.build.lib.windows;
1616

1717
import com.google.devtools.build.lib.jni.JniLoader;
18+
import java.io.FileNotFoundException;
1819
import java.io.IOException;
20+
import java.nio.file.AccessDeniedException;
1921

2022
/** File operations on Windows. */
2123
public class WindowsFileOperations {
@@ -82,6 +84,12 @@ public Status getStatus() {
8284
// IS_SYMLINK_OR_JUNCTION_ERROR = 1;
8385
private static final int IS_SYMLINK_OR_JUNCTION_DOES_NOT_EXIST = 2;
8486

87+
// Keep GET_CHANGE_TIME_* values in sync with src/main/native/windows/file.cc.
88+
private static final int GET_CHANGE_TIME_SUCCESS = 0;
89+
// private static final int GET_CHANGE_TIME_ERROR = 1;
90+
private static final int GET_CHANGE_TIME_DOES_NOT_EXIST = 2;
91+
private static final int GET_CHANGE_TIME_ACCESS_DENIED = 3;
92+
8593
// Keep CREATE_JUNCTION_* values in sync with src/main/native/windows/file.h.
8694
private static final int CREATE_JUNCTION_SUCCESS = 0;
8795
// CREATE_JUNCTION_ERROR = 1;
@@ -114,6 +122,9 @@ public Status getStatus() {
114122
private static native int nativeIsSymlinkOrJunction(
115123
String path, boolean[] result, String[] error);
116124

125+
private static native int nativeGetChangeTime(
126+
String path, boolean followReparsePoints, long[] result, String[] error);
127+
117128
private static native boolean nativeGetLongPath(String path, String[] result, String[] error);
118129

119130
private static native int nativeCreateJunction(String name, String target, String[] error);
@@ -143,6 +154,25 @@ public static boolean isSymlinkOrJunction(String path) throws IOException {
143154
throw new IOException(String.format("Cannot tell if '%s' is link: %s", path, error[0]));
144155
}
145156

157+
/** Returns the time at which the file was last changed, including metadata changes. */
158+
public static long getLastChangeTime(String path, boolean followReparsePoints)
159+
throws IOException {
160+
long[] result = new long[] {0};
161+
String[] error = new String[] {null};
162+
switch (nativeGetChangeTime(asLongPath(path), followReparsePoints, result, error)) {
163+
case GET_CHANGE_TIME_SUCCESS:
164+
return result[0];
165+
case GET_CHANGE_TIME_DOES_NOT_EXIST:
166+
throw new FileNotFoundException(path);
167+
case GET_CHANGE_TIME_ACCESS_DENIED:
168+
throw new AccessDeniedException(path);
169+
default:
170+
// This is GET_CHANGE_TIME_ERROR (1). The JNI code puts a custom message in 'error[0]'.
171+
break;
172+
}
173+
throw new IOException(String.format("Cannot get last change time of '%s': %s", path, error[0]));
174+
}
175+
146176
/**
147177
* Returns the long path associated with the input `path`.
148178
*

src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOEx
156156
}
157157

158158
final boolean isSymbolicLink = !followSymlinks && fileIsSymbolicLink(file);
159+
final long lastChangeTime =
160+
WindowsFileOperations.getLastChangeTime(getNioPath(path).toString(), followSymlinks);
159161
FileStatus status =
160162
new FileStatus() {
161163
@Override
@@ -193,15 +195,20 @@ public long getLastModifiedTime() {
193195

194196
@Override
195197
public long getLastChangeTime() {
196-
// This is the best we can do with Java NIO...
197-
return attributes.lastModifiedTime().toMillis();
198+
return lastChangeTime;
198199
}
199200

200201
@Override
201202
public long getNodeId() {
202203
// TODO(bazel-team): Consider making use of attributes.fileKey().
203204
return -1;
204205
}
206+
207+
@Override
208+
public int getPermissions() {
209+
// Files on Windows are implicitly readable and executable.
210+
return 0555 | (attributes.isReadOnly() ? 0 : 0200);
211+
}
205212
};
206213

207214
return status;

src/main/native/windows/file-jni.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,29 @@ Java_com_google_devtools_build_lib_windows_WindowsFileOperations_nativeIsSymlink
6262
return static_cast<jint>(result);
6363
}
6464

65+
extern "C" JNIEXPORT jint JNICALL
66+
Java_com_google_devtools_build_lib_windows_WindowsFileOperations_nativeGetChangeTime(
67+
JNIEnv* env, jclass clazz, jstring path, jboolean follow_reparse_points,
68+
jlongArray result_holder, jobjectArray error_msg_holder) {
69+
std::wstring wpath(bazel::windows::GetJavaWstring(env, path));
70+
std::wstring error;
71+
jlong ctime = 0;
72+
int result =
73+
bazel::windows::GetChangeTime(wpath.c_str(), follow_reparse_points,
74+
reinterpret_cast<int64_t*>(&ctime), &error);
75+
if (result == bazel::windows::GetChangeTimeResult::kSuccess) {
76+
env->SetLongArrayRegion(result_holder, 0, 1, &ctime);
77+
} else {
78+
if (!error.empty() && CanReportError(env, error_msg_holder)) {
79+
ReportLastError(
80+
bazel::windows::MakeErrorMessage(
81+
WSTR(__FILE__), __LINE__, L"nativeGetChangeTime", wpath, error),
82+
env, error_msg_holder);
83+
}
84+
}
85+
return static_cast<jint>(result);
86+
}
87+
6588
extern "C" JNIEXPORT jboolean JNICALL
6689
Java_com_google_devtools_build_lib_windows_WindowsFileOperations_nativeGetLongPath(
6790
JNIEnv* env, jclass clazz, jstring path, jobjectArray result_holder,

0 commit comments

Comments
 (0)