diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java | 112 |
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 |