aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java')
-rw-r--r--org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java373
1 files changed, 373 insertions, 0 deletions
diff --git a/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java
new file mode 100644
index 0000000000..300e03d798
--- /dev/null
+++ b/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Google, Inc.
+ * Copyright (C) 2009, JetBrains s.r.o.
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 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
+ */
+
+//TODO(ms): move to org.eclipse.jgit.ssh.jsch in 6.0
+package org.eclipse.jgit.transport;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.internal.transport.jsch.JSchText;
+import org.eclipse.jgit.util.io.IsolatedOutputStream;
+
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import com.jcraft.jsch.SftpException;
+
+/**
+ * Run remote commands using Jsch.
+ * <p>
+ * This class is the default session implementation using Jsch. Note that
+ * {@link org.eclipse.jgit.transport.JschConfigSessionFactory} is used to create
+ * the actual session passed to the constructor.
+ */
+public class JschSession implements RemoteSession {
+ final Session sock;
+ final URIish uri;
+
+ /**
+ * Create a new session object by passing the real Jsch session and the URI
+ * information.
+ *
+ * @param session
+ * the real Jsch session created elsewhere.
+ * @param uri
+ * the URI information for the remote connection
+ */
+ public JschSession(Session session, URIish uri) {
+ sock = session;
+ this.uri = uri;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Process exec(String command, int timeout) throws IOException {
+ return new JschProcess(command, timeout);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void disconnect() {
+ if (sock.isConnected())
+ sock.disconnect();
+ }
+
+ /**
+ * A kludge to allow {@link org.eclipse.jgit.transport.TransportSftp} to get
+ * an Sftp channel from Jsch. Ideally, this method would be generic, which
+ * would require implementing generic Sftp channel operations in the
+ * RemoteSession class.
+ *
+ * @return a channel suitable for Sftp operations.
+ * @throws com.jcraft.jsch.JSchException
+ * on problems getting the channel.
+ * @deprecated since 5.2; use {@link #getFtpChannel()} instead
+ */
+ @Deprecated
+ public Channel getSftpChannel() throws JSchException {
+ return sock.openChannel("sftp"); //$NON-NLS-1$
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 5.2
+ */
+ @Override
+ public FtpChannel getFtpChannel() {
+ return new JschFtpChannel();
+ }
+
+ /**
+ * Implementation of Process for running a single command using Jsch.
+ * <p>
+ * Uses the Jsch session to do actual command execution and manage the
+ * execution.
+ */
+ private class JschProcess extends Process {
+ private ChannelExec channel;
+
+ final int timeout;
+
+ private InputStream inputStream;
+
+ private OutputStream outputStream;
+
+ private InputStream errStream;
+
+ /**
+ * Opens a channel on the session ("sock") for executing the given
+ * command, opens streams, and starts command execution.
+ *
+ * @param commandName
+ * the command to execute
+ * @param tms
+ * the timeout value, in seconds, for the command.
+ * @throws TransportException
+ * on problems opening a channel or connecting to the remote
+ * host
+ * @throws IOException
+ * on problems opening streams
+ */
+ JschProcess(String commandName, int tms)
+ throws TransportException, IOException {
+ timeout = tms;
+ try {
+ channel = (ChannelExec) sock.openChannel("exec"); //$NON-NLS-1$
+ channel.setCommand(commandName);
+ setupStreams();
+ channel.connect(timeout > 0 ? timeout * 1000 : 0);
+ if (!channel.isConnected()) {
+ closeOutputStream();
+ throw new TransportException(uri,
+ JSchText.get().connectionFailed);
+ }
+ } catch (JSchException e) {
+ closeOutputStream();
+ throw new TransportException(uri, e.getMessage(), e);
+ }
+ }
+
+ private void closeOutputStream() {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ private void setupStreams() throws IOException {
+ inputStream = channel.getInputStream();
+
+ // JSch won't let us interrupt writes when we use our InterruptTimer
+ // to break out of a long-running write operation. To work around
+ // that we spawn a background thread to shuttle data through a pipe,
+ // as we can issue an interrupted write out of that. Its slower, so
+ // we only use this route if there is a timeout.
+ OutputStream out = channel.getOutputStream();
+ if (timeout <= 0) {
+ outputStream = out;
+ } else {
+ IsolatedOutputStream i = new IsolatedOutputStream(out);
+ outputStream = new BufferedOutputStream(i, 16 * 1024);
+ }
+
+ errStream = channel.getErrStream();
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return inputStream;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return outputStream;
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return errStream;
+ }
+
+ @Override
+ public int exitValue() {
+ if (isRunning())
+ throw new IllegalStateException();
+ return channel.getExitStatus();
+ }
+
+ private boolean isRunning() {
+ return channel.getExitStatus() < 0 && channel.isConnected();
+ }
+
+ @Override
+ public void destroy() {
+ if (channel.isConnected())
+ channel.disconnect();
+ closeOutputStream();
+ }
+
+ @Override
+ public int waitFor() throws InterruptedException {
+ while (isRunning())
+ Thread.sleep(100);
+ return exitValue();
+ }
+ }
+
+ private class JschFtpChannel implements FtpChannel {
+
+ private ChannelSftp ftp;
+
+ @Override
+ public void connect(int timeout, TimeUnit unit) throws IOException {
+ try {
+ ftp = (ChannelSftp) sock.openChannel("sftp"); //$NON-NLS-1$
+ ftp.connect((int) unit.toMillis(timeout));
+ } catch (JSchException e) {
+ ftp = null;
+ throw new IOException(e.getLocalizedMessage(), e);
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ ftp.disconnect();
+ ftp = null;
+ }
+
+ private <T> T map(Callable<T> op) throws IOException {
+ try {
+ return op.call();
+ } catch (Exception e) {
+ if (e instanceof SftpException) {
+ throw new FtpChannel.FtpException(e.getLocalizedMessage(),
+ ((SftpException) e).id, e);
+ }
+ throw new IOException(e.getLocalizedMessage(), e);
+ }
+ }
+
+ @Override
+ public boolean isConnected() {
+ return ftp != null && sock.isConnected();
+ }
+
+ @Override
+ public void cd(String path) throws IOException {
+ map(() -> {
+ ftp.cd(path);
+ return null;
+ });
+ }
+
+ @Override
+ public String pwd() throws IOException {
+ return map(() -> ftp.pwd());
+ }
+
+ @Override
+ public Collection<DirEntry> ls(String path) throws IOException {
+ return map(() -> {
+ List<DirEntry> result = new ArrayList<>();
+ for (Object e : ftp.ls(path)) {
+ ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) e;
+ result.add(new DirEntry() {
+
+ @Override
+ public String getFilename() {
+ return entry.getFilename();
+ }
+
+ @Override
+ public long getModifiedTime() {
+ return entry.getAttrs().getMTime();
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return entry.getAttrs().isDir();
+ }
+ });
+ }
+ return result;
+ });
+ }
+
+ @Override
+ public void rmdir(String path) throws IOException {
+ map(() -> {
+ ftp.rm(path);
+ return null;
+ });
+ }
+
+ @Override
+ public void mkdir(String path) throws IOException {
+ map(() -> {
+ ftp.mkdir(path);
+ return null;
+ });
+ }
+
+ @Override
+ public InputStream get(String path) throws IOException {
+ return map(() -> ftp.get(path));
+ }
+
+ @Override
+ public OutputStream put(String path) throws IOException {
+ return map(() -> ftp.put(path));
+ }
+
+ @Override
+ public void rm(String path) throws IOException {
+ map(() -> {
+ ftp.rm(path);
+ return null;
+ });
+ }
+
+ @Override
+ public void rename(String from, String to) throws IOException {
+ map(() -> {
+ // Plain FTP rename will fail if "to" exists. Jsch knows about
+ // the FTP extension "posix-rename@openssh.com", which will
+ // remove "to" first if it exists.
+ if (hasPosixRename()) {
+ ftp.rename(from, to);
+ } else if (!to.equals(from)) {
+ // Try to remove "to" first. With git, we typically get this
+ // when a lock file is moved over the file locked. Note that
+ // the check for to being equal to from may still fail in
+ // the general case, but for use with JGit's TransportSftp
+ // it should be good enough.
+ delete(to);
+ ftp.rename(from, to);
+ }
+ return null;
+ });
+ }
+
+ /**
+ * Determine whether the server has the posix-rename extension.
+ *
+ * @return {@code true} if it is supported, {@code false} otherwise
+ * @see <a href=
+ * "https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD">OpenSSH
+ * deviations and extensions to the published SSH protocol</a>
+ * @see <a href=
+ * "http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html">stdio.h:
+ * rename()</a>
+ */
+ private boolean hasPosixRename() {
+ return "1".equals(ftp.getExtension("posix-rename@openssh.com")); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+}