001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs.optional.ssh;
020:
021: import org.apache.tools.ant.BuildException;
022: import org.apache.tools.ant.Project;
023: import org.apache.tools.ant.util.TeeOutputStream;
024: import org.apache.tools.ant.util.KeepAliveOutputStream;
025:
026: import java.io.ByteArrayOutputStream;
027: import java.io.File;
028: import java.io.FileWriter;
029: import java.io.IOException;
030: import java.io.StringReader;
031:
032: import com.jcraft.jsch.ChannelExec;
033: import com.jcraft.jsch.JSchException;
034: import com.jcraft.jsch.Session;
035:
036: /**
037: * Executes a command on a remote machine via ssh.
038: * @since Ant 1.6 (created February 2, 2003)
039: */
040: public class SSHExec extends SSHBase {
041:
042: /** the command to execute via ssh */
043: private String command = null;
044:
045: /** units are milliseconds, default is 0=infinite */
046: private long maxwait = 0;
047:
048: /** for waiting for the command to finish */
049: private Thread thread = null;
050:
051: private String outputProperty = null; // like <exec>
052: private File outputFile = null; // like <exec>
053: private boolean append = false; // like <exec>
054:
055: private static final String TIMEOUT_MESSAGE = "Timeout period exceeded, connection dropped.";
056:
057: /**
058: * Constructor for SSHExecTask.
059: */
060: public SSHExec() {
061: super ();
062: }
063:
064: /**
065: * Sets the command to execute on the remote host.
066: *
067: * @param command The new command value
068: */
069: public void setCommand(String command) {
070: this .command = command;
071: }
072:
073: /**
074: * The connection can be dropped after a specified number of
075: * milliseconds. This is sometimes useful when a connection may be
076: * flaky. Default is 0, which means "wait forever".
077: *
078: * @param timeout The new timeout value in seconds
079: */
080: public void setTimeout(long timeout) {
081: maxwait = timeout;
082: }
083:
084: /**
085: * If used, stores the output of the command to the given file.
086: *
087: * @param output The file to write to.
088: */
089: public void setOutput(File output) {
090: outputFile = output;
091: }
092:
093: /**
094: * Determines if the output is appended to the file given in
095: * <code>setOutput</code>. Default is false, that is, overwrite
096: * the file.
097: *
098: * @param append True to append to an existing file, false to overwrite.
099: */
100: public void setAppend(boolean append) {
101: this .append = append;
102: }
103:
104: /**
105: * If set, the output of the command will be stored in the given property.
106: *
107: * @param property The name of the property in which the command output
108: * will be stored.
109: */
110: public void setOutputproperty(String property) {
111: outputProperty = property;
112: }
113:
114: /**
115: * Execute the command on the remote host.
116: *
117: * @exception BuildException Most likely a network error or bad parameter.
118: */
119: public void execute() throws BuildException {
120: if (getHost() == null) {
121: throw new BuildException("Host is required.");
122: }
123: if (getUserInfo().getName() == null) {
124: throw new BuildException("Username is required.");
125: }
126: if (getUserInfo().getKeyfile() == null
127: && getUserInfo().getPassword() == null) {
128: throw new BuildException("Password or Keyfile is required.");
129: }
130: if (command == null) {
131: throw new BuildException("Command is required.");
132: }
133:
134: ByteArrayOutputStream out = new ByteArrayOutputStream();
135: TeeOutputStream tee = new TeeOutputStream(out,
136: new KeepAliveOutputStream(System.out));
137:
138: Session session = null;
139: try {
140: // execute the command
141: session = openSession();
142: session.setTimeout((int) maxwait);
143: final ChannelExec channel = (ChannelExec) session
144: .openChannel("exec");
145: channel.setCommand(command);
146: channel.setOutputStream(tee);
147: channel.setExtOutputStream(tee);
148: channel.connect();
149:
150: // wait for it to finish
151: thread = new Thread() {
152: public void run() {
153: while (!channel.isEOF()) {
154: if (thread == null) {
155: return;
156: }
157: try {
158: sleep(500);
159: } catch (Exception e) {
160: // ignored
161: }
162: }
163: }
164: };
165:
166: thread.start();
167: thread.join(maxwait);
168:
169: if (thread.isAlive()) {
170: // ran out of time
171: thread = null;
172: if (getFailonerror()) {
173: throw new BuildException(TIMEOUT_MESSAGE);
174: } else {
175: log(TIMEOUT_MESSAGE, Project.MSG_ERR);
176: }
177: } else {
178: // completed successfully
179: if (outputProperty != null) {
180: getProject().setProperty(outputProperty,
181: out.toString());
182: }
183: if (outputFile != null) {
184: writeToFile(out.toString(), append, outputFile);
185: }
186:
187: // this is the wrong test if the remote OS is OpenVMS,
188: // but there doesn't seem to be a way to detect it.
189: int ec = channel.getExitStatus();
190: if (ec != 0) {
191: String msg = "Remote command failed with exit status "
192: + ec;
193: if (getFailonerror()) {
194: throw new BuildException(msg);
195: } else {
196: log(msg, Project.MSG_ERR);
197: }
198: }
199: }
200: } catch (BuildException e) {
201: throw e;
202: } catch (JSchException e) {
203: if (e.getMessage().indexOf("session is down") >= 0) {
204: if (getFailonerror()) {
205: throw new BuildException(TIMEOUT_MESSAGE, e);
206: } else {
207: log(TIMEOUT_MESSAGE, Project.MSG_ERR);
208: }
209: } else {
210: if (getFailonerror()) {
211: throw new BuildException(e);
212: } else {
213: log("Caught exception: " + e.getMessage(),
214: Project.MSG_ERR);
215: }
216: }
217: } catch (Exception e) {
218: if (getFailonerror()) {
219: throw new BuildException(e);
220: } else {
221: log("Caught exception: " + e.getMessage(),
222: Project.MSG_ERR);
223: }
224: } finally {
225: if (session != null && session.isConnected()) {
226: session.disconnect();
227: }
228: }
229: }
230:
231: /**
232: * Writes a string to a file. If destination file exists, it may be
233: * overwritten depending on the "append" value.
234: *
235: * @param from string to write
236: * @param to file to write to
237: * @param append if true, append to existing file, else overwrite
238: * @exception Exception most likely an IOException
239: */
240: private void writeToFile(String from, boolean append, File to)
241: throws IOException {
242: FileWriter out = null;
243: try {
244: out = new FileWriter(to.getAbsolutePath(), append);
245: StringReader in = new StringReader(from);
246: char[] buffer = new char[8192];
247: int bytesRead;
248: while (true) {
249: bytesRead = in.read(buffer);
250: if (bytesRead == -1) {
251: break;
252: }
253: out.write(buffer, 0, bytesRead);
254: }
255: out.flush();
256: } finally {
257: if (out != null) {
258: out.close();
259: }
260: }
261: }
262:
263: }
|