diff --git a/core/src/main/java/hudson/os/SU.java b/core/src/main/java/hudson/os/SU.java
index 0bb1e10d22eb6ccf1c9d50a2a149df1d2363af48..d3ef54eb2f56570e55409cada8a7cf6e19d8aa84 100644
--- a/core/src/main/java/hudson/os/SU.java
+++ b/core/src/main/java/hudson/os/SU.java
@@ -29,18 +29,18 @@ import hudson.Util;
import hudson.model.Computer;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
-import hudson.remoting.Channel;
import hudson.remoting.Launcher;
+import hudson.remoting.LocalChannel;
+import hudson.remoting.VirtualChannel;
import hudson.remoting.Which;
+import hudson.slaves.Channels;
import hudson.util.ArgumentListBuilder;
-import hudson.util.StreamCopyThread;
import static hudson.util.jna.GNUCLibrary.LIBC;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collections;
-import java.util.concurrent.Future;
/**
* Executes {@link Callable} as the super user, by forking a new process and executing the closure in there
@@ -51,7 +51,7 @@ import java.util.concurrent.Future;
* in the non-root privilege, so the closure should expect that and handle it gracefully.
*
*
- * Still very much experimental. Subject to change.
+ * Still very much experimental. Subject to change. Don't use it.
*
* @author Kohsuke Kawaguchi
*/
@@ -59,54 +59,17 @@ public abstract class SU {
private SU() { // not meant to be instantiated
}
- private interface Actor {
- V actHere() throws T;
- V actThere(Channel ch) throws T, IOException, InterruptedException;
- }
-
- public static V execute(TaskListener listener, String rootUsername, String rootPassword, final Callable closure) throws T, IOException, InterruptedException {
- return _execute(listener,rootUsername,rootPassword,new Actor() {
- public V actHere() throws T {
- return closure.call();
- }
-
- public V actThere(Channel ch) throws T, IOException, InterruptedException {
- return ch.call(closure);
- }
- });
- }
-
/**
- * Executes the closure asynchronously.
+ * Returns a {@link VirtualChannel} that's connected to the priviledge-escalated environment.
+ *
+ * @return
+ * Never null. This may represent a channel to a separate JVM, or just {@link LocalChannel}.
+ * Close this channel and the SU environment will be shut down.
*/
- public static Future executeAsync(TaskListener listener, String rootUsername, String rootPassword, final Callable closure) throws IOException, InterruptedException {
- return _execute(listener,rootUsername,rootPassword,new Actor,IOException>() {
- public Future actHere() {
- return Computer.threadPoolForRemoting.submit(new java.util.concurrent.Callable() {
- public V call() throws Exception {
- try {
- return closure.call();
- } catch (Exception e) {
- throw e;
- } catch (Error e) {
- throw e;
- } catch (Throwable t) {
- throw new Error(t);
- }
- }
- });
- }
-
- public Future actThere(Channel ch) throws IOException, InterruptedException {
- return ch.callAsync(closure);
- }
- });
- }
-
- private static V _execute(final TaskListener listener, final String rootUsername, final String rootPassword, Actor closure) throws T, IOException, InterruptedException {
+ public static VirtualChannel start(final TaskListener listener, final String rootUsername, final String rootPassword) throws IOException, InterruptedException {
if(File.pathSeparatorChar==';') // on Windows
- return closure.actHere(); // TODO: perhaps use RunAs to run as an Administrator?
-
+ return newLocalChannel(); // TODO: perhaps use RunAs to run as an Administrator?
+
String os = Util.fixNull(System.getProperty("os.name"));
if(os.equals("Linux"))
return new UnixSu() {
@@ -127,7 +90,7 @@ public abstract class SU {
ps.println(rootPassword);
return p;
}
- }.execute(closure,listener, rootPassword);
+ }.start(listener,rootPassword);
if(os.equals("SunOS"))
return new UnixSu() {
@@ -140,12 +103,29 @@ public abstract class SU {
ProcessBuilder pb = new ProcessBuilder(args.prepend(sudoExe()).toCommandArray());
return EmbeddedSu.startWithSu(rootUsername, rootPassword, pb);
}
- }.execute(closure,listener, rootPassword);
+ }.start(listener,rootPassword);
// TODO: Mac?
-
+
// unsupported platform, take a chance
- return closure.actHere();
+ return newLocalChannel();
+ }
+
+ private static LocalChannel newLocalChannel() {
+ return new LocalChannel(Computer.threadPoolForRemoting);
+ }
+
+ /**
+ * Starts a new priviledge-escalated environment, execute a closure, and shut it down.
+ */
+ public static V execute(TaskListener listener, String rootUsername, String rootPassword, final Callable closure) throws T, IOException, InterruptedException {
+ VirtualChannel ch = start(listener, rootUsername, rootPassword);
+ try {
+ return ch.call(closure);
+ } finally {
+ ch.close();
+ ch.join(3000); // give some time for orderly shutdown, but don't block forever.
+ }
}
private static abstract class UnixSu {
@@ -154,50 +134,31 @@ public abstract class SU {
protected abstract Process sudoWithPass(ArgumentListBuilder args) throws IOException;
-
- V execute(Actor task, TaskListener listener, String rootPassword) throws T, IOException, InterruptedException {
+ VirtualChannel start(TaskListener listener, String rootPassword) throws IOException, InterruptedException {
final int uid = LIBC.geteuid();
if(uid==0) // already running as root
- return task.actHere();
+ return newLocalChannel();
String javaExe = System.getProperty("java.home") + "/bin/java";
File slaveJar = Which.jarFile(Launcher.class);
- // otherwise first attempt pfexec, as that doesn't require password
- Channel channel;
- Process proc=null;
-
ArgumentListBuilder args = new ArgumentListBuilder().add(javaExe);
if(slaveJar.isFile())
args.add("-jar").add(slaveJar);
else // in production code this never happens, but during debugging this is convenientud
args.add("-cp").add(slaveJar).add(hudson.remoting.Launcher.class.getName());
- StreamCopyThread thread=null;
if(rootPassword==null) {
// try sudo, in the hope that the user has the permission to do so without password
- channel = new LocalLauncher(listener).launchChannel(
+ return new LocalLauncher(listener).launchChannel(
args.prepend(sudoExe()).toCommandArray(),
listener.getLogger(), null, Collections.emptyMap());
} else {
// try sudo with the given password. Also run in pfexec so that we can elevate the privileges
- proc = sudoWithPass(args);
- channel = new Channel(args.toStringWithQuote(), Computer.threadPoolForRemoting,
- proc.getInputStream(), proc.getOutputStream(), listener.getLogger());
- thread = new StreamCopyThread(args.toStringWithQuote() + " stderr", proc.getErrorStream(), listener.getLogger());
- thread.start();
- }
-
- try {
- return task.actThere(channel);
- } finally {
- channel.close();
- channel.join(3000); // give some time for orderly shutdown, but don't block forever.
- if(proc!=null)
- proc.destroy();
- if(thread!=null)
- thread.join();
+ Process proc = sudoWithPass(args);
+ return Channels.forProcess(args.toStringWithQuote(), Computer.threadPoolForRemoting, proc,
+ listener.getLogger() );
}
}
}
diff --git a/core/src/main/java/hudson/slaves/Channels.java b/core/src/main/java/hudson/slaves/Channels.java
index 638c4d3eba2bf734621934b681359cf910209d64..7025e8e170ff1ff86cbdc47361065570c312406b 100644
--- a/core/src/main/java/hudson/slaves/Channels.java
+++ b/core/src/main/java/hudson/slaves/Channels.java
@@ -32,6 +32,7 @@ import hudson.remoting.Channel;
import hudson.remoting.Which;
import hudson.util.ArgumentListBuilder;
import hudson.util.ClasspathBuilder;
+import hudson.util.StreamCopyThread;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -53,12 +54,20 @@ import java.util.logging.Logger;
* @author Kohsuke Kawaguchi
*/
public class Channels {
+ /**
+ * @deprecated
+ * Use {@link #forProcess(String, ExecutorService, InputStream, OutputStream, OutputStream, Proc)}
+ */
+ public static Channel forProcess(String name, ExecutorService execService, InputStream in, OutputStream out, Proc proc) throws IOException {
+ return forProcess(name,execService,in,out,null,proc);
+ }
+
/**
* Creates a channel that wraps a remote process, so that when we shut down the connection
* we kill the process.
*/
- public static Channel forProcess(String name, ExecutorService execService, InputStream in, OutputStream out, final Proc proc) throws IOException {
- return new Channel(name, execService, in, out) {
+ public static Channel forProcess(String name, ExecutorService execService, InputStream in, OutputStream out, OutputStream header, final Proc proc) throws IOException {
+ return new Channel(name, execService, in, out, header) {
/**
* Kill the process when the channel is severed.
*/
@@ -88,6 +97,34 @@ public class Channels {
};
}
+ public static Channel forProcess(String name, ExecutorService execService, final Process proc, OutputStream header) throws IOException {
+ final Thread thread = new StreamCopyThread(name + " stderr", proc.getErrorStream(), header);
+ thread.start();
+
+ return new Channel(name, execService, proc.getInputStream(), proc.getOutputStream(), header) {
+ /**
+ * Kill the process when the channel is severed.
+ */
+ protected synchronized void terminate(IOException e) {
+ super.terminate(e);
+ proc.destroy();
+ // the stderr copier should exit by itself
+ }
+
+ public synchronized void close() throws IOException {
+ super.close();
+ // wait for Maven to complete
+ try {
+ proc.waitFor();
+ thread.join();
+ } catch (InterruptedException e) {
+ // process the interrupt later
+ Thread.currentThread().interrupt();
+ }
+ }
+ };
+ }
+
/**
* Launches a new JVM with the given classpath and system properties, establish a communication channel,
* and return a {@link Channel} to it.
diff --git a/remoting/src/main/java/hudson/remoting/LocalChannel.java b/remoting/src/main/java/hudson/remoting/LocalChannel.java
index c9b285355890ade3f5f9937b47ff7dca56ffa2e4..251626c1b928cba5bb91c9023835ee3d0e3e2478 100644
--- a/remoting/src/main/java/hudson/remoting/LocalChannel.java
+++ b/remoting/src/main/java/hudson/remoting/LocalChannel.java
@@ -87,6 +87,14 @@ public class LocalChannel implements VirtualChannel {
// noop
}
+ public void join() throws InterruptedException {
+ // noop
+ }
+
+ public void join(long timeout) throws InterruptedException {
+ // noop
+ }
+
public T export(Class intf, T instance) {
return instance;
}
diff --git a/remoting/src/main/java/hudson/remoting/VirtualChannel.java b/remoting/src/main/java/hudson/remoting/VirtualChannel.java
index ee2a395422b4e6cc4a29bf16d6d2823703bc6673..9d035de86e724b9f2a2a53e9b40c5bddd734b4e9 100644
--- a/remoting/src/main/java/hudson/remoting/VirtualChannel.java
+++ b/remoting/src/main/java/hudson/remoting/VirtualChannel.java
@@ -68,6 +68,26 @@ public interface VirtualChannel {
*/
void close() throws IOException;
+ /**
+ * Waits for this {@link Channel} to be closed down.
+ *
+ * The close-down of a {@link Channel} might be initiated locally or remotely.
+ *
+ * @throws InterruptedException
+ * If the current thread is interrupted while waiting for the completion.
+ * @since 1.300
+ */
+ public void join() throws InterruptedException;
+
+ /**
+ * Waits for this {@link Channel} to be closed down, but only up the given milliseconds.
+ *
+ * @throws InterruptedException
+ * If the current thread is interrupted while waiting for the completion.
+ * @since 1.300
+ */
+ public void join(long timeout) throws InterruptedException;
+
/**
* Exports an object for remoting to the other {@link Channel}
* by creating a remotable proxy.