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/03/17 20:15:40 UTC

mina-sshd git commit: [SSHD-428] Add ScpTransferEventListener

Repository: mina-sshd
Updated Branches:
  refs/heads/master 4039a11ae -> 2cd0ebbaf


[SSHD-428] Add ScpTransferEventListener

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

Branch: refs/heads/master
Commit: 2cd0ebbafabe232a483b244dcefb82477ec67580
Parents: 4039a11
Author: Guillaume Nodet <gn...@apache.org>
Authored: Tue Mar 17 18:51:19 2015 +0100
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Tue Mar 17 18:56:14 2015 +0100

----------------------------------------------------------------------
 .../java/org/apache/sshd/ClientSession.java     |  28 +
 .../sshd/client/scp/DefaultScpClient.java       |  22 +-
 .../sshd/client/session/ClientSessionImpl.java  |  23 +-
 .../org/apache/sshd/common/scp/ScpHelper.java   | 249 ++++---
 .../common/scp/ScpTransferEventListener.java    | 103 +++
 .../org/apache/sshd/common/util/IoUtils.java    |  44 +-
 .../apache/sshd/server/command/ScpCommand.java  |  11 +-
 .../sshd/server/command/ScpCommandFactory.java  |  59 +-
 .../apache/sshd/server/sftp/SftpSubsystem.java  |   4 +-
 .../src/test/java/org/apache/sshd/ScpTest.java  | 703 ++++++++++---------
 10 files changed, 796 insertions(+), 450 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
index 867dadb..a84534e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
@@ -36,6 +36,7 @@ import org.apache.sshd.common.Session;
 import org.apache.sshd.common.SshdSocketAddress;
 import org.apache.sshd.common.future.CloseFuture;
 import org.apache.sshd.common.future.SshFuture;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
 
 /**
  * An authenticated session to a given SSH server
@@ -153,10 +154,37 @@ public interface ClientSession extends Session {
 
     /**
      * Create an SCP client from this session.
+     * @return An {@link ScpClient} instance. <B>Note:</B> uses the currently
+     * registered {@link ScpTransferEventListener} if any
+     * @see #setScpTransferEventListener(ScpTransferEventListener)
      */
     ScpClient createScpClient();
 
     /**
+     * Create an SCP client from this session.
+     * @param listener A {@link ScpTransferEventListener} that can be used
+     * to receive information about the SCP operations - may be {@code null}
+     * to indicate no more events are required. <B>Note:</B> this listener
+     * is used <U>instead</U> of any listener set via {@link #setScpTransferEventListener(ScpTransferEventListener)}
+     * @return An {@link ScpClient} instance
+     */
+    ScpClient createScpClient(ScpTransferEventListener listener);
+
+    /**
+     * @return The last {@link ScpTransferEventListener} set via
+     * {@link #setScpTransferEventListener(ScpTransferEventListener)}
+     */
+    ScpTransferEventListener getScpTransferEventListener();
+
+    /**
+     * @param listener A default {@link ScpTransferEventListener} that can be used
+     * to receive information about the SCP operations - may be {@code null}
+     * to indicate no more events are required
+     * @see #createScpClient(ScpTransferEventListener)
+     */
+    void setScpTransferEventListener(ScpTransferEventListener listener);
+
+    /**
      * Create an SFTP client from this session.
      */
     SftpClient createSftpClient() throws IOException;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/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 c0b23a0..bb5019b 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
@@ -23,10 +23,11 @@ import java.io.InterruptedIOException;
 import java.nio.file.FileSystem;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.EnumSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.sshd.ClientSession;
 import org.apache.sshd.client.ScpClient;
@@ -34,6 +35,7 @@ import org.apache.sshd.client.channel.ChannelExec;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.file.FileSystemFactory;
 import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -41,9 +43,15 @@ import org.apache.sshd.common.scp.ScpHelper;
 public class DefaultScpClient implements ScpClient {
 
     private final ClientSession clientSession;
+    private final ScpTransferEventListener listener;
 
     public DefaultScpClient(ClientSession clientSession) {
+        this(clientSession, ScpTransferEventListener.EMPTY);
+    }
+
+    public DefaultScpClient(ClientSession clientSession, ScpTransferEventListener eventListener) {
         this.clientSession = clientSession;
+        this.listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
     }
 
     public void download(String remote, String local, Option... options) throws IOException {
@@ -55,7 +63,7 @@ public class DefaultScpClient implements ScpClient {
     public void download(String[] remote, String local, Option... options) throws IOException {
         local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
         remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
-        List<Option> opts = options(options);
+        Set<Option> opts = options(options);
         if (remote.length > 1) {
             opts.add(Option.TargetIsDirectory);
         }
@@ -100,7 +108,7 @@ public class DefaultScpClient implements ScpClient {
                 throw (IOException) new InterruptedIOException().initCause(e);
             }
 
-            ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
+            ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs, listener);
 
             helper.receive(target,
                            options.contains(Option.Recursive),
@@ -127,7 +135,7 @@ public class DefaultScpClient implements ScpClient {
     public void upload(String[] local, String remote, Option... options) throws IOException {
         local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
         remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
-        List<Option> opts = options(options);
+        Set<Option> opts = options(options);
         if (local.length > 1) {
             opts.add(Option.TargetIsDirectory);
         }
@@ -161,7 +169,7 @@ public class DefaultScpClient implements ScpClient {
         FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
         FileSystem fs = factory.createFileSystem(clientSession);
         try {
-            ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
+            ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs, listener);
             helper.send(Arrays.asList(local),
                         options.contains(Option.Recursive),
                         options.contains(Option.PreserveAttributes),
@@ -176,8 +184,8 @@ public class DefaultScpClient implements ScpClient {
         channel.close(false);
     }
 
-    private List<Option> options(Option... options) {
-        List<Option> opts = new ArrayList<>();
+    private Set<Option> options(Option... options) {
+        Set<Option> opts = EnumSet.noneOf(Option.class);
         if (options != null) {
             opts.addAll(Arrays.asList(options));
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index a1872b8..492f5f4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -23,7 +23,6 @@ import java.net.SocketAddress;
 import java.nio.file.FileSystem;
 import java.security.KeyPair;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -61,6 +60,7 @@ import org.apache.sshd.common.cipher.CipherNone;
 import org.apache.sshd.common.future.DefaultSshFuture;
 import org.apache.sshd.common.future.SshFuture;
 import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.session.AbstractConnectionService;
 import org.apache.sshd.common.session.AbstractSession;
 import org.apache.sshd.common.session.ConnectionService;
@@ -85,6 +85,7 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
     private ServiceFactory nextServiceFactory;
     private final List<Object> identities = new ArrayList<Object>();
     private UserInteraction userInteraction;
+    private ScpTransferEventListener scpListener;
 
     protected AuthFuture authFuture;
 
@@ -164,7 +165,7 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
 
     public AuthFuture authInteractive(String user, String password) throws IOException {
         return tryAuth(user, new UserAuthKeyboardInteractive(this, nextServiceName(), password));
-   }
+    }
 
     public AuthFuture authPublicKey(String user, KeyPair key) throws IOException {
         return tryAuth(user, new UserAuthPublicKey(this, nextServiceName(), key));
@@ -273,8 +274,20 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
         return getService(ConnectionService.class);
     }
 
+    public ScpTransferEventListener getScpTransferEventListener() {
+        return scpListener;
+    }
+
+    public void setScpTransferEventListener(ScpTransferEventListener listener) {
+        scpListener = listener;
+    }
+
     public ScpClient createScpClient() {
-        return new DefaultScpClient(this);
+        return createScpClient(getScpTransferEventListener());
+    }
+
+    public ScpClient createScpClient(ScpTransferEventListener listener) {
+        return new DefaultScpClient(this, listener);
     }
 
     public SftpClient createSftpClient() throws IOException {
@@ -428,7 +441,7 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
     }
 
     public Map<Object, Object> getMetadataMap() {
-		return metadataMap;
-	}
+        return metadataMap;
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/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 c5f6ae6..0583928 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
@@ -33,12 +33,14 @@ import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.PosixFilePermission;
+import java.util.Collection;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.scp.ScpTransferEventListener.FileOperation;
 import org.apache.sshd.common.util.DirectoryScanner;
 import org.apache.sshd.common.util.IoUtils;
 import org.slf4j.Logger;
@@ -82,11 +84,13 @@ public class ScpHelper {
     protected final FileSystem fileSystem;
     protected final InputStream in;
     protected final OutputStream out;
+    protected final ScpTransferEventListener listener;
 
-    public ScpHelper(InputStream in, OutputStream out, FileSystem fileSystem) {
+    public ScpHelper(InputStream in, OutputStream out, FileSystem fileSystem, ScpTransferEventListener eventListener) {
         this.in = in;
         this.out = out;
         this.fileSystem = fileSystem;
+        this.listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
     }
 
     public void receive(Path path, boolean recursive, boolean shouldBeDir, boolean preserve, int bufferSize) throws IOException {
@@ -153,7 +157,7 @@ public class ScpHelper {
             throw new IOException("Expected a D message but got '" + header + "'");
         }
 
-        String perms = header.substring(1, 5);
+        Set<PosixFilePermission> perms = parseOctalPerms(header.substring(1, 5));
         int length = Integer.parseInt(header.substring(6, header.indexOf(' ', 6)));
         String name = header.substring(header.indexOf(' ', 6) + 1);
 
@@ -174,7 +178,7 @@ public class ScpHelper {
         }
 
         if (preserve) {
-            setOctalPerms(file, perms);
+            IoUtils.setPermissions(path, perms);
             if (time != null) {
                 Files.getFileAttributeView(file, BasicFileAttributeView.class)
                         .setTimes(FileTime.from(time[0], TimeUnit.SECONDS),
@@ -186,24 +190,32 @@ public class ScpHelper {
         ack();
 
         time = null;
-        for (;;) {
-            header = readLine();
-            log.debug("Received header: " + header);
-            if (header.startsWith("C")) {
-                receiveFile(header, file, time, preserve, bufferSize);
-                time = null;
-            } else if (header.startsWith("D")) {
-                receiveDir(header, file, time, preserve, bufferSize);
-                time = null;
-            } else if (header.equals("E")) {
-                ack();
-                break;
-            } else if (header.startsWith("T")) {
-                time = parseTime(header);
-                ack();
-            } else {
-                throw new IOException("Unexpected message: '" + header + "'");
+        try {
+            listener.startFolderEvent(FileOperation.RECEIVE, path, perms);
+            for (; ; ) {
+                header = readLine();
+                if (log.isDebugEnabled()) {
+                    log.debug("Received header: " + header);
+                }
+                if (header.startsWith("C")) {
+                    receiveFile(header, file, time, preserve, bufferSize);
+                    time = null;
+                } else if (header.startsWith("D")) {
+                    receiveDir(header, file, time, preserve, bufferSize);
+                    time = null;
+                } else if (header.equals("E")) {
+                    ack();
+                    break;
+                } else if (header.startsWith("T")) {
+                    time = parseTime(header);
+                    ack();
+                } else {
+                    throw new IOException("Unexpected message: '" + header + "'");
+                }
             }
+        } catch (IOException | RuntimeException e) {
+            listener.endFolderEvent(FileOperation.RECEIVE, path, perms, e);
+            throw e;
         }
     }
 
@@ -219,7 +231,7 @@ public class ScpHelper {
             throw new IOException("receiveFile(" + path + ") buffer size (" + bufferSize + ") below minimum (" + MIN_RECEIVE_BUFFER_SIZE + ")");
         }
 
-        String perms = header.substring(1, 5);
+        Set<PosixFilePermission> perms = parseOctalPerms(header.substring(1, 5));
         final 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...
@@ -234,7 +246,7 @@ public class ScpHelper {
             }
             bufSize = MIN_RECEIVE_BUFFER_SIZE;
         } else {
-            bufSize= (int) Math.min(length, bufferSize);
+            bufSize = (int) Math.min(length, bufferSize);
         }
 
         if (bufSize < 0) { // TODO consider throwing an exception
@@ -258,15 +270,25 @@ public class ScpHelper {
         } else if (Files.exists(file) && !Files.isWritable(file)) {
             throw new IOException("Can not write to file: " + file);
         }
-        
-        try (InputStream is = new LimitInputStream(this.in, length);
-             OutputStream os = Files.newOutputStream(file)) {
+
+        try (
+                InputStream is = new LimitInputStream(this.in, length);
+                OutputStream os = Files.newOutputStream(file)
+        ) {
             ack();
-            IoUtils.copy(is, os, bufSize);
+
+            try {
+                listener.startFileEvent(FileOperation.RECEIVE, file, length, perms);
+                IoUtils.copy(is, os, bufSize);
+                listener.endFileEvent(FileOperation.RECEIVE, file, length, perms, null);
+            } catch (IOException | RuntimeException e) {
+                listener.endFileEvent(FileOperation.RECEIVE, file, length, perms, e);
+                throw e;
+            }
         }
 
         if (preserve) {
-            setOctalPerms(file, perms);
+            IoUtils.setPermissions(file, perms);
             if (time != null) {
                 Files.getFileAttributeView(file, BasicFileAttributeView.class)
                         .setTimes(FileTime.from(time[0], TimeUnit.SECONDS),
@@ -285,13 +307,13 @@ public class ScpHelper {
 
     public String readLine(boolean canEof) throws IOException {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        for (;;) {
+        for (; ; ) {
             int c = in.read();
             if (c == '\n') {
                 return baos.toString();
             } else if (c == -1) {
                 if (!canEof) {
-                    throw new EOFException();
+                    throw new EOFException("EOF while await end of line");
                 }
                 return null;
             } else {
@@ -370,7 +392,7 @@ public class ScpHelper {
 
         BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
         if (preserve) {
-            StringBuffer buf = new StringBuffer();
+            StringBuilder buf = new StringBuilder();
             buf.append("T");
             buf.append(basic.lastModifiedTime().to(TimeUnit.SECONDS));
             buf.append(" ");
@@ -385,9 +407,10 @@ public class ScpHelper {
             readAck(false);
         }
 
-        StringBuffer buf = new StringBuffer();
+        Set<PosixFilePermission> perms = IoUtils.getPermissions(path);
+        StringBuilder buf = new StringBuilder();
         buf.append("C");
-        buf.append(preserve ? getOctalPerms(path) : "0644");
+        buf.append(preserve ? getOctalPerms(perms) : "0644");
         buf.append(" ");
         buf.append(basic.size()); // length
         buf.append(" ");
@@ -418,9 +441,15 @@ public class ScpHelper {
             bufSize = MIN_SEND_BUFFER_SIZE;
         }
 
-        // TODO: use bufSize
         try (InputStream in = Files.newInputStream(path)) {
-            IoUtils.copy(in, out, bufSize);
+            try {
+                listener.startFileEvent(FileOperation.SEND, path, fileSize, perms);
+                IoUtils.copy(in, out, bufSize);
+                listener.endFileEvent(FileOperation.SEND, path, fileSize, perms, null);
+            } catch (IOException | RuntimeException e) {
+                listener.endFileEvent(FileOperation.SEND, path, fileSize, perms, e);
+                throw e;
+            }
         }
         ack();
         readAck(false);
@@ -432,7 +461,7 @@ public class ScpHelper {
         }
         BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
         if (preserve) {
-            StringBuffer buf = new StringBuffer();
+            StringBuilder buf = new StringBuilder();
             buf.append("T");
             buf.append(basic.lastModifiedTime().to(TimeUnit.SECONDS));
             buf.append(" ");
@@ -447,9 +476,10 @@ public class ScpHelper {
             readAck(false);
         }
 
-        StringBuffer buf = new StringBuffer();
+        Set<PosixFilePermission> perms = IoUtils.getPermissions(path);
+        StringBuilder buf = new StringBuilder();
         buf.append("D");
-        buf.append(preserve ? getOctalPerms(path) : "0755");
+        buf.append(preserve ? getOctalPerms(perms) : "0755");
         buf.append(" ");
         buf.append("0"); // length
         buf.append(" ");
@@ -460,12 +490,21 @@ public class ScpHelper {
         readAck(false);
 
         try (DirectoryStream<Path> children = Files.newDirectoryStream(path)) {
-            for (Path child : children) {
-                if (Files.isRegularFile(child)) {
-                    sendFile(child, preserve, bufferSize);
-                } else if (Files.isDirectory(child)) {
-                    sendDir(child, preserve, bufferSize);
+            listener.startFolderEvent(FileOperation.SEND, path, perms);
+
+            try {
+                for (Path child : children) {
+                    if (Files.isRegularFile(child)) {
+                        sendFile(child, preserve, bufferSize);
+                    } else if (Files.isDirectory(child)) {
+                        sendDir(child, preserve, bufferSize);
+                    }
                 }
+
+                listener.endFolderEvent(FileOperation.SEND, path, perms, null);
+            } catch (IOException | RuntimeException e) {
+                listener.endFolderEvent(FileOperation.SEND, path, perms, e);
+                throw e;
             }
         }
 
@@ -476,61 +515,60 @@ public class ScpHelper {
 
     private long[] parseTime(String line) {
         String[] numbers = line.substring(1).split(" ");
-        return new long[] { Long.parseLong(numbers[0]), Long.parseLong(numbers[2]) };
+        return new long[]{Long.parseLong(numbers[0]), Long.parseLong(numbers[2])};
     }
 
     public static String getOctalPerms(Path path) throws IOException {
+        return getOctalPerms(IoUtils.getPermissions(path));
+    }
+
+    public static String getOctalPerms(Collection<PosixFilePermission> perms) {
         int pf = 0;
-        if (path.getFileSystem().supportedFileAttributeViews().contains("posix")) {
-            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(path);
-            for (PosixFilePermission p : perms) {
-                switch (p) {
-                case OWNER_READ:
-                    pf |= S_IRUSR;
-                    break;
-                case OWNER_WRITE:
-                    pf |= S_IWUSR;
-                    break;
-                case OWNER_EXECUTE:
-                    pf |= S_IXUSR;
-                    break;
-                case GROUP_READ:
-                    pf |= S_IRGRP;
-                    break;
-                case GROUP_WRITE:
-                    pf |= S_IWGRP;
-                    break;
-                case GROUP_EXECUTE:
-                    pf |= S_IXGRP;
-                    break;
-                case OTHERS_READ:
-                    pf |= S_IROTH;
-                    break;
-                case OTHERS_WRITE:
-                    pf |= S_IWOTH;
-                    break;
-                case OTHERS_EXECUTE:
-                    pf |= S_IXOTH;
-                    break;
-                }
-            }
-        } else {
-            if (Files.isReadable(path)) {
-                pf |= S_IRUSR | S_IRGRP | S_IROTH;
-            }
-            if (Files.isWritable(path)) {
-                pf |= S_IWUSR | S_IWGRP | S_IWOTH;
-            }
-            if (Files.isExecutable(path)) {
-                pf |= S_IXUSR | S_IXGRP | S_IXOTH;
+
+        for (PosixFilePermission p : perms) {
+            switch (p) {
+            case OWNER_READ:
+                pf |= S_IRUSR;
+                break;
+            case OWNER_WRITE:
+                pf |= S_IWUSR;
+                break;
+            case OWNER_EXECUTE:
+                pf |= S_IXUSR;
+                break;
+            case GROUP_READ:
+                pf |= S_IRGRP;
+                break;
+            case GROUP_WRITE:
+                pf |= S_IWGRP;
+                break;
+            case GROUP_EXECUTE:
+                pf |= S_IXGRP;
+                break;
+            case OTHERS_READ:
+                pf |= S_IROTH;
+                break;
+            case OTHERS_WRITE:
+                pf |= S_IWOTH;
+                break;
+            case OTHERS_EXECUTE:
+                pf |= S_IXOTH;
+                break;
             }
         }
+
         return String.format("%04o", pf);
     }
 
-    public static void setOctalPerms(Path path, String str) throws IOException {
+    public static Set<PosixFilePermission> setOctalPerms(Path path, String str) throws IOException {
+        Set<PosixFilePermission> perms = parseOctalPerms(str);
+        IoUtils.setPermissions(path, perms);
+        return perms;
+    }
+
+    public static Set<PosixFilePermission> parseOctalPerms(String str) {
         int perms = Integer.parseInt(str, 8);
-        EnumSet<PosixFilePermission> p = EnumSet.noneOf(PosixFilePermission.class);
+        Set<PosixFilePermission> p = EnumSet.noneOf(PosixFilePermission.class);
         if ((perms & S_IRUSR) != 0) {
             p.add(PosixFilePermission.OWNER_READ);
         }
@@ -558,11 +596,8 @@ public class ScpHelper {
         if ((perms & S_IXOTH) != 0) {
             p.add(PosixFilePermission.OTHERS_EXECUTE);
         }
-        if (path.getFileSystem().supportedFileAttributeViews().contains("posix")) {
-            Files.setPosixFilePermissions(path, p);
-        } else {
-            log.warn("Unable to set file permissions because the underlying file system does not support posix permissions");
-        }
+
+        return p;
     }
 
     public void ack() throws IOException {
@@ -573,20 +608,20 @@ public class ScpHelper {
     public int readAck(boolean canEof) throws IOException {
         int c = in.read();
         switch (c) {
-            case -1:
-                if (!canEof) {
-                    throw new EOFException();
-                }
-                break;
-            case OK:
-                break;
-            case WARNING:
-                log.warn("Received warning: " + readLine());
-                break;
-            case ERROR:
-                throw new IOException("Received nack: " + readLine());
-            default:
-                break;
+        case -1:
+            if (!canEof) {
+                throw new EOFException("readAck - EOF before ACK");
+            }
+            break;
+        case OK:
+            break;
+        case WARNING:
+            log.warn("Received warning: " + readLine());
+            break;
+        case ERROR:
+            throw new IOException("Received nack: " + readLine());
+        default:
+            break;
         }
         return c;
     }
@@ -605,7 +640,7 @@ public class ScpHelper {
             if (remaining > 0) {
                 remaining--;
                 return super.read();
-            } else{
+            } else {
                 return -1;
             }
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTransferEventListener.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTransferEventListener.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTransferEventListener.java
new file mode 100644
index 0000000..fb82707
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTransferEventListener.java
@@ -0,0 +1,103 @@
+/*
+ * 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.sshd.common.scp;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.EventListener;
+import java.util.Set;
+
+/**
+ * Can be registered in order to receive events about SCP transfers
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ScpTransferEventListener extends EventListener {
+
+    enum FileOperation {
+        SEND,
+        RECEIVE
+    }
+
+    /**
+     * @param op     The {@link FileOperation}
+     * @param file   The <U>local</U> referenced file {@link Path}
+     * @param length Size (in bytes) of transfered data
+     * @param perms  A {@link Set} of {@link PosixFilePermission}s to be applied
+     *               once transfer is complete
+     */
+    void startFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms);
+
+    /**
+     * @param op     The {@link FileOperation}
+     * @param file   The <U>local</U> referenced file {@link Path}
+     * @param length Size (in bytes) of transfered data
+     * @param perms  A {@link Set} of {@link PosixFilePermission}s to be applied
+     *               once transfer is complete
+     * @param thrown The result of the operation attempt - if {@code null} then
+     *               reception was successful
+     */
+    void endFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms, Throwable thrown);
+
+    /**
+     * @param op    The {@link FileOperation}
+     * @param file  The <U>local</U> referenced folder {@link Path}
+     * @param perms A {@link Set} of {@link PosixFilePermission}s to be applied
+     *              once transfer is complete
+     */
+    void startFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms);
+
+    /**
+     * @param op     The {@link FileOperation}
+     * @param file   The <U>local</U> referenced file {@link Path}
+     * @param perms  A {@link Set} of {@link PosixFilePermission}s to be applied
+     *               once transfer is complete
+     * @param thrown The result of the operation attempt - if {@code null} then
+     *               reception was successful
+     */
+    void endFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms, Throwable thrown);
+
+    /**
+     * An &quot;empty&quot; implementation to be used instead of {@code null}s
+     */
+    ScpTransferEventListener EMPTY = new ScpTransferEventListener() {
+        // TODO in JDK 8.0 implement all methods as default with empty body in the interface itself
+
+        @Override
+        public void startFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms) {
+            // ignored
+        }
+
+        @Override
+        public void endFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms, Throwable thrown) {
+            // ignored
+        }
+
+        @Override
+        public void startFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms) {
+            // ignored
+        }
+
+        @Override
+        public void endFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms, Throwable thrown) {
+            // ignored
+        }
+    };
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
index cb21985..e62b231 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
@@ -23,7 +23,10 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
 import java.nio.file.LinkOption;
+import java.nio.file.Path;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.Arrays;
 import java.util.Collection;
@@ -42,7 +45,7 @@ public class IoUtils {
     public static final LinkOption[] EMPTY_OPTIONS = new LinkOption[0];
 
     private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
-    
+
     public static LinkOption[] getLinkOptions(boolean followLinks) {
         if (followLinks) {
             return EMPTY_OPTIONS;
@@ -97,6 +100,25 @@ public class IoUtils {
     }
 
     /**
+     * If the &quot;posix&quot; view is supported, then it returns
+     * {@link Files#getPosixFilePermissions(Path, LinkOption...)}, otherwise
+     * uses the {@link #getPermissionsFromFile(File)} method
+     * @param path The {@link Path}
+     * @return A {@link Set} of {@link PosixFilePermission}
+     * @throws IOException If failed to access the file system in order to
+     * retrieve the permissions
+     */
+    public static Set<PosixFilePermission> getPermissions(Path path) throws IOException {
+        FileSystem          fs = path.getFileSystem();
+        Collection<String>  views = fs.supportedFileAttributeViews();
+        if (views.contains("posix")) {
+            return Files.getPosixFilePermissions(path, getLinkOptions(false));
+        } else {
+            return getPermissionsFromFile(path.toFile());
+        }
+    }
+
+    /**
      * @param f The {@link File} to be checked
      * @return A {@link Set} of {@link PosixFilePermission}s based on whether
      * the file is readable/writable/executable. If so, then <U>all</U> the
@@ -124,7 +146,25 @@ public class IoUtils {
 
         return perms;
     }
-    
+
+    /**
+     * If the &quot;posix&quot; view is supported, then it invokes
+     * {@link Files#setPosixFilePermissions(Path, Set)}, otherwise
+     * uses the {@link #setPermissionsToFile(File, Collection)} method
+     * @param path The {@link Path}
+     * @param perms The {@link Set} of {@link PosixFilePermission}s
+     * @throws IOException If failed to access the file system
+     */
+    public static void setPermissions(Path path, Set<PosixFilePermission> perms) throws IOException {
+        FileSystem          fs = path.getFileSystem();
+        Collection<String>  views = fs.supportedFileAttributeViews();
+        if (views.contains("posix")) {
+            Files.setPosixFilePermissions(path, perms);
+        } else {
+            setPermissionsToFile(path.toFile(), perms);
+        }
+    }
+
     /**
      * @param f The {@link File}
      * @param perms A {@link Collection} of {@link PosixFilePermission}s to set on it.

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/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 381773d..ade3866 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.nio.file.FileSystem;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
-import java.nio.file.FileSystem;
 
 import org.apache.sshd.common.file.FileSystemAware;
 import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.util.ThreadUtils;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
@@ -65,6 +66,7 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
     protected Future<?> pendingFuture;
     protected int sendBufferSize;
     protected int receiveBufferSize;
+    protected ScpTransferEventListener listener;
 
     /**
      * @param command         The command to be executed
@@ -76,11 +78,12 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
      *                        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
+     * @param eventListener   An {@link ScpTransferEventListener} - may be {@code null}
      * @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) {
+    public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit, int sendSize, int receiveSize, ScpTransferEventListener eventListener) {
         name = command;
 
         if ((executors = executorService) == null) {
@@ -99,6 +102,8 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
             throw new IllegalArgumentException("<ScpCommmand>(" + command + ") receive buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_RECEIVE_BUFFER_SIZE + ")");
         }
 
+        listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
+
         log.debug("Executing command {}", command);
         String[] args = command.split(" ");
         for (int i = 1; i < args.length; i++) {
@@ -204,7 +209,7 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
     public void run() {
         int exitValue = ScpHelper.OK;
         String exitMessage = null;
-        ScpHelper helper = new ScpHelper(in, out, fileSystem);
+        ScpHelper helper = new ScpHelper(in, out, fileSystem, listener);
         try {
             if (optT) {
                 helper.receive(fileSystem.getPath(path), optR, optD, optP, receiveBufferSize);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/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 2daf2d8..ed3db39 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,9 +18,13 @@
  */
 package org.apache.sshd.server.command;
 
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.ExecutorService;
 
 import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
+import org.apache.sshd.common.util.EventListenerUtils;
 import org.apache.sshd.common.util.ObjectBuilder;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.CommandFactory;
@@ -44,7 +48,6 @@ public class ScpCommandFactory implements CommandFactory, Cloneable {
      * A useful {@link ObjectBuilder} for {@link ScpCommandFactory}
      */
     public static class Builder implements ObjectBuilder<ScpCommandFactory> {
-
         private final ScpCommandFactory factory = new ScpCommandFactory();
 
         public Builder() {
@@ -76,8 +79,17 @@ public class ScpCommandFactory implements CommandFactory, Cloneable {
             return this;
         }
 
+        public Builder addEventListener(ScpTransferEventListener listener) {
+            factory.addEventListener(listener);
+            return this;
+        }
+
+        public Builder removeEventListener(ScpTransferEventListener listener) {
+            factory.removeEventListener(listener);
+            return this;
+        }
+
         public ScpCommandFactory build() {
-            // return a clone so that each invocation returns a different instance - avoid shared instances
             return factory.clone();
         }
     }
@@ -91,9 +103,11 @@ public class ScpCommandFactory implements CommandFactory, Cloneable {
     private boolean shutdownExecutor;
     private int sendBufferSize = ScpHelper.MIN_SEND_BUFFER_SIZE;
     private int receiveBufferSize = ScpHelper.MIN_RECEIVE_BUFFER_SIZE;
+    private Collection<ScpTransferEventListener> listeners = new CopyOnWriteArraySet<>();
+    private ScpTransferEventListener listenerProxy;
 
     public ScpCommandFactory() {
-        super();
+        listenerProxy = EventListenerUtils.proxyWrapper(ScpTransferEventListener.class, getClass().getClassLoader(), listeners);
     }
 
     public CommandFactory getDelegateCommandFactory() {
@@ -167,6 +181,34 @@ public class ScpCommandFactory implements CommandFactory, Cloneable {
     }
 
     /**
+     * @param listener The {@link ScpTransferEventListener} to add
+     * @return {@code true} if this is a <U>new</U> listener instance,
+     * {@code false} if the listener is already registered
+     * @throws IllegalArgumentException if {@code null} listener
+     */
+    public boolean addEventListener(ScpTransferEventListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("No listener instance");
+        }
+
+        return listeners.add(listener);
+    }
+
+    /**
+     * @param listener The {@link ScpTransferEventListener} to remove
+     * @return {@code true} if the listener was registered and removed,
+     * {@code false} if the listener was not registered to begin with
+     * @throws IllegalArgumentException if {@code null} listener
+     */
+    public boolean removeEventListener(ScpTransferEventListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("No listener instance");
+        }
+
+        return listeners.remove(listener);
+    }
+
+    /**
      * Parses a command string and verifies that the basic syntax is
      * correct. If parsing fails the responsibility is delegated to
      * the configured {@link CommandFactory} instance; if one exist.
@@ -179,7 +221,7 @@ public class ScpCommandFactory implements CommandFactory, Cloneable {
      */
     public Command createCommand(String command) {
         if (command.startsWith(SCP_COMMAND_PREFIX)) {
-            return new ScpCommand(command, getExecutorService(), isShutdownOnExit(), getSendBufferSize(), getReceiveBufferSize());
+            return new ScpCommand(command, getExecutorService(), isShutdownOnExit(), getSendBufferSize(), getReceiveBufferSize(), listenerProxy);
         }
 
         CommandFactory factory = getDelegateCommandFactory();
@@ -193,7 +235,14 @@ public class ScpCommandFactory implements CommandFactory, Cloneable {
     @Override
     public ScpCommandFactory clone() {
         try {
-            return getClass().cast(super.clone());    // shallow clone is good enough
+            ScpCommandFactory other = getClass().cast(super.clone());
+            // clone the listeners set as well
+            other.listeners = this.listeners.isEmpty()
+                            ? new CopyOnWriteArraySet<ScpTransferEventListener>()
+                            : new CopyOnWriteArraySet<>(this.listeners)
+                            ;
+            other.listenerProxy = EventListenerUtils.proxyWrapper(ScpTransferEventListener.class, getClass().getClassLoader(), other.listeners);
+            return other;
         } catch(CloneNotSupportedException e) {
             throw new RuntimeException(e);    // un-expected...
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
index 77f09b5..1e6d305 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
@@ -806,7 +806,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             Path p = resolveFile(path);
             if (!Files.exists(p)) {
                 sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
-            } else if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) {
+            } else if (Files.isDirectory(p, IoUtils.getLinkOptions(false))) {
                 sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
             } else {
                 Files.delete(p);
@@ -1521,7 +1521,7 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
             }
             if (view != null && value != null) {
                 try {
-                    Files.setAttribute(file, view + ":" + attribute, value, LinkOption.NOFOLLOW_LINKS);
+                    Files.setAttribute(file, view + ":" + attribute, value, IoUtils.getLinkOptions(false));
                 } catch (UnsupportedOperationException e) {
                     unsupported.add(attribute);
                 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2cd0ebba/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/ScpTest.java b/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
index a92c4c7..7b33ca1 100644
--- a/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
@@ -25,17 +25,21 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.channels.FileChannel;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Collection;
 import java.util.Properties;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import ch.ethz.ssh2.Connection;
 import ch.ethz.ssh2.SCPClient;
-
 import com.jcraft.jsch.ChannelExec;
 import com.jcraft.jsch.JSch;
 import com.jcraft.jsch.JSchException;
-
 import org.apache.sshd.client.ScpClient;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
+import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.server.command.ScpCommandFactory;
 import org.apache.sshd.util.BaseTest;
 import org.apache.sshd.util.BogusPasswordAuthenticator;
@@ -44,9 +48,12 @@ import org.apache.sshd.util.JSchLogger;
 import org.apache.sshd.util.SimpleUserInfo;
 import org.apache.sshd.util.Utils;
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -101,413 +108,432 @@ public class ScpTest extends BaseTest {
 
     @Test
     public void testUploadAbsoluteDriveLetter() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        ClientSession session = client.connect("test", "localhost", port).await().getSession();
-        session.addPasswordIdentity("test");
-        session.auth().verify();
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
 
-        ScpClient scp = session.createScpClient();
+            try {
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
 
-        String data = "0123456789\n";
+                    ScpClient scp = createScpClient(session);
 
-        File root = new File("target/scp");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        new File(root, "local").mkdirs();
-        assertTrue(root.exists());
+                    String data = "0123456789\n";
 
+                    File root = new File("target/scp");
+                    Utils.deleteRecursive(root);
+                    assertHierarchyTargetFolderExists(new File(root, "local"));
+                    assertTrue("Root folder not created", root.exists());
 
-        writeFile(new File("target/scp/local/out.txt"), data);
-        new File(root, "remote").mkdirs();
-        scp.upload(new File("target/scp/local/out.txt").getAbsolutePath(), "/" + new File("target/scp/remote/out.txt").getAbsolutePath().replace(File.separatorChar, '/'));
-        assertFileLength(new File("target/scp/remote/out.txt"), data.length(), 5000);
-        scp.upload(new File("target/scp/local/out.txt").getAbsolutePath(), new File("target/scp/remote/out2.txt").getAbsolutePath());
-        assertFileLength(new File("target/scp/remote/out2.txt"), data.length(), 5000);
+                    writeFile(new File("target/scp/local/out.txt"), data);
+                    assertHierarchyTargetFolderExists(new File(root, "remote"));
+                    scp.upload(new File("target/scp/local/out.txt").getAbsolutePath(), "/" + new File("target/scp/remote/out.txt").getAbsolutePath().replace(File.separatorChar, '/'));
+                    assertFileLength(new File("target/scp/remote/out.txt"), data.length(), 5000);
+                    scp.upload(new File("target/scp/local/out.txt").getAbsolutePath(), new File("target/scp/remote/out2.txt").getAbsolutePath());
+                    assertFileLength(new File("target/scp/remote/out2.txt"), data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
     }
 
     @Test
     public void testScpUploadOverwrite() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        ClientSession session = client.connect("test", "localhost", port).await().getSession();
-        session.addPasswordIdentity("test");
-        session.auth().verify();
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
 
-        ScpClient scp = session.createScpClient();
+            try {
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
 
-        String data = "0123456789\n";
+                    ScpClient scp = createScpClient(session);
 
-        File root = new File("target/scp");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        new File(root, "local").mkdirs();
-        assertTrue(root.exists());
+                    String data = "0123456789\n";
 
-        new File(root, "remote").mkdirs();
-        writeFile(new File("target/scp/remote/out.txt"), data + data);
+                    File root = new File("target/scp");
+                    Utils.deleteRecursive(root);
+                    root.mkdirs();
+                    new File(root, "local").mkdirs();
+                    assertTrue(root.exists());
 
-        writeFile(new File("target/scp/local/out.txt"), data);
-        scp.upload("target/scp/local/out.txt", "target/scp/remote/out.txt");
-        assertFileLength(new File("target/scp/remote/out.txt"), data.length(), 5000);
+                    new File(root, "remote").mkdirs();
+                    writeFile(new File("target/scp/remote/out.txt"), data + data);
 
-        session.close(false).await();
-        client.stop();
+                    writeFile(new File("target/scp/local/out.txt"), data);
+                    scp.upload("target/scp/local/out.txt", "target/scp/remote/out.txt");
+                    assertFileLength(new File("target/scp/remote/out.txt"), data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
     }
 
     @Test
     public void testScpUploadZeroLengthFile() throws Exception {
-        File    root=assertHierarchyTargetFolderExists(new File("target/scp"));
-        File    local=assertHierarchyTargetFolderExists(new File(root, "local"));
-        File    remote=assertHierarchyTargetFolderExists(new File(root, "remote"));
-        File    zeroLocal=new File(local, getCurrentTestName());
+        File root = assertHierarchyTargetFolderExists(new File("target/scp"));
+        File local = assertHierarchyTargetFolderExists(new File(root, "local"));
+        File remote = assertHierarchyTargetFolderExists(new File(root, "remote"));
+        File zeroLocal = new File(local, getCurrentTestName());
 
-        FileOutputStream    fout=new FileOutputStream(zeroLocal);
-        try {
+        try (FileOutputStream fout = new FileOutputStream(zeroLocal)) {
             if (zeroLocal.length() > 0L) {
-                FileChannel fch=fout.getChannel();
+                FileChannel fch = fout.getChannel();
                 try {
                     fch.truncate(0L);
                 } finally {
                     fch.close();
                 }
             }
-        } finally {
-            fout.close();
         }
 
         assertEquals("Non-zero size for local file=" + zeroLocal.getAbsolutePath(), 0L, zeroLocal.length());
 
-        File    zeroRemote=new File(remote, zeroLocal.getName());
+        File zeroRemote = new File(remote, zeroLocal.getName());
         if (zeroRemote.exists()) {
             assertTrue("Failed to delete remote target " + zeroRemote.getAbsolutePath(), zeroRemote.delete());
         }
 
-        SshClient client = SshClient.setUpDefaultClient();
-        try {
-            client.start();
-
-            ClientSession session = client.connect("test", "localhost", port).await().getSession();
+        try (SshClient client = SshClient.setUpDefaultClient()) {
             try {
-                session.addPasswordIdentity("test");
-                session.auth().verify();
-    
-                ScpClient scp = session.createScpClient();
-                scp.upload("target/scp/local/" + zeroLocal.getName(), "target/scp/remote/" + zeroRemote.getName());
-                assertFileLength(zeroRemote, 0L, TimeUnit.SECONDS.toMillis(5L));
+                client.start();
+
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
+
+                    ScpClient scp = createScpClient(session);
+                    scp.upload("target/scp/local/" + zeroLocal.getName(), "target/scp/remote/" + zeroRemote.getName());
+                    assertFileLength(zeroRemote, 0L, TimeUnit.SECONDS.toMillis(5L));
+                }
             } finally {
-                session.close(false).await();
+                client.stop();
             }
-        } finally {
-            client.stop();
         }
     }
 
     @Test
     public void testScpDownloadZeroLengthFile() throws Exception {
-        File    root=assertHierarchyTargetFolderExists(new File("target/scp"));
-        File    local=assertHierarchyTargetFolderExists(new File(root, "local"));
-        File    remote=assertHierarchyTargetFolderExists(new File(root, "remote"));
-        File    zeroLocal=new File(local, getCurrentTestName());
+        File root = assertHierarchyTargetFolderExists(new File("target/scp"));
+        File local = assertHierarchyTargetFolderExists(new File(root, "local"));
+        File remote = assertHierarchyTargetFolderExists(new File(root, "remote"));
+        File zeroLocal = new File(local, getCurrentTestName());
         if (zeroLocal.exists()) {
             assertTrue("Failed to delete local target " + zeroLocal.getAbsolutePath(), zeroLocal.delete());
         }
 
-        File                zeroRemote=new File(remote, zeroLocal.getName());
-        FileOutputStream    fout=new FileOutputStream(zeroRemote);
-        try {
+        File zeroRemote = new File(remote, zeroLocal.getName());
+        try (FileOutputStream fout = new FileOutputStream(zeroRemote)) {
             if (zeroRemote.length() > 0L) {
-                FileChannel fch=fout.getChannel();
+                FileChannel fch = fout.getChannel();
                 try {
                     fch.truncate(0L);
                 } finally {
                     fch.close();
                 }
             }
-        } finally {
-            fout.close();
         }
 
         assertEquals("Non-zero size for remote file=" + zeroRemote.getAbsolutePath(), 0L, zeroRemote.length());
 
-        SshClient client = SshClient.setUpDefaultClient();
-        try {
-            client.start();
-
-            ClientSession session = client.connect("test", "localhost", port).await().getSession();
+        try (SshClient client = SshClient.setUpDefaultClient()) {
             try {
-                session.addPasswordIdentity("test");
-                session.auth().verify();
-    
-                ScpClient scp = session.createScpClient();
-                scp.download("target/scp/remote/" + zeroRemote.getName(), "target/scp/local/" + zeroLocal.getName());
-                assertFileLength(zeroLocal, 0L, TimeUnit.SECONDS.toMillis(5L));
+                client.start();
+
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
+
+                    ScpClient scp = createScpClient(session);
+                    scp.download("target/scp/remote/" + zeroRemote.getName(), "target/scp/local/" + zeroLocal.getName());
+                    assertFileLength(zeroLocal, 0L, TimeUnit.SECONDS.toMillis(5L));
+                }
             } finally {
-                session.close(false).await();
+                client.stop();
             }
-        } finally {
-            client.stop();
         }
     }
 
     @Test
     public void testScpNativeOnSingleFile() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        ClientSession session = client.connect("test", "localhost", port).await().getSession();
-        session.addPasswordIdentity("test");
-        session.auth().verify();
-
-        ScpClient scp = session.createScpClient();
-
-        String data = "0123456789\n";
-
-        File root = new File("target/scp");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        new File(root, "local").mkdirs();
-        assertTrue(root.exists());
-
-
-        writeFile(new File("target/scp/local/out.txt"), data);
-        try {
-            scp.upload("target/scp/local/out.txt", "target/scp/remote/out.txt");
-            fail("Expected IOException");
-        } catch (IOException e) {
-            // ok
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+            try {
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
+
+                    ScpClient scp = createScpClient(session);
+
+                    String data = "0123456789\n";
+
+                    File root = new File("target/scp");
+                    Utils.deleteRecursive(root);
+                    root.mkdirs();
+                    new File(root, "local").mkdirs();
+                    assertTrue(root.exists());
+
+
+                    writeFile(new File("target/scp/local/out.txt"), data);
+                    try {
+                        scp.upload("target/scp/local/out.txt", "target/scp/remote/out.txt");
+                        fail("Expected IOException");
+                    } catch (IOException e) {
+                        // ok
+                    }
+                    new File(root, "remote").mkdirs();
+                    scp.upload("target/scp/local/out.txt", "target/scp/remote/out.txt");
+                    assertFileLength(new File("target/scp/remote/out.txt"), data.length(), 5000);
+
+                    scp.download("target/scp/remote/out.txt", "target/scp/local/out2.txt");
+                    assertFileLength(new File("target/scp/local/out2.txt"), data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
         }
-        new File(root, "remote").mkdirs();
-        scp.upload("target/scp/local/out.txt", "target/scp/remote/out.txt");
-        assertFileLength(new File("target/scp/remote/out.txt"), data.length(), 5000);
-
-        scp.download("target/scp/remote/out.txt", "target/scp/local/out2.txt");
-        assertFileLength(new File("target/scp/local/out2.txt"), data.length(), 5000);
-
-        session.close(false).await();
-        client.stop();
     }
 
     @Test
     public void testScpNativeOnMultipleFiles() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        ClientSession session = client.connect("test", "localhost", port).await().getSession();
-        session.addPasswordIdentity("test");
-        session.auth().verify();
-
-        ScpClient scp = session.createScpClient();
-
-        String data = "0123456789\n";
-
-        File root = new File("target/scp");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        new File(root, "local").mkdirs();
-        new File(root, "remote").mkdirs();
-        assertTrue(root.exists());
-
-
-        writeFile(new File("target/scp/local/out1.txt"), data);
-        writeFile(new File("target/scp/local/out2.txt"), data);
-        try {
-            scp.upload(new String[] { "target/scp/local/out1.txt", "target/scp/local/out2.txt" }, "target/scp/remote/out.txt");
-            fail("Expected IOException");
-        } catch (IOException e) {
-            // Ok
-        }
-        writeFile(new File("target/scp/remote/out.txt"), data);
-        try {
-            scp.upload(new String[] { "target/scp/local/out1.txt", "target/scp/local/out2.txt" }, "target/scp/remote/out.txt");
-            fail("Expected IOException");
-        } catch (IOException e) {
-            // Ok
-        }
-        new File(root, "remote/dir").mkdirs();
-        scp.upload(new String[] { "target/scp/local/out1.txt", "target/scp/local/out2.txt" }, "target/scp/remote/dir");
-        assertFileLength(new File("target/scp/remote/dir/out1.txt"), data.length(), 5000);
-        assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
 
-        try {
-            scp.download(new String[] { "target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt" }, "target/scp/local/out1.txt");
-            fail("Expected IOException");
-        } catch (IOException e) {
-            // Ok
-        }
-        try {
-            scp.download(new String[] { "target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt" }, "target/scp/local/dir");
-            fail("Expected IOException");
-        } catch (IOException e) {
-            // Ok
+            try {
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
+
+                    ScpClient scp = createScpClient(session);
+
+                    String data = "0123456789\n";
+
+                    File root = new File("target/scp");
+                    Utils.deleteRecursive(root);
+                    root.mkdirs();
+                    new File(root, "local").mkdirs();
+                    new File(root, "remote").mkdirs();
+                    assertTrue(root.exists());
+
+                    writeFile(new File("target/scp/local/out1.txt"), data);
+                    writeFile(new File("target/scp/local/out2.txt"), data);
+                    try {
+                        scp.upload(new String[]{"target/scp/local/out1.txt", "target/scp/local/out2.txt"}, "target/scp/remote/out.txt");
+                        fail("Expected IOException");
+                    } catch (IOException e) {
+                        // Ok
+                    }
+                    writeFile(new File("target/scp/remote/out.txt"), data);
+                    try {
+                        scp.upload(new String[]{"target/scp/local/out1.txt", "target/scp/local/out2.txt"}, "target/scp/remote/out.txt");
+                        fail("Expected IOException");
+                    } catch (IOException e) {
+                        // Ok
+                    }
+                    new File(root, "remote/dir").mkdirs();
+                    scp.upload(new String[]{"target/scp/local/out1.txt", "target/scp/local/out2.txt"}, "target/scp/remote/dir");
+                    assertFileLength(new File("target/scp/remote/dir/out1.txt"), data.length(), 5000);
+                    assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+
+                    try {
+                        scp.download(new String[]{"target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt"}, "target/scp/local/out1.txt");
+                        fail("Expected IOException");
+                    } catch (IOException e) {
+                        // Ok
+                    }
+                    try {
+                        scp.download(new String[]{"target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt"}, "target/scp/local/dir");
+                        fail("Expected IOException");
+                    } catch (IOException e) {
+                        // Ok
+                    }
+                    new File(root, "local/dir").mkdirs();
+                    scp.download(new String[]{"target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt"}, "target/scp/local/dir");
+                    assertFileLength(new File("target/scp/local/dir/out1.txt"), data.length(), 5000);
+                    assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
         }
-        new File(root, "local/dir").mkdirs();
-        scp.download(new String[] { "target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt" }, "target/scp/local/dir");
-        assertFileLength(new File("target/scp/local/dir/out1.txt"), data.length(), 5000);
-        assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
-
-        session.close(false).await();
-        client.stop();
     }
 
     @Test
     public void testScpNativeOnRecursiveDirs() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        ClientSession session = client.connect("test", "localhost", port).await().getSession();
-        session.addPasswordIdentity("test");
-        session.auth().verify();
-
-        ScpClient scp = session.createScpClient();
-
-        String data = "0123456789\n";
-
-        File root = new File("target/scp");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        new File(root, "local").mkdirs();
-        new File(root, "remote").mkdirs();
-        assertTrue(root.exists());
-
-        new File("target/scp/local/dir").mkdirs();
-        writeFile(new File("target/scp/local/dir/out1.txt"), data);
-        writeFile(new File("target/scp/local/dir/out2.txt"), data);
-        scp.upload("target/scp/local/dir", "target/scp/remote/", ScpClient.Option.Recursive);
-        assertFileLength(new File("target/scp/remote/dir/out1.txt"), data.length(), 5000);
-        assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
-
-        Utils.deleteRecursive(new File("target/scp/local/dir"));
-        scp.download("target/scp/remote/dir", "target/scp/local", ScpClient.Option.Recursive);
-        assertFileLength(new File("target/scp/local/dir/out1.txt"), data.length(), 5000);
-        assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
 
-        session.close(false).await();
-        client.stop();
+            try {
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
+
+                    ScpClient scp = createScpClient(session);
+
+                    String data = "0123456789\n";
+
+                    File root = new File("target/scp");
+                    Utils.deleteRecursive(root);
+                    root.mkdirs();
+                    new File(root, "local").mkdirs();
+                    new File(root, "remote").mkdirs();
+                    assertTrue(root.exists());
+
+                    new File("target/scp/local/dir").mkdirs();
+                    writeFile(new File("target/scp/local/dir/out1.txt"), data);
+                    writeFile(new File("target/scp/local/dir/out2.txt"), data);
+                    scp.upload("target/scp/local/dir", "target/scp/remote/", ScpClient.Option.Recursive);
+                    assertFileLength(new File("target/scp/remote/dir/out1.txt"), data.length(), 5000);
+                    assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+
+                    Utils.deleteRecursive(new File("target/scp/local/dir"));
+                    scp.download("target/scp/remote/dir", "target/scp/local", ScpClient.Option.Recursive);
+                    assertFileLength(new File("target/scp/local/dir/out1.txt"), data.length(), 5000);
+                    assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
     }
 
     @Test
     public void testScpNativeOnDirWithPattern() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        ClientSession session = client.connect("test", "localhost", port).await().getSession();
-        session.addPasswordIdentity("test");
-        session.auth().verify();
-
-        ScpClient scp = session.createScpClient();
-
-        String data = "0123456789\n";
-
-        File root = new File("target/scp");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        new File(root, "local").mkdirs();
-        new File(root, "remote").mkdirs();
-        assertTrue(root.exists());
-
-        writeFile(new File("target/scp/local/out1.txt"), data);
-        writeFile(new File("target/scp/local/out2.txt"), data);
-        scp.upload("target/scp/local/*", "target/scp/remote/");
-        assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
-        assertFileLength(new File("target/scp/remote/out2.txt"), data.length(), 5000);
-
-        new File("target/scp/local/out1.txt").delete();
-        new File("target/scp/local/out2.txt").delete();
-        scp.download("target/scp/remote/*", "target/scp/local");
-        assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
-        assertFileLength(new File("target/scp/local/out2.txt"), data.length(), 5000);
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
 
-        session.close(false).await();
-        client.stop();
+            try {
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
+
+                    ScpClient scp = createScpClient(session);
+
+                    String data = "0123456789\n";
+
+                    File root = new File("target/scp");
+                    Utils.deleteRecursive(root);
+                    root.mkdirs();
+                    new File(root, "local").mkdirs();
+                    new File(root, "remote").mkdirs();
+                    assertTrue(root.exists());
+
+                    writeFile(new File("target/scp/local/out1.txt"), data);
+                    writeFile(new File("target/scp/local/out2.txt"), data);
+                    scp.upload("target/scp/local/*", "target/scp/remote/");
+                    assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
+                    assertFileLength(new File("target/scp/remote/out2.txt"), data.length(), 5000);
+
+                    new File("target/scp/local/out1.txt").delete();
+                    new File("target/scp/local/out2.txt").delete();
+                    scp.download("target/scp/remote/*", "target/scp/local");
+                    assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
+                    assertFileLength(new File("target/scp/local/out2.txt"), data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
     }
 
     @Test
     public void testScpNativeOnMixedDirAndFiles() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        ClientSession session = client.connect("test", "localhost", port).await().getSession();
-        session.addPasswordIdentity("test");
-        session.auth().verify();
-
-        ScpClient scp = session.createScpClient();
-
-        String data = "0123456789\n";
-
-        File root = new File("target/scp");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        new File(root, "local").mkdirs();
-        new File(root, "remote").mkdirs();
-        assertTrue(root.exists());
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
 
-        new File("target/scp/local/dir").mkdirs();
-        writeFile(new File("target/scp/local/out1.txt"), data);
-        writeFile(new File("target/scp/local/dir/out2.txt"), data);
-        scp.upload("target/scp/local/*", "target/scp/remote/", ScpClient.Option.Recursive);
-        assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
-        assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
-
-        Utils.deleteRecursive(new File("target/scp/local/out1.txt"));
-        Utils.deleteRecursive(new File("target/scp/local/dir"));
-        scp.download("target/scp/remote/*", "target/scp/local");
-        assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
-        assertFalse(new File("target/scp/local/dir/out2.txt").exists());
-
-        Utils.deleteRecursive(new File("target/scp/local/out1.txt"));
-        scp.download("target/scp/remote/*", "target/scp/local", ScpClient.Option.Recursive);
-        assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
-        assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
-
-        session.close(false).await();
-        client.stop();
+            try {
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
+
+                    ScpClient scp = createScpClient(session);
+
+                    String data = "0123456789\n";
+
+                    File root = new File("target/scp");
+                    Utils.deleteRecursive(root);
+                    root.mkdirs();
+                    new File(root, "local").mkdirs();
+                    new File(root, "remote").mkdirs();
+                    assertTrue(root.exists());
+
+                    new File("target/scp/local/dir").mkdirs();
+                    writeFile(new File("target/scp/local/out1.txt"), data);
+                    writeFile(new File("target/scp/local/dir/out2.txt"), data);
+                    scp.upload("target/scp/local/*", "target/scp/remote/", ScpClient.Option.Recursive);
+                    assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
+                    assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+
+                    Utils.deleteRecursive(new File("target/scp/local/out1.txt"));
+                    Utils.deleteRecursive(new File("target/scp/local/dir"));
+                    scp.download("target/scp/remote/*", "target/scp/local");
+                    assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
+                    assertFalse(new File("target/scp/local/dir/out2.txt").exists());
+
+                    Utils.deleteRecursive(new File("target/scp/local/out1.txt"));
+                    scp.download("target/scp/remote/*", "target/scp/local", ScpClient.Option.Recursive);
+                    assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
+                    assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
     }
 
     @Test
     public void testScpNativePreserveAttributes() throws Exception {
         // Ignore this test if running a Windows system
-        if (System.getProperty("os.name").toLowerCase().contains("win")) {
-            return;
-        }
-
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        ClientSession session = client.connect("test", "localhost", port).await().getSession();
-        session.addPasswordIdentity("test");
-        session.auth().verify();
+        Assume.assumeFalse("Skip test for Windows", OsUtils.isWin32());
 
-        ScpClient scp = session.createScpClient();
-
-        String data = "0123456789\n";
-
-        File root = new File("target/scp");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        new File(root, "local").mkdirs();
-        new File(root, "remote").mkdirs();
-        assertTrue(root.exists());
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
 
-        new File("target/scp/local/dir").mkdirs();
-        long lastMod = new File("target/scp/local/dir").lastModified() - TimeUnit.DAYS.toMillis(1);
-
-        writeFile(new File("target/scp/local/out1.txt"), data);
-        writeFile(new File("target/scp/local/dir/out2.txt"), data);
-        new File("target/scp/local/out1.txt").setLastModified(lastMod);
-        new File("target/scp/local/out1.txt").setExecutable(true, true);
-        new File("target/scp/local/out1.txt").setWritable(false, false);
-        new File("target/scp/local/dir/out2.txt").setLastModified(lastMod);
-        scp.upload("target/scp/local/*", "target/scp/remote/", ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
-        assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
-        assertEquals(lastMod, new File("target/scp/remote/out1.txt").lastModified());
-        assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
-        assertEquals(lastMod, new File("target/scp/remote/dir/out2.txt").lastModified());
-
-        Utils.deleteRecursive(new File("target/scp/local"));
-        new File("target/scp/local").mkdirs();
-        scp.download("target/scp/remote/*", "target/scp/local", ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
-        assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
-        assertEquals(lastMod, new File("target/scp/local/out1.txt").lastModified());
-        assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
-        assertEquals(lastMod, new File("target/scp/local/dir/out2.txt").lastModified());
-
-        session.close(false).await();
-        client.stop();
+            try {
+                try (ClientSession session = client.connect("test", "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity("test");
+                    session.auth().verify();
+
+                    ScpClient scp = createScpClient(session);
+
+                    String data = "0123456789\n";
+
+                    File root = new File("target/scp");
+                    Utils.deleteRecursive(root);
+                    root.mkdirs();
+                    new File(root, "local").mkdirs();
+                    new File(root, "remote").mkdirs();
+                    assertTrue(root.exists());
+
+                    new File("target/scp/local/dir").mkdirs();
+                    long lastMod = new File("target/scp/local/dir").lastModified() - TimeUnit.DAYS.toMillis(1);
+
+                    writeFile(new File("target/scp/local/out1.txt"), data);
+                    writeFile(new File("target/scp/local/dir/out2.txt"), data);
+                    new File("target/scp/local/out1.txt").setLastModified(lastMod);
+                    new File("target/scp/local/out1.txt").setExecutable(true, true);
+                    new File("target/scp/local/out1.txt").setWritable(false, false);
+                    new File("target/scp/local/dir/out2.txt").setLastModified(lastMod);
+                    scp.upload("target/scp/local/*", "target/scp/remote/", ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
+                    assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
+                    assertEquals(lastMod, new File("target/scp/remote/out1.txt").lastModified());
+                    assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+                    assertEquals(lastMod, new File("target/scp/remote/dir/out2.txt").lastModified());
+
+                    Utils.deleteRecursive(new File("target/scp/local"));
+                    new File("target/scp/local").mkdirs();
+                    scp.download("target/scp/remote/*", "target/scp/local", ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
+                    assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
+                    assertEquals(lastMod, new File("target/scp/local/out1.txt").lastModified());
+                    assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+                    assertEquals(lastMod, new File("target/scp/local/dir/out2.txt").lastModified());
+                }
+            } finally {
+                client.stop();
+            }
+        }
     }
 
     private void writeFile(File file, String data) throws IOException {
@@ -598,7 +624,7 @@ public class ScpTest extends BaseTest {
         }
     }
 
-    protected void assertFileLength(File file, long length, long timeout) throws Exception{
+    protected void assertFileLength(File file, long length, long timeout) throws Exception {
         boolean ok = false;
         while (timeout > 0) {
             if (file.exists() && file.length() == length) {
@@ -694,7 +720,7 @@ public class ScpTest extends BaseTest {
         InputStream is = c.getInputStream();
         c.connect();
         assertEquals(0, is.read());
-        os.write(("C7777 "+ data.length() + " " + name + "\n").getBytes());
+        os.write(("C7777 " + data.length() + " " + name + "\n").getBytes());
         os.flush();
         assertEquals(0, is.read());
         os.write(data.getBytes());
@@ -713,7 +739,7 @@ public class ScpTest extends BaseTest {
         c.setCommand("scp -t " + path);
         c.connect();
         assertEquals(0, is.read());
-        os.write(("C7777 "+ data.length() + " " + name + "\n").getBytes());
+        os.write(("C7777 " + data.length() + " " + name + "\n").getBytes());
         os.flush();
         assertEquals(2, is.read());
         c.disconnect();
@@ -742,9 +768,9 @@ public class ScpTest extends BaseTest {
         assertEquals(0, is.read());
     }
 
-    private String readLine(InputStream in) throws IOException {
+    private static String readLine(InputStream in) throws IOException {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        for (;;) {
+        for (; ; ) {
             int c = in.read();
             if (c == '\n') {
                 return baos.toString();
@@ -756,4 +782,43 @@ public class ScpTest extends BaseTest {
         }
     }
 
+    private ScpClient createScpClient(ClientSession session) {
+        final Logger logger = LoggerFactory.getLogger(getClass().getName() + "[" + getCurrentTestName() + "]");
+        return session.createScpClient(new ScpTransferEventListener() {
+            @Override
+            public void startFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms) {
+                logEvent("starFolderEvent", op, file, false, -1L, perms, null);
+            }
+
+            @Override
+            public void startFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms) {
+                logEvent("startFileEvent", op, file, true, length, perms, null);
+
+            }
+
+            @Override
+            public void endFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms, Throwable thrown) {
+                logEvent("endFolderEvent", op, file, false, -1L, perms, thrown);
+            }
+
+            @Override
+            public void endFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms, Throwable thrown) {
+                logEvent("endFileEvent", op, file, true, length, perms, thrown);
+            }
+
+            private void logEvent(String type, FileOperation op, Path path, boolean isFile, long length, Collection<PosixFilePermission> perms, Throwable t) {
+                StringBuilder sb = new StringBuilder(Byte.MAX_VALUE);
+                sb.append('\t').append(type)
+                        .append('[').append(op).append(']')
+                        .append(' ').append(isFile ? "File" : "Directory").append('=').append(path)
+                        .append(' ').append("length=").append(length)
+                        .append(' ').append("perms=").append(perms)
+                ;
+                if (t != null) {
+                    sb.append(' ').append("ERROR=").append(t.getClass().getSimpleName()).append(": ").append(t.getMessage());
+                }
+                logger.info(sb.toString());
+            }
+        });
+    }
 }