From 0bd2563f6c8e21e73303e0fba77246556c009b90 Mon Sep 17 00:00:00 2001 From: Raihaan Shouhell Date: Wed, 15 May 2019 09:48:07 +0800 Subject: [PATCH 1/6] Add remoting workdir --- .../java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java | 9 +++++---- .../java/hudson/plugins/ec2/win/EC2WindowsLauncher.java | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java index bcefa2b2d..4cd488036 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java @@ -218,10 +218,11 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws logInfo(computer, listener, "Copying remoting.jar to: " + tmpDir); scp.put(Jenkins.getInstance().getJnlpJars("remoting.jar").readFully(), "remoting.jar", tmpDir); - String jvmopts = node.jvmopts; - String prefix = computer.getSlaveCommandPrefix(); - String suffix = computer.getSlaveCommandSuffix(); - String launchString = prefix + " java " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + "/remoting.jar" + suffix; + final String jvmopts = node.jvmopts; + final String prefix = computer.getSlaveCommandPrefix(); + final String suffix = computer.getSlaveCommandSuffix(); + final String remoteFS = computer.getNode().getRemoteFS(); + String launchString = prefix + " java " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + "/remoting.jar -workDir " + remoteFS + suffix; // launchString = launchString.trim(); SlaveTemplate slaveTemplate = computer.getSlaveTemplate(); diff --git a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java index 5480ad7fa..853c26420 100644 --- a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java +++ b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java @@ -73,8 +73,9 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws logger.println("remoting.jar sent remotely. Bootstrapping it"); final String jvmopts = node.jvmopts; + final String remoteFS = node.getRemoteFS(); final WindowsProcess process = connection.execute("java " + (jvmopts != null ? jvmopts : "") + " -jar " - + tmpDir + AGENT_JAR, 86400); + + tmpDir + AGENT_JAR + " -workDir " + remoteFS, 86400); computer.setChannel(process.getStdout(), process.getStdin(), logger, new Listener() { @Override public void onClosed(Channel channel, IOException cause) { From 611deb86659eec4ca5b4de31d9933963116f08fa Mon Sep 17 00:00:00 2001 From: Raihaan Shouhell Date: Tue, 21 May 2019 17:33:37 +0800 Subject: [PATCH 2/6] Quote remoteFS on windows --- src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java index 853c26420..76d2a2572 100644 --- a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java +++ b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java @@ -74,8 +74,9 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws final String jvmopts = node.jvmopts; final String remoteFS = node.getRemoteFS(); - final WindowsProcess process = connection.execute("java " + (jvmopts != null ? jvmopts : "") + " -jar " - + tmpDir + AGENT_JAR + " -workDir " + remoteFS, 86400); + final String launchString = "java " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + AGENT_JAR + " -workDir \"" + remoteFS + "\""; + logger.println(launchString); + final WindowsProcess process = connection.execute(launchString, 86400); computer.setChannel(process.getStdout(), process.getStdin(), logger, new Listener() { @Override public void onClosed(Channel channel, IOException cause) { From b500c696d4ad7b81692d16836ceccc4b179d4cb0 Mon Sep 17 00:00:00 2001 From: Raihaan Shouhell Date: Thu, 23 May 2019 10:28:41 +0800 Subject: [PATCH 3/6] Add WindowsUtil from core with todo, fix windows tmpdir and quote appropriate arguments --- src/main/java/hudson/os/WindowsUtil.java | 119 ++++++++++++++++++ .../plugins/ec2/win/EC2WindowsLauncher.java | 10 +- 2 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 src/main/java/hudson/os/WindowsUtil.java diff --git a/src/main/java/hudson/os/WindowsUtil.java b/src/main/java/hudson/os/WindowsUtil.java new file mode 100644 index 000000000..05e67a3f5 --- /dev/null +++ b/src/main/java/hudson/os/WindowsUtil.java @@ -0,0 +1,119 @@ +/* + * The MIT License + * + * Copyright (c) 2019 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +//TODO: Have this added to core +//https://github.com/jenkinsci/ec2-plugin/pull/352/ +package hudson.os; + +import hudson.Functions; +import org.apache.commons.io.IOUtils; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertTrue; + +// adapted from: +// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ +public class WindowsUtil { + private static final Pattern NEEDS_QUOTING = Pattern.compile("[\\s\"]"); + + /** + * Quotes an argument while escaping special characters interpreted by CreateProcess. + */ + public static @Nonnull String quoteArgument(@Nonnull String argument) { + if (!NEEDS_QUOTING.matcher(argument).find()) return argument; + StringBuilder sb = new StringBuilder(); + sb.append('"'); + int end = argument.length(); + for (int i = 0; i < end; i++) { + int nrBackslashes = 0; + while (i < end && argument.charAt(i) == '\\') { + i++; + nrBackslashes++; + } + + if (i == end) { + // backslashes at the end of the argument must be escaped so the terminate quote isn't + nrBackslashes = nrBackslashes * 2; + } else if (argument.charAt(i) == '"') { + // backslashes preceding a quote all need to be escaped along with the quote + nrBackslashes = nrBackslashes * 2 + 1; + } + // else backslashes have no special meaning and don't need to be escaped here + + for (int j = 0; j < nrBackslashes; j++) { + sb.append('\\'); + } + + if (i < end) { + sb.append(argument.charAt(i)); + } + } + return sb.append('"').toString(); + } + + private static final Pattern CMD_METACHARS = Pattern.compile("[()%!^\"<>&|]"); + + /** + * Quotes an argument while escaping special characters suitable for use as an argument to {@code cmd.exe}. + */ + public static @Nonnull String quoteArgumentForCmd(@Nonnull String argument) { + return CMD_METACHARS.matcher(quoteArgument(argument)).replaceAll("^$0"); + } + + /** + * Executes a command and arguments using {@code cmd.exe /C ...}. + */ + public static @Nonnull Process execCmd(String... argv) throws IOException { + String command = Arrays.stream(argv).map(WindowsUtil::quoteArgumentForCmd).collect(Collectors.joining(" ")); + return Runtime.getRuntime().exec(new String[]{"cmd.exe", "/C", command}); + } + + /** + * Creates an NTFS junction point if supported. Similar to symbolic links, NTFS provides junction points which + * provide different features than symbolic links. + * @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 + */ + public static @Nonnull File createJunction(@Nonnull File junction, @Nonnull File target) throws IOException, InterruptedException { + assertTrue(Functions.isWindows()); + Process mklink = execCmd("mklink", "/J", junction.getAbsolutePath(), target.getAbsolutePath()); + int result = mklink.waitFor(); + if (result != 0) { + String stderr = IOUtils.toString(mklink.getErrorStream()); + String stdout = IOUtils.toString(mklink.getInputStream()); + throw new IOException("Process exited with " + result + "\nStandard Output:\n" + stdout + "\nError Output:\n" + stderr); + } + return junction; + } +} \ No newline at end of file diff --git a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java index 76d2a2572..a09bf2969 100644 --- a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java +++ b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java @@ -11,6 +11,8 @@ import hudson.remoting.Channel; import hudson.remoting.Channel.Listener; import hudson.slaves.ComputerLauncher; +import hudson.Util; +import hudson.os.WindowsUtil; import java.io.IOException; import java.io.OutputStream; @@ -42,7 +44,7 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws try { String initScript = node.initScript; - String tmpDir = (node.tmpDir != null && !node.tmpDir.equals("") ? node.tmpDir + String tmpDir = (node.tmpDir != null && !node.tmpDir.equals("") ? WindowsUtil.quoteArgument(Util.ensureEndsWith(node.tmpDir,"\\")) : "C:\\Windows\\Temp\\"); logger.println("Creating tmp directory if it does not exist"); @@ -73,9 +75,9 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws logger.println("remoting.jar sent remotely. Bootstrapping it"); final String jvmopts = node.jvmopts; - final String remoteFS = node.getRemoteFS(); - final String launchString = "java " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + AGENT_JAR + " -workDir \"" + remoteFS + "\""; - logger.println(launchString); + final String remoteFS = WindowsUtil.quoteArgument(node.getRemoteFS()); + final String launchString = "java " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + AGENT_JAR + " -workDir " + remoteFS; + logger.println("Launching via WinRM:" + launchString); final WindowsProcess process = connection.execute(launchString, 86400); computer.setChannel(process.getStdout(), process.getStdin(), logger, new Listener() { @Override From a3119423f849208d01861d9a7cee2292a2801227 Mon Sep 17 00:00:00 2001 From: Raihaan Shouhell Date: Thu, 23 May 2019 10:45:52 +0800 Subject: [PATCH 4/6] Remove assert from WindowsUtil --- src/main/java/hudson/os/WindowsUtil.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/hudson/os/WindowsUtil.java b/src/main/java/hudson/os/WindowsUtil.java index 05e67a3f5..e222f3fc2 100644 --- a/src/main/java/hudson/os/WindowsUtil.java +++ b/src/main/java/hudson/os/WindowsUtil.java @@ -36,8 +36,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static org.junit.Assert.assertTrue; - // adapted from: // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ public class WindowsUtil { @@ -103,10 +101,12 @@ public class WindowsUtil { * @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 + * @throws UnsupportedOperationException if this method is called on a non-Windows platform */ public static @Nonnull File createJunction(@Nonnull File junction, @Nonnull File target) throws IOException, InterruptedException { - assertTrue(Functions.isWindows()); + if(Functions.isWindows() == false) { + throw new UnsupportedOperationException("Can only be called on windows platform"); + } Process mklink = execCmd("mklink", "/J", junction.getAbsolutePath(), target.getAbsolutePath()); int result = mklink.waitFor(); if (result != 0) { From dc2e18433afe7e5235a6f8b1e8d4ab6c5e5881d6 Mon Sep 17 00:00:00 2001 From: Raihaan Shouhell Date: Thu, 23 May 2019 10:55:48 +0800 Subject: [PATCH 5/6] Fix spotbugs --- src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java index 4cd488036..7106d893d 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java @@ -221,7 +221,7 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws final String jvmopts = node.jvmopts; final String prefix = computer.getSlaveCommandPrefix(); final String suffix = computer.getSlaveCommandSuffix(); - final String remoteFS = computer.getNode().getRemoteFS(); + final String remoteFS = node.getRemoteFS(); String launchString = prefix + " java " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + "/remoting.jar -workDir " + remoteFS + suffix; // launchString = launchString.trim(); From 79c2a47cad254094f2b6c20d724e205afc9a713e Mon Sep 17 00:00:00 2001 From: Raihaan Shouhell Date: Thu, 23 May 2019 22:39:53 +0800 Subject: [PATCH 6/6] Changes based on feedback --- src/main/java/hudson/os/WindowsUtil.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/hudson/os/WindowsUtil.java b/src/main/java/hudson/os/WindowsUtil.java index e222f3fc2..56d321f80 100644 --- a/src/main/java/hudson/os/WindowsUtil.java +++ b/src/main/java/hudson/os/WindowsUtil.java @@ -28,6 +28,8 @@ import hudson.Functions; import org.apache.commons.io.IOUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; import javax.annotation.Nonnull; import java.io.File; @@ -38,6 +40,7 @@ // adapted from: // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ +@Restricted(NoExternalUse.class) public class WindowsUtil { private static final Pattern NEEDS_QUOTING = Pattern.compile("[\\s\"]"); @@ -116,4 +119,4 @@ public class WindowsUtil { } return junction; } -} \ No newline at end of file +}