View Javadoc
1   /*
2    * Copyright (C) 2018, Markus Duft <markus.duft@ssi-schaefer.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.util;
11  
12  import java.io.IOException;
13  import java.text.MessageFormat;
14  import java.util.concurrent.TimeUnit;
15  
16  import org.eclipse.jgit.annotations.Nullable;
17  import org.eclipse.jgit.errors.CommandFailedException;
18  import org.eclipse.jgit.internal.JGitText;
19  import org.eclipse.jgit.transport.CredentialsProvider;
20  import org.eclipse.jgit.transport.RemoteSession;
21  import org.eclipse.jgit.transport.SshSessionFactory;
22  import org.eclipse.jgit.transport.URIish;
23  import org.eclipse.jgit.util.io.MessageWriter;
24  import org.eclipse.jgit.util.io.StreamCopyThread;
25  
26  /**
27   * Extra utilities to support usage of SSH.
28   *
29   * @since 5.0
30   */
31  public class SshSupport {
32  
33  	/**
34  	 * Utility to execute a remote SSH command and read the first line of
35  	 * output.
36  	 *
37  	 * @param sshUri
38  	 *            the SSH remote URI
39  	 * @param provider
40  	 *            the {@link CredentialsProvider} or <code>null</code>.
41  	 * @param fs
42  	 *            the {@link FS} implementation passed to
43  	 *            {@link SshSessionFactory}
44  	 * @param command
45  	 *            the remote command to execute.
46  	 * @param timeout
47  	 *            a timeout in seconds. The timeout may be exceeded in corner
48  	 *            cases.
49  	 * @return The entire output read from stdout.
50  	 * @throws IOException
51  	 * @throws CommandFailedException
52  	 *             if the ssh command execution failed, error message contains
53  	 *             the content of stderr.
54  	 */
55  	public static String runSshCommand(URIish sshUri,
56  			@Nullable CredentialsProvider provider, FS fs, String command,
57  			int timeout) throws IOException, CommandFailedException {
58  		RemoteSession session = null;
59  		Process process = null;
60  		StreamCopyThread errorThread = null;
61  		StreamCopyThread outThread = null;
62  		CommandFailedException failure = null;
63  		@SuppressWarnings("resource")
64  		MessageWriter stderr = new MessageWriter();
65  		@SuppressWarnings("resource")
66  		MessageWriter stdout = new MessageWriter();
67  		String out;
68  		try {
69  			long start = System.nanoTime();
70  			session = SshSessionFactory.getInstance().getSession(sshUri,
71  					provider, fs, 1000 * timeout);
72  			int commandTimeout = timeout;
73  			if (timeout > 0) {
74  				commandTimeout = checkTimeout(command, timeout, start);
75  			}
76  			process = session.exec(command, commandTimeout);
77  			if (timeout > 0) {
78  				commandTimeout = checkTimeout(command, timeout, start);
79  			}
80  			errorThread = new StreamCopyThread(process.getErrorStream(),
81  					stderr.getRawStream());
82  			errorThread.start();
83  			outThread = new StreamCopyThread(process.getInputStream(),
84  					stdout.getRawStream());
85  			outThread.start();
86  			try {
87  				boolean finished = false;
88  				if (timeout <= 0) {
89  					process.waitFor();
90  					finished = true;
91  				} else {
92  					finished = process.waitFor(commandTimeout,
93  							TimeUnit.SECONDS);
94  				}
95  				if (finished) {
96  					out = stdout.toString();
97  				} else {
98  					out = null; // still running after timeout
99  				}
100 			} catch (InterruptedException e) {
101 				out = null; // error
102 			}
103 		} finally {
104 			if (errorThread != null) {
105 				try {
106 					errorThread.halt();
107 				} catch (InterruptedException e) {
108 					// Stop waiting and return anyway.
109 				} finally {
110 					errorThread = null;
111 				}
112 			}
113 			if (outThread != null) {
114 				try {
115 					outThread.halt();
116 				} catch (InterruptedException e) {
117 					// Stop waiting and return anyway.
118 				} finally {
119 					outThread = null;
120 				}
121 			}
122 			if (process != null) {
123 				try {
124 					if (process.exitValue() != 0) {
125 						failure = new CommandFailedException(
126 								process.exitValue(),
127 								MessageFormat.format(
128 										JGitText.get().sshCommandFailed,
129 										command, stderr.toString()));
130 					}
131 					// It was successful after all
132 					out = stdout.toString();
133 				} catch (IllegalThreadStateException e) {
134 					failure = new CommandFailedException(0,
135 							MessageFormat.format(
136 									JGitText.get().sshCommandTimeout, command,
137 									Integer.valueOf(timeout)));
138 				}
139 				process.destroy();
140 			}
141 			stderr.close();
142 			stdout.close();
143 			if (session != null) {
144 				SshSessionFactory.getInstance().releaseSession(session);
145 			}
146 		}
147 		if (failure != null) {
148 			throw failure;
149 		}
150 		return out;
151 	}
152 
153 	private static int checkTimeout(String command, int timeout, long since)
154 			throws CommandFailedException {
155 		long elapsed = System.nanoTime() - since;
156 		int newTimeout = timeout
157 				- (int) TimeUnit.NANOSECONDS.toSeconds(elapsed);
158 		if (newTimeout <= 0) {
159 			// All time used up for connecting the session
160 			throw new CommandFailedException(0,
161 					MessageFormat.format(JGitText.get().sshCommandTimeout,
162 							command, Integer.valueOf(timeout)));
163 		}
164 		return newTimeout;
165 	}
166 }