diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index ce381d7a0a3a9d51f761c8577fa6c4d7b58b46c8..2f7ea8fcc65d4d3d806d754fa50b85e4eb29996d 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -1403,7 +1403,7 @@ public class Util { * The relative path is meant to be resolved from the location of the symlink. */ @CheckForNull - public static String resolveSymlink(@Nonnull File link) throws InterruptedException, IOException { + public static String resolveSymlink(@Nonnull File link) throws IOException { try { Path path = link.toPath(); return Files.readSymbolicLink(path).toString(); @@ -1415,7 +1415,7 @@ public class Util { } catch (IOException x) { throw x; } catch (Exception x) { - throw (IOException) new IOException(x.toString()).initCause(x); + throw new IOException(x); } } diff --git a/core/src/main/java/jenkins/util/VirtualFile.java b/core/src/main/java/jenkins/util/VirtualFile.java index 6cf765f3981e10285ce2d004647e4ddd59806de2..434fefd8271086479be08e28260fd8c884c2a992 100644 --- a/core/src/main/java/jenkins/util/VirtualFile.java +++ b/core/src/main/java/jenkins/util/VirtualFile.java @@ -139,8 +139,20 @@ public abstract class VirtualFile implements Comparable, Serializab */ public abstract boolean isFile() throws IOException; + /** + * If this file is a symlink, returns the link target. + *

The default implementation always returns null. + * Some implementations may not support symlinks under any conditions. + * @return a target (typically a relative path in some format), or null if this is not a link + * @throws IOException if reading the link, or even determining whether this file is a link, failed + */ + public @CheckForNull String readLink() throws IOException { + return null; + } + /** * Checks whether this file exists. + * The behavior is undefined for symlinks; if in doubt, check {@link #readLink} first. * @return true if it is a plain file or directory, false if nonexistent * @throws IOException in case checking status failed */ @@ -378,6 +390,12 @@ public abstract class VirtualFile implements Comparable, Serializab } return f.exists(); } + @Override public String readLink() throws IOException { + if (isIllegalSymlink()) { + return null; // best to just ignore link -> ../whatever + } + return Util.resolveSymlink(f); + } @Override public VirtualFile[] list() throws IOException { if (isIllegalSymlink()) { return new VirtualFile[0]; @@ -497,6 +515,13 @@ public abstract class VirtualFile implements Comparable, Serializab throw new IOException(x); } } + @Override public String readLink() throws IOException { + try { + return f.readLink(); + } catch (InterruptedException x) { + throw new IOException(x); + } + } @Override public VirtualFile[] list() throws IOException { try { List kids = f.list(); diff --git a/core/src/test/java/jenkins/util/VirtualFileTest.java b/core/src/test/java/jenkins/util/VirtualFileTest.java index 8be31af5a71cb8bb8431cfd62b80a1e2423fb56c..d704cf8a8c97c735c340373442b2d2cb1d7341d8 100644 --- a/core/src/test/java/jenkins/util/VirtualFileTest.java +++ b/core/src/test/java/jenkins/util/VirtualFileTest.java @@ -175,4 +175,21 @@ public class VirtualFileTest { } } + @Issue("JENKINS-26810") + @Test public void readLink() throws Exception { + assumeFalse("Symlinks do not work well on Windows", Functions.isWindows()); + File root = tmp.getRoot(); + FilePath rootF = new FilePath(root); + rootF.child("plain").write("", null); + rootF.child("link").symlinkTo("physical", TaskListener.NULL); + for (VirtualFile vf : new VirtualFile[] {VirtualFile.forFile(root), VirtualFile.forFilePath(rootF)}) { + assertNull(vf.readLink()); + assertNull(vf.child("plain").readLink()); + VirtualFile link = vf.child("link"); + assertEquals("physical", link.readLink()); + assertFalse(link.isFile()); + assertFalse(link.isDirectory()); + } + } + }