SshSupport.java

  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. import java.io.IOException;
  12. import java.text.MessageFormat;
  13. import java.util.concurrent.TimeUnit;

  14. import org.eclipse.jgit.annotations.Nullable;
  15. import org.eclipse.jgit.errors.CommandFailedException;
  16. import org.eclipse.jgit.internal.JGitText;
  17. import org.eclipse.jgit.transport.CredentialsProvider;
  18. import org.eclipse.jgit.transport.RemoteSession;
  19. import org.eclipse.jgit.transport.SshSessionFactory;
  20. import org.eclipse.jgit.transport.URIish;
  21. import org.eclipse.jgit.util.io.MessageWriter;
  22. import org.eclipse.jgit.util.io.StreamCopyThread;

  23. /**
  24.  * Extra utilities to support usage of SSH.
  25.  *
  26.  * @since 5.0
  27.  */
  28. public class SshSupport {

  29.     /**
  30.      * Utility to execute a remote SSH command and read the first line of
  31.      * output.
  32.      *
  33.      * @param sshUri
  34.      *            the SSH remote URI
  35.      * @param provider
  36.      *            the {@link CredentialsProvider} or <code>null</code>.
  37.      * @param fs
  38.      *            the {@link FS} implementation passed to
  39.      *            {@link SshSessionFactory}
  40.      * @param command
  41.      *            the remote command to execute.
  42.      * @param timeout
  43.      *            a timeout in seconds. The timeout may be exceeded in corner
  44.      *            cases.
  45.      * @return The entire output read from stdout.
  46.      * @throws IOException
  47.      * @throws CommandFailedException
  48.      *             if the ssh command execution failed, error message contains
  49.      *             the content of stderr.
  50.      */
  51.     public static String runSshCommand(URIish sshUri,
  52.             @Nullable CredentialsProvider provider, FS fs, String command,
  53.             int timeout) throws IOException, CommandFailedException {
  54.         RemoteSession session = null;
  55.         Process process = null;
  56.         StreamCopyThread errorThread = null;
  57.         StreamCopyThread outThread = null;
  58.         CommandFailedException failure = null;
  59.         @SuppressWarnings("resource")
  60.         MessageWriter stderr = new MessageWriter();
  61.         @SuppressWarnings("resource")
  62.         MessageWriter stdout = new MessageWriter();
  63.         String out;
  64.         try {
  65.             long start = System.nanoTime();
  66.             session = SshSessionFactory.getInstance().getSession(sshUri,
  67.                     provider, fs, 1000 * timeout);
  68.             int commandTimeout = timeout;
  69.             if (timeout > 0) {
  70.                 commandTimeout = checkTimeout(command, timeout, start);
  71.             }
  72.             process = session.exec(command, commandTimeout);
  73.             if (timeout > 0) {
  74.                 commandTimeout = checkTimeout(command, timeout, start);
  75.             }
  76.             errorThread = new StreamCopyThread(process.getErrorStream(),
  77.                     stderr.getRawStream());
  78.             errorThread.start();
  79.             outThread = new StreamCopyThread(process.getInputStream(),
  80.                     stdout.getRawStream());
  81.             outThread.start();
  82.             try {
  83.                 boolean finished = false;
  84.                 if (timeout <= 0) {
  85.                     process.waitFor();
  86.                     finished = true;
  87.                 } else {
  88.                     finished = process.waitFor(commandTimeout,
  89.                             TimeUnit.SECONDS);
  90.                 }
  91.                 if (finished) {
  92.                     out = stdout.toString();
  93.                 } else {
  94.                     out = null; // still running after timeout
  95.                 }
  96.             } catch (InterruptedException e) {
  97.                 out = null; // error
  98.             }
  99.         } finally {
  100.             if (errorThread != null) {
  101.                 try {
  102.                     errorThread.halt();
  103.                 } catch (InterruptedException e) {
  104.                     // Stop waiting and return anyway.
  105.                 } finally {
  106.                     errorThread = null;
  107.                 }
  108.             }
  109.             if (outThread != null) {
  110.                 try {
  111.                     outThread.halt();
  112.                 } catch (InterruptedException e) {
  113.                     // Stop waiting and return anyway.
  114.                 } finally {
  115.                     outThread = null;
  116.                 }
  117.             }
  118.             if (process != null) {
  119.                 try {
  120.                     if (process.exitValue() != 0) {
  121.                         failure = new CommandFailedException(
  122.                                 process.exitValue(),
  123.                                 MessageFormat.format(
  124.                                         JGitText.get().sshCommandFailed,
  125.                                         command, stderr.toString()));
  126.                     }
  127.                     // It was successful after all
  128.                     out = stdout.toString();
  129.                 } catch (IllegalThreadStateException e) {
  130.                     failure = new CommandFailedException(0,
  131.                             MessageFormat.format(
  132.                                     JGitText.get().sshCommandTimeout, command,
  133.                                     Integer.valueOf(timeout)));
  134.                 }
  135.                 process.destroy();
  136.             }
  137.             stderr.close();
  138.             stdout.close();
  139.             if (session != null) {
  140.                 SshSessionFactory.getInstance().releaseSession(session);
  141.             }
  142.         }
  143.         if (failure != null) {
  144.             throw failure;
  145.         }
  146.         return out;
  147.     }

  148.     private static int checkTimeout(String command, int timeout, long since)
  149.             throws CommandFailedException {
  150.         long elapsed = System.nanoTime() - since;
  151.         int newTimeout = timeout
  152.                 - (int) TimeUnit.NANOSECONDS.toSeconds(elapsed);
  153.         if (newTimeout <= 0) {
  154.             // All time used up for connecting the session
  155.             throw new CommandFailedException(0,
  156.                     MessageFormat.format(JGitText.get().sshCommandTimeout,
  157.                             command, Integer.valueOf(timeout)));
  158.         }
  159.         return newTimeout;
  160.     }
  161. }