aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Lukacs Berki <lberki@google.com>2016-01-21 08:38:24 +0000
committerGravatar Lukacs Berki <lberki@google.com>2016-01-21 10:35:31 +0000
commit0338a35cbd1d55e24a97958da39e76615fe670aa (patch)
tree236bf848ca6a1a32c9880baad5a35644298fdb0e
parent0acd006477cf4c741fbea5384bcc30dda99ba210 (diff)
Scaffolding for implementing symlinks on Windows.
At first, this will only be used for emulating the planned implementation on Linux to validate it a little more before starting with the big work of porting everything to Windows in case it is doomed to failure. In logging mode, the only places where we create symbolic links that we can't emulate with the plan (pointing to a non-existent file or to a file outside the output base and the source root, which are assumed to be writable): - ExecutionTool.createOutputDirectoryLinks(). If we won't have the convenience symlinks on Windows, I won't shed a tear (I'm wondering why, though, because they are between the output base and the source tree) - In the implementation of new_local_repository (Would need to be special-cased for Windows. No big deal.) - In the implementation of the .tar.gz decompressor (doesn't seem to be serious, either.) So this seems to be alright. Note, however, that we didn't check build-runfiles.cc, which might cause trouble. I don't remember any place where we create a link there that is illegal according to the above rules, though. -- MOS_MIGRATED_REVID=112659070
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java112
1 files changed, 84 insertions, 28 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
index 9bb9555268..1f46215916 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.vfs;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
@@ -26,9 +27,7 @@ import com.google.devtools.build.lib.unix.FilesystemUtils.ReadTypes;
import com.google.devtools.build.lib.util.Preconditions;
import java.io.IOException;
-import java.io.PrintWriter;
import java.io.RandomAccessFile;
-import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
@@ -37,6 +36,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Random;
/**
* This class implements the FileSystem interface using direct calls to the
@@ -78,12 +78,14 @@ public class UnixFileSystem extends AbstractFileSystemWithCustomStat {
SymlinkStrategy symlinkStrategy = SymlinkStrategy.SYMLINK;
String strategyString = System.getProperty("io.bazel.SymlinkStrategy");
symlinkLogFile = System.getProperty("io.bazel.SymlinkLogFile");
- if (strategyString != null && symlinkLogFile != null) {
+ if (strategyString != null) {
try {
symlinkStrategy = SymlinkStrategy.valueOf(strategyString.toUpperCase());
} catch (IllegalArgumentException e) {
// We just go with the default, this is just an experimental option so it's fine.
}
+
+ writeLogMessage("Logging started");
}
this.symlinkStrategy = symlinkStrategy;
@@ -368,10 +370,22 @@ public class UnixFileSystem extends AbstractFileSystemWithCustomStat {
@Override
protected void createSymbolicLink(Path linkPath, PathFragment targetFragment)
throws IOException {
- checkForWindowsCompatibility(linkPath, targetFragment);
+ SymlinkImplementation strategy = computeSymlinkImplementation(linkPath, targetFragment);
+ switch (strategy) {
+ case HARDLINK: // TBD, fallthrough for now
+ case JUNCTION: // Junctions are emulated on Linux with symlinks, fall through
+ case SYMLINK:
+ synchronized (linkPath) {
+ FilesystemUtils.symlink(targetFragment.toString(), linkPath.toString());
+ }
+ break;
- synchronized (linkPath) {
- FilesystemUtils.symlink(targetFragment.toString(), linkPath.toString());
+ case FAIL:
+ if (symlinkLogFile == null) {
+ // Otherwise, it was logged in computeSymlinkImplementation().
+ throw new IOException(String.format("Symlink emulation failed for symlink: %s -> %s",
+ linkPath, targetFragment));
+ }
}
}
@@ -385,53 +399,95 @@ public class UnixFileSystem extends AbstractFileSystemWithCustomStat {
return false;
}
- private void emitSymlinkCompatibilityMessage(
- String reason, Path linkPath, PathFragment targetFragment)
- throws IOException {
+ private static final int JVM_ID = new Random().nextInt(10000);
+
+ private void writeLogMessage(String message) {
+ String logLine = String.format("[%04d] %s\n", JVM_ID, message);
// FileLock does not work for synchronization between threads in the same JVM as per its Javadoc
synchronized (symlinkLogFile) {
- Exception e = new Exception();
- e.fillInStackTrace();
- StringWriter sw = new StringWriter();
- e.printStackTrace(new PrintWriter(sw));
- String msg = String.format("ILLEGAL (%s): %s -> %s\nStack:\n%s",
- reason, linkPath.getPathString(), targetFragment.getPathString(), sw.toString());
try (FileChannel channel = new RandomAccessFile(symlinkLogFile, "rwd").getChannel()) {
try (FileLock lock = channel.lock()) {
channel.position(channel.size());
- ByteBuffer data = Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(msg));
+ ByteBuffer data = Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(logLine));
channel.write(data);
}
+ } catch (IOException e) {
+ // Not much intelligent we can do here
}
}
}
- private void checkForWindowsCompatibility(Path linkPath, PathFragment targetFragment)
- throws IOException {
+ /**
+ * How to create a particular symbolic link.
+ *
+ * <p>Necessary because Windows doesn't support symlinks properly, so we have to work around it.
+ * No, even though they say <i>"Microsoft has implemented its symbolic links to function just like
+ * UNIX links"</i>, it's a lie.
+ */
+ private enum SymlinkImplementation {
+ /**
+ * We can't emulate this link. Fail.
+ */
+ FAIL,
+
+ /**
+ * Create a hard link. This only works if we have write access to the ultimate destination on
+ * the link.
+ */
+ HARDLINK,
+
+ /**
+ * Create a junction. This only works if the ultimate target of the "symlink" is a directory.
+ */
+ JUNCTION,
+
+ /**
+ * Use a symlink. Always works, but only on Unix-based operating systems.
+ */
+ SYMLINK,
+ }
+
+ private SymlinkImplementation emitSymlinkCompatibilityMessage(
+ String reason, Path linkPath, PathFragment targetFragment) {
+ if (symlinkLogFile == null) {
+ return SymlinkImplementation.FAIL;
+ }
+
+ Exception e = new Exception();
+ e.fillInStackTrace();
+ String msg = String.format("ILLEGAL (%s): %s -> %s\nStack:\n%s",
+ reason, linkPath.getPathString(), targetFragment.getPathString(),
+ Throwables.getStackTraceAsString(e));
+ writeLogMessage(msg);
+ return SymlinkImplementation.SYMLINK; // We are in logging mode, pretend everything is A-OK
+ }
+
+ private SymlinkImplementation computeSymlinkImplementation(
+ Path linkPath, PathFragment targetFragment) throws IOException {
if (symlinkStrategy != SymlinkStrategy.WINDOWS_COMPATIBLE) {
- return;
+ return SymlinkImplementation.SYMLINK;
}
Path targetPath = linkPath.getRelative(targetFragment);
if (!targetPath.exists(Symlinks.FOLLOW)) {
- // On Windows, one needs to know if the target is a directory or a file.
- emitSymlinkCompatibilityMessage("Target does not exist", linkPath, targetFragment);
- return;
+ return emitSymlinkCompatibilityMessage(
+ "Target does not exist", linkPath, targetFragment);
}
targetPath = targetPath.resolveSymbolicLinks();
- if (targetPath.isDirectory()) {
- // We can use a junction
- return;
+ if (targetPath.isDirectory(Symlinks.FOLLOW)) {
+ // We can create junctions to any directory.
+ return SymlinkImplementation.JUNCTION;
}
if (isHardLinkAllowed(targetPath)) {
- // We can use a hard link
- return;
+ // We have write access to the destination and it's a file, so we can do this
+ return SymlinkImplementation.HARDLINK;
}
- emitSymlinkCompatibilityMessage("Link to non-writable file", linkPath, targetFragment);
+ return emitSymlinkCompatibilityMessage(
+ "Target is a non-writable file", linkPath, targetFragment);
}
@Override