You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2015/02/10 11:11:21 UTC

[1/2] mina-sshd git commit: [SSHD-395] Use an ExecutorService to run ScpCommand(s)

Repository: mina-sshd
Updated Branches:
  refs/heads/master faacb6c35 -> 6af9457d3


[SSHD-395] Use an ExecutorService to run ScpCommand(s)

Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/d6ab2b83
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/d6ab2b83
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/d6ab2b83

Branch: refs/heads/master
Commit: d6ab2b83b419a3af3eeeb19e5950d7f9cedb660b
Parents: faacb6c
Author: Guillaume Nodet <gn...@apache.org>
Authored: Tue Feb 10 10:38:13 2015 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Tue Feb 10 10:38:13 2015 +0100

----------------------------------------------------------------------
 .../apache/sshd/server/command/ScpCommand.java  | 70 +++++++++++++--
 .../sshd/server/command/ScpCommandFactory.java  | 90 ++++++++++++++++++--
 2 files changed, 144 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/d6ab2b83/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
index be5fc91..4355be4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
@@ -21,14 +21,15 @@ package org.apache.sshd.server.command;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
 import org.apache.sshd.common.file.FileSystemAware;
 import org.apache.sshd.common.file.FileSystemView;
 import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.util.ThreadUtils;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
@@ -59,9 +60,39 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
     protected OutputStream err;
     protected ExitCallback callback;
     protected IOException error;
+    protected ExecutorService executors;
+    protected boolean shutdownExecutor;
+    protected Future<?> pendingFuture;
 
     public ScpCommand(String command) {
-        this.name = command;
+        this(command, null);
+    }
+
+    public ScpCommand(String command, ExecutorService executorService) {
+        this(command, executorService, false);
+    }
+
+    /**
+     * @param command The command to be executed
+     * @param executorService An {@link ExecutorService} to be used when
+     *        {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc
+     *        single-threaded service is created and used.
+     * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
+     *        will be called when command terminates - unless it is the ad-hoc
+     *        service, which will be shutdown regardless
+     * @see ThreadUtils#newSingleThreadExecutor(String)
+     */
+    public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit) {
+        name = command;
+
+        if ((executors = executorService) == null) {
+            String poolName = command.replace(' ', '_').replace('/', ':');
+            executors = ThreadUtils.newSingleThreadExecutor(poolName);
+            shutdownExecutor = true;    // we always close the ad-hoc executor service
+        } else {
+            shutdownExecutor = shutdownOnExit;
+        }
+
         log.debug("Executing command {}", command);
         String[] args = command.split(" ");
         for (int i = 1; i < args.length; i++) {
@@ -97,7 +128,7 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
             }
         }
         if (!optF && !optT) {
-            error = new IOException("Either -f or -t option should be set");
+            error = new IOException("Either -f or -t option should be set for " + command);
         }
     }
 
@@ -125,10 +156,35 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
         if (error != null) {
             throw error;
         }
-        new Thread(this, "ScpCommand: " + name).start();
+
+        try {
+            pendingFuture = executors.submit(this);
+        } catch (RuntimeException e) {    // e.g., RejectedExecutionException
+            log.error("Failed (" + e.getClass().getSimpleName() + ") to start command=" + name + ": " + e.getMessage(), e);
+            throw new IOException(e);
+        }
     }
 
     public void destroy() {
+        // if thread has not completed, cancel it
+        if ((pendingFuture != null) && (!pendingFuture.isDone())) {
+            boolean result = pendingFuture.cancel(true);
+            // TODO consider waiting some reasonable (?) amount of time for cancellation
+            if (log.isDebugEnabled()) {
+                log.debug("destroy() - cancel pending future=" + result);
+            }
+        }
+
+        pendingFuture = null;
+
+        if ((executors != null) && shutdownExecutor) {
+            Collection<Runnable> runners = executors.shutdownNow();
+            if (log.isDebugEnabled()) {
+                log.debug("destroy() - shutdown executor service - runners count=" + ((runners == null) ? 0 : runners.size()));
+            }
+        }
+
+        executors = null;
     }
 
     public void run() {
@@ -154,7 +210,7 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
             } catch (IOException e2) {
                 // Ignore
             }
-            log.info("Error in scp command", e);
+            log.info("Error in scp command=" + name, e);
         } finally {
             if (callback != null) {
                 callback.onExit(exitValue, exitMessage);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/d6ab2b83/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
index 51eee6c..07ec477 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
@@ -18,8 +18,7 @@
  */
 package org.apache.sshd.server.command;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.concurrent.ExecutorService;
 
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.CommandFactory;
@@ -34,14 +33,85 @@ import org.apache.sshd.server.CommandFactory;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class ScpCommandFactory implements CommandFactory {
+	/**
+	 * Command prefix used to identify SCP commands
+	 */
+    public static final String SCP_COMMAND_PREFIX = "scp";
 
     private CommandFactory delegate;
+    private ExecutorService executors;
+    private boolean shutdownExecutor;
 
     public ScpCommandFactory() {
+        this(null, null);
     }
 
-    public ScpCommandFactory(CommandFactory delegate) {
-        this.delegate = delegate;
+    /**
+     * @param executorService An {@link ExecutorService} to be used when
+     *        starting {@link ScpCommand} execution. If {@code null} an ad-hoc
+     *        single-threaded service is created and used.
+     */
+    public ScpCommandFactory(ExecutorService executorService) {
+        this(null, executorService);
+    }
+
+    /**
+     * @param delegateFactory A {@link CommandFactory} to be used if the
+     *        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+     *        will be thrown when attempting to invoke {@link #createCommand(String)}
+     *        with a non-SCP command
+     * @see #SCP_COMMAND_PREFIX
+     */
+    public ScpCommandFactory(CommandFactory delegateFactory) {
+        this(delegateFactory, null);
+    }
+
+    /**
+     * @param delegateFactory A {@link CommandFactory} to be used if the
+     *        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+     *        will be thrown when attempting to invoke {@link #createCommand(String)}
+     *        with a non-SCP command
+     * @param executorService An {@link ExecutorService} to be used when
+     *        starting {@link ScpCommand} execution. If {@code null} then a single-threaded
+     *        ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
+     *        when the command is terminated - unless it is the ad-hoc service, which will be
+     *        shutdown regardless
+     * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean)
+     */
+    public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService) {
+        this(delegateFactory, executorService, false);
+    }
+
+    /**
+     * @param delegateFactory A {@link CommandFactory} to be used if the
+     *        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+     *        will be thrown when attempting to invoke {@link #createCommand(String)}
+     *        with a non-SCP command
+     * @param executorService An {@link ExecutorService} to be used when
+     *        starting {@link ScpCommand} execution. If {@code null} then a single-threaded
+     *        ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
+     *        when the command is terminated - unless it is the ad-hoc service, which will be
+     *        shutdown regardless
+     * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
+     *        will be called when command terminates - unless it is the ad-hoc
+     *        service, which will be shutdown regardless
+     */
+    public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService, boolean shutdownOnExit) {
+        delegate = delegateFactory;
+        executors = executorService;
+        shutdownExecutor = shutdownOnExit;
+    }
+
+    public CommandFactory getDelegateCommandFactory() {
+        return delegate;
+    }
+
+    public ExecutorService getExecutorService() {
+        return executors;
+    }
+
+    public boolean isShutdownOnExit() {
+        return shutdownExecutor;
     }
 
     /**
@@ -51,14 +121,17 @@ public class ScpCommandFactory implements CommandFactory {
      *
      * @param command command to parse 
      * @return configured {@link Command} instance
-     * @throws IllegalArgumentException
+     * @throws IllegalArgumentException if not an SCP command and no
+     *         delegate command factory is available
+     * @see #SCP_COMMAND_PREFIX
      */
     public Command createCommand(String command) {
         try {
-            if (!command.startsWith("scp")) {
-                throw new IllegalArgumentException("Unknown command, does not begin with 'scp'");
+            if (!command.startsWith(SCP_COMMAND_PREFIX)) {
+                throw new IllegalArgumentException("Unknown command, does not begin with '" + SCP_COMMAND_PREFIX + "': " + command);
             }
-            return new ScpCommand(command);
+
+            return new ScpCommand(command, getExecutorService(), isShutdownOnExit());
         } catch (IllegalArgumentException iae) {
             if (delegate != null) {
                 return delegate.createCommand(command);
@@ -66,5 +139,4 @@ public class ScpCommandFactory implements CommandFactory {
             throw iae;
         }
     }
-
 }


[2/2] mina-sshd git commit: [SSHD-401] Allow user control over ScpCommand send/receive buffer size

Posted by gn...@apache.org.
[SSHD-401] Allow user control over ScpCommand send/receive buffer size

Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/6af9457d
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/6af9457d
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/6af9457d

Branch: refs/heads/master
Commit: 6af9457d3032080d49e6dbf41d2dad0d376c9b7f
Parents: d6ab2b8
Author: Guillaume Nodet <gn...@apache.org>
Authored: Tue Feb 10 11:11:14 2015 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Tue Feb 10 11:11:14 2015 +0100

----------------------------------------------------------------------
 .../sshd/client/scp/DefaultScpClient.java       |   6 +-
 .../org/apache/sshd/common/scp/ScpHelper.java   |  81 ++++++++---
 .../apache/sshd/server/command/ScpCommand.java  | 116 +++++++++++++--
 .../sshd/server/command/ScpCommandFactory.java  | 145 ++++++++++++++-----
 4 files changed, 283 insertions(+), 65 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6af9457d/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
index 911eb5c..7527102 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
@@ -104,7 +104,8 @@ public class DefaultScpClient implements ScpClient {
         helper.receive(target,
                        options.contains(Option.Recursive),
                        options.contains(Option.TargetIsDirectory),
-                       options.contains(Option.PreserveAttributes));
+                       options.contains(Option.PreserveAttributes),
+                       ScpHelper.DEFAULT_RECEIVE_BUFFER_SIZE);
 
         channel.close(false);
     }
@@ -155,7 +156,8 @@ public class DefaultScpClient implements ScpClient {
 
         helper.send(Arrays.asList(local),
                     options.contains(Option.Recursive),
-                    options.contains(Option.PreserveAttributes));
+                    options.contains(Option.PreserveAttributes),
+                    ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
 
         channel.close(false);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6af9457d/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index af4daa5..517cb36 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -46,6 +46,20 @@ public class ScpHelper {
     public static final int WARNING = 1;
     public static final int ERROR = 2;
 
+    /**
+     * Default size (in bytes) of send / receive buffer size
+     */
+    public static final int DEFAULT_COPY_BUFFER_SIZE = 8192;
+    public static final int DEFAULT_RECEIVE_BUFFER_SIZE = DEFAULT_COPY_BUFFER_SIZE;
+    public static final int DEFAULT_SEND_BUFFER_SIZE = DEFAULT_COPY_BUFFER_SIZE;
+
+    /**
+     * The minimum size for sending / receiving files
+     */
+    public static final int MIN_COPY_BUFFER_SIZE = Byte.MAX_VALUE;
+    public static final int MIN_RECEIVE_BUFFER_SIZE = MIN_COPY_BUFFER_SIZE;
+    public static final int MIN_SEND_BUFFER_SIZE = MIN_COPY_BUFFER_SIZE;
+
     public static final int S_IRUSR =  0000400;
     public static final int S_IWUSR =  0000200;
     public static final int S_IXUSR =  0000100;
@@ -66,7 +80,7 @@ public class ScpHelper {
         this.root = root;
     }
 
-    public void receive(SshFile path, boolean recursive, boolean shouldBeDir, boolean preserve) throws IOException {
+    public void receive(SshFile path, boolean recursive, boolean shouldBeDir, boolean preserve, int bufferSize) throws IOException {
         if (shouldBeDir) {
             if (!path.doesExist()) {
                 throw new SshException("Target directory " + path.toString() + " does not exists");
@@ -110,19 +124,19 @@ public class ScpHelper {
 
             if (recursive && isDir)
             {
-                receiveDir(line, path, time, preserve);
+                receiveDir(line, path, time, preserve, bufferSize);
                 time = null;
             }
             else
             {
-                receiveFile(line, path, time, preserve);
+                receiveFile(line, path, time, preserve, bufferSize);
                 time = null;
             }
         }
     }
 
 
-    public void receiveDir(String header, SshFile path, long[] time, boolean preserve) throws IOException {
+    public void receiveDir(String header, SshFile path, long[] time, boolean preserve, int bufferSize) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Receiving directory {}", path);
         }
@@ -166,10 +180,10 @@ public class ScpHelper {
             header = readLine();
             log.debug("Received header: " + header);
             if (header.startsWith("C")) {
-                receiveFile(header, file, time, preserve);
+                receiveFile(header, file, time, preserve, bufferSize);
                 time = null;
             } else if (header.startsWith("D")) {
-                receiveDir(header, file, time, preserve);
+                receiveDir(header, file, time, preserve, bufferSize);
                 time = null;
             } else if (header.equals("E")) {
                 ack();
@@ -184,7 +198,7 @@ public class ScpHelper {
 
     }
 
-    public void receiveFile(String header, SshFile path, long[] time, boolean preserve) throws IOException {
+    public void receiveFile(String header, SshFile path, long[] time, boolean preserve, int bufferSize) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Receiving file {}", path);
         }
@@ -192,9 +206,24 @@ public class ScpHelper {
             throw new IOException("Expected a C message but got '" + header + "'");
         }
 
+        if (bufferSize < MIN_RECEIVE_BUFFER_SIZE) {
+            throw new IOException("receiveFile(" + path + ") buffer size (" + bufferSize + ") below minimum (" + MIN_RECEIVE_BUFFER_SIZE + ")");
+        }
+
         String perms = header.substring(1, 5);
         long length = Long.parseLong(header.substring(6, header.indexOf(' ', 6)));
         String name = header.substring(header.indexOf(' ', 6) + 1);
+        if (length < 0L) { // TODO consider throwing an exception...
+            log.warn("receiveFile(" + path + ") bad length in header: " + header);
+        }
+
+        // if file size is less than buffer size allocate only expected file size
+        int bufSize = (int) Math.min(length, bufferSize);
+        if (bufSize < 0) { // TODO consider throwing an exception
+            log.warn("receiveFile(" + path + ") bad buffer size (" + bufSize + ") using default (" + MIN_RECEIVE_BUFFER_SIZE + ")");
+            bufSize = MIN_RECEIVE_BUFFER_SIZE;
+        }
+
 
         SshFile file;
         if (path.doesExist() && path.isDirectory()) {
@@ -218,7 +247,7 @@ public class ScpHelper {
         try {
             ack();
 
-            byte[] buffer = new byte[8192];
+            byte[] buffer = new byte[bufSize];
             while (length > 0) {
                 int len = (int) Math.min(length, buffer.length);
                 len = in.read(buffer, 0, len);
@@ -267,7 +296,7 @@ public class ScpHelper {
         }
     }
 
-    public void send(List<String> paths, boolean recursive, boolean preserve) throws IOException {
+    public void send(List<String> paths, boolean recursive, boolean preserve, int bufferSize) throws IOException {
         readAck(false);
         for (String pattern : paths) {
             int idx = pattern.indexOf('*');
@@ -282,13 +311,13 @@ public class ScpHelper {
                 for (String path : included) {
                     SshFile file = root.getFile(basedir + "/" + path);
                     if (file.isFile()) {
-                        sendFile(file, preserve);
+                        sendFile(file, preserve, bufferSize);
                     } else if (file.isDirectory()) {
                         if (!recursive) {
                             out.write(ScpHelper.WARNING);
                             out.write((path + " not a regular file\n").getBytes());
                         } else {
-                            sendDir(file, preserve);
+                            sendDir(file, preserve, bufferSize);
                         }
                     } else {
                         out.write(ScpHelper.WARNING);
@@ -307,12 +336,12 @@ public class ScpHelper {
                     throw new IOException(file + ": no such file or directory");
                 }
                 if (file.isFile()) {
-                    sendFile(file, preserve);
+                    sendFile(file, preserve, bufferSize);
                 } else if (file.isDirectory()) {
                     if (!recursive) {
                         throw new IOException(file + " not a regular file");
                     } else {
-                        sendDir(file, preserve);
+                        sendDir(file, preserve, bufferSize);
                     }
                 } else {
                     throw new IOException(file + ": unknown file type");
@@ -321,11 +350,15 @@ public class ScpHelper {
         }
     }
 
-    public void sendFile(SshFile path, boolean preserve) throws IOException {
+    public void sendFile(SshFile path, boolean preserve, int bufferSize) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Sending file {}", path);
         }
 
+        if (bufferSize < MIN_SEND_BUFFER_SIZE) {
+            throw new IOException("sendFile(" + path + ") buffer size (" + bufferSize + ") below minimum (" + MIN_SEND_BUFFER_SIZE + ")");
+        }
+
         Map<SshFile.Attribute,Object> attrs =  path.getAttributes(true);
         if (preserve) {
             StringBuffer buf = new StringBuffer();
@@ -355,9 +388,21 @@ public class ScpHelper {
         out.flush();
         readAck(false);
 
+        long fileSize = path.getSize();
+        if (fileSize < 0L) { // TODO consider throwing an exception...
+            log.warn("sendFile(" + path + ") bad file size: " + fileSize);
+        }
+
+        // if file size is less than buffer size allocate only expected file size
+        int bufSize = (int) Math.min(fileSize, bufferSize);
+        if (bufSize < 0) { // TODO consider throwing an exception
+            log.warn("sendFile(" + path + ") bad buffer size (" + bufSize + ") using default (" + MIN_SEND_BUFFER_SIZE + ")");
+            bufSize = MIN_SEND_BUFFER_SIZE;
+        }
+
         InputStream is = path.createInputStream(0);
         try {
-            byte[] buffer = new byte[8192];
+            byte[] buffer = new byte[bufSize];
             for (;;) {
                 int len = is.read(buffer, 0, buffer.length);
                 if (len == -1) {
@@ -372,7 +417,7 @@ public class ScpHelper {
         readAck(false);
     }
 
-    public void sendDir(SshFile path, boolean preserve) throws IOException {
+    public void sendDir(SshFile path, boolean preserve, int bufferSize) throws IOException {
         if (log.isDebugEnabled()) {
             log.debug("Sending directory {}", path);
         }
@@ -407,9 +452,9 @@ public class ScpHelper {
 
         for (SshFile child : path.listSshFiles()) {
             if (child.isFile()) {
-                sendFile(child, preserve);
+                sendFile(child, preserve, bufferSize);
             } else if (child.isDirectory()) {
-                sendDir(child, preserve);
+                sendDir(child, preserve, bufferSize);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6af9457d/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
index 4355be4..68dc9ef 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
@@ -63,26 +63,107 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
     protected ExecutorService executors;
     protected boolean shutdownExecutor;
     protected Future<?> pendingFuture;
+    protected int sendBufferSize;
+    protected int receiveBufferSize;
 
+    /**
+     * Simple constructor - uses an ad-hoc {@link ExecutorService} to
+     * run the command as well as default send / receive buffer sizes
+     *
+     * @param command The command to be executed
+     * @see #ScpCommand(String, ExecutorService, boolean, int, int)
+     * @see ScpHelper#DEFAULT_COPY_BUFFER_SIZE
+     */
     public ScpCommand(String command) {
-        this(command, null);
+        this(command, ScpHelper.DEFAULT_COPY_BUFFER_SIZE);
     }
 
+    /**
+     * Uses an ad-hoc {@link ExecutorService} to run the command
+     *
+     * @param command    The command to be executed
+     * @param bufferSize Size (in bytes) of buffer to be used for <U>both</U>
+     *                   sending and receiving files
+     * @see #ScpCommand(String, ExecutorService, int)
+     */
+    public ScpCommand(String command, int bufferSize) {
+        this(command, null, bufferSize);
+    }
+
+    /**
+     * @param command         The command to be executed
+     * @param executorService An {@link ExecutorService} to be used when
+     *                        {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc
+     *                        single-threaded service is created and used. <B>Note:</B> the
+     *                        executor service will <U>not</U> be shutdown when command terminates
+     *                        unless it is the ad-hoc service
+     * @param bufferSize      Size (in bytes) of buffer to be used for <U>both</U>
+     *                        sending and receiving files
+     * @see #ScpCommand(String, ExecutorService, boolean, int, int)
+     */
+    public ScpCommand(String command, ExecutorService executorService, int bufferSize) {
+        this(command, executorService, false, bufferSize);
+    }
+
+    /**
+     * @param command         The command to be executed
+     * @param executorService An {@link ExecutorService} to be used when
+     *                        {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc
+     *                        single-threaded service is created and used. <B>Note:</B> the
+     *                        executor service will <U>not</U> be shutdown when command terminates
+     *                        unless it is the ad-hoc service
+     * @see #ScpCommand(String, ExecutorService, boolean, int)
+     */
     public ScpCommand(String command, ExecutorService executorService) {
         this(command, executorService, false);
     }
 
     /**
-     * @param command The command to be executed
+     * @param command         The command to be executed
      * @param executorService An {@link ExecutorService} to be used when
-     *        {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc
-     *        single-threaded service is created and used.
-     * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
-     *        will be called when command terminates - unless it is the ad-hoc
-     *        service, which will be shutdown regardless
-     * @see ThreadUtils#newSingleThreadExecutor(String)
+     *                        {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc
+     *                        single-threaded service is created and used.
+     * @param shutdownOnExit  If {@code true} the {@link ExecutorService#shutdownNow()}
+     *                        will be called when command terminates - unless it is the ad-hoc
+     *                        service, which will be shutdown regardless
+     * @see #ScpCommand(String, ExecutorService, boolean, int)
+     * @see ScpHelper#DEFAULT_COPY_BUFFER_SIZE
      */
     public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit) {
+        this(command, executorService, false, ScpHelper.DEFAULT_COPY_BUFFER_SIZE);
+    }
+
+    /**
+     * @param command         The command to be executed
+     * @param executorService An {@link ExecutorService} to be used when
+     *                        {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc
+     *                        single-threaded service is created and used.
+     * @param shutdownOnExit  If {@code true} the {@link ExecutorService#shutdownNow()}
+     *                        will be called when command terminates - unless it is the ad-hoc
+     *                        service, which will be shutdown regardless
+     * @param bufferSize      Size (in bytes) of buffer to be used for <U>both</U>
+     *                        sending and receiving files
+     * @see #ScpCommand(String, ExecutorService, boolean, int, int)
+     */
+    public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit, int bufferSize) {
+        this(command, executorService, shutdownOnExit, bufferSize, bufferSize);
+    }
+
+    /**
+     * @param command         The command to be executed
+     * @param executorService An {@link ExecutorService} to be used when
+     *                        {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc
+     *                        single-threaded service is created and used.
+     * @param shutdownOnExit  If {@code true} the {@link ExecutorService#shutdownNow()}
+     *                        will be called when command terminates - unless it is the ad-hoc
+     *                        service, which will be shutdown regardless
+     * @param sendSize        Size (in bytes) of buffer to use when sending files
+     * @param receiveSize     Size (in bytes) of buffer to use when receiving files
+     * @see ThreadUtils#newSingleThreadExecutor(String)
+     * @see ScpHelper#MIN_SEND_BUFFER_SIZE
+     * @see ScpHelper#MIN_RECEIVE_BUFFER_SIZE
+     */
+    public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit, int sendSize, int receiveSize) {
         name = command;
 
         if ((executors = executorService) == null) {
@@ -93,6 +174,14 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
             shutdownExecutor = shutdownOnExit;
         }
 
+        if ((sendBufferSize = sendSize) < ScpHelper.MIN_SEND_BUFFER_SIZE) {
+            throw new IllegalArgumentException("<ScpCommmand>(" + command + ") send buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_SEND_BUFFER_SIZE + ")");
+        }
+
+        if ((receiveBufferSize = receiveSize) < ScpHelper.MIN_RECEIVE_BUFFER_SIZE) {
+            throw new IllegalArgumentException("<ScpCommmand>(" + command + ") receive buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_RECEIVE_BUFFER_SIZE + ")");
+        }
+
         log.debug("Executing command {}", command);
         String[] args = command.split(" ");
         for (int i = 1; i < args.length; i++) {
@@ -120,7 +209,7 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
                     }
                 }
             } else {
-                path = command.substring(command.indexOf(args[i-1]) + args[i-1].length() + 1);
+                path = command.substring(command.indexOf(args[i - 1]) + args[i - 1].length() + 1);
                 if (path.startsWith("\"") && path.endsWith("\"") || path.startsWith("'") && path.endsWith("'")) {
                     path = path.substring(1, path.length() - 1);
                 }
@@ -193,9 +282,9 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
         ScpHelper helper = new ScpHelper(in, out, root);
         try {
             if (optT) {
-                helper.receive(root.getFile(path), optR, optD, optP);
+                helper.receive(root.getFile(path), optR, optD, optP, receiveBufferSize);
             } else if (optF) {
-                helper.send(Collections.singletonList(path), optR, optP);
+                helper.send(Collections.singletonList(path), optR, optP, sendBufferSize);
             } else {
                 throw new IOException("Unsupported mode");
             }
@@ -218,5 +307,8 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
         }
     }
 
-
+    @Override
+    public String toString() {
+        return name;
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6af9457d/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
index 07ec477..87e980f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
@@ -20,6 +20,7 @@ package org.apache.sshd.server.command;
 
 import java.util.concurrent.ExecutorService;
 
+import org.apache.sshd.common.scp.ScpHelper;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.CommandFactory;
 
@@ -41,15 +42,37 @@ public class ScpCommandFactory implements CommandFactory {
     private CommandFactory delegate;
     private ExecutorService executors;
     private boolean shutdownExecutor;
+    private int sendBufferSize;
+    private int receiveBufferSize;
 
+    /**
+     * Default constructor - uses an ad-hoc {@link ExecutorService} with
+     * no delegate {@link CommandFactory} and default send/receive buffer
+     * sizes
+     *
+     * @see ScpHelper#DEFAULT_COPY_BUFFER_SIZE
+     */
     public ScpCommandFactory() {
         this(null, null);
     }
 
     /**
+     * Uses an ad-hoc {@link ExecutorService} with no delegate {@link CommandFactory}
+     *
+     * @param bufferSize Size (in bytes) of buffer to be used for <U>both</U>
+     *                   sending and receiving files
+     * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean, int, int)
+     */
+    public ScpCommandFactory(int bufferSize) {
+        this(null, null, true, bufferSize);
+    }
+
+    /**
      * @param executorService An {@link ExecutorService} to be used when
-     *        starting {@link ScpCommand} execution. If {@code null} an ad-hoc
-     *        single-threaded service is created and used.
+     *                        starting {@link ScpCommand} execution. If {@code null} an ad-hoc
+     *                        single-threaded service is created and used. <B>Note:</B> the
+     *                        executor service will <U>not</U> be shutdown when command terminates
+     *                        unless it is the ad-hoc service
      */
     public ScpCommandFactory(ExecutorService executorService) {
         this(null, executorService);
@@ -57,9 +80,9 @@ public class ScpCommandFactory implements CommandFactory {
 
     /**
      * @param delegateFactory A {@link CommandFactory} to be used if the
-     *        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
-     *        will be thrown when attempting to invoke {@link #createCommand(String)}
-     *        with a non-SCP command
+     *                        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+     *                        will be thrown when attempting to invoke {@link #createCommand(String)}
+     *                        with a non-SCP command
      * @see #SCP_COMMAND_PREFIX
      */
     public ScpCommandFactory(CommandFactory delegateFactory) {
@@ -68,14 +91,14 @@ public class ScpCommandFactory implements CommandFactory {
 
     /**
      * @param delegateFactory A {@link CommandFactory} to be used if the
-     *        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
-     *        will be thrown when attempting to invoke {@link #createCommand(String)}
-     *        with a non-SCP command
+     *                        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+     *                        will be thrown when attempting to invoke {@link #createCommand(String)}
+     *                        with a non-SCP command
      * @param executorService An {@link ExecutorService} to be used when
-     *        starting {@link ScpCommand} execution. If {@code null} then a single-threaded
-     *        ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
-     *        when the command is terminated - unless it is the ad-hoc service, which will be
-     *        shutdown regardless
+     *                        starting {@link ScpCommand} execution. If {@code null} then a single-threaded
+     *                        ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
+     *                        when the command is terminated - unless it is the ad-hoc service, which will be
+     *                        shutdown regardless
      * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean)
      */
     public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService) {
@@ -84,22 +107,72 @@ public class ScpCommandFactory implements CommandFactory {
 
     /**
      * @param delegateFactory A {@link CommandFactory} to be used if the
-     *        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
-     *        will be thrown when attempting to invoke {@link #createCommand(String)}
-     *        with a non-SCP command
+     *                        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+     *                        will be thrown when attempting to invoke {@link #createCommand(String)}
+     *                        with a non-SCP command
      * @param executorService An {@link ExecutorService} to be used when
-     *        starting {@link ScpCommand} execution. If {@code null} then a single-threaded
-     *        ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
-     *        when the command is terminated - unless it is the ad-hoc service, which will be
-     *        shutdown regardless
-     * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
-     *        will be called when command terminates - unless it is the ad-hoc
-     *        service, which will be shutdown regardless
+     *                        starting {@link ScpCommand} execution. If {@code null} then a single-threaded
+     *                        ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
+     *                        when the command is terminated - unless it is the ad-hoc service, which will be
+     *                        shutdown regardless
+     * @param shutdownOnExit  If {@code true} the {@link ExecutorService#shutdownNow()}
+     *                        will be called when command terminates - unless it is the ad-hoc
+     *                        service, which will be shutdown regardless
+     * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean, int)
      */
     public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService, boolean shutdownOnExit) {
+        this(delegateFactory, executorService, shutdownOnExit, ScpHelper.DEFAULT_COPY_BUFFER_SIZE);
+    }
+
+    /**
+     * @param delegateFactory A {@link CommandFactory} to be used if the
+     *                        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+     *                        will be thrown when attempting to invoke {@link #createCommand(String)}
+     *                        with a non-SCP command
+     * @param executorService An {@link ExecutorService} to be used when
+     *                        starting {@link ScpCommand} execution. If {@code null} then a single-threaded
+     *                        ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
+     *                        when the command is terminated - unless it is the ad-hoc service, which will be
+     *                        shutdown regardless
+     * @param shutdownOnExit  If {@code true} the {@link ExecutorService#shutdownNow()}
+     *                        will be called when command terminates - unless it is the ad-hoc
+     *                        service, which will be shutdown regardless
+     * @param bufferSize      Size (in bytes) of buffer to be used for <U>both</U>
+     *                        sending and receiving files
+     * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean, int, int)
+     */
+    public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService, boolean shutdownOnExit, int bufferSize) {
+        this(delegateFactory, executorService, shutdownOnExit, bufferSize, bufferSize);
+    }
+
+    /**
+     * @param delegateFactory A {@link CommandFactory} to be used if the
+     *                        command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+     *                        will be thrown when attempting to invoke {@link #createCommand(String)}
+     *                        with a non-SCP command
+     * @param executorService An {@link ExecutorService} to be used when
+     *                        starting {@link ScpCommand} execution. If {@code null} then a single-threaded
+     *                        ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
+     *                        when the command is terminated - unless it is the ad-hoc service, which will be
+     *                        shutdown regardless
+     * @param shutdownOnExit  If {@code true} the {@link ExecutorService#shutdownNow()}
+     *                        will be called when command terminates - unless it is the ad-hoc
+     *                        service, which will be shutdown regardless
+     * @param sendSize        Size (in bytes) of buffer to use when sending files
+     * @param receiveSize     Size (in bytes) of buffer to use when receiving files
+     * @see ScpHelper#MIN_SEND_BUFFER_SIZE
+     * @see ScpHelper#MIN_RECEIVE_BUFFER_SIZE
+     */
+    public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService, boolean shutdownOnExit, int sendSize, int receiveSize) {
         delegate = delegateFactory;
         executors = executorService;
         shutdownExecutor = shutdownOnExit;
+        if ((sendBufferSize = sendSize) < ScpHelper.MIN_SEND_BUFFER_SIZE) {
+            throw new IllegalArgumentException("<ScpCommandFactory>() send buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_SEND_BUFFER_SIZE + ")");
+        }
+        if ((receiveBufferSize = receiveSize) < ScpHelper.MIN_RECEIVE_BUFFER_SIZE) {
+            throw new IllegalArgumentException("<ScpCommandFactory>() receive buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_RECEIVE_BUFFER_SIZE + ")");
+        }
     }
 
     public CommandFactory getDelegateCommandFactory() {
@@ -114,6 +187,14 @@ public class ScpCommandFactory implements CommandFactory {
         return shutdownExecutor;
     }
 
+    public int getSendBufferSize() {
+        return sendBufferSize;
+    }
+
+    public int getReceiveBufferSize() {
+        return receiveBufferSize;
+    }
+
     /**
      * Parses a command string and verifies that the basic syntax is
      * correct. If parsing fails the responsibility is delegated to
@@ -126,17 +207,15 @@ public class ScpCommandFactory implements CommandFactory {
      * @see #SCP_COMMAND_PREFIX
      */
     public Command createCommand(String command) {
-        try {
-            if (!command.startsWith(SCP_COMMAND_PREFIX)) {
-                throw new IllegalArgumentException("Unknown command, does not begin with '" + SCP_COMMAND_PREFIX + "': " + command);
-            }
-
-            return new ScpCommand(command, getExecutorService(), isShutdownOnExit());
-        } catch (IllegalArgumentException iae) {
-            if (delegate != null) {
-                return delegate.createCommand(command);
-            }
-            throw iae;
+        if (command.startsWith(SCP_COMMAND_PREFIX)) {
+            return new ScpCommand(command, getExecutorService(), isShutdownOnExit(), getSendBufferSize(), getReceiveBufferSize());
         }
+
+        CommandFactory factory = getDelegateCommandFactory();
+        if (factory != null) {
+            return factory.createCommand(command);
+        }
+
+        throw new IllegalArgumentException("Unknown command, does not begin with '" + SCP_COMMAND_PREFIX + "': " + command);
     }
 }