Unfortunately the getExitStatus() method always returns -1 so i can't use that to tell me whether or not a file upload worked. I tried to use the getInputStream() method of the Channel class but whenever i tried to read from the inputstream my code blocked forever as if the Channel instance was still open/connected (even though the isConnected and isClosed() were false and true respectively - suggesting that the Channel was indeed closed). The following code always blocks after i try to read a byte of data from the input stream:
public class Put {
public static void main(String[] args) throws IOException {
Session session = null;
Channel channel = null;
ChannelSftp channelSftp = null;
InputStream in = null;
JSch jsch = new JSch();
try {
jsch.setKnownHosts("known_hosts");
session = jsch.getSession("user", "host", 22);
session.setPassword("password");
session.connect();
channel = session.openChannel("sftp");
channel.setInputStream(null);
stdout = channel.getInputStream();
channel.connect();
channelSftp = (ChannelSftp)channel;
channelSftp.cd("/path/to/sftp");
channelSftp.put("/path/to/localfile", "/path/to/remotefile");
} catch (JSchException e) {
System.out.println(e.getMessage());
e.printStackTrace();
} catch (SftpException e) {
System.out.println(e.id);
System.out.println(e.getMessage());
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(channelSftp != null && channelSftp.isConnected())channelSftp.exit();
if(channel != null && channel.isConnected()) channel.disconnect();
if(session != null && session.isConnected()) session.disconnect();
}
System.out.println("Channel is connected? " + channel.isConnected()); // returns false as i would expect
System.out.println("Channel is closed? " + channel.isClosed()); // returns true as i would expect
System.out.println(stdout.available()); // returns 0
System.out.println(stdout.read()); // code blocks here
}
}
I suppose my questions are:
Why is my code blocking whenever i try to read from the input stream (even though the Channel is indeed closed)
What is the way to tell if a file upload worked or not. I guess if a SFTPException is thrown that's unsuccessful otherwise i can assume it was successful?
I guess if a SFTPException is thrown that's unsuccessful otherwise i can assume it was successful?
That is correct. The various ChannelSftp.put() functions will throw an exception if they fail for any reason. If you want to double-check, you could call ChannelSftp.stat() or ...lstat() on the remote filename afterwards to check it. But be aware that another process could hypothetically delete or move the remote file before you got a chance to check it.
You don't normally need to access the input or output streams of a ChannelSftp. getExitStatus() would tell you the exit status of the SFTP session as a whole, rather than the result of a particular operation.
JCraft has an example program illustrating how to do SFTP that you might find useful.
Related
I am using Jsch and my task is to login to server and run command as following
sudo "command"
Using following code I am successfully able to connect and execute commands (without the error "sudo: sorry, you must have a tty")
public String runSudo(RemoteHost remoteHost, String[] commands, OperationData operationData, String known_hosts, String id_rsa) {
String result = "";
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
JSch jsch = new JSch();
// Create a JSch session to connect to the server
Session session = null;
try {
session = jsch.getSession(remoteHost.getUsername(), remoteHost.getHost(), remoteHost.getPort());
if (remoteHost.getPassword() != null)
session.setPassword(remoteHost.getPassword());
else{
session.setConfig("PreferredAuthentications", "publickey");
jsch.setKnownHosts(known_hosts);
jsch.addIdentity(id_rsa);
}
session.setConfig(config);
// Establish the connection
session.connect();
logger.debug("Connected...");
for (int k = 0; k < commands.length; k++) {
ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(commands[k]);
OutputStream outputStreamStdErr = new ByteArrayOutputStream();
channel.setErrStream(outputStreamStdErr, true);
channel.setPty(true);
InputStream in = channel.getInputStream();
channel.connect();
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0) {
break;
}
result = result + new String(tmp, 0, i);
}
if (channel.isClosed()) {
this.exitStatus += channel.getExitStatus();
break;
}
Thread.sleep(1000);
}
String errors = readInputStream(outputstream2inputstream(outputStreamStdErr));
if (!errors.equals("")) {
operationData.setErrorMessage(errors);
}
channel.getErrStream().close();
channel.disconnect();
}
session.disconnect();
} catch (JSchException e) {
operationData.setErrorMessage(e.getMessage());
logger.error(e.getMessage());
return result;
} catch (IOException e) {
operationData.setErrorMessage(e.getMessage());
logger.error(e.getMessage());
return result;
} catch (InterruptedException e) {
operationData.setErrorMessage(e.getMessage());
logger.error(e.getMessage());
return result;
} finally {
return result;
}
}
But using the property channel.setPty(true); I can't read the error stream because it's null and the errors are displayed in the output of the command.
Instead, if I don't use the property channel.setPty(true); I can read the error stream, but the method return the error "sudo: sorry, you must have a tty" that i want to avoid.
Can someone give me help to use the error stream and the property channel.setPty(true); in the same code?
It's the standard SSH behaviour (at least on Linux) that when terminal emulation is enabled, all output goes to one stream in the SSH connection. That has nothing do you with your Java/JSch code.
Do not use PTY for command automation. PTY is intended to implement an interactive terminal for a human use.
You can configure sudo not to require PTY.
There are also other alternatives.
See
Allowing automatic command execution as root on Linux using SSH.
Another workaround is to redirect the stdout and stderr to temporary files on the server. And then print the files separately to the terminal/output.
A naive implementation can be like:
sudo ... > /tmp/my_stdout 2> /tmp/my_stderr ; echo "stdout:" ; cat /tmp/my_stdout ; echo "stderr:" ; cat /tmp/my_stderr
If someone have an actual solution to this problem i would much appreciate it. So far all implementation that I have used close the session as soon as one of the channel is "connected" what ever that means. Like most i need to be able to script ssh interaction meaning that i need the result of my operation with a still alive channel I'm not looking for a command with "cmd1;cmd2;cmd3" type ..
The best example I can think of is if you were trying to browse trough a file system.
If each command is a new session you would be going going no where since at each new session you go back to square one.
In command line the ssh session remain open when you type an operation why all java implementation differ so much from this approach is beyond me. My next step if i cant find an answer is actually to use command shell from java and interacting from there instead of using java ssh libraries..
public void connect() {
Session session;
try {
session = createConnectedSession();
java.util.logging.Logger.getLogger("test").log(Level.INFO,"isConnected "+session.isConnected());
ByteArrayOutputStream output = new ByteArrayOutputStream();
Channel channel = session.openChannel("shell");
channel.setOutputStream(output);
PrintStream ps = new PrintStream(channel.getOutputStream(), true);
// InputStream is = new InputStream(channel.getInputStream());
channel.connect();
sleep();
java.util.logging.Logger.getLogger("test").log(Level.INFO,"isConnected "+session.isConnected());
Stack<String> mstack = getCommandStack();
//readChannel(channel);
while (!mstack.isEmpty()) {
String cmd = mstack.pop();
java.util.logging.Logger.getLogger("test").log(Level.INFO,"sending command "+cmd);
ps.println(cmd);
sleep();
System.out.println(output.toString());
java.util.logging.Logger.getLogger("test").log(Level.INFO,"command result"+output.toString());
sleep();
// System.out.println(output.toString());
ps.flush();
}
channel.disconnect();
session.disconnect();
} catch (JSchException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Here is most of the relevant code, running on a cRIO with the FRC Java image:
try {
SocketConnection http = (SocketConnection) Connector.open("socket://" + BEAGELIP);
InputStream data = http.openInputStream();
database = "";
int p = data.read();
while (p >= 0) {
database += (char) p;
p = data.read();
}
data.close();
http.close();
} catch (Exception e) {
System.out.println(e);
}
This method is being repeatedly called.
After repeated failures to connect (e.g., the server is not plugged in), the IOException switches from java.io.IOException: ConnectException: connect refused to java.io.IOException: errno: 24 on fd:-1 during socket create
We think the reason this might be happening is because on failure, Connector.open returns null, which we cannot close(), freezing up a filedescriptor.
What is the correct way to prevent all of the file descriptors from being used up in this procedure?
If anyone can give a suggestion on how this should be done, that would be wonderful.
Your closes must be in a finally {} block. Otherwise they don't happen if there was an exception, and there will be an exception sooner or later,
When I read the documentation for SocketChannel, it seems pretty clear that a blocking SocketChannel connect() call will only ever return true or throw an exception. In other words, it can only return false in non-blocking mode. Is that correct, or am I missing/misreading something?
Is it possible for channel.configureBlocking(true) to return and the channel not be in blocking mode? I would expect that if configureBlocking(true) were not able to successfully put the channel in blocking mode (before the return of the method call), an exception would be thrown. Is that correct?
Finally, is there any way for the following code to fail to connect and yet return TRUE? (The code only tests whether the connection succeeds or not, it doesn't do anything with the channel, hence the immediate close):
SocketChannel channel = null;
try {
channel = SocketChannel.open();
channel.configureBlocking(true);
channel.connect(new InetSocketAddress(addr, port));
return Boolean.TRUE;
}
catch (Exception e) {
return Boolean.FALSE;
}
finally {
if (channel != null) {
try { channel.close() } catch (Exception e) {}
}
}
Thanks!
The Javadoc clearly states "If this channel is in blocking mode then an invocation of this method will block until the connection is established or an I/O error occurs". So it either returns true or throws an exception.
'Is it possible for channel.configureBlocking(true) to return and the channel not be in blocking mode?' No. It will throw an exception if it can't perform the operation. This is also clearly stated in the Javadoc.
How can I check service using java? I found this article, but it is only for checking hostname.
My question is how can I check is service on port x running, ex: myhostname:8080 or myhostname:8099 , I might be running service n or p on those ports but it would be visible trough web if I do it manually, how can I achieve same effect in java?
That snippet sends a ping, which you can't manipulate to achieve what you want. Just open a socket and catch any exceptions.
bool success = true;
try {
(new Socket(host, port)).close();
} catch (UnknownHostException e) {
// unknown host
success = false;
} catch (IOException e) {
// io exception, service probably not running
success = false;
}
If you need to detect which service is running, then you need to read the first bytes sent and compare them to what you know each service should send. This might require a few passes back and forth.
Since your services are web, you might want to add a verification of the response code.
boolean available = false;
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL("http://yourdomain/").openConnection();
conn.connect();
if(conn.getResponseCode() == 200)
available = true;
}
catch(IOException e) {
}
finally {
if(conn != null)
conn.disconnect();
}
Just attempt to connect to the socket.
InetAddress addr = InetAddress.getByName("myhostname");
Socket socket = new Socket(addr, 8099);
If that doesn't thrown an exception then the host is accepting connections on that port. If it isn't you'll typically get a ConnectException.
Just attempt to use the service and deal with the exceptions as they arise. There's generally no point in testing any resource for availability prior to using it. What if it was available when you tested and not when you used it?
You can use sockets to do this.
new Socket("localhost", 8080).getInputStream()
Surround that with a try catch block and you have a solution.