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 2013/07/26 10:37:26 UTC
[1/3] git commit: [SSHD-243] The sshd client always send to channel 0
Updated Branches:
refs/heads/master e05ade90c -> f549a71bc
[SSHD-243] The sshd client always send to channel 0
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/f31ac81b
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/f31ac81b
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/f31ac81b
Branch: refs/heads/master
Commit: f31ac81b98e2b7b7468c2a39fcd4eceed8c533c1
Parents: e05ade9
Author: Guillaume Nodet <gn...@apache.org>
Authored: Thu Jul 25 21:32:43 2013 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Thu Jul 25 21:32:43 2013 +0200
----------------------------------------------------------------------
.../org/apache/sshd/client/channel/ChannelSession.java | 10 +++++-----
.../org/apache/sshd/common/session/AbstractSession.java | 6 ++----
sshd-core/src/test/java/org/apache/sshd/LoadTest.java | 2 +-
3 files changed, 8 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f31ac81b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java
index 3166666..d6c9124 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java
@@ -42,6 +42,11 @@ public class ChannelSession extends AbstractClientChannel {
}
public OpenFuture open() throws IOException {
+ return internalOpen();
+ }
+
+ @Override
+ protected void doOpen() throws IOException {
invertedIn = new ChannelOutputStream(this, remoteWindow, log, SshConstants.Message.SSH_MSG_CHANNEL_DATA);
if (out == null) {
ChannelPipedInputStream pis = new ChannelPipedInputStream(localWindow);
@@ -55,11 +60,6 @@ public class ChannelSession extends AbstractClientChannel {
err = pos;
invertedErr = pis;
}
- return internalOpen();
- }
-
- @Override
- protected void doOpen() throws IOException {
if (in != null) {
streamPumper = new Thread("ClientInputStreamPump") {
@Override
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f31ac81b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
index ad83261..f86e787 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
@@ -100,7 +100,7 @@ public abstract class AbstractSession implements Session {
/** Map of channels keyed by the identifier */
protected final Map<Integer, Channel> channels = new ConcurrentHashMap<Integer, Channel>();
/** Next channel identifier */
- protected int nextChannelId;
+ protected static AtomicInteger nextChannelId = new AtomicInteger(100);
/** Session listener */
protected final List<SessionListener> listeners = new ArrayList<SessionListener>();
@@ -983,9 +983,7 @@ public abstract class AbstractSession implements Session {
protected int getNextChannelId() {
- synchronized (channels) {
- return nextChannelId++;
- }
+ return nextChannelId.incrementAndGet();
}
public int registerChannel(Channel channel) throws IOException {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f31ac81b/sshd-core/src/test/java/org/apache/sshd/LoadTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/LoadTest.java b/sshd-core/src/test/java/org/apache/sshd/LoadTest.java
index 299b8e3..2fd30a7 100644
--- a/sshd-core/src/test/java/org/apache/sshd/LoadTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/LoadTest.java
@@ -123,7 +123,7 @@ public class LoadTest {
ByteArrayOutputStream err = new ByteArrayOutputStream();
channel.setOut(out);
channel.setErr(err);
- channel.open();
+ channel.open().await();
OutputStream pipedIn = channel.getInvertedIn();
msg += "\nexit\n";
[2/3] git commit: [SSHD-90] Fix some problems with SCP client Support
for attributes preservation on copy
Posted by gn...@apache.org.
[SSHD-90] Fix some problems with SCP client
Support for attributes preservation on copy
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/725bcd9f
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/725bcd9f
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/725bcd9f
Branch: refs/heads/master
Commit: 725bcd9f2fc7c0027eaec7c30e439b375f088254
Parents: f31ac81
Author: Guillaume Nodet <gn...@apache.org>
Authored: Thu Jul 25 23:49:52 2013 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Thu Jul 25 23:49:52 2013 +0200
----------------------------------------------------------------------
.../java/org/apache/sshd/client/ScpClient.java | 18 +-
.../client/channel/AbstractClientChannel.java | 4 +
.../sshd/client/scp/DefaultScpClient.java | 105 +++++---
.../common/channel/ChannelPipedInputStream.java | 2 +-
.../org/apache/sshd/common/scp/ScpHelper.java | 189 +++++++++++--
.../apache/sshd/server/command/ScpCommand.java | 25 +-
.../sshd/server/command/ScpCommandFactory.java | 32 +--
.../src/test/java/org/apache/sshd/ScpTest.java | 263 +++++++++++++++++++
8 files changed, 518 insertions(+), 120 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
index d2bdaa8..381a210 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
@@ -24,18 +24,18 @@ import java.io.IOException;
*/
public interface ScpClient {
- void download(String remote, String local) throws IOException;
+ enum Option {
+ Recursive,
+ PreserveAttributes,
+ TargetIsDirectory
+ }
- void download(String remote, String local, boolean recursive) throws IOException;
+ void download(String remote, String local, Option... options) throws IOException;
- void download(String[] remote, String local) throws Exception;
+ void download(String[] remote, String local, Option... options) throws Exception;
- void download(String[] remote, String local, boolean recursive) throws Exception;
+ void upload(String local, String remote, Option... options) throws IOException;
- void upload(String remote, String local) throws IOException;
-
- void upload(String remote, String local, boolean recursive) throws IOException;
-
- void upload(String[] local, String remote, boolean recursive) throws IOException;
+ void upload(String[] local, String remote, Option... options) throws IOException;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
index 8d04dba..b786c39 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
@@ -227,6 +227,10 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C
}
protected void doWriteData(byte[] data, int off, int len) throws IOException {
+ // If we're already closing, ignore incoming data
+ if (closing.get()) {
+ return;
+ }
if (out != null) {
out.write(data, off, len);
out.flush();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/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 1b590c2..911eb5c 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
@@ -20,7 +20,11 @@ package org.apache.sshd.client.scp;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import org.apache.sshd.ClientSession;
import org.apache.sshd.client.ScpClient;
@@ -42,39 +46,44 @@ public class DefaultScpClient implements ScpClient {
this.clientSession = clientSession;
}
- public void download(String remote, String local) throws IOException {
- download(new String[] { remote }, local, false, false);
- }
-
- public void download(String remote, String local, boolean recursive) throws IOException {
- download(new String[] { remote }, local, recursive, false);
- }
-
- public void download(String[] remote, String local) throws IOException {
- download(remote, local, false, true);
+ public void download(String remote, String local, Option... options) throws IOException {
+ local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
+ remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
+ download(remote, local, Arrays.asList(options));
}
- public void download(String[] remote, String local, boolean recursive) throws IOException {
- download(remote, local, recursive, true);
+ 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);
+ if (remote.length > 1) {
+ opts.add(Option.TargetIsDirectory);
+ }
+ for (String r : remote) {
+ download(r, local, opts);
+ }
}
- private void download(String[] remote, String local, boolean recursive, boolean shouldBeDir) throws IOException {
+ protected void download(String remote, String local, Collection<Option> options) throws IOException {
local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
+
StringBuilder sb = new StringBuilder("scp");
- if (recursive) {
+ if (options.contains(Option.Recursive)) {
sb.append(" -r");
}
- sb.append(" -f");
- for (String r : remote) {
- r = checkNotNullAndNotEmpty(r, "Invalid argument remote: {}");
- sb.append(" ").append(r);
+ if (options.contains(Option.PreserveAttributes)) {
+ sb.append(" -p");
}
+ sb.append(" -f");
+ sb.append(" --");
+ sb.append(" ");
+ sb.append(remote);
FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
FileSystemView fs = factory.createFileSystemView(clientSession);
SshFile target = fs.getFile(local);
- if (shouldBeDir) {
+ if (options.contains(Option.TargetIsDirectory)) {
if (!target.doesExist()) {
throw new SshException("Target directory " + target.toString() + " does not exists");
}
@@ -92,42 +101,47 @@ public class DefaultScpClient implements ScpClient {
ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
- helper.receive(target, recursive, shouldBeDir);
+ helper.receive(target,
+ options.contains(Option.Recursive),
+ options.contains(Option.TargetIsDirectory),
+ options.contains(Option.PreserveAttributes));
channel.close(false);
}
- public void upload(String remote, String local) throws IOException {
- upload(new String[] { remote }, local, false, false);
- }
-
- public void upload(String remote, String local, boolean recursive) throws IOException {
- upload(new String[] { remote }, local, recursive, false);
- }
-
- public void upload(String[] local, String remote) throws IOException {
- upload(local, remote, false, true);
+ public void upload(String local, String remote, Option... options) throws IOException {
+ local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
+ remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
+ upload(new String[] { local }, remote, options(options));
}
- public void upload(String[] local, String remote, boolean recursive) throws IOException {
- upload(local, remote, false, true);
+ 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);
+ if (local.length > 1) {
+ opts.add(Option.TargetIsDirectory);
+ }
+ upload(local, remote, opts);
}
- private void upload(String[] local, String remote, boolean recursive, boolean shouldBeDir) throws IOException {
+ protected void upload(String[] local, String remote, Collection<Option> options) throws IOException {
local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
StringBuilder sb = new StringBuilder("scp");
- if (recursive) {
+ if (options.contains(Option.Recursive)) {
sb.append(" -r");
}
- if (shouldBeDir) {
+ if (options.contains(Option.TargetIsDirectory)) {
sb.append(" -d");
}
- sb.append(" -t");
- for (String r : local) {
- r = checkNotNullAndNotEmpty(r, "Invalid argument remote: {}");
- sb.append(" ").append(r);
+ if (options.contains(Option.PreserveAttributes)) {
+ sb.append(" -p");
}
+ sb.append(" -t");
+ sb.append(" --");
+ sb.append(" ");
+ sb.append(remote);
ChannelExec channel = clientSession.createExecChannel(sb.toString());
try {
channel.open().await();
@@ -138,13 +152,22 @@ public class DefaultScpClient implements ScpClient {
FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
FileSystemView fs = factory.createFileSystemView(clientSession);
ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
- SshFile target = fs.getFile(remote);
- helper.send(Arrays.asList(local), recursive);
+ helper.send(Arrays.asList(local),
+ options.contains(Option.Recursive),
+ options.contains(Option.PreserveAttributes));
channel.close(false);
}
+ private List<Option> options(Option... options) {
+ List<Option> opts = new ArrayList<Option>();
+ if (options != null) {
+ opts.addAll(Arrays.asList(options));
+ }
+ return opts;
+ }
+
private <T> T checkNotNull(T t, String message) {
if (t == null) {
throw new IllegalStateException(String.format(message, t));
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
index e68d074..1d7b489 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
@@ -96,7 +96,7 @@ public class ChannelPipedInputStream extends InputStream {
lock.lock();
try {
for (;;) {
- if (closed) {
+ if (closed && !writerClosed) {
throw new IOException("Pipe closed");
}
if (buffer.available() > 0) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/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 17b55c1..b9198f3 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
@@ -23,7 +23,10 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.EnumSet;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.file.FileSystemView;
@@ -43,6 +46,16 @@ public class ScpHelper {
public static final int WARNING = 1;
public static final int ERROR = 2;
+ public static final int S_IRUSR = 0000400;
+ public static final int S_IWUSR = 0000200;
+ public static final int S_IXUSR = 0000100;
+ public static final int S_IRGRP = 0000040;
+ public static final int S_IWGRP = 0000020;
+ public static final int S_IXGRP = 0000010;
+ public static final int S_IROTH = 0000004;
+ public static final int S_IWOTH = 0000002;
+ public static final int S_IXOTH = 0000001;
+
protected final FileSystemView root;
protected final InputStream in;
protected final OutputStream out;
@@ -53,7 +66,7 @@ public class ScpHelper {
this.root = root;
}
- public void receive(SshFile path, boolean recursive, boolean shouldBeDir) throws IOException {
+ public void receive(SshFile path, boolean recursive, boolean shouldBeDir, boolean preserve) throws IOException {
if (shouldBeDir) {
if (!path.doesExist()) {
throw new SshException("Target directory " + path.toString() + " does not exists");
@@ -63,6 +76,7 @@ public class ScpHelper {
}
}
ack();
+ long[] time = null;
for (;;)
{
String line;
@@ -76,13 +90,17 @@ public class ScpHelper {
isDir = true;
case 'C':
line = ((char) c) + readLine();
+ log.debug("Received header: " + line);
break;
case 'T':
- readLine();
+ line = ((char) c) + readLine();
+ log.debug("Received header: " + line);
+ time = parseTime(line);
ack();
continue;
case 'E':
- readLine();
+ line = ((char) c) + readLine();
+ log.debug("Received header: " + line);
return;
default:
//a real ack that has been acted upon already
@@ -91,19 +109,21 @@ public class ScpHelper {
if (recursive && isDir)
{
- receiveDir(line, path);
+ receiveDir(line, path, time, preserve);
+ time = null;
}
else
{
- receiveFile(line, path);
+ receiveFile(line, path, time, preserve);
+ time = null;
}
}
}
- public void receiveDir(String header, SshFile path) throws IOException {
+ public void receiveDir(String header, SshFile path, long[] time, boolean preserve) throws IOException {
if (log.isDebugEnabled()) {
- log.debug("Writing dir {}", path);
+ log.debug("Receiving directory {}", path);
}
if (!header.startsWith("D")) {
throw new IOException("Expected a D message but got '" + header + "'");
@@ -128,20 +148,34 @@ public class ScpHelper {
throw new IOException("Could not create directory " + file);
}
+ if (preserve) {
+ Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>();
+ attrs.put(SshFile.Attribute.Permissions, fromOctalPerms(perms));
+ if (time != null) {
+ attrs.put(SshFile.Attribute.LastModifiedTime, time[0]);
+ attrs.put(SshFile.Attribute.LastAccessTime, time[1]);
+ }
+ file.setAttributes(attrs);
+ }
+
ack();
+ time = null;
for (;;) {
header = readLine();
+ log.debug("Received header: " + header);
if (header.startsWith("C")) {
- receiveFile(header, file);
+ receiveFile(header, file, time, preserve);
+ time = null;
} else if (header.startsWith("D")) {
- receiveDir(header, file);
+ receiveDir(header, file, time, preserve);
+ time = null;
} else if (header.equals("E")) {
ack();
break;
- } else if (header.equals("T")) {
+ } else if (header.startsWith("T")) {
+ time = parseTime(header);
ack();
- break;
} else {
throw new IOException("Unexpected message: '" + header + "'");
}
@@ -149,9 +183,9 @@ public class ScpHelper {
}
- public void receiveFile(String header, SshFile path) throws IOException {
+ public void receiveFile(String header, SshFile path, long[] time, boolean preserve) throws IOException {
if (log.isDebugEnabled()) {
- log.debug("Writing file {}", path);
+ log.debug("Receiving file {}", path);
}
if (!header.startsWith("C")) {
throw new IOException("Expected a C message but got '" + header + "'");
@@ -194,6 +228,16 @@ public class ScpHelper {
os.close();
}
+ if (preserve) {
+ Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>();
+ attrs.put(SshFile.Attribute.Permissions, fromOctalPerms(perms));
+ if (time != null) {
+ attrs.put(SshFile.Attribute.LastModifiedTime, time[0]);
+ attrs.put(SshFile.Attribute.LastAccessTime, time[1]);
+ }
+ file.setAttributes(attrs);
+ }
+
ack();
readAck(false);
}
@@ -219,7 +263,7 @@ public class ScpHelper {
}
}
- public void send(List<String> paths, boolean recursive) throws IOException {
+ public void send(List<String> paths, boolean recursive, boolean preserve) throws IOException {
for (String pattern : paths) {
int idx = pattern.indexOf('*');
if (idx >= 0) {
@@ -233,13 +277,13 @@ public class ScpHelper {
for (String path : included) {
SshFile file = root.getFile(basedir + "/" + path);
if (file.isFile()) {
- sendFile(file);
+ sendFile(file, preserve);
} else if (file.isDirectory()) {
if (!recursive) {
out.write(ScpHelper.WARNING);
out.write((path + " not a regular file\n").getBytes());
} else {
- sendDir(file);
+ sendDir(file, preserve);
}
} else {
out.write(ScpHelper.WARNING);
@@ -258,12 +302,12 @@ public class ScpHelper {
throw new IOException(file + ": no such file or directory");
}
if (file.isFile()) {
- sendFile(file);
+ sendFile(file, preserve);
} else if (file.isDirectory()) {
if (!recursive) {
throw new IOException(file + " not a regular file");
} else {
- sendDir(file);
+ sendDir(file, preserve);
}
} else {
throw new IOException(file + ": unknown file type");
@@ -272,15 +316,33 @@ public class ScpHelper {
}
}
- public void sendFile(SshFile path) throws IOException {
+ public void sendFile(SshFile path, boolean preserve) throws IOException {
if (log.isDebugEnabled()) {
- log.debug("Reading file {}", path);
+ log.debug("Sending file {}", path);
+ }
+
+ Map<SshFile.Attribute,Object> attrs = path.getAttributes(true);
+ if (preserve) {
+ StringBuffer buf = new StringBuffer();
+ buf.append("T");
+ buf.append(attrs.get(SshFile.Attribute.LastModifiedTime));
+ buf.append(" ");
+ buf.append("0");
+ buf.append(" ");
+ buf.append(attrs.get(SshFile.Attribute.LastAccessTime));
+ buf.append(" ");
+ buf.append("0");
+ buf.append("\n");
+ out.write(buf.toString().getBytes());
+ out.flush();
+ readAck(false);
}
+
StringBuffer buf = new StringBuffer();
buf.append("C");
- buf.append("0644"); // TODO: what about perms
+ buf.append(preserve ? toOctalPerms((EnumSet<SshFile.Permission>) attrs.get(SshFile.Attribute.Permissions)) : "0644");
buf.append(" ");
- buf.append(path.getSize()); // length
+ buf.append(attrs.get(SshFile.Attribute.Size)); // length
buf.append(" ");
buf.append(path.getName());
buf.append("\n");
@@ -305,13 +367,30 @@ public class ScpHelper {
readAck(false);
}
- public void sendDir(SshFile path) throws IOException {
+ public void sendDir(SshFile path, boolean preserve) throws IOException {
if (log.isDebugEnabled()) {
- log.debug("Reading directory {}", path);
+ log.debug("Sending directory {}", path);
+ }
+ Map<SshFile.Attribute,Object> attrs = path.getAttributes(true);
+ if (preserve) {
+ StringBuffer buf = new StringBuffer();
+ buf.append("T");
+ buf.append(attrs.get(SshFile.Attribute.LastModifiedTime));
+ buf.append(" ");
+ buf.append("0");
+ buf.append(" ");
+ buf.append(attrs.get(SshFile.Attribute.LastAccessTime));
+ buf.append(" ");
+ buf.append("0");
+ buf.append("\n");
+ out.write(buf.toString().getBytes());
+ out.flush();
+ readAck(false);
}
+
StringBuffer buf = new StringBuffer();
buf.append("D");
- buf.append("0755"); // what about perms
+ buf.append(preserve ? toOctalPerms((EnumSet<SshFile.Permission>) attrs.get(SshFile.Attribute.Permissions)) : "0755");
buf.append(" ");
buf.append("0"); // length
buf.append(" ");
@@ -323,9 +402,9 @@ public class ScpHelper {
for (SshFile child : path.listSshFiles()) {
if (child.isFile()) {
- sendFile(child);
+ sendFile(child, preserve);
} else if (child.isDirectory()) {
- sendDir(child);
+ sendDir(child, preserve);
}
}
@@ -334,6 +413,62 @@ public class ScpHelper {
readAck(false);
}
+ private long[] parseTime(String line) {
+ String[] numbers = line.substring(1).split(" ");
+ return new long[] { Long.parseLong(numbers[0]), Long.parseLong(numbers[2]) };
+ }
+
+ public static String toOctalPerms(EnumSet<SshFile.Permission> perms) {
+ int pf = 0;
+ for (SshFile.Permission p : perms) {
+ switch (p) {
+ case UserRead: pf |= S_IRUSR; break;
+ case UserWrite: pf |= S_IWUSR; break;
+ case UserExecute: pf |= S_IXUSR; break;
+ case GroupRead: pf |= S_IRGRP; break;
+ case GroupWrite: pf |= S_IWGRP; break;
+ case GroupExecute: pf |= S_IXGRP; break;
+ case OthersRead: pf |= S_IROTH; break;
+ case OthersWrite: pf |= S_IWOTH; break;
+ case OthersExecute: pf |= S_IXOTH; break;
+ }
+ }
+ return String.format("%04o", pf);
+ }
+
+ public static EnumSet<SshFile.Permission> fromOctalPerms(String str) {
+ int perms = Integer.parseInt(str, 8);
+ EnumSet<SshFile.Permission> p = EnumSet.noneOf(SshFile.Permission.class);
+ if ((perms & S_IRUSR) != 0) {
+ p.add(SshFile.Permission.UserRead);
+ }
+ if ((perms & S_IWUSR) != 0) {
+ p.add(SshFile.Permission.UserWrite);
+ }
+ if ((perms & S_IXUSR) != 0) {
+ p.add(SshFile.Permission.UserExecute);
+ }
+ if ((perms & S_IRGRP) != 0) {
+ p.add(SshFile.Permission.GroupRead);
+ }
+ if ((perms & S_IWGRP) != 0) {
+ p.add(SshFile.Permission.GroupWrite);
+ }
+ if ((perms & S_IXGRP) != 0) {
+ p.add(SshFile.Permission.GroupExecute);
+ }
+ if ((perms & S_IROTH) != 0) {
+ p.add(SshFile.Permission.OthersRead);
+ }
+ if ((perms & S_IWOTH) != 0) {
+ p.add(SshFile.Permission.OthersWrite);
+ }
+ if ((perms & S_IXOTH) != 0) {
+ p.add(SshFile.Permission.OthersExecute);
+ }
+ return p;
+ }
+
public void ack() throws IOException {
out.write(0);
out.flush();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/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 8ecd7cd..d67715c 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
@@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.apache.sshd.common.file.FileSystemAware;
@@ -52,19 +53,17 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
protected boolean optD;
protected boolean optP; // TODO: handle modification times
protected FileSystemView root;
- protected List<String> paths;
+ protected String path;
protected InputStream in;
protected OutputStream out;
protected OutputStream err;
protected ExitCallback callback;
protected IOException error;
- public ScpCommand(String[] args) {
- name = Arrays.asList(args).toString();
- if (log.isDebugEnabled()) {
- log.debug("Executing command {}", name);
- }
- paths = new ArrayList<String>();
+ public ScpCommand(String command) {
+ this.name = command;
+ log.debug("Executing command {}", command);
+ String[] args = command.split(" ");
for (int i = 1; i < args.length; i++) {
if (args[i].charAt(0) == '-') {
for (int j = 1; j < args[i].length(); j++) {
@@ -89,16 +88,14 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
// return;
}
}
- } else if (i == args.length - 1) {
- paths.add(args[args.length - 1]);
+ } else {
+ path = command.substring(command.indexOf(args[i-1]) + args[i-1].length() + 1);
+ break;
}
}
if (!optF && !optT) {
error = new IOException("Either -f or -t option should be set");
}
- if (optT && paths.size() != 1) {
- error = new IOException("One and only one path must be given with -t option");
- }
}
public void setInputStream(InputStream in) {
@@ -137,9 +134,9 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
ScpHelper helper = new ScpHelper(in, out, root);
try {
if (optT) {
- helper.receive(root.getFile(paths.get(0)), optR, optD);
+ helper.receive(root.getFile(path), optR, optD, optP);
} else if (optF) {
- helper.send(paths, optR);
+ helper.send(Collections.singletonList(path), optR, optP);
} else {
throw new IOException("Unsupported mode");
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/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 61bded0..51eee6c 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
@@ -55,7 +55,10 @@ public class ScpCommandFactory implements CommandFactory {
*/
public Command createCommand(String command) {
try {
- return new ScpCommand(splitCommandString(command));
+ if (!command.startsWith("scp")) {
+ throw new IllegalArgumentException("Unknown command, does not begin with 'scp'");
+ }
+ return new ScpCommand(command);
} catch (IllegalArgumentException iae) {
if (delegate != null) {
return delegate.createCommand(command);
@@ -64,31 +67,4 @@ public class ScpCommandFactory implements CommandFactory {
}
}
- private String[] splitCommandString(String command) {
- if (!command.trim().startsWith("scp")) {
- throw new IllegalArgumentException("Unknown command, does not begin with 'scp'");
- }
-
- String[] args = command.split(" ");
- List<String> parts = new ArrayList<String>();
- parts.add(args[0]);
- for (int i = 1; i < args.length; i++) {
- if (!args[i].trim().startsWith("-")) {
- parts.add(concatenateWithSpace(args, i));
- break;
- } else {
- parts.add(args[i]);
- }
- }
- return parts.toArray(new String[parts.size()]);
- }
-
- private String concatenateWithSpace(String[] args, int from) {
- StringBuilder sb = new StringBuilder();
-
- for (int i = from; i < args.length; i++) {
- sb.append(args[i] + " ");
- }
- return sb.toString().trim();
- }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/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 835b931..19572b6 100644
--- a/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
@@ -20,11 +20,13 @@ package org.apache.sshd;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.util.Properties;
+import java.util.concurrent.TimeUnit;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SCPClient;
@@ -33,6 +35,7 @@ import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Logger;
import com.jcraft.jsch.UserInfo;
+import org.apache.sshd.client.ScpClient;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.util.BogusPasswordAuthenticator;
import org.apache.sshd.util.EchoShellFactory;
@@ -45,6 +48,7 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* Test for SCP support.
@@ -62,6 +66,7 @@ public class ScpTest {
ServerSocket s = new ServerSocket(0);
port = s.getLocalPort();
s.close();
+// port = 8102;
sshd = SshServer.setUpDefaultServer();
sshd.setPort(port);
@@ -123,6 +128,264 @@ public class ScpTest {
}
@Test
+ public void testScpNativeOnSingleFile() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ 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
+ }
+ 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);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativeOnMultipleFiles() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ 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 {
+ 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);
+
+ session.close(false);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativeOnRecursiveDirs() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ 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);
+
+ session.close(false);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativeOnDirWithPattern() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ 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);
+
+ session.close(false);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativeOnMixedDirAndFiles() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ 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/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);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativePreserveAttributes() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ 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();
+ 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);
+ client.stop();
+ }
+
+ private void writeFile(File file, String data) throws IOException {
+ FileOutputStream fos = new FileOutputStream(file);
+ try {
+ fos.write(data.getBytes());
+ } finally {
+ fos.close();
+ }
+ }
+
+ @Test
public void testScp() throws Exception {
session = getJschSession();
[3/3] git commit: [SSHD-246] Let commands finish stream consumption
and cleanly exit
Posted by gn...@apache.org.
[SSHD-246] Let commands finish stream consumption and cleanly exit
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/f549a71b
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/f549a71b
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/f549a71b
Branch: refs/heads/master
Commit: f549a71bc1559df036b2149db0f2a405595b329b
Parents: 725bcd9
Author: Guillaume Nodet <gn...@apache.org>
Authored: Fri Jul 26 10:37:12 2013 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Fri Jul 26 10:37:12 2013 +0200
----------------------------------------------------------------------
.../sshd/agent/unix/AgentForwardedChannel.java | 4 +-
.../client/channel/AbstractClientChannel.java | 8 +-
.../sshd/client/channel/ChannelSession.java | 5 +-
.../sshd/common/channel/AbstractChannel.java | 77 ++++++++++++--------
.../common/channel/ChannelPipedInputStream.java | 4 +-
.../sshd/common/forward/TcpipClientChannel.java | 14 +++-
.../sshd/server/ServerFactoryManager.java | 7 ++
.../server/channel/AbstractServerChannel.java | 1 +
.../sshd/server/channel/ChannelSession.java | 45 +++++++++++-
.../sshd/server/x11/X11ForwardSupport.java | 14 +++-
10 files changed, 131 insertions(+), 48 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentForwardedChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentForwardedChannel.java b/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentForwardedChannel.java
index 3207074..4f23584 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentForwardedChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentForwardedChannel.java
@@ -66,9 +66,9 @@ public class AgentForwardedChannel extends AbstractClientChannel implements Runn
}
@Override
- protected synchronized void doClose() {
+ protected synchronized void postClose() {
Socket.close(socket);
- super.doClose();
+ super.postClose();
}
protected synchronized void doWriteData(byte[] data, int off, int len) throws IOException {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
index b786c39..6a332d4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
@@ -123,9 +123,9 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C
}
@Override
- protected void doClose() {
- super.doClose();
+ protected void postClose() {
IoUtils.closeQuietly(invertedIn, invertedOut, invertedErr, in, out, err);
+ super.postClose();
}
public int waitFor(int mask, long timeout) {
@@ -207,7 +207,7 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C
} catch (Exception e) {
this.openFuture.setException(e);
this.closeFuture.setClosed();
- this.doClose();
+ this.postClose();
} finally {
notifyStateChanged();
}
@@ -222,7 +222,7 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C
this.openFailureMsg = msg;
this.openFuture.setException(new SshException(msg));
this.closeFuture.setClosed();
- this.doClose();
+ this.postClose();
notifyStateChanged();
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java
index d6c9124..e18378a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java
@@ -26,6 +26,7 @@ import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.channel.ChannelOutputStream;
import org.apache.sshd.common.channel.ChannelPipedInputStream;
import org.apache.sshd.common.channel.ChannelPipedOutputStream;
+import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.util.Buffer;
/**
@@ -76,12 +77,12 @@ public class ChannelSession extends AbstractClientChannel {
}
@Override
- protected void doClose() {
- super.doClose();
+ protected void postClose() {
if (streamPumper != null) {
streamPumper.interrupt();
streamPumper = null;
}
+ super.postClose();
}
protected void pumpInputStream() {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
index 71d9fa4..a0caaf8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/AbstractChannel.java
@@ -29,6 +29,9 @@ import org.apache.sshd.common.Session;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.DefaultSshFuture;
+import org.apache.sshd.common.future.SshFuture;
+import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.common.util.BufferUtils;
import org.slf4j.Logger;
@@ -93,36 +96,43 @@ public abstract class AbstractChannel implements Channel {
}
public CloseFuture close(boolean immediately) {
- if (closeFuture.isClosed()) {
- return closeFuture;
- }
if (closing.compareAndSet(false, true)) {
- try {
- if (immediately) {
- log.debug("Closing channel {} immediately", id);
- doClose();
- closeFuture.setClosed();
- notifyStateChanged();
- session.unregisterChannel(this);
- } else {
- log.debug("Closing channel {} gracefully", id);
- doClose();
- log.debug("Send SSH_MSG_CHANNEL_CLOSE on channel {}", id);
- Buffer buffer = session.createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_CLOSE, 0);
- buffer.putInt(recipient);
- session.writePacket(buffer).addListener(new IoFutureListener<WriteFuture>() {
- public void operationComplete(WriteFuture future) {
- if (closedByOtherSide) {
- log.debug("Message SSH_MSG_CHANNEL_CLOSE written on channel {}", id);
- closeFuture.setClosed();
- notifyStateChanged();
- }
+ if (immediately) {
+ log.debug("Closing channel {} immediately", id);
+ preClose(immediately).addListener(new SshFutureListener<CloseFuture>() {
+ public void operationComplete(CloseFuture future) {
+ postClose();
+ closeFuture.setClosed();
+ notifyStateChanged();
+ session.unregisterChannel(AbstractChannel.this);
+ }
+ });
+ } else {
+ log.debug("Closing channel {} gracefully", id);
+ preClose(immediately).addListener(new SshFutureListener<CloseFuture>() {
+ public void operationComplete(CloseFuture future) {
+ log.debug("Send SSH_MSG_CHANNEL_CLOSE on channel {}", id);
+ Buffer buffer = session.createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_CLOSE, 0);
+ buffer.putInt(recipient);
+ try {
+ session.writePacket(buffer).addListener(new IoFutureListener<WriteFuture>() {
+ public void operationComplete(WriteFuture future) {
+ if (closedByOtherSide) {
+ log.debug("Message SSH_MSG_CHANNEL_CLOSE written on channel {}", id);
+ postClose();
+ closeFuture.setClosed();
+ notifyStateChanged();
+ }
+ }
+ });
+ } catch (IOException e) {
+ log.debug("Exception caught while writing SSH_MSG_CHANNEL_CLOSE packet on channel " + id, e);
+ postClose();
+ closeFuture.setClosed();
+ notifyStateChanged();
}
- });
- }
- } catch (IOException e) {
- session.exceptionCaught(e);
- closeFuture.setClosed();
+ }
+ });
}
}
return closeFuture;
@@ -134,12 +144,19 @@ public abstract class AbstractChannel implements Channel {
if (closedByOtherSide) {
close(false);
} else {
- close(false).setClosed();
+ postClose();
+ closeFuture.setClosed();
notifyStateChanged();
}
}
- protected void doClose() {
+ protected CloseFuture preClose(boolean immediately) {
+ CloseFuture future = new DefaultCloseFuture(lock);
+ future.setClosed();
+ return future;
+ }
+
+ protected void postClose() {
}
protected void writePacket(Buffer buffer) throws IOException {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
index 1d7b489..2257cd9 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
@@ -40,6 +40,7 @@ public class ChannelPipedInputStream extends InputStream {
private final Buffer buffer = new Buffer();
private final byte[] b = new byte[1];
private boolean closed;
+ private boolean eofSent;
private final Lock lock = new ReentrantLock();
private final Condition dataAvailable = lock.newCondition();
@@ -96,13 +97,14 @@ public class ChannelPipedInputStream extends InputStream {
lock.lock();
try {
for (;;) {
- if (closed && !writerClosed) {
+ if (closed && writerClosed && eofSent || closed && !writerClosed) {
throw new IOException("Pipe closed");
}
if (buffer.available() > 0) {
break;
}
if (writerClosed) {
+ eofSent = true;
return -1; // no more data to read
}
try {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java
index d0c4de8..ccd1f77 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/TcpipClientChannel.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import org.apache.mina.core.buffer.IoBuffer;
+import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.session.IoSession;
import org.apache.sshd.client.channel.AbstractClientChannel;
import org.apache.sshd.client.future.DefaultOpenFuture;
@@ -30,6 +31,8 @@ import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.SshdSocketAddress;
import org.apache.sshd.common.channel.ChannelOutputStream;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
import org.apache.sshd.common.util.Buffer;
/**
@@ -96,9 +99,14 @@ public class TcpipClientChannel extends AbstractClientChannel {
}
@Override
- protected synchronized void doClose() {
- serverSession.close(false);
- super.doClose();
+ protected synchronized CloseFuture preClose(boolean immediately) {
+ final CloseFuture future = new DefaultCloseFuture(null);
+ serverSession.close(immediately).addListener(new IoFutureListener<org.apache.mina.core.future.CloseFuture>() {
+ public void operationComplete(org.apache.mina.core.future.CloseFuture f) {
+ future.setClosed();
+ }
+ });
+ return future;
}
protected synchronized void doWriteData(byte[] data, int off, int len) throws IOException {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
index 81a437d..d47e10c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
@@ -83,6 +83,13 @@ public interface ServerFactoryManager extends FactoryManager {
public static final String AUTH_METHODS = "auth-methods";
/**
+ * Key used to configure the timeout used when receiving a close request
+ * on a channel to wait until the command cleanly exits after setting
+ * an EOF on the input stream. In milliseconds.
+ */
+ public static final String COMMAND_EXIT_TIMEOUT = "command-exit-timeout";
+
+ /**
* Retrieve the list of named factories for <code>UserAuth<code> objects.
*
* @return a list of named <code>UserAuth</code> factories, never <code>null</code>
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java
index 4ab0412..e96f483 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/AbstractServerChannel.java
@@ -66,6 +66,7 @@ public abstract class AbstractServerChannel extends AbstractChannel {
buffer.putByte((byte) 0);
buffer.putInt(v);
writePacket(buffer);
+ notifyStateChanged();
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
index 5388d8a..1fcf6d0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
@@ -25,8 +25,10 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.TimeUnit;
import org.apache.sshd.agent.SshAgent;
import org.apache.sshd.agent.SshAgentFactory;
@@ -37,6 +39,7 @@ import org.apache.sshd.common.PtyMode;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.channel.ChannelOutputStream;
import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
import org.apache.sshd.common.future.SshFuture;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.util.Buffer;
@@ -48,6 +51,7 @@ import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.common.file.FileSystemAware;
import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.server.ServerFactoryManager;
import org.apache.sshd.server.SessionAware;
import org.apache.sshd.server.Signal;
import org.apache.sshd.server.SignalListener;
@@ -61,6 +65,8 @@ import org.apache.sshd.server.x11.X11ForwardSupport;
*/
public class ChannelSession extends AbstractServerChannel {
+ public static final long DEFAULT_COMMAND_EXIT_TIMEOUT = 5000;
+
public static class Factory implements NamedFactory<Channel> {
public String getName() {
@@ -171,19 +177,52 @@ public class ChannelSession extends AbstractServerChannel {
protected ChannelDataReceiver receiver;
protected StandardEnvironment env = new StandardEnvironment();
protected Buffer tempBuffer;
+ protected final CloseFuture commandExitFuture = new DefaultCloseFuture(lock);
public ChannelSession() {
}
@Override
- protected void doClose() {
+ protected CloseFuture preClose(boolean immediately) {
+ if (immediately) {
+ commandExitFuture.setClosed();
+ } else if (!commandExitFuture.isClosed()) {
+ log.debug("Wait 5s for shell to exit cleanly");
+ IoUtils.closeQuietly(receiver);
+ final TimerTask task = new TimerTask() {
+ @Override
+ public void run() {
+ commandExitFuture.setClosed();
+ }
+ };
+ long timeout = DEFAULT_COMMAND_EXIT_TIMEOUT;
+ String val = getSession().getFactoryManager().getProperties().get(ServerFactoryManager.COMMAND_EXIT_TIMEOUT);
+ if (val != null) {
+ try {
+ timeout = Long.parseLong(val);
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+ getSession().getFactoryManager().getScheduledExecutorService().schedule(task, timeout, TimeUnit.MILLISECONDS);
+ commandExitFuture.addListener(new SshFutureListener<CloseFuture>() {
+ public void operationComplete(CloseFuture future) {
+ task.cancel();
+ }
+ });
+ }
+ return commandExitFuture;
+ }
+
+ @Override
+ protected void postClose() {
if (command != null) {
command.destroy();
command = null;
}
remoteWindow.notifyClosed();
IoUtils.closeQuietly(out, err, receiver);
- super.doClose();
+ super.postClose();
}
@Override
@@ -568,9 +607,9 @@ public class ChannelSession extends AbstractServerChannel {
if (!closing.get()) {
sendEof();
sendExitStatus(exitValue);
- // TODO: We should wait for all streams to be consumed before closing the channel
close(false);
}
+ commandExitFuture.setClosed();
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f549a71b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
index 37ec331..84d8e9a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java
@@ -24,6 +24,7 @@ import java.net.InetSocketAddress;
import java.util.EnumSet;
import org.apache.mina.core.buffer.IoBuffer;
+import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoEventType;
@@ -36,6 +37,8 @@ import org.apache.sshd.client.future.OpenFuture;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.ChannelOutputStream;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.future.DefaultCloseFuture;
import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.server.session.ServerSession;
import org.slf4j.Logger;
@@ -207,9 +210,14 @@ public class X11ForwardSupport extends IoHandlerAdapter {
}
@Override
- protected synchronized void doClose() {
- serverSession.close(false);
- super.doClose();
+ protected synchronized CloseFuture preClose(boolean immediately) {
+ final CloseFuture future = new DefaultCloseFuture(null);
+ serverSession.close(immediately).addListener(new IoFutureListener<org.apache.mina.core.future.CloseFuture>() {
+ public void operationComplete(org.apache.mina.core.future.CloseFuture f) {
+ future.setClosed();
+ }
+ });
+ return future;
}
protected synchronized void doWriteData(byte[] data, int off, int len) throws IOException {