1. /*
  2. * Copyright 2003-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional.ssh;
  18. import org.apache.tools.ant.BuildException;
  19. import org.apache.tools.ant.Project;
  20. import org.apache.tools.ant.util.TeeOutputStream;
  21. import java.io.ByteArrayOutputStream;
  22. import java.io.File;
  23. import java.io.FileWriter;
  24. import java.io.IOException;
  25. import java.io.StringReader;
  26. import com.jcraft.jsch.ChannelExec;
  27. import com.jcraft.jsch.JSchException;
  28. import com.jcraft.jsch.Session;
  29. /**
  30. * Executes a command on a remote machine via ssh.
  31. *
  32. * @version $Revision: 1.9.2.7 $
  33. * @created February 2, 2003
  34. * @since Ant 1.6
  35. */
  36. public class SSHExec extends SSHBase {
  37. private final int BUFFER_SIZE = 1024;
  38. /** the command to execute via ssh */
  39. private String command = null;
  40. /** units are milliseconds, default is 0=infinite */
  41. private long maxwait = 0;
  42. /** for waiting for the command to finish */
  43. private Thread thread = null;
  44. private String outputProperty = null; // like <exec>
  45. private File outputFile = null; // like <exec>
  46. private boolean append = false; // like <exec>
  47. private static final String TIMEOUT_MESSAGE =
  48. "Timeout period exceeded, connection dropped.";
  49. /**
  50. * Constructor for SSHExecTask.
  51. */
  52. public SSHExec() {
  53. super();
  54. }
  55. /**
  56. * Sets the command to execute on the remote host.
  57. *
  58. * @param command The new command value
  59. */
  60. public void setCommand(String command) {
  61. this.command = command;
  62. }
  63. /**
  64. * The connection can be dropped after a specified number of
  65. * milliseconds. This is sometimes useful when a connection may be
  66. * flaky. Default is 0, which means "wait forever".
  67. *
  68. * @param timeout The new timeout value in seconds
  69. */
  70. public void setTimeout(long timeout) {
  71. maxwait = timeout;
  72. }
  73. /**
  74. * If used, stores the output of the command to the given file.
  75. *
  76. * @param output The file to write to.
  77. */
  78. public void setOutput(File output) {
  79. outputFile = output;
  80. }
  81. /**
  82. * Determines if the output is appended to the file given in
  83. * <code>setOutput</code>. Default is false, that is, overwrite
  84. * the file.
  85. *
  86. * @param append True to append to an existing file, false to overwrite.
  87. */
  88. public void setAppend(boolean append) {
  89. this.append = append;
  90. }
  91. /**
  92. * If set, the output of the command will be stored in the given property.
  93. *
  94. * @param property The name of the property in which the command output
  95. * will be stored.
  96. */
  97. public void setOutputproperty(String property) {
  98. outputProperty = property;
  99. }
  100. /**
  101. * Execute the command on the remote host.
  102. *
  103. * @exception BuildException Most likely a network error or bad parameter.
  104. */
  105. public void execute() throws BuildException {
  106. if (getHost() == null) {
  107. throw new BuildException("Host is required.");
  108. }
  109. if (getUserInfo().getName() == null) {
  110. throw new BuildException("Username is required.");
  111. }
  112. if (getUserInfo().getKeyfile() == null
  113. && getUserInfo().getPassword() == null) {
  114. throw new BuildException("Password or Keyfile is required.");
  115. }
  116. if (command == null) {
  117. throw new BuildException("Command is required.");
  118. }
  119. ByteArrayOutputStream out = new ByteArrayOutputStream();
  120. TeeOutputStream tee = new TeeOutputStream(out, System.out);
  121. try {
  122. // execute the command
  123. Session session = openSession();
  124. session.setTimeout((int) maxwait);
  125. final ChannelExec channel = (ChannelExec) session.openChannel("exec");
  126. channel.setCommand(command);
  127. channel.setOutputStream(tee);
  128. channel.setExtOutputStream(tee);
  129. channel.connect();
  130. // wait for it to finish
  131. thread =
  132. new Thread() {
  133. public void run() {
  134. while (!channel.isEOF()) {
  135. if (thread == null) {
  136. return;
  137. }
  138. try {
  139. sleep(500);
  140. } catch (Exception e) {
  141. // ignored
  142. }
  143. }
  144. }
  145. };
  146. thread.start();
  147. thread.join(maxwait);
  148. if (thread.isAlive()) {
  149. // ran out of time
  150. thread = null;
  151. if (getFailonerror()) {
  152. throw new BuildException(TIMEOUT_MESSAGE);
  153. } else {
  154. log(TIMEOUT_MESSAGE, Project.MSG_ERR);
  155. }
  156. } else {
  157. // completed successfully
  158. if (outputProperty != null) {
  159. getProject().setProperty(outputProperty, out.toString());
  160. }
  161. if (outputFile != null) {
  162. writeToFile(out.toString(), append, outputFile);
  163. }
  164. // this is the wrong test if the remote OS is OpenVMS,
  165. // but there doesn't seem to be a way to detect it.
  166. int ec = channel.getExitStatus();
  167. if (ec != 0) {
  168. String msg = "Remote command failed with exit status " + ec;
  169. if (getFailonerror()) {
  170. throw new BuildException(msg);
  171. } else {
  172. log(msg, Project.MSG_ERR);
  173. }
  174. }
  175. }
  176. } catch (BuildException e) {
  177. throw e;
  178. } catch (JSchException e) {
  179. if (e.getMessage().indexOf("session is down") >= 0) {
  180. if (getFailonerror()) {
  181. throw new BuildException(TIMEOUT_MESSAGE, e);
  182. } else {
  183. log(TIMEOUT_MESSAGE, Project.MSG_ERR);
  184. }
  185. } else {
  186. if (getFailonerror()) {
  187. throw new BuildException(e);
  188. } else {
  189. log("Caught exception: " + e.getMessage(),
  190. Project.MSG_ERR);
  191. }
  192. }
  193. } catch (Exception e) {
  194. if (getFailonerror()) {
  195. throw new BuildException(e);
  196. } else {
  197. log("Caught exception: " + e.getMessage(), Project.MSG_ERR);
  198. }
  199. }
  200. }
  201. /**
  202. * Writes a string to a file. If destination file exists, it may be
  203. * overwritten depending on the "append" value.
  204. *
  205. * @param from string to write
  206. * @param to file to write to
  207. * @param append if true, append to existing file, else overwrite
  208. * @exception Exception most likely an IOException
  209. */
  210. private void writeToFile(String from, boolean append, File to)
  211. throws IOException {
  212. FileWriter out = null;
  213. try {
  214. out = new FileWriter(to.getAbsolutePath(), append);
  215. StringReader in = new StringReader(from);
  216. char[] buffer = new char[8192];
  217. int bytesRead;
  218. while (true) {
  219. bytesRead = in.read(buffer);
  220. if (bytesRead == -1) {
  221. break;
  222. }
  223. out.write(buffer, 0, bytesRead);
  224. }
  225. out.flush();
  226. } finally {
  227. if (out != null) {
  228. out.close();
  229. }
  230. }
  231. }
  232. }