[SSHD-677] Provide a quick default implementation for executing a single simple command that does not require any input
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 9384fbe..1299518 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -1437,7 +1437,7 @@
((ChannelShell) channel).setAgentForwarding(agentForward);
channel.setIn(new NoCloseInputStream(System.in));
} else {
- StringBuilder w = new StringBuilder();
+ StringBuilder w = new StringBuilder(command.size() * Integer.SIZE);
for (String cmd : command) {
w.append(cmd).append(' ');
}
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ClientChannel.java
index b0b1f77..2b221c4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ClientChannel.java
@@ -26,7 +26,6 @@
import org.apache.sshd.client.future.OpenFuture;
import org.apache.sshd.common.channel.Channel;
-import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.io.IoInputStream;
import org.apache.sshd.common.io.IoOutputStream;
@@ -100,9 +99,6 @@
*/
Set<ClientChannelEvent> waitFor(Collection<ClientChannelEvent> mask, long timeout);
- @Override
- CloseFuture close(boolean immediate);
-
/**
* @return The signaled exit status via "exit-status" request
* - {@code null} if not signaled
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 622a9a1..05edaac 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
@@ -18,9 +18,18 @@
*/
package org.apache.sshd.client.session;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.rmi.RemoteException;
+import java.rmi.ServerException;
import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
@@ -31,11 +40,14 @@
import org.apache.sshd.client.channel.ChannelShell;
import org.apache.sshd.client.channel.ChannelSubsystem;
import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.future.AuthFuture;
import org.apache.sshd.client.scp.ScpClientCreator;
import org.apache.sshd.client.subsystem.sftp.SftpClientCreator;
import org.apache.sshd.common.future.KeyExchangeFuture;
import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.NullOutputStream;
import org.apache.sshd.common.util.net.SshdSocketAddress;
/**
@@ -76,6 +88,9 @@
AUTHED;
}
+ Set<ClientChannelEvent> REMOTE_COMMAND_WAIT_EVENTS =
+ Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED, ClientChannelEvent.EXIT_STATUS));
+
/**
* Returns the original address (after having been translated through host
* configuration entries if any) that was request to connect. It contains the
@@ -138,6 +153,79 @@
ChannelExec createExecChannel(String command) throws IOException;
/**
+ * Execute a command that requires no input and returns its output
+ *
+ * @param command The command to execute
+ * @return The command's standard output result (assumed to be in US-ASCII)
+ * @throws IOException If failed to execute the command - including
+ * if <U>anything</U> was written to the standard error or a non-zero exit
+ * status was received. If this happens, then a {@link RemoteException} is
+ * thrown with a cause of {@link ServerException} containing the remote
+ * captured standard error - including CR/LF(s)
+ * @see #executeRemoteCommand(String, OutputStream, Charset)
+ */
+ default String executeRemoteCommand(String command) throws IOException {
+ try (ByteArrayOutputStream stderr = new ByteArrayOutputStream()) {
+ String response = executeRemoteCommand(command, stderr, StandardCharsets.US_ASCII);
+ if (stderr.size() > 0) {
+ byte[] error = stderr.toByteArray();
+ String errorMessage = new String(error, StandardCharsets.US_ASCII);
+ throw new RemoteException("Error reported from remote command='" + command, new ServerException(errorMessage));
+ }
+
+ return response;
+ }
+ }
+
+ /**
+ * Execute a command that requires no input and returns its output
+ *
+ * @param command The command to execute - without a terminating LF
+ * @param stderr Standard error output stream - if {@code null} then
+ * error stream data is ignored. <B>Note:</B> if the stream is not {@code null}
+ * then it will be left <U>open</U> when this method returns or exception
+ * is thrown
+ * @param charset The command {@link Charset} for input/output/error - if
+ * {@code null} then US_ASCII is assumed
+ * @return The command's standard output result
+ * @throws IOException If failed to manage the command channel - <B>Note:</B>
+ * the code does not check if anything was output to the standard error stream,
+ * but does check the reported exit status (if any) for non-zero value. If
+ * non-zero exit status received then a {@link RemoteException} is thrown with'
+ * a {@link ServerException} cause containing the exits value
+ */
+ default String executeRemoteCommand(String command, OutputStream stderr, Charset charset) throws IOException {
+ if (charset == null) {
+ charset = StandardCharsets.US_ASCII;
+ }
+
+ try (ByteArrayOutputStream channelOut = new ByteArrayOutputStream(Byte.MAX_VALUE);
+ OutputStream channelErr = (stderr == null) ? new NullOutputStream() : new NoCloseOutputStream(stderr);
+ ClientChannel channel = createExecChannel(command)) {
+ channel.setOut(channelOut);
+ channel.setErr(channelErr);
+ channel.open().await(); // TODO use verify and a configurable timeout
+
+ OutputStream invertedStream = channel.getInvertedIn();
+ invertedStream.write(command.getBytes(charset));
+ invertedStream.flush();
+
+ // TODO use a configurable timeout
+ Collection<ClientChannelEvent> waitMask = channel.waitFor(REMOTE_COMMAND_WAIT_EVENTS, 0L);
+ if (waitMask.contains(ClientChannelEvent.TIMEOUT)) {
+ throw new SocketTimeoutException("Failed to retrieve command result in time: " + command);
+ }
+
+ Integer exitStatus = channel.getExitStatus();
+ if ((exitStatus != null) && (exitStatus.intValue() != 0)) {
+ throw new RemoteException("Remote command failed (" + exitStatus + "): " + command, new ServerException(exitStatus.toString()));
+ }
+ byte[] response = channelOut.toByteArray();
+ return new String(response, charset);
+ }
+ }
+
+ /**
* Create a subsystem channel.
*
* @param subsystem The subsystem name
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ExitCallback.java b/sshd-core/src/main/java/org/apache/sshd/server/ExitCallback.java
index f6372ee..4da17f9 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ExitCallback.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ExitCallback.java
@@ -28,7 +28,9 @@
*
* @param exitValue the exit value
*/
- void onExit(int exitValue);
+ default void onExit(int exitValue) {
+ onExit(exitValue, "");
+ }
/**
* Informs the SSH client/server that the shell has exited
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 61241e1..5843917 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
@@ -677,11 +677,6 @@
}
command.setExitCallback(new ExitCallback() {
@Override
- public void onExit(int exitValue) {
- onExit(exitValue, "");
- }
-
- @Override
@SuppressWarnings("synthetic-access")
public void onExit(int exitValue, String exitMessage) {
try {
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index 7ca04aa..7ac8825 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -135,14 +135,13 @@
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ClientTest extends BaseTestSupport {
-
private SshServer sshd;
private SshClient client;
private int port;
private CountDownLatch authLatch;
private CountDownLatch channelLatch;
- private final AtomicReference<ClientSession> clientSessionHolder = new AtomicReference<ClientSession>(null);
+ private final AtomicReference<ClientSession> clientSessionHolder = new AtomicReference<>(null);
@SuppressWarnings("synthetic-access")
private final SessionListener clientSessionListener = new SessionListener() {
@Override
@@ -1068,7 +1067,7 @@
@Test // see SSHD-504
public void testDefaultKeyboardInteractivePasswordPromptLocationIndependence() throws Exception {
- final Collection<String> mismatchedPrompts = new LinkedList<String>();
+ final Collection<String> mismatchedPrompts = new LinkedList<>();
client.setUserAuthFactories(Arrays.<NamedFactory<UserAuth>>asList(new UserAuthKeyboardInteractiveFactory() {
@Override
public UserAuthKeyboardInteractive create() {
@@ -1405,7 +1404,7 @@
channels.add(session.createChannel(Channel.CHANNEL_EXEC, getCurrentTestName()));
channels.add(session.createChannel(Channel.CHANNEL_SHELL, getClass().getSimpleName()));
- Set<Integer> ids = new HashSet<Integer>(channels.size());
+ Set<Integer> ids = new HashSet<>(channels.size());
for (ClientChannel c : channels) {
int id = ((AbstractChannel) c).getId();
assertTrue("Channel ID repeated: " + id, ids.add(Integer.valueOf(id)));
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionTest.java
new file mode 100644
index 0000000..52b8faa
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.session;
+
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.rmi.RemoteException;
+import java.rmi.ServerException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.SshClient;
+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.junit.After;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ClientSessionTest extends BaseTestSupport {
+ private SshServer sshd;
+ private SshClient client;
+ private int port;
+
+ public ClientSessionTest() {
+ super();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ sshd = setupTestServer();
+ sshd.start();
+ port = sshd.getPort();
+
+ client = setupTestClient();
+ client.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (sshd != null) {
+ sshd.stop(true);
+ }
+ if (client != null) {
+ client.stop();
+ }
+ }
+
+ @Test
+ public void testDefaultExecuteCommandMethod() throws Exception {
+ final String expectedCommand = getCurrentTestName() + "-CMD";
+ final String expectedResponse = getCurrentTestName() + "-RSP";
+ sshd.setCommandFactory(new CommandFactory() {
+ @Override
+ public Command createCommand(String command) {
+ return new CommandExecutionHelper() {
+ private boolean cmdProcessed;
+
+ @Override
+ protected boolean handleCommandLine(String command) throws Exception {
+ assertEquals("Mismatched incoming command", expectedCommand, command);
+ assertFalse("Duplicated command call", cmdProcessed);
+ OutputStream stdout = getOut();
+ stdout.write(expectedResponse.getBytes(StandardCharsets.US_ASCII));
+ stdout.flush();
+ cmdProcessed = true;
+ return false;
+ }
+ };
+ }
+ });
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ // NOTE !!! The LF is only because we are using a buffered reader on the server end to read the command
+ String actualResponse = session.executeRemoteCommand(expectedCommand + "\n");
+ assertEquals("Mismatched command response", expectedResponse, actualResponse);
+ } finally {
+ client.stop();
+ }
+ }
+
+ @Test
+ public void testExceptionThrownIfRemoteStderrWrittenTo() throws Exception {
+ final String expectedCommand = getCurrentTestName() + "-CMD";
+ final String expectedErrorMessage = getCurrentTestName() + "-ERR";
+ sshd.setCommandFactory(new CommandFactory() {
+ @Override
+ public Command createCommand(String command) {
+ return new CommandExecutionHelper() {
+ private boolean cmdProcessed;
+
+ @Override
+ protected boolean handleCommandLine(String command) throws Exception {
+ assertEquals("Mismatched incoming command", expectedCommand, command);
+ assertFalse("Duplicated command call", cmdProcessed);
+ OutputStream stderr = getErr();
+ stderr.write(expectedErrorMessage.getBytes(StandardCharsets.US_ASCII));
+ stderr.flush();
+ cmdProcessed = true;
+ return false;
+ }
+ };
+ }
+ });
+
+ String actualErrorMessage = null;
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ // NOTE !!! The LF is only because we are using a buffered reader on the server end to read the command
+ String response = session.executeRemoteCommand(expectedCommand + "\n");
+ fail("Unexpected successful response: " + response);
+ } catch (Exception e) {
+ if (!(e instanceof RemoteException)) {
+ throw e;
+ }
+
+ Throwable cause = e.getCause();
+ if (!(cause instanceof ServerException)) {
+ throw e;
+ }
+
+ actualErrorMessage = cause.getMessage();
+ } finally {
+ client.stop();
+ }
+
+ assertEquals("Mismatched captured error message", expectedErrorMessage, actualErrorMessage);
+ }
+
+ @Test
+ public void testExceptionThrownIfNonZeroExitStatus() throws Exception {
+ final String expectedCommand = getCurrentTestName() + "-CMD";
+ final int exepectedErrorCode = 7365;
+ sshd.setCommandFactory(new CommandFactory() {
+ @Override
+ public Command createCommand(String command) {
+ return new CommandExecutionHelper() {
+ private boolean cmdProcessed;
+
+ @Override
+ public void onExit(int exitValue, String exitMessage) {
+ super.onExit((exitValue == 0) ? exepectedErrorCode : exitValue, exitMessage);
+ }
+
+ @Override
+ protected boolean handleCommandLine(String command) throws Exception {
+ assertEquals("Mismatched incoming command", expectedCommand, command);
+ assertFalse("Duplicated command call", cmdProcessed);
+ OutputStream stdout = getOut();
+ stdout.write(command.getBytes(StandardCharsets.US_ASCII));
+ stdout.flush();
+ cmdProcessed = true;
+ return false;
+ }
+ };
+ }
+ });
+
+ String actualErrorMessage = null;
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ // NOTE !!! The LF is only because we are using a buffered reader on the server end to read the command
+ String response = session.executeRemoteCommand(expectedCommand + "\n");
+ fail("Unexpected successful response: " + response);
+ } catch (Exception e) {
+ if (!(e instanceof RemoteException)) {
+ throw e;
+ }
+
+ Throwable cause = e.getCause();
+ if (!(cause instanceof ServerException)) {
+ throw e;
+ }
+
+ actualErrorMessage = cause.getMessage();
+ } finally {
+ client.stop();
+ }
+
+ assertEquals("Mismatched captured error code", Integer.toString(exepectedErrorCode), actualErrorMessage);
+ }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/CommandExecutionHelper.java b/sshd-core/src/test/java/org/apache/sshd/util/test/CommandExecutionHelper.java
new file mode 100644
index 0000000..a2006d5
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/CommandExecutionHelper.java
@@ -0,0 +1,152 @@
+/*
+ * 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.util.test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class CommandExecutionHelper implements Command, Runnable, ExitCallback {
+ private InputStream in;
+ private OutputStream out;
+ private OutputStream err;
+ private ExitCallback callback;
+ private Environment environment;
+ private Thread thread;
+ private boolean cbCalled;
+
+ protected CommandExecutionHelper() {
+ super();
+ }
+
+ public InputStream getIn() {
+ return in;
+ }
+
+ public OutputStream getOut() {
+ return out;
+ }
+
+ public OutputStream getErr() {
+ return err;
+ }
+
+ public Environment getEnvironment() {
+ return environment;
+ }
+
+ public ExitCallback getExitCallback() {
+ return callback;
+ }
+
+ @Override
+ public void setInputStream(InputStream in) {
+ this.in = in;
+ }
+
+ @Override
+ public void setOutputStream(OutputStream out) {
+ this.out = out;
+ }
+
+ @Override
+ public void setErrorStream(OutputStream err) {
+ this.err = err;
+ }
+
+ @Override
+ public void setExitCallback(ExitCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void start(Environment env) throws IOException {
+ environment = env;
+ thread = new Thread(this, "CommandExecutionHelper");
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ @Override
+ public void destroy() {
+ thread.interrupt();
+ }
+
+ @Override
+ public void run() {
+ String command = null;
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(getIn(), StandardCharsets.UTF_8))) {
+ for (;;) {
+ command = r.readLine();
+ if (command == null) {
+ return;
+ }
+
+ if (!handleCommandLine(command)) {
+ return;
+ }
+ }
+ } catch (InterruptedIOException e) {
+ // Ignore - signaled end
+ } catch (Exception e) {
+ String message = "Failed (" + e.getClass().getSimpleName() + ") to handle '" + command + "': " + e.getMessage();
+ try {
+ OutputStream stderr = getErr();
+ stderr.write(message.getBytes(StandardCharsets.US_ASCII));
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ } finally {
+ onExit(-1, message);
+ }
+ } finally {
+ onExit(0);
+ }
+ }
+
+ @Override
+ public void onExit(int exitValue, String exitMessage) {
+ if (!cbCalled) {
+ ExitCallback cb = getExitCallback();
+ try {
+ cb.onExit(exitValue, exitMessage);
+ } finally {
+ cbCalled = true;
+ }
+ }
+ }
+
+ /**
+ * @param command The command line
+ * @return {@code true} if continue accepting command
+ * @throws Exception If failed to handle the command line
+ */
+ protected abstract boolean handleCommandLine(String command) throws Exception;
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/EchoShell.java b/sshd-core/src/test/java/org/apache/sshd/util/test/EchoShell.java
index 4d12093..637ce43 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/EchoShell.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/EchoShell.java
@@ -18,104 +18,27 @@
*/
package org.apache.sshd.util.test;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
-
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class EchoShell implements Command, Runnable {
-
- private InputStream in;
- private OutputStream out;
- private OutputStream err;
- private ExitCallback callback;
- private Environment environment;
- private Thread thread;
-
+public class EchoShell extends CommandExecutionHelper {
public EchoShell() {
super();
}
- public InputStream getIn() {
- return in;
- }
-
- public OutputStream getOut() {
- return out;
- }
-
- public OutputStream getErr() {
- return err;
- }
-
- public Environment getEnvironment() {
- return environment;
- }
-
@Override
- public void setInputStream(InputStream in) {
- this.in = in;
- }
+ protected boolean handleCommandLine(String command) throws Exception {
+ OutputStream out = getOut();
+ out.write((command + "\n").getBytes(StandardCharsets.UTF_8));
+ out.flush();
- @Override
- public void setOutputStream(OutputStream out) {
- this.out = out;
- }
-
- @Override
- public void setErrorStream(OutputStream err) {
- this.err = err;
- }
-
- @Override
- public void setExitCallback(ExitCallback callback) {
- this.callback = callback;
- }
-
- @Override
- public void start(Environment env) throws IOException {
- environment = env;
- thread = new Thread(this, "EchoShell");
- thread.setDaemon(true);
- thread.start();
- }
-
- @Override
- public void destroy() {
- thread.interrupt();
- }
-
- @Override
- public void run() {
- BufferedReader r = new BufferedReader(new InputStreamReader(in));
- try {
- for (;;) {
- String s = r.readLine();
- if (s == null) {
- return;
- }
- out.write((s + "\n").getBytes(StandardCharsets.UTF_8));
- out.flush();
- if ("exit".equals(s)) {
- return;
- }
- }
- } catch (InterruptedIOException e) {
- // Ignore
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- callback.onExit(0);
+ if ("exit".equals(command)) {
+ return false;
}
+
+ return true;
}
}
\ No newline at end of file