diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index 45d8f3f5e2..a55b90d05e 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -23,6 +23,7 @@ */ package hudson; +import com.google.common.annotations.VisibleForTesting; import hudson.model.TaskListener; import jenkins.util.MemoryReductionUtil; import hudson.util.QuotedStringTokenizer; @@ -1277,6 +1278,38 @@ public class Util { } } + /** + * Creates an NTFS junction point if supported. Similar to symbolic links, NTFS provides junction points which + * provide different features than symbolic links. This method is primarily useful for unit testing on Windows. + * @param junction NTFS junction point to create + * @param target target directory to junction + * @return the newly created junction point + * @throws IOException if the call to mklink exits with a non-zero status code + * @throws InterruptedException if the call to mklink is interrupted before completing + * @throws AssertionError if this method is called on a non-Windows platform + */ + static @Nonnull File createJunction(@Nonnull File junction, @Nonnull File target) throws IOException, InterruptedException { + if (!Functions.isWindows()) throw new AssertionError("Cannot create junctions on non-Windows platforms"); + String command = "mklink /J " + escapeCmdArgument(junction.getAbsolutePath()) + ' ' + escapeCmdArgument(target.getAbsolutePath()); + Process process = new ProcessBuilder("cmd.exe", "/C", command).start(); + int result = process.waitFor(); + if (result != 0) throw new IOException("Command `" + command + "` failed with status code " + result); + return junction; + } + + private static final Pattern CMD_METACHARS = Pattern.compile("[()%!^\"<>&|]"); + + /** + * Escapes a Windows cmd argument. Cmd supports escaping characters with carats which simplifies nested escapes. + * @param arg argument to escape + * @return the quoted argument for use in cmd.exe + * @see