Repository: mina-sshd
Updated Branches:
  refs/heads/master ae8d1c995 -> c64e73acb


[SSHD-692] Cannot repeatedly call ChannelSession#executeRemoteCommand


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

Branch: refs/heads/master
Commit: c64e73acbfb94ea7e3e0c474150b621ad16f80c0
Parents: ae8d1c9
Author: Lyor Goldstein <[email protected]>
Authored: Sun Aug 21 20:46:23 2016 +0300
Committer: Lyor Goldstein <[email protected]>
Committed: Sun Aug 21 20:46:23 2016 +0300

----------------------------------------------------------------------
 .../sshd/client/session/ClientSession.java      |  14 ++-
 .../sshd/client/channel/ChannelExecMain.java    |  91 ++++++++++++++++
 .../sshd/client/channel/ChannelExecTest.java    | 109 +++++++++++++++++++
 3 files changed, 213 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c64e73ac/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java 
b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index ff88df1..dbd2b14 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -49,6 +49,7 @@ import 
org.apache.sshd.client.subsystem.sftp.SftpClientCreator;
 import org.apache.sshd.common.forward.PortForwardingManager;
 import org.apache.sshd.common.future.KeyExchangeFuture;
 import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.closeable.CloseableUtils;
 import org.apache.sshd.common.util.io.NoCloseOutputStream;
 import org.apache.sshd.common.util.io.NullOutputStream;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
@@ -94,6 +95,8 @@ public interface ClientSession
 
     Set<ClientChannelEvent> REMOTE_COMMAND_WAIT_EVENTS =
             Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED, 
ClientChannelEvent.EXIT_STATUS));
+    Set<ClientChannelEvent> REMOTE_COMMAND_END_EVENTS =
+            Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED));
 
     /**
      * Returns the original address (after having been translated through host
@@ -224,7 +227,16 @@ public interface ClientSession
             if ((exitStatus != null) && (exitStatus.intValue() != 0)) {
                 throw new RemoteException("Remote command failed (" + 
exitStatus + "): " + command, new ServerException(exitStatus.toString()));
             }
-            byte[]  response = channelOut.toByteArray();
+
+            if (!waitMask.contains(ClientChannelEvent.CLOSED)) {
+                long maxWait = CloseableUtils.getMaxCloseWaitTime(this);
+                waitMask = channel.waitFor(REMOTE_COMMAND_END_EVENTS, maxWait);
+                if (waitMask.contains(ClientChannelEvent.TIMEOUT)) {
+                    throw new SocketTimeoutException("Failed to receive 
command channel close in time: " + command);
+                }
+            }
+
+            byte[] response = channelOut.toByteArray();
             return new String(response, charset);
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c64e73ac/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java 
b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java
new file mode 100644
index 0000000..495459a
--- /dev/null
+++ 
b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java
@@ -0,0 +1,91 @@
+/*
+ * 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.client.channel;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.util.test.BaseTestSupport;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class ChannelExecMain extends BaseTestSupport {
+    public static void doExecuteCommands(
+            BufferedReader stdin, PrintStream stdout, PrintStream stderr, 
ClientSession session) throws Exception {
+        while (true) {
+            stdout.print("> ");
+
+            String command = stdin.readLine();
+            if ("q".equalsIgnoreCase(command) || 
"quit".equalsIgnoreCase(command)) {
+                break;
+            }
+            if (GenericUtils.isEmpty(command)) {
+                continue;
+            }
+
+            while (true) {
+                try {
+                    String response = session.executeRemoteCommand(command);
+                    String[] lines = GenericUtils.split(response, '\n');
+                    for (String l : lines) {
+                        stdout.append('\t').println(l);
+                    }
+                } catch (Exception e) {
+                    stderr.append(e.getClass().getSimpleName()).append(": 
").println(e.getMessage());
+                }
+
+                stdout.append("Execute ").append(command).print(" again [y]/n 
");
+                String ans = stdin.readLine();
+                if ((GenericUtils.length(ans) > 0) && 
(Character.toLowerCase(ans.charAt(0)) != 'y')) {
+                    break;
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        PrintStream stdout = System.out;
+        PrintStream stderr = System.err;
+        try (BufferedReader stdin = new BufferedReader(new 
InputStreamReader(new NoCloseInputStream(System.in)))) {
+            ClientSession session = SshClient.setupClientSession("-P", stdin, 
stdout, stderr, args);
+            if (session == null) {
+                System.err.println("usage: channelExec [-i identity] [-l 
login] [-P port] [-o option=value]"
+                        + " [-w password] [-c cipherlist]  [-m maclist] [-C] 
hostname/user@host");
+                System.exit(-1);
+                return;
+            }
+
+            try (SshClient client = (SshClient) session.getFactoryManager()) {
+                try (ClientSession clientSession = session) {
+                    doExecuteCommands(stdin, stdout, stderr, session);
+                } finally {
+                    client.stop();
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c64e73ac/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecTest.java 
b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecTest.java
new file mode 100644
index 0000000..5b227fc
--- /dev/null
+++ 
b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.client.channel;
+
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.CommandExecutionHelper;
+import org.apache.sshd.util.test.Utils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ChannelExecTest extends BaseTestSupport {
+    private static SshServer sshd;
+    private static int port;
+    private static SshClient client;
+
+    public ChannelExecTest() {
+        super();
+    }
+
+    @BeforeClass
+    public static void setupClientAndServer() throws Exception {
+        sshd = Utils.setupTestServer(ChannelExecTest.class);
+        sshd.setCommandFactory(new CommandFactory() {
+            @Override
+            public Command createCommand(String command) {
+                return new CommandExecutionHelper() {
+                    @Override
+                    protected boolean handleCommandLine(String command) throws 
Exception {
+                        OutputStream stdout = getOut();
+                        
stdout.write(command.getBytes(StandardCharsets.US_ASCII));
+                        stdout.flush();
+                        return false;
+                    }
+                };
+            }
+        });
+        sshd.start();
+        port = sshd.getPort();
+
+        client = Utils.setupTestClient(ChannelExecTest.class);
+        client.start();
+    }
+
+    @AfterClass
+    public static void tearDownClientAndServer() throws Exception {
+        if (sshd != null) {
+            try {
+                sshd.stop(true);
+            } finally {
+                sshd = null;
+            }
+        }
+
+        if (client != null) {
+            try {
+                client.stop();
+            } finally {
+                client = null;
+            }
+        }
+    }
+
+    @Test   // see SSHD-692
+    public void testMultipleRemoteCommandExecutions() throws Exception {
+        try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+            session.addPasswordIdentity(getCurrentTestName());
+            session.auth().verify(5L, TimeUnit.SECONDS);
+
+            for (int index = 1; index <= Byte.SIZE; index++) {
+                String expected = getCurrentTestName() + "[" + index + "]";
+                String actual = session.executeRemoteCommand(expected + "\n");
+                assertEquals("Mismatched reply", expected, actual);
+            }
+        }
+    }
+}

Reply via email to