diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD index 169711f8d1285a..dc0ff89bbbb598 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD @@ -74,6 +74,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/bazel/repository/downloader", "//src/main/java/com/google/devtools/build/lib/cmdline", "//src/main/java/com/google/devtools/build/lib/events", + "//src/main/java/com/google/devtools/build/lib/util:os", "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", "//third_party:gson", "//third_party:guava", diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java index 869ae3bcea7a50..83266c3032518f 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java @@ -24,6 +24,7 @@ import com.google.devtools.build.lib.bazel.repository.downloader.DownloadManager; import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.events.ExtendedEventHandler; +import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; @@ -175,7 +176,15 @@ private RepoSpec createLocalPathRepoSpec( path = moduleBase + "/" + path; if (!PathFragment.isAbsolute(moduleBase)) { if (uri.getScheme().equals("file")) { - path = uri.getPath() + "/" + path; + if (uri.getPath().isEmpty() || !uri.getPath().startsWith("/")) { + throw new IOException( + String.format( + "Provided non absolute local registry path for module %s: %s", + key, uri.getPath())); + } + // Unix: file:///tmp --> /tmp + // Windows: file:///C:/tmp --> C:/tmp + path = uri.getPath().substring(OS.getCurrent() == OS.WINDOWS ? 1 : 0) + "/" + path; } else { throw new IOException(String.format("Provided non local registry for module %s", key)); } diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java index af848e30a71dfe..f0efff1a98d438 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java @@ -719,7 +719,10 @@ private static FileArtifactValue fileArtifactValueFromStat( } private void setPathPermissionsIfFile(Path path) throws IOException { - if (path.isFile(Symlinks.NOFOLLOW)) { + FileStatus stat = path.statIfFound(Symlinks.NOFOLLOW); + if (stat != null + && stat.isFile() + && stat.getPermissions() != outputPermissions.getPermissionsMode()) { setPathPermissions(path); } } diff --git a/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java b/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java index c92fba61afae5a..0e123214675a7f 100644 --- a/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java @@ -120,7 +120,8 @@ public long getNodeId() { return status.getInodeNumber(); } - int getPermissions() { + @Override + public int getPermissions() { return status.getPermissions(); } diff --git a/src/main/java/com/google/devtools/build/lib/vfs/DigestUtils.java b/src/main/java/com/google/devtools/build/lib/vfs/DigestUtils.java index 6c2f2b5393a5b1..77bd98d39ce3f5 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/DigestUtils.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/DigestUtils.java @@ -46,6 +46,9 @@ private static class CacheKey { /** File system identifier of the file (typically the inode number). */ private final long nodeId; + /** Last change time of the file. */ + private final long changeTime; + /** Last modification time of the file. */ private final long modifiedTime; @@ -62,6 +65,7 @@ private static class CacheKey { public CacheKey(Path path, FileStatus status) throws IOException { this.path = path.asFragment(); this.nodeId = status.getNodeId(); + this.changeTime = status.getLastChangeTime(); this.modifiedTime = status.getLastModifiedTime(); this.size = status.getSize(); } @@ -76,6 +80,7 @@ public boolean equals(Object object) { CacheKey key = (CacheKey) object; return path.equals(key.path) && nodeId == key.nodeId + && changeTime == key.changeTime && modifiedTime == key.modifiedTime && size == key.size; } @@ -86,6 +91,7 @@ public int hashCode() { int result = 17; result = 31 * result + path.hashCode(); result = 31 * result + Longs.hashCode(nodeId); + result = 31 * result + Longs.hashCode(changeTime); result = 31 * result + Longs.hashCode(modifiedTime); result = 31 * result + Longs.hashCode(size); return result; diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java b/src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java index f6112beaf89a2a..5827576381cd9a 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/FileStatus.java @@ -85,4 +85,16 @@ public interface FileStatus { * ought to cause the node ID of b to change, but appending / modifying b should not. */ long getNodeId() throws IOException; + + /** + * Returns the file's permissions in POSIX format (e.g. 0755) if possible without performing + * additional IO, otherwise (or if unsupported by the file system) returns -1. + * + *
If accurate group and other permissions aren't available, the returned value should attempt
+ * to mimic a umask of 022 (i.e. read and execute permissions extend to group and other, write
+ * does not).
+ */
+ default int getPermissions() {
+ return -1;
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java
index 5686308aeab5dc..51b9b65f580aad 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java
@@ -120,6 +120,22 @@ public long getNodeId() {
return System.identityHashCode(this);
}
+ @Override
+ public final int getPermissions() {
+ int permissions = 0;
+ // Emulate the default umask of 022.
+ if (isReadable) {
+ permissions |= 0444;
+ }
+ if (isWritable) {
+ permissions |= 0200;
+ }
+ if (isExecutable) {
+ permissions |= 0111;
+ }
+ return permissions;
+ }
+
@Override
public final InMemoryContentInfo inode() {
return this;
diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java
index 25cba40bd0d3a4..7b8e691ce4500b 100644
--- a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java
+++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileOperations.java
@@ -15,7 +15,9 @@
package com.google.devtools.build.lib.windows;
import com.google.devtools.build.lib.jni.JniLoader;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.nio.file.AccessDeniedException;
/** File operations on Windows. */
public class WindowsFileOperations {
@@ -82,6 +84,12 @@ public Status getStatus() {
// IS_SYMLINK_OR_JUNCTION_ERROR = 1;
private static final int IS_SYMLINK_OR_JUNCTION_DOES_NOT_EXIST = 2;
+ // Keep GET_CHANGE_TIME_* values in sync with src/main/native/windows/file.cc.
+ private static final int GET_CHANGE_TIME_SUCCESS = 0;
+ // private static final int GET_CHANGE_TIME_ERROR = 1;
+ private static final int GET_CHANGE_TIME_DOES_NOT_EXIST = 2;
+ private static final int GET_CHANGE_TIME_ACCESS_DENIED = 3;
+
// Keep CREATE_JUNCTION_* values in sync with src/main/native/windows/file.h.
private static final int CREATE_JUNCTION_SUCCESS = 0;
// CREATE_JUNCTION_ERROR = 1;
@@ -114,6 +122,9 @@ public Status getStatus() {
private static native int nativeIsSymlinkOrJunction(
String path, boolean[] result, String[] error);
+ private static native int nativeGetChangeTime(
+ String path, boolean followReparsePoints, long[] result, String[] error);
+
private static native boolean nativeGetLongPath(String path, String[] result, String[] error);
private static native int nativeCreateJunction(String name, String target, String[] error);
@@ -143,6 +154,25 @@ public static boolean isSymlinkOrJunction(String path) throws IOException {
throw new IOException(String.format("Cannot tell if '%s' is link: %s", path, error[0]));
}
+ /** Returns the time at which the file was last changed, including metadata changes. */
+ public static long getLastChangeTime(String path, boolean followReparsePoints)
+ throws IOException {
+ long[] result = new long[] {0};
+ String[] error = new String[] {null};
+ switch (nativeGetChangeTime(asLongPath(path), followReparsePoints, result, error)) {
+ case GET_CHANGE_TIME_SUCCESS:
+ return result[0];
+ case GET_CHANGE_TIME_DOES_NOT_EXIST:
+ throw new FileNotFoundException(path);
+ case GET_CHANGE_TIME_ACCESS_DENIED:
+ throw new AccessDeniedException(path);
+ default:
+ // This is GET_CHANGE_TIME_ERROR (1). The JNI code puts a custom message in 'error[0]'.
+ break;
+ }
+ throw new IOException(String.format("Cannot get last change time of '%s': %s", path, error[0]));
+ }
+
/**
* Returns the long path associated with the input `path`.
*
diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
index 30afecc1c2415f..2c4eda788c0f22 100644
--- a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
@@ -156,6 +156,8 @@ protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOEx
}
final boolean isSymbolicLink = !followSymlinks && fileIsSymbolicLink(file);
+ final long lastChangeTime =
+ WindowsFileOperations.getLastChangeTime(getNioPath(path).toString(), followSymlinks);
FileStatus status =
new FileStatus() {
@Override
@@ -193,8 +195,7 @@ public long getLastModifiedTime() throws IOException {
@Override
public long getLastChangeTime() {
- // This is the best we can do with Java NIO...
- return attributes.lastModifiedTime().toMillis();
+ return lastChangeTime;
}
@Override
@@ -202,6 +203,12 @@ public long getNodeId() {
// TODO(bazel-team): Consider making use of attributes.fileKey().
return -1;
}
+
+ @Override
+ public int getPermissions() {
+ // Files on Windows are implicitly readable and executable.
+ return 0555 | (attributes.isReadOnly() ? 0 : 0200);
+ }
};
return status;
diff --git a/src/main/native/windows/file-jni.cc b/src/main/native/windows/file-jni.cc
index d920b0825021ba..4ed2470c4029df 100644
--- a/src/main/native/windows/file-jni.cc
+++ b/src/main/native/windows/file-jni.cc
@@ -62,6 +62,29 @@ Java_com_google_devtools_build_lib_windows_WindowsFileOperations_nativeIsSymlink
return static_cast