aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java167
1 files changed, 167 insertions, 0 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
new file mode 100644
index 0000000000..42a76b5b16
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2018, Markus Duft <markus.duft@ssi-schaefer.com> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.util;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.CommandFailedException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.RemoteSession;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.io.MessageWriter;
+import org.eclipse.jgit.util.io.StreamCopyThread;
+
+/**
+ * Extra utilities to support usage of SSH.
+ *
+ * @since 5.0
+ */
+public class SshSupport {
+
+ /**
+ * Utility to execute a remote SSH command and read the first line of
+ * output.
+ *
+ * @param sshUri
+ * the SSH remote URI
+ * @param provider
+ * the {@link CredentialsProvider} or <code>null</code>.
+ * @param fs
+ * the {@link FS} implementation passed to
+ * {@link SshSessionFactory}
+ * @param command
+ * the remote command to execute.
+ * @param timeout
+ * a timeout in seconds. The timeout may be exceeded in corner
+ * cases.
+ * @return The entire output read from stdout.
+ * @throws IOException
+ * if an IO error occurred
+ * @throws CommandFailedException
+ * if the ssh command execution failed, error message contains
+ * the content of stderr.
+ */
+ public static String runSshCommand(URIish sshUri,
+ @Nullable CredentialsProvider provider, FS fs, String command,
+ int timeout) throws IOException, CommandFailedException {
+ RemoteSession session = null;
+ Process process = null;
+ StreamCopyThread errorThread = null;
+ StreamCopyThread outThread = null;
+ CommandFailedException failure = null;
+ @SuppressWarnings("resource")
+ MessageWriter stderr = new MessageWriter();
+ @SuppressWarnings("resource")
+ MessageWriter stdout = new MessageWriter();
+ String out;
+ try {
+ long start = System.nanoTime();
+ session = SshSessionFactory.getInstance().getSession(sshUri,
+ provider, fs, 1000 * timeout);
+ int commandTimeout = timeout;
+ if (timeout > 0) {
+ commandTimeout = checkTimeout(command, timeout, start);
+ }
+ process = session.exec(command, commandTimeout);
+ if (timeout > 0) {
+ commandTimeout = checkTimeout(command, timeout, start);
+ }
+ errorThread = new StreamCopyThread(process.getErrorStream(),
+ stderr.getRawStream());
+ errorThread.start();
+ outThread = new StreamCopyThread(process.getInputStream(),
+ stdout.getRawStream());
+ outThread.start();
+ try {
+ boolean finished = false;
+ if (timeout <= 0) {
+ process.waitFor();
+ finished = true;
+ } else {
+ finished = process.waitFor(commandTimeout,
+ TimeUnit.SECONDS);
+ }
+ if (finished) {
+ out = stdout.toString();
+ } else {
+ out = null; // still running after timeout
+ }
+ } catch (InterruptedException e) {
+ out = null; // error
+ }
+ } finally {
+ if (errorThread != null) {
+ try {
+ errorThread.halt();
+ } catch (InterruptedException e) {
+ // Stop waiting and return anyway.
+ } finally {
+ errorThread = null;
+ }
+ }
+ if (outThread != null) {
+ try {
+ outThread.halt();
+ } catch (InterruptedException e) {
+ // Stop waiting and return anyway.
+ } finally {
+ outThread = null;
+ }
+ }
+ if (process != null) {
+ try {
+ if (process.exitValue() != 0) {
+ failure = new CommandFailedException(
+ process.exitValue(),
+ MessageFormat.format(
+ JGitText.get().sshCommandFailed,
+ command, stderr.toString()));
+ }
+ // It was successful after all
+ out = stdout.toString();
+ } catch (IllegalThreadStateException e) {
+ failure = new CommandFailedException(0,
+ MessageFormat.format(
+ JGitText.get().sshCommandTimeout, command,
+ Integer.valueOf(timeout)));
+ }
+ process.destroy();
+ }
+ stderr.close();
+ stdout.close();
+ if (session != null) {
+ SshSessionFactory.getInstance().releaseSession(session);
+ }
+ }
+ if (failure != null) {
+ throw failure;
+ }
+ return out;
+ }
+
+ private static int checkTimeout(String command, int timeout, long since)
+ throws CommandFailedException {
+ long elapsed = System.nanoTime() - since;
+ int newTimeout = timeout
+ - (int) TimeUnit.NANOSECONDS.toSeconds(elapsed);
+ if (newTimeout <= 0) {
+ // All time used up for connecting the session
+ throw new CommandFailedException(0,
+ MessageFormat.format(JGitText.get().sshCommandTimeout,
+ command, Integer.valueOf(timeout)));
+ }
+ return newTimeout;
+ }
+}