xref: /JGit/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/JschSession.java (revision 0853a2410f22c8bd97a179dec14e3c083a27abbb)
18d2d6836SMatthias Sohn /*
28d2d6836SMatthias Sohn  * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
38d2d6836SMatthias Sohn  * Copyright (C) 2008-2009, Google Inc.
48d2d6836SMatthias Sohn  * Copyright (C) 2009, Google, Inc.
58d2d6836SMatthias Sohn  * Copyright (C) 2009, JetBrains s.r.o.
68d2d6836SMatthias Sohn  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
78d2d6836SMatthias Sohn  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
88d2d6836SMatthias Sohn  *
98d2d6836SMatthias Sohn  * This program and the accompanying materials are made available under the
108d2d6836SMatthias Sohn  * terms of the Eclipse Distribution License v. 1.0 which is available at
118d2d6836SMatthias Sohn  * https://www.eclipse.org/org/documents/edl-v10.php.
128d2d6836SMatthias Sohn  *
138d2d6836SMatthias Sohn  * SPDX-License-Identifier: BSD-3-Clause
148d2d6836SMatthias Sohn  */
158d2d6836SMatthias Sohn 
168d2d6836SMatthias Sohn //TODO(ms): move to org.eclipse.jgit.ssh.jsch in 6.0
178d2d6836SMatthias Sohn package org.eclipse.jgit.transport;
188d2d6836SMatthias Sohn 
198d2d6836SMatthias Sohn import java.io.BufferedOutputStream;
208d2d6836SMatthias Sohn import java.io.IOException;
218d2d6836SMatthias Sohn import java.io.InputStream;
228d2d6836SMatthias Sohn import java.io.OutputStream;
238d2d6836SMatthias Sohn import java.util.ArrayList;
248d2d6836SMatthias Sohn import java.util.Collection;
25*0853a241SThomas Wolf import java.util.Collections;
268d2d6836SMatthias Sohn import java.util.List;
27*0853a241SThomas Wolf import java.util.Map;
288d2d6836SMatthias Sohn import java.util.concurrent.Callable;
298d2d6836SMatthias Sohn import java.util.concurrent.TimeUnit;
308d2d6836SMatthias Sohn 
318d2d6836SMatthias Sohn import org.eclipse.jgit.errors.TransportException;
328d2d6836SMatthias Sohn import org.eclipse.jgit.internal.transport.jsch.JSchText;
338d2d6836SMatthias Sohn import org.eclipse.jgit.util.io.IsolatedOutputStream;
348d2d6836SMatthias Sohn 
358d2d6836SMatthias Sohn import com.jcraft.jsch.Channel;
368d2d6836SMatthias Sohn import com.jcraft.jsch.ChannelExec;
378d2d6836SMatthias Sohn import com.jcraft.jsch.ChannelSftp;
388d2d6836SMatthias Sohn import com.jcraft.jsch.JSchException;
398d2d6836SMatthias Sohn import com.jcraft.jsch.Session;
408d2d6836SMatthias Sohn import com.jcraft.jsch.SftpException;
418d2d6836SMatthias Sohn 
428d2d6836SMatthias Sohn /**
438d2d6836SMatthias Sohn  * Run remote commands using Jsch.
448d2d6836SMatthias Sohn  * <p>
458d2d6836SMatthias Sohn  * This class is the default session implementation using Jsch. Note that
468d2d6836SMatthias Sohn  * {@link org.eclipse.jgit.transport.JschConfigSessionFactory} is used to create
478d2d6836SMatthias Sohn  * the actual session passed to the constructor.
488d2d6836SMatthias Sohn  */
49*0853a241SThomas Wolf public class JschSession implements RemoteSession2 {
508d2d6836SMatthias Sohn 	final Session sock;
518d2d6836SMatthias Sohn 	final URIish uri;
528d2d6836SMatthias Sohn 
538d2d6836SMatthias Sohn 	/**
548d2d6836SMatthias Sohn 	 * Create a new session object by passing the real Jsch session and the URI
558d2d6836SMatthias Sohn 	 * information.
568d2d6836SMatthias Sohn 	 *
578d2d6836SMatthias Sohn 	 * @param session
588d2d6836SMatthias Sohn 	 *            the real Jsch session created elsewhere.
598d2d6836SMatthias Sohn 	 * @param uri
608d2d6836SMatthias Sohn 	 *            the URI information for the remote connection
618d2d6836SMatthias Sohn 	 */
JschSession(Session session, URIish uri)628d2d6836SMatthias Sohn 	public JschSession(Session session, URIish uri) {
638d2d6836SMatthias Sohn 		sock = session;
648d2d6836SMatthias Sohn 		this.uri = uri;
658d2d6836SMatthias Sohn 	}
668d2d6836SMatthias Sohn 
678d2d6836SMatthias Sohn 	/** {@inheritDoc} */
688d2d6836SMatthias Sohn 	@Override
exec(String command, int timeout)698d2d6836SMatthias Sohn 	public Process exec(String command, int timeout) throws IOException {
70*0853a241SThomas Wolf 		return exec(command, Collections.emptyMap(), timeout);
71*0853a241SThomas Wolf 	}
72*0853a241SThomas Wolf 
73*0853a241SThomas Wolf 	/** {@inheritDoc} */
74*0853a241SThomas Wolf 	@Override
exec(String command, Map<String, String> environment, int timeout)75*0853a241SThomas Wolf 	public Process exec(String command, Map<String, String> environment,
76*0853a241SThomas Wolf 			int timeout) throws IOException {
77*0853a241SThomas Wolf 		return new JschProcess(command, environment, timeout);
788d2d6836SMatthias Sohn 	}
798d2d6836SMatthias Sohn 
808d2d6836SMatthias Sohn 	/** {@inheritDoc} */
818d2d6836SMatthias Sohn 	@Override
disconnect()828d2d6836SMatthias Sohn 	public void disconnect() {
838d2d6836SMatthias Sohn 		if (sock.isConnected())
848d2d6836SMatthias Sohn 			sock.disconnect();
858d2d6836SMatthias Sohn 	}
868d2d6836SMatthias Sohn 
878d2d6836SMatthias Sohn 	/**
888d2d6836SMatthias Sohn 	 * A kludge to allow {@link org.eclipse.jgit.transport.TransportSftp} to get
898d2d6836SMatthias Sohn 	 * an Sftp channel from Jsch. Ideally, this method would be generic, which
908d2d6836SMatthias Sohn 	 * would require implementing generic Sftp channel operations in the
918d2d6836SMatthias Sohn 	 * RemoteSession class.
928d2d6836SMatthias Sohn 	 *
938d2d6836SMatthias Sohn 	 * @return a channel suitable for Sftp operations.
948d2d6836SMatthias Sohn 	 * @throws com.jcraft.jsch.JSchException
958d2d6836SMatthias Sohn 	 *             on problems getting the channel.
968d2d6836SMatthias Sohn 	 * @deprecated since 5.2; use {@link #getFtpChannel()} instead
978d2d6836SMatthias Sohn 	 */
988d2d6836SMatthias Sohn 	@Deprecated
getSftpChannel()998d2d6836SMatthias Sohn 	public Channel getSftpChannel() throws JSchException {
1008d2d6836SMatthias Sohn 		return sock.openChannel("sftp"); //$NON-NLS-1$
1018d2d6836SMatthias Sohn 	}
1028d2d6836SMatthias Sohn 
1038d2d6836SMatthias Sohn 	/**
1048d2d6836SMatthias Sohn 	 * {@inheritDoc}
1058d2d6836SMatthias Sohn 	 *
1068d2d6836SMatthias Sohn 	 * @since 5.2
1078d2d6836SMatthias Sohn 	 */
1088d2d6836SMatthias Sohn 	@Override
getFtpChannel()1098d2d6836SMatthias Sohn 	public FtpChannel getFtpChannel() {
1108d2d6836SMatthias Sohn 		return new JschFtpChannel();
1118d2d6836SMatthias Sohn 	}
1128d2d6836SMatthias Sohn 
1138d2d6836SMatthias Sohn 	/**
1148d2d6836SMatthias Sohn 	 * Implementation of Process for running a single command using Jsch.
1158d2d6836SMatthias Sohn 	 * <p>
1168d2d6836SMatthias Sohn 	 * Uses the Jsch session to do actual command execution and manage the
1178d2d6836SMatthias Sohn 	 * execution.
1188d2d6836SMatthias Sohn 	 */
1198d2d6836SMatthias Sohn 	private class JschProcess extends Process {
1208d2d6836SMatthias Sohn 		private ChannelExec channel;
1218d2d6836SMatthias Sohn 
1228d2d6836SMatthias Sohn 		final int timeout;
1238d2d6836SMatthias Sohn 
1248d2d6836SMatthias Sohn 		private InputStream inputStream;
1258d2d6836SMatthias Sohn 
1268d2d6836SMatthias Sohn 		private OutputStream outputStream;
1278d2d6836SMatthias Sohn 
1288d2d6836SMatthias Sohn 		private InputStream errStream;
1298d2d6836SMatthias Sohn 
1308d2d6836SMatthias Sohn 		/**
1318d2d6836SMatthias Sohn 		 * Opens a channel on the session ("sock") for executing the given
1328d2d6836SMatthias Sohn 		 * command, opens streams, and starts command execution.
1338d2d6836SMatthias Sohn 		 *
1348d2d6836SMatthias Sohn 		 * @param commandName
1358d2d6836SMatthias Sohn 		 *            the command to execute
136*0853a241SThomas Wolf 		 * @param environment
137*0853a241SThomas Wolf 		 *            environment variables to pass on
1388d2d6836SMatthias Sohn 		 * @param tms
1398d2d6836SMatthias Sohn 		 *            the timeout value, in seconds, for the command.
1408d2d6836SMatthias Sohn 		 * @throws TransportException
1418d2d6836SMatthias Sohn 		 *             on problems opening a channel or connecting to the remote
1428d2d6836SMatthias Sohn 		 *             host
1438d2d6836SMatthias Sohn 		 * @throws IOException
1448d2d6836SMatthias Sohn 		 *             on problems opening streams
1458d2d6836SMatthias Sohn 		 */
JschProcess(String commandName, Map<String, String> environment, int tms)146*0853a241SThomas Wolf 		JschProcess(String commandName, Map<String, String> environment,
147*0853a241SThomas Wolf 				int tms) throws TransportException, IOException {
1488d2d6836SMatthias Sohn 			timeout = tms;
1498d2d6836SMatthias Sohn 			try {
1508d2d6836SMatthias Sohn 				channel = (ChannelExec) sock.openChannel("exec"); //$NON-NLS-1$
151*0853a241SThomas Wolf 				if (environment != null) {
152*0853a241SThomas Wolf 					for (Map.Entry<String, String> envVar : environment
153*0853a241SThomas Wolf 							.entrySet()) {
154*0853a241SThomas Wolf 						channel.setEnv(envVar.getKey(), envVar.getValue());
155*0853a241SThomas Wolf 					}
156*0853a241SThomas Wolf 				}
1578d2d6836SMatthias Sohn 				channel.setCommand(commandName);
1588d2d6836SMatthias Sohn 				setupStreams();
1598d2d6836SMatthias Sohn 				channel.connect(timeout > 0 ? timeout * 1000 : 0);
1608d2d6836SMatthias Sohn 				if (!channel.isConnected()) {
1618d2d6836SMatthias Sohn 					closeOutputStream();
1628d2d6836SMatthias Sohn 					throw new TransportException(uri,
1638d2d6836SMatthias Sohn 							JSchText.get().connectionFailed);
1648d2d6836SMatthias Sohn 				}
1658d2d6836SMatthias Sohn 			} catch (JSchException e) {
1668d2d6836SMatthias Sohn 				closeOutputStream();
1678d2d6836SMatthias Sohn 				throw new TransportException(uri, e.getMessage(), e);
1688d2d6836SMatthias Sohn 			}
1698d2d6836SMatthias Sohn 		}
1708d2d6836SMatthias Sohn 
closeOutputStream()1718d2d6836SMatthias Sohn 		private void closeOutputStream() {
1728d2d6836SMatthias Sohn 			if (outputStream != null) {
1738d2d6836SMatthias Sohn 				try {
1748d2d6836SMatthias Sohn 					outputStream.close();
1758d2d6836SMatthias Sohn 				} catch (IOException ioe) {
1768d2d6836SMatthias Sohn 					// ignore
1778d2d6836SMatthias Sohn 				}
1788d2d6836SMatthias Sohn 			}
1798d2d6836SMatthias Sohn 		}
1808d2d6836SMatthias Sohn 
setupStreams()1818d2d6836SMatthias Sohn 		private void setupStreams() throws IOException {
1828d2d6836SMatthias Sohn 			inputStream = channel.getInputStream();
1838d2d6836SMatthias Sohn 
1848d2d6836SMatthias Sohn 			// JSch won't let us interrupt writes when we use our InterruptTimer
1858d2d6836SMatthias Sohn 			// to break out of a long-running write operation. To work around
1868d2d6836SMatthias Sohn 			// that we spawn a background thread to shuttle data through a pipe,
1878d2d6836SMatthias Sohn 			// as we can issue an interrupted write out of that. Its slower, so
1888d2d6836SMatthias Sohn 			// we only use this route if there is a timeout.
1898d2d6836SMatthias Sohn 			OutputStream out = channel.getOutputStream();
1908d2d6836SMatthias Sohn 			if (timeout <= 0) {
1918d2d6836SMatthias Sohn 				outputStream = out;
1928d2d6836SMatthias Sohn 			} else {
1938d2d6836SMatthias Sohn 				IsolatedOutputStream i = new IsolatedOutputStream(out);
1948d2d6836SMatthias Sohn 				outputStream = new BufferedOutputStream(i, 16 * 1024);
1958d2d6836SMatthias Sohn 			}
1968d2d6836SMatthias Sohn 
1978d2d6836SMatthias Sohn 			errStream = channel.getErrStream();
1988d2d6836SMatthias Sohn 		}
1998d2d6836SMatthias Sohn 
2008d2d6836SMatthias Sohn 		@Override
getInputStream()2018d2d6836SMatthias Sohn 		public InputStream getInputStream() {
2028d2d6836SMatthias Sohn 			return inputStream;
2038d2d6836SMatthias Sohn 		}
2048d2d6836SMatthias Sohn 
2058d2d6836SMatthias Sohn 		@Override
getOutputStream()2068d2d6836SMatthias Sohn 		public OutputStream getOutputStream() {
2078d2d6836SMatthias Sohn 			return outputStream;
2088d2d6836SMatthias Sohn 		}
2098d2d6836SMatthias Sohn 
2108d2d6836SMatthias Sohn 		@Override
getErrorStream()2118d2d6836SMatthias Sohn 		public InputStream getErrorStream() {
2128d2d6836SMatthias Sohn 			return errStream;
2138d2d6836SMatthias Sohn 		}
2148d2d6836SMatthias Sohn 
2158d2d6836SMatthias Sohn 		@Override
exitValue()2168d2d6836SMatthias Sohn 		public int exitValue() {
2178d2d6836SMatthias Sohn 			if (isRunning())
21824fdc1d0SThomas Wolf 				throw new IllegalThreadStateException();
2198d2d6836SMatthias Sohn 			return channel.getExitStatus();
2208d2d6836SMatthias Sohn 		}
2218d2d6836SMatthias Sohn 
isRunning()2228d2d6836SMatthias Sohn 		private boolean isRunning() {
2238d2d6836SMatthias Sohn 			return channel.getExitStatus() < 0 && channel.isConnected();
2248d2d6836SMatthias Sohn 		}
2258d2d6836SMatthias Sohn 
2268d2d6836SMatthias Sohn 		@Override
destroy()2278d2d6836SMatthias Sohn 		public void destroy() {
2288d2d6836SMatthias Sohn 			if (channel.isConnected())
2298d2d6836SMatthias Sohn 				channel.disconnect();
2308d2d6836SMatthias Sohn 			closeOutputStream();
2318d2d6836SMatthias Sohn 		}
2328d2d6836SMatthias Sohn 
2338d2d6836SMatthias Sohn 		@Override
waitFor()2348d2d6836SMatthias Sohn 		public int waitFor() throws InterruptedException {
2358d2d6836SMatthias Sohn 			while (isRunning())
2368d2d6836SMatthias Sohn 				Thread.sleep(100);
2378d2d6836SMatthias Sohn 			return exitValue();
2388d2d6836SMatthias Sohn 		}
2398d2d6836SMatthias Sohn 	}
2408d2d6836SMatthias Sohn 
2418d2d6836SMatthias Sohn 	private class JschFtpChannel implements FtpChannel {
2428d2d6836SMatthias Sohn 
2438d2d6836SMatthias Sohn 		private ChannelSftp ftp;
2448d2d6836SMatthias Sohn 
2458d2d6836SMatthias Sohn 		@Override
connect(int timeout, TimeUnit unit)2468d2d6836SMatthias Sohn 		public void connect(int timeout, TimeUnit unit) throws IOException {
2478d2d6836SMatthias Sohn 			try {
2488d2d6836SMatthias Sohn 				ftp = (ChannelSftp) sock.openChannel("sftp"); //$NON-NLS-1$
2498d2d6836SMatthias Sohn 				ftp.connect((int) unit.toMillis(timeout));
2508d2d6836SMatthias Sohn 			} catch (JSchException e) {
2518d2d6836SMatthias Sohn 				ftp = null;
2528d2d6836SMatthias Sohn 				throw new IOException(e.getLocalizedMessage(), e);
2538d2d6836SMatthias Sohn 			}
2548d2d6836SMatthias Sohn 		}
2558d2d6836SMatthias Sohn 
2568d2d6836SMatthias Sohn 		@Override
disconnect()2578d2d6836SMatthias Sohn 		public void disconnect() {
2588d2d6836SMatthias Sohn 			ftp.disconnect();
2598d2d6836SMatthias Sohn 			ftp = null;
2608d2d6836SMatthias Sohn 		}
2618d2d6836SMatthias Sohn 
map(Callable<T> op)2628d2d6836SMatthias Sohn 		private <T> T map(Callable<T> op) throws IOException {
2638d2d6836SMatthias Sohn 			try {
2648d2d6836SMatthias Sohn 				return op.call();
2658d2d6836SMatthias Sohn 			} catch (Exception e) {
2668d2d6836SMatthias Sohn 				if (e instanceof SftpException) {
2678d2d6836SMatthias Sohn 					throw new FtpChannel.FtpException(e.getLocalizedMessage(),
2688d2d6836SMatthias Sohn 							((SftpException) e).id, e);
2698d2d6836SMatthias Sohn 				}
2708d2d6836SMatthias Sohn 				throw new IOException(e.getLocalizedMessage(), e);
2718d2d6836SMatthias Sohn 			}
2728d2d6836SMatthias Sohn 		}
2738d2d6836SMatthias Sohn 
2748d2d6836SMatthias Sohn 		@Override
isConnected()2758d2d6836SMatthias Sohn 		public boolean isConnected() {
2768d2d6836SMatthias Sohn 			return ftp != null && sock.isConnected();
2778d2d6836SMatthias Sohn 		}
2788d2d6836SMatthias Sohn 
2798d2d6836SMatthias Sohn 		@Override
cd(String path)2808d2d6836SMatthias Sohn 		public void cd(String path) throws IOException {
2818d2d6836SMatthias Sohn 			map(() -> {
2828d2d6836SMatthias Sohn 				ftp.cd(path);
2838d2d6836SMatthias Sohn 				return null;
2848d2d6836SMatthias Sohn 			});
2858d2d6836SMatthias Sohn 		}
2868d2d6836SMatthias Sohn 
2878d2d6836SMatthias Sohn 		@Override
pwd()2888d2d6836SMatthias Sohn 		public String pwd() throws IOException {
2898d2d6836SMatthias Sohn 			return map(() -> ftp.pwd());
2908d2d6836SMatthias Sohn 		}
2918d2d6836SMatthias Sohn 
2928d2d6836SMatthias Sohn 		@Override
ls(String path)2938d2d6836SMatthias Sohn 		public Collection<DirEntry> ls(String path) throws IOException {
2948d2d6836SMatthias Sohn 			return map(() -> {
2958d2d6836SMatthias Sohn 				List<DirEntry> result = new ArrayList<>();
2968d2d6836SMatthias Sohn 				for (Object e : ftp.ls(path)) {
2978d2d6836SMatthias Sohn 					ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) e;
2988d2d6836SMatthias Sohn 					result.add(new DirEntry() {
2998d2d6836SMatthias Sohn 
3008d2d6836SMatthias Sohn 						@Override
3018d2d6836SMatthias Sohn 						public String getFilename() {
3028d2d6836SMatthias Sohn 							return entry.getFilename();
3038d2d6836SMatthias Sohn 						}
3048d2d6836SMatthias Sohn 
3058d2d6836SMatthias Sohn 						@Override
3068d2d6836SMatthias Sohn 						public long getModifiedTime() {
3078d2d6836SMatthias Sohn 							return entry.getAttrs().getMTime();
3088d2d6836SMatthias Sohn 						}
3098d2d6836SMatthias Sohn 
3108d2d6836SMatthias Sohn 						@Override
3118d2d6836SMatthias Sohn 						public boolean isDirectory() {
3128d2d6836SMatthias Sohn 							return entry.getAttrs().isDir();
3138d2d6836SMatthias Sohn 						}
3148d2d6836SMatthias Sohn 					});
3158d2d6836SMatthias Sohn 				}
3168d2d6836SMatthias Sohn 				return result;
3178d2d6836SMatthias Sohn 			});
3188d2d6836SMatthias Sohn 		}
3198d2d6836SMatthias Sohn 
3208d2d6836SMatthias Sohn 		@Override
rmdir(String path)3218d2d6836SMatthias Sohn 		public void rmdir(String path) throws IOException {
3228d2d6836SMatthias Sohn 			map(() -> {
3238d2d6836SMatthias Sohn 				ftp.rm(path);
3248d2d6836SMatthias Sohn 				return null;
3258d2d6836SMatthias Sohn 			});
3268d2d6836SMatthias Sohn 		}
3278d2d6836SMatthias Sohn 
3288d2d6836SMatthias Sohn 		@Override
mkdir(String path)3298d2d6836SMatthias Sohn 		public void mkdir(String path) throws IOException {
3308d2d6836SMatthias Sohn 			map(() -> {
3318d2d6836SMatthias Sohn 				ftp.mkdir(path);
3328d2d6836SMatthias Sohn 				return null;
3338d2d6836SMatthias Sohn 			});
3348d2d6836SMatthias Sohn 		}
3358d2d6836SMatthias Sohn 
3368d2d6836SMatthias Sohn 		@Override
get(String path)3378d2d6836SMatthias Sohn 		public InputStream get(String path) throws IOException {
3388d2d6836SMatthias Sohn 			return map(() -> ftp.get(path));
3398d2d6836SMatthias Sohn 		}
3408d2d6836SMatthias Sohn 
3418d2d6836SMatthias Sohn 		@Override
put(String path)3428d2d6836SMatthias Sohn 		public OutputStream put(String path) throws IOException {
3438d2d6836SMatthias Sohn 			return map(() -> ftp.put(path));
3448d2d6836SMatthias Sohn 		}
3458d2d6836SMatthias Sohn 
3468d2d6836SMatthias Sohn 		@Override
rm(String path)3478d2d6836SMatthias Sohn 		public void rm(String path) throws IOException {
3488d2d6836SMatthias Sohn 			map(() -> {
3498d2d6836SMatthias Sohn 				ftp.rm(path);
3508d2d6836SMatthias Sohn 				return null;
3518d2d6836SMatthias Sohn 			});
3528d2d6836SMatthias Sohn 		}
3538d2d6836SMatthias Sohn 
3548d2d6836SMatthias Sohn 		@Override
rename(String from, String to)3558d2d6836SMatthias Sohn 		public void rename(String from, String to) throws IOException {
3568d2d6836SMatthias Sohn 			map(() -> {
3578d2d6836SMatthias Sohn 				// Plain FTP rename will fail if "to" exists. Jsch knows about
3588d2d6836SMatthias Sohn 				// the FTP extension "posix-rename@openssh.com", which will
3598d2d6836SMatthias Sohn 				// remove "to" first if it exists.
3608d2d6836SMatthias Sohn 				if (hasPosixRename()) {
3618d2d6836SMatthias Sohn 					ftp.rename(from, to);
3628d2d6836SMatthias Sohn 				} else if (!to.equals(from)) {
3638d2d6836SMatthias Sohn 					// Try to remove "to" first. With git, we typically get this
3648d2d6836SMatthias Sohn 					// when a lock file is moved over the file locked. Note that
3658d2d6836SMatthias Sohn 					// the check for to being equal to from may still fail in
3668d2d6836SMatthias Sohn 					// the general case, but for use with JGit's TransportSftp
3678d2d6836SMatthias Sohn 					// it should be good enough.
3688d2d6836SMatthias Sohn 					delete(to);
3698d2d6836SMatthias Sohn 					ftp.rename(from, to);
3708d2d6836SMatthias Sohn 				}
3718d2d6836SMatthias Sohn 				return null;
3728d2d6836SMatthias Sohn 			});
3738d2d6836SMatthias Sohn 		}
3748d2d6836SMatthias Sohn 
3758d2d6836SMatthias Sohn 		/**
3768d2d6836SMatthias Sohn 		 * Determine whether the server has the posix-rename extension.
3778d2d6836SMatthias Sohn 		 *
3788d2d6836SMatthias Sohn 		 * @return {@code true} if it is supported, {@code false} otherwise
3798d2d6836SMatthias Sohn 		 * @see <a href=
3808d2d6836SMatthias Sohn 		 *      "https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD">OpenSSH
3818d2d6836SMatthias Sohn 		 *      deviations and extensions to the published SSH protocol</a>
3828d2d6836SMatthias Sohn 		 * @see <a href=
3838d2d6836SMatthias Sohn 		 *      "http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html">stdio.h:
3848d2d6836SMatthias Sohn 		 *      rename()</a>
3858d2d6836SMatthias Sohn 		 */
hasPosixRename()3868d2d6836SMatthias Sohn 		private boolean hasPosixRename() {
3878d2d6836SMatthias Sohn 			return "1".equals(ftp.getExtension("posix-rename@openssh.com")); //$NON-NLS-1$//$NON-NLS-2$
3888d2d6836SMatthias Sohn 		}
3898d2d6836SMatthias Sohn 	}
3908d2d6836SMatthias Sohn }
391