diff --git a/pom.xml b/pom.xml
index 111cfed..42bb866 100644
--- a/pom.xml
+++ b/pom.xml
@@ -121,6 +121,7 @@
         <felix.framework.version>3.0.2</felix.framework.version>
         <felix.gogo.version>0.4.0</felix.gogo.version>
         <felix.osgi.version>1.4.0</felix.osgi.version>
+        <osgi.version>4.2.0</osgi.version>
         <felix.plugin.version>2.1.0</felix.plugin.version>
         <felix.utils.version>1.0.0</felix.utils.version>
         <felix.webconsole.version>3.1.2</felix.webconsole.version>
@@ -413,6 +414,16 @@
                 </exclusions>
             </dependency>
             <dependency>
+                <groupId>org.osgi</groupId>
+                <artifactId>org.osgi.core</artifactId>
+                <version>${osgi.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.osgi</groupId>
+                <artifactId>org.osgi.compendium</artifactId>
+                <version>${osgi.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>org.osgi.compendium</artifactId>
                 <version>${felix.compendium.version}</version>
diff --git a/shell/commands/pom.xml b/shell/commands/pom.xml
index 36281e5..69f2b14 100644
--- a/shell/commands/pom.xml
+++ b/shell/commands/pom.xml
@@ -50,7 +50,16 @@
             <artifactId>org.apache.felix.gogo.runtime</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.felix</groupId>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
             <scope>provided</scope>
         </dependency>
@@ -85,6 +94,9 @@
                             org.apache.karaf.shell.console,
                             *
                         </Import-Package>
+                        <Private-Package>
+                            org.apache.karaf.util.process
+                        </Private-Package>
                         <_versionpolicy>${bnd.version.policy}</_versionpolicy>
                     </instructions>
                 </configuration>
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/ExecuteAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/ExecuteAction.java
index 8e4bd1c..282c214 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/ExecuteAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/ExecuteAction.java
@@ -20,8 +20,8 @@ import java.util.List;
 
 import org.apache.felix.gogo.commands.Argument;
 import org.apache.felix.gogo.commands.Command;
-import org.apache.karaf.shell.commands.utils.PumpStreamHandler;
 import org.apache.karaf.shell.console.AbstractAction;
+import org.apache.karaf.util.process.PumpStreamHandler;
 
 /**
  * Execute system processes.
@@ -37,7 +37,7 @@ public class ExecuteAction extends AbstractAction {
     protected Object doExecute() throws Exception {
         ProcessBuilder builder = new ProcessBuilder(args);
 
-        PumpStreamHandler handler = new PumpStreamHandler(System.in, System.out, System.err);
+        PumpStreamHandler handler = new PumpStreamHandler(System.in, System.out, System.err, "Command" + args.toString());
 
         log.info("Executing: {}", builder.command());
         Process p = builder.start();
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/utils/PumpStreamHandler.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/utils/PumpStreamHandler.java
deleted file mode 100644
index d0c3aff..0000000
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/utils/PumpStreamHandler.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.karaf.shell.commands.utils;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-//
-// Based on Apache Ant 1.6.5
-//
-
-/**
- * Copies standard output and error of children streams to standard output and error of the parent.
- *
- * @version $Rev: 705608 $ $Date: 2008-10-17 15:28:45 +0200 (Fri, 17 Oct 2008) $
- */
-public class PumpStreamHandler
-{
-    private InputStream in;
-
-    private OutputStream out;
-
-    private OutputStream err;
-
-    private Thread outputThread;
-
-    private Thread errorThread;
-
-    private StreamPumper inputPump;
-
-    //
-    // NOTE: May want to use a ThreadPool here, 3 threads per/pair seems kinda expensive :-(
-    //
-
-    public PumpStreamHandler(final InputStream in, final OutputStream out, final OutputStream err) {
-        assert in != null;
-        assert out != null;
-        assert err != null;
-
-        this.in = in;
-        this.out = out;
-        this.err = err;
-    }
-
-    public PumpStreamHandler(final OutputStream out, final OutputStream err) {
-        this(null, out, err);
-    }
-
-    public PumpStreamHandler(final OutputStream outAndErr) {
-        this(outAndErr, outAndErr);
-    }
-
-    /**
-     * Set the input stream from which to read the standard output of the child.
-     */
-    public void setChildOutputStream(final InputStream in) {
-        assert in != null;
-
-        createChildOutputPump(in, out);
-    }
-
-    /**
-     * Set the input stream from which to read the standard error of the child.
-     */
-    public void setChildErrorStream(final InputStream in) {
-        assert in != null;
-
-        if (err != null) {
-            createChildErrorPump(in, err);
-        }
-    }
-
-    /**
-     * Set the output stream by means of which input can be sent to the child.
-     */
-    public void setChildInputStream(final OutputStream out) {
-        assert out != null;
-
-        if (in != null) {
-            inputPump = createInputPump(in, out, true);
-        }
-        else {
-            try {
-                out.close();
-            } catch (IOException e) { }
-        }
-    }
-
-    /**
-     * Attach to a child streams from the given process.
-     *
-     * @param p     The process to attach to.
-     */
-    public void attach(final Process p) {
-        assert p != null;
-
-        setChildInputStream(p.getOutputStream());
-        setChildOutputStream(p.getInputStream());
-        setChildErrorStream(p.getErrorStream());
-    }
-    /**
-     * Start pumping the streams.
-     */
-    public void start() {
-        if (outputThread != null) {
-            outputThread.start();
-        }
-
-        if (errorThread != null) {
-            errorThread.start();
-        }
-
-        if (inputPump != null) {
-            Thread inputThread = new Thread(inputPump);
-            inputThread.setDaemon(true);
-            inputThread.start();
-        }
-    }
-
-    /**
-     * Stop pumping the streams.
-     */
-    public void stop() {
-        if (outputThread != null) {
-            try {
-                outputThread.join();
-            }
-            catch (InterruptedException e) {
-                // ignore
-            }
-        }
-
-        if (errorThread != null) {
-            try {
-                errorThread.join();
-            }
-            catch (InterruptedException e) {
-                // ignore
-            }
-        }
-
-        if (inputPump != null) {
-            inputPump.stop();
-        }
-
-        try {
-            err.flush();
-        } catch (IOException e) { }
-        try {
-            out.flush();
-        } catch (IOException e) { }
-    }
-
-    /**
-     * Create the pump to handle child output.
-     */
-    protected void createChildOutputPump(final InputStream in, final OutputStream out) {
-        assert in != null;
-        assert out != null;
-
-        outputThread = createPump(in, out);
-    }
-
-    /**
-     * Create the pump to handle error output.
-     */
-    protected void createChildErrorPump(final InputStream in, final OutputStream out) {
-        assert in != null;
-        assert out != null;
-
-        errorThread = createPump(in, out);
-    }
-
-    /**
-     * Creates a stream pumper to copy the given input stream to the given output stream.
-     */
-    protected Thread createPump(final InputStream in, final OutputStream out) {
-        assert in != null;
-        assert out != null;
-
-        return createPump(in, out, false);
-    }
-
-    /**
-     * Creates a stream pumper to copy the given input stream to the
-     * given output stream.
-     *
-     * @param in                    The input stream to copy from.
-     * @param out                   The output stream to copy to.
-     * @param closeWhenExhausted    If true close the inputstream.
-     * @return                      A thread object that does the pumping.
-     */
-    protected Thread createPump(final InputStream in, final OutputStream out, final boolean closeWhenExhausted) {
-        assert in != null;
-        assert out != null;
-
-        final Thread result = new Thread(new StreamPumper(in, out, closeWhenExhausted));
-        result.setDaemon(true);
-        return result;
-    }
-
-    /**
-     * Creates a stream pumper to copy the given input stream to the
-     * given output stream. Used for standard input.
-     */
-    protected StreamPumper createInputPump(final InputStream in, final OutputStream out, final boolean closeWhenExhausted) {
-        assert in != null;
-        assert out != null;
-
-        StreamPumper pumper = new StreamPumper(in, out, closeWhenExhausted);
-        pumper.setAutoflush(true);
-        return pumper;
-    }
-}
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/utils/StreamPumper.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/utils/StreamPumper.java
deleted file mode 100644
index 3deb903..0000000
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/utils/StreamPumper.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.karaf.shell.commands.utils;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-//
-// Based on Apache Ant 1.6.5
-//
-
-/**
- * Copies all data from an input stream to an output stream.
- *
- * @version $Rev: 705608 $ $Date: 2008-10-17 15:28:45 +0200 (Fri, 17 Oct 2008) $
- */
-public class StreamPumper
-    implements Runnable
-{
-    private InputStream in;
-
-    private OutputStream out;
-
-    private volatile boolean finish;
-
-    private volatile boolean finished;
-
-    private boolean closeWhenExhausted;
-
-    private boolean autoflush;
-
-    private Exception exception;
-
-    private int bufferSize = 128;
-
-    private boolean started;
-
-    /**
-     * Create a new stream pumper.
-     *
-     * @param in                    Input stream to read data from
-     * @param out                   Output stream to write data to.
-     * @param closeWhenExhausted    If true, the output stream will be closed when
-     *                              the input is exhausted.
-     */
-    public StreamPumper(final InputStream in, final OutputStream out, final boolean closeWhenExhausted) {
-        assert in != null;
-        assert out != null;
-
-        this.in = in;
-        this.out = out;
-        this.closeWhenExhausted = closeWhenExhausted;
-    }
-
-    /**
-     * Create a new stream pumper.
-     *
-     * @param in    Input stream to read data from
-     * @param out   Output stream to write data to.
-     */
-    public StreamPumper(final InputStream in, final OutputStream out) {
-        this(in, out, false);
-    }
-
-    /**
-     * Set whether data should be flushed through to the output stream.
-     *
-     * @param autoflush     If true, push through data; if false, let it be buffered
-     */
-    public void setAutoflush(boolean autoflush) {
-        this.autoflush = autoflush;
-    }
-
-    /**
-     * Copies data from the input stream to the output stream.
-     *
-     * Terminates as soon as the input stream is closed or an error occurs.
-     */
-    public void run() {
-        synchronized (this) {
-            started = true;
-        }
-        finished = false;
-        finish = false;
-
-        final byte[] buf = new byte[bufferSize];
-
-        int length;
-        try {
-            do {
-                while (in.available() > 0 && !finish) {
-                    length = in.read(buf);
-                    if (length < 1 ) {
-                        break;
-                    }
-                    out.write(buf, 0, length);
-                    if (autoflush) {
-                        out.flush();
-                    }
-                }
-                out.flush();
-                Thread.sleep(200);  // Pause to avoid tight loop if external proc is slow
-            } while (!finish && closeWhenExhausted);
-        }
-        catch (Exception e) {
-            synchronized (this) {
-                exception = e;
-            }
-        }
-        finally {
-            if (closeWhenExhausted) {
-                try {
-                    out.close();
-                } catch (IOException e) { }
-            }
-            finished = true;
-
-            synchronized (this) {
-                notifyAll();
-            }
-        }
-    }
-
-    /**
-     * Tells whether the end of the stream has been reached.
-     *
-     * @return true     If the stream has been exhausted.
-     */
-    public boolean isFinished() {
-        return finished;
-    }
-
-    /**
-     * This method blocks until the stream pumper finishes.
-     *
-     * @see #isFinished()
-     */
-    public synchronized void waitFor() throws InterruptedException {
-        while (!isFinished()) {
-            wait();
-        }
-    }
-
-    /**
-     * Set the size in bytes of the read buffer.
-     *
-     * @param bufferSize the buffer size to use.
-     * @throws IllegalStateException if the StreamPumper is already running.
-     */
-    public synchronized void setBufferSize(final int bufferSize) {
-        if (started) {
-            throw new IllegalStateException("Cannot set buffer size on a running StreamPumper");
-        }
-
-        this.bufferSize = bufferSize;
-    }
-
-    /**
-     * Get the size in bytes of the read buffer.
-     *
-     * @return The size of the read buffer.
-     */
-    public synchronized int getBufferSize() {
-        return bufferSize;
-    }
-
-    /**
-     * Get the exception encountered, if any.
-     *
-     * @return The Exception encountered; or null if there was none.
-     */
-    public synchronized Exception getException() {
-        return exception;
-    }
-
-    /**
-     * Stop the pumper as soon as possible.
-     *
-     * Note that it may continue to block on the input stream
-     * but it will really stop the thread as soon as it gets EOF
-     * or any byte, and it will be marked as finished.
-     */
-    public synchronized void stop() {
-        finish = true;
-
-        notifyAll();
-    }
-}
diff --git a/shell/wrapper/pom.xml b/shell/wrapper/pom.xml
index f272a55..a8f1871 100644
--- a/shell/wrapper/pom.xml
+++ b/shell/wrapper/pom.xml
@@ -50,6 +50,10 @@
             <artifactId>org.apache.karaf.main</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
             <groupId>tanukisoft</groupId>
             <artifactId>wrapper</artifactId>
         </dependency>
@@ -100,7 +104,9 @@
                             org.apache.karaf.shell.console,
                             *
                         </Import-Package>
-                        <Private-Package>!*</Private-Package>
+                        <Private-Package>
+                            org.apache.karaf.util.process
+                        </Private-Package>
                         <_versionpolicy>${bnd.version.policy}</_versionpolicy>
                     </instructions>
                 </configuration>
diff --git a/shell/wrapper/src/main/java/org/apache/karaf/shell/wrapper/PumpStreamHandler.java b/shell/wrapper/src/main/java/org/apache/karaf/shell/wrapper/PumpStreamHandler.java
index cc611a7..1956192 100644
--- a/shell/wrapper/src/main/java/org/apache/karaf/shell/wrapper/PumpStreamHandler.java
+++ b/shell/wrapper/src/main/java/org/apache/karaf/shell/wrapper/PumpStreamHandler.java
@@ -34,15 +34,17 @@ import java.io.IOException;
  */
 public class PumpStreamHandler
 {
-    private InputStream in;
+    private final InputStream in;
 
-    private OutputStream out;
+    private final OutputStream out;
 
-    private OutputStream err;
+    private final OutputStream err;
 
-    private Thread outputThread;
+    private final String name;
 
-    private Thread errorThread;
+    private StreamPumper outputPump;
+
+    private StreamPumper errorPump;
 
     private StreamPumper inputPump;
 
@@ -50,14 +52,20 @@ public class PumpStreamHandler
     // NOTE: May want to use a ThreadPool here, 3 threads per/pair seems kinda expensive :-(
     //
 
-    public PumpStreamHandler(final InputStream in, final OutputStream out, final OutputStream err) {
+    public PumpStreamHandler(final InputStream in, final OutputStream out, final OutputStream err, String name) {
         assert in != null;
         assert out != null;
         assert err != null;
+        assert name != null;
 
         this.in = in;
         this.out = out;
         this.err = err;
+        this.name = name;
+    }
+
+    public PumpStreamHandler(final InputStream in, final OutputStream out, final OutputStream err) {
+        this(in, out, err, "<unknown>");
     }
 
     public PumpStreamHandler(final OutputStream out, final OutputStream err) {
@@ -120,18 +128,25 @@ public class PumpStreamHandler
      * Start pumping the streams.
      */
     public void start() {
-        if (outputThread != null) {
-            outputThread.start();
+        if (outputPump != null) {
+            Thread thread = new Thread(outputPump);
+            thread.setDaemon(true);
+            thread.setName("Output pump for " + this.name);
+            thread.start();
         }
 
-        if (errorThread != null) {
-            errorThread.start();
+        if (errorPump != null) {
+            Thread thread = new Thread(errorPump);
+            thread.setDaemon(true);
+            thread.setName("Error pump for " + this.name);
+            thread.start();
         }
 
         if (inputPump != null) {
-            Thread inputThread = new Thread(inputPump);
-            inputThread.setDaemon(true);
-            inputThread.start();
+            Thread thread = new Thread(inputPump);
+            thread.setDaemon(true);
+            thread.setName("Input pump for " + this.name);
+            thread.start();
         }
     }
 
@@ -139,18 +154,20 @@ public class PumpStreamHandler
      * Stop pumping the streams.
      */
     public void stop() {
-        if (outputThread != null) {
+        if (outputPump != null) {
             try {
-                outputThread.join();
+                outputPump.stop();
+                outputPump.waitFor();
             }
             catch (InterruptedException e) {
                 // ignore
             }
         }
 
-        if (errorThread != null) {
+        if (errorPump != null) {
             try {
-                errorThread.join();
+                errorPump.stop();
+                errorPump.waitFor();
             }
             catch (InterruptedException e) {
                 // ignore
@@ -176,7 +193,7 @@ public class PumpStreamHandler
         assert in != null;
         assert out != null;
 
-        outputThread = createPump(in, out);
+        outputPump = createPump(in, out);
     }
 
     /**
@@ -186,13 +203,13 @@ public class PumpStreamHandler
         assert in != null;
         assert out != null;
 
-        errorThread = createPump(in, out);
+        errorPump = createPump(in, out);
     }
 
     /**
      * Creates a stream pumper to copy the given input stream to the given output stream.
      */
-    protected Thread createPump(final InputStream in, final OutputStream out) {
+    protected StreamPumper createPump(final InputStream in, final OutputStream out) {
         assert in != null;
         assert out != null;
 
@@ -208,13 +225,12 @@ public class PumpStreamHandler
      * @param closeWhenExhausted    If true close the inputstream.
      * @return                      A thread object that does the pumping.
      */
-    protected Thread createPump(final InputStream in, final OutputStream out, final boolean closeWhenExhausted) {
+    protected StreamPumper createPump(final InputStream in, final OutputStream out, final boolean closeWhenExhausted) {
         assert in != null;
         assert out != null;
 
-        final Thread result = new Thread(new StreamPumper(in, out, closeWhenExhausted));
-        result.setDaemon(true);
-        return result;
+        StreamPumper pumper = new StreamPumper(in, out, closeWhenExhausted);
+        return pumper;
     }
 
     /**
diff --git a/util/src/main/java/org/apache/karaf/util/process/PumpStreamHandler.java b/util/src/main/java/org/apache/karaf/util/process/PumpStreamHandler.java
new file mode 100644
index 0000000..df54ddc
--- /dev/null
+++ b/util/src/main/java/org/apache/karaf/util/process/PumpStreamHandler.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.karaf.util.process;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+//
+// Based on Apache Ant 1.6.5
+//
+
+/**
+ * Copies standard output and error of children streams to standard output and error of the parent.
+ *
+ * @version $Rev: 705608 $ $Date: 2008-10-17 15:28:45 +0200 (Fri, 17 Oct 2008) $
+ */
+public class PumpStreamHandler
+{
+    private final InputStream in;
+
+    private final OutputStream out;
+
+    private final OutputStream err;
+
+    private final String name;
+
+    private StreamPumper outputPump;
+
+    private StreamPumper errorPump;
+
+    private StreamPumper inputPump;
+
+    //
+    // NOTE: May want to use a ThreadPool here, 3 threads per/pair seems kinda expensive :-(
+    //
+
+    public PumpStreamHandler(final InputStream in, final OutputStream out, final OutputStream err, String name) {
+        assert in != null;
+        assert out != null;
+        assert err != null;
+        assert name != null;
+
+        this.in = in;
+        this.out = out;
+        this.err = err;
+        this.name = name;
+    }
+
+    public PumpStreamHandler(final InputStream in, final OutputStream out, final OutputStream err) {
+        this(in, out, err, "<unknown>");
+    }
+
+    public PumpStreamHandler(final OutputStream out, final OutputStream err) {
+        this(null, out, err);
+    }
+
+    public PumpStreamHandler(final OutputStream outAndErr) {
+        this(outAndErr, outAndErr);
+    }
+
+    /**
+     * Set the input stream from which to read the standard output of the child.
+     */
+    public void setChildOutputStream(final InputStream in) {
+        assert in != null;
+
+        createChildOutputPump(in, out);
+    }
+
+    /**
+     * Set the input stream from which to read the standard error of the child.
+     */
+    public void setChildErrorStream(final InputStream in) {
+        assert in != null;
+
+        if (err != null) {
+            createChildErrorPump(in, err);
+        }
+    }
+
+    /**
+     * Set the output stream by means of which input can be sent to the child.
+     */
+    public void setChildInputStream(final OutputStream out) {
+        assert out != null;
+
+        if (in != null) {
+            inputPump = createInputPump(in, out, true);
+        }
+        else {
+            try {
+                out.close();
+            } catch (IOException e) { }
+        }
+    }
+
+    /**
+     * Attach to a child streams from the given process.
+     *
+     * @param p     The process to attach to.
+     */
+    public void attach(final Process p) {
+        assert p != null;
+
+        setChildInputStream(p.getOutputStream());
+        setChildOutputStream(p.getInputStream());
+        setChildErrorStream(p.getErrorStream());
+    }
+    /**
+     * Start pumping the streams.
+     */
+    public void start() {
+        if (outputPump != null) {
+            Thread thread = new Thread(outputPump);
+            thread.setDaemon(true);
+            thread.setName("Output pump for " + this.name);
+            thread.start();
+        }
+
+        if (errorPump != null) {
+            Thread thread = new Thread(errorPump);
+            thread.setDaemon(true);
+            thread.setName("Error pump for " + this.name);
+            thread.start();
+        }
+
+        if (inputPump != null) {
+            Thread thread = new Thread(inputPump);
+            thread.setDaemon(true);
+            thread.setName("Input pump for " + this.name);
+            thread.start();
+        }
+    }
+
+    /**
+     * Stop pumping the streams.
+     */
+    public void stop() {
+        if (outputPump != null) {
+            try {
+                outputPump.stop();
+                outputPump.waitFor();
+            }
+            catch (InterruptedException e) {
+                // ignore
+            }
+        }
+
+        if (errorPump != null) {
+            try {
+                errorPump.stop();
+                errorPump.waitFor();
+            }
+            catch (InterruptedException e) {
+                // ignore
+            }
+        }
+
+        if (inputPump != null) {
+            inputPump.stop();
+        }
+
+        try {
+            err.flush();
+        } catch (IOException e) { }
+        try {
+            out.flush();
+        } catch (IOException e) { }
+    }
+
+    /**
+     * Create the pump to handle child output.
+     */
+    protected void createChildOutputPump(final InputStream in, final OutputStream out) {
+        assert in != null;
+        assert out != null;
+
+        outputPump = createPump(in, out);
+    }
+
+    /**
+     * Create the pump to handle error output.
+     */
+    protected void createChildErrorPump(final InputStream in, final OutputStream out) {
+        assert in != null;
+        assert out != null;
+
+        errorPump = createPump(in, out);
+    }
+
+    /**
+     * Creates a stream pumper to copy the given input stream to the given output stream.
+     */
+    protected StreamPumper createPump(final InputStream in, final OutputStream out) {
+        assert in != null;
+        assert out != null;
+
+        return createPump(in, out, false);
+    }
+
+    /**
+     * Creates a stream pumper to copy the given input stream to the
+     * given output stream.
+     *
+     * @param in                    The input stream to copy from.
+     * @param out                   The output stream to copy to.
+     * @param closeWhenExhausted    If true close the inputstream.
+     * @return                      A thread object that does the pumping.
+     */
+    protected StreamPumper createPump(final InputStream in, final OutputStream out, final boolean closeWhenExhausted) {
+        assert in != null;
+        assert out != null;
+
+        StreamPumper pumper = new StreamPumper(in, out, closeWhenExhausted);
+        return pumper;
+    }
+
+    /**
+     * Creates a stream pumper to copy the given input stream to the
+     * given output stream. Used for standard input.
+     */
+    protected StreamPumper createInputPump(final InputStream in, final OutputStream out, final boolean closeWhenExhausted) {
+        assert in != null;
+        assert out != null;
+
+        StreamPumper pumper = new StreamPumper(in, out, closeWhenExhausted);
+        pumper.setAutoflush(true);
+        return pumper;
+    }
+}
diff --git a/util/src/main/java/org/apache/karaf/util/process/StreamPumper.java b/util/src/main/java/org/apache/karaf/util/process/StreamPumper.java
new file mode 100644
index 0000000..b0b35a5
--- /dev/null
+++ b/util/src/main/java/org/apache/karaf/util/process/StreamPumper.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.karaf.util.process;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+//
+// Based on Apache Ant 1.6.5
+//
+
+/**
+ * Copies all data from an input stream to an output stream.
+ *
+ * @version $Rev: 705608 $ $Date: 2008-10-17 15:28:45 +0200 (Fri, 17 Oct 2008) $
+ */
+public class StreamPumper
+    implements Runnable
+{
+    private InputStream in;
+
+    private OutputStream out;
+
+    private volatile boolean finish;
+
+    private volatile boolean finished;
+
+    private boolean closeWhenExhausted;
+
+    private boolean autoflush;
+
+    private Throwable exception;
+
+    private int bufferSize = 128;
+
+    private boolean started;
+
+    /**
+     * Create a new stream pumper.
+     *
+     * @param in                    Input stream to read data from
+     * @param out                   Output stream to write data to.
+     * @param closeWhenExhausted    If true, the output stream will be closed when
+     *                              the input is exhausted.
+     */
+    public StreamPumper(final InputStream in, final OutputStream out, final boolean closeWhenExhausted) {
+        assert in != null;
+        assert out != null;
+
+        this.in = in;
+        this.out = out;
+        this.closeWhenExhausted = closeWhenExhausted;
+    }
+
+    /**
+     * Create a new stream pumper.
+     *
+     * @param in    Input stream to read data from
+     * @param out   Output stream to write data to.
+     */
+    public StreamPumper(final InputStream in, final OutputStream out) {
+        this(in, out, false);
+    }
+
+    /**
+     * Set whether data should be flushed through to the output stream.
+     *
+     * @param autoflush     If true, push through data; if false, let it be buffered
+     */
+    public void setAutoflush(boolean autoflush) {
+        this.autoflush = autoflush;
+    }
+
+    /**
+     * Copies data from the input stream to the output stream.
+     *
+     * Terminates as soon as the input stream is closed or an error occurs.
+     */
+    public void run() {
+        synchronized (this) {
+            started = true;
+        }
+        finished = false;
+        finish = false;
+
+        final byte[] buf = new byte[bufferSize];
+
+        int length;
+        try {
+            do {
+                while (in.available() > 0) {
+                    length = in.read(buf);
+                    if (length < 1 ) {
+                        break;
+                    }
+                    out.write(buf, 0, length);
+                    if (autoflush) {
+                        out.flush();
+                    }
+                }
+                out.flush();
+                Thread.sleep(200);  // Pause to avoid tight loop if external proc is slow
+            } while (!finish);
+        }
+        catch (Throwable t) {
+            synchronized (this) {
+                exception = t;
+            }
+        }
+        finally {
+            if (closeWhenExhausted) {
+                try {
+                    out.close();
+                } catch (IOException e) { }
+            }
+            finished = true;
+
+            synchronized (this) {
+                notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Tells whether the end of the stream has been reached.
+     *
+     * @return true     If the stream has been exhausted.
+     */
+    public boolean isFinished() {
+        return finished;
+    }
+
+    /**
+     * This method blocks until the stream pumper finishes.
+     *
+     * @see #isFinished()
+     */
+    public synchronized void waitFor() throws InterruptedException {
+        while (!isFinished()) {
+            wait();
+        }
+    }
+
+    /**
+     * Set the size in bytes of the read buffer.
+     *
+     * @param bufferSize the buffer size to use.
+     * @throws IllegalStateException if the StreamPumper is already running.
+     */
+    public synchronized void setBufferSize(final int bufferSize) {
+        if (started) {
+            throw new IllegalStateException("Cannot set buffer size on a running StreamPumper");
+        }
+
+        this.bufferSize = bufferSize;
+    }
+
+    /**
+     * Get the size in bytes of the read buffer.
+     *
+     * @return The size of the read buffer.
+     */
+    public synchronized int getBufferSize() {
+        return bufferSize;
+    }
+
+    /**
+     * Get the exception encountered, if any.
+     *
+     * @return The Exception encountered; or null if there was none.
+     */
+    public synchronized Throwable getException() {
+        return exception;
+    }
+
+    /**
+     * Stop the pumper as soon as possible.
+     *
+     * Note that it may continue to block on the input stream
+     * but it will really stop the thread as soon as it gets EOF
+     * or any byte, and it will be marked as finished.
+     */
+    public synchronized void stop() {
+        finish = true;
+
+        notifyAll();
+    }
+}
