All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sshtools.sftp.SftpSubsystemChannel Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2003-2016 SSHTOOLS Limited. All Rights Reserved.
 *
 * For product documentation visit https://www.sshtools.com/
 *
 * This file is part of J2SSH Maverick.
 *
 * J2SSH Maverick is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * J2SSH Maverick is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with J2SSH Maverick.  If not, see .
 */
package com.sshtools.sftp;

import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;

import com.sshtools.events.Event;
import com.sshtools.events.EventServiceImplementation;
import com.sshtools.events.J2SSHEventCodes;
import com.sshtools.logging.Log;
import com.sshtools.ssh.Packet;
import com.sshtools.ssh.SshException;
import com.sshtools.ssh.SshIOException;
import com.sshtools.ssh.SshSession;
import com.sshtools.ssh.SubsystemChannel;
import com.sshtools.ssh.message.Message;
import com.sshtools.ssh.message.MessageHolder;
import com.sshtools.util.Base64;
import com.sshtools.util.ByteArrayReader;
import com.sshtools.util.UnsignedInteger32;
import com.sshtools.util.UnsignedInteger64;

/**
 * This class implements the SFTP protocol which is executed as an SSH
 * subsystem. The basic initialization procedure is as follows: 
* *
 * // Create an SshClient
 * SshConnector con = SshConnector.getInstance();
 * 
 * // Connect and authenticate an SshClient
 * SshClient ssh = con.connect(....);
 * ....
 * 
 * // Create and initialize an SftpSubsystemChannel
 * SshSession session = ssh.openSessionChannel();
 * if(session instanceof Ssh2Session)
 *    ((Ssh2Session)session).startSubsystem("sftp");
 * 
 * SftpSubsystemChannel sftp = new SftpSubsystemChannel(session);
 * sftp.initialize();
 * 
* * @author Lee David Painter */ public class SftpSubsystemChannel extends SubsystemChannel { private String CHARSET_ENCODING = "ISO-8859-1"; /** * File open flag, opens the file for reading. */ public static final int OPEN_READ = 0x00000001; /** * File open flag, opens the file for writing. */ public static final int OPEN_WRITE = 0x00000002; /** * File open flag, forces all writes to append data at the end of the file. */ public static final int OPEN_APPEND = 0x00000004; /** * File open flag, if specified a new file will be created if one does not * already exist. */ public static final int OPEN_CREATE = 0x00000008; /** * File open flag, forces an existing file with the same name to be * truncated to zero length when creating a file by specifying OPEN_CREATE. */ public static final int OPEN_TRUNCATE = 0x00000010; /** * File open flag, causes an open request to fail if the named file already * exists. OPEN_CREATE must also be specified if this flag is used. */ public static final int OPEN_EXCLUSIVE = 0x00000020; /** * File open flag, causes the file to be opened in text mode. This instructs * the server to convert the text file to the canonical newline convention * in use. Any files retrieved using this mode should then be converted from * the canonical newline convention to that of the clients. */ public static final int OPEN_TEXT = 0x00000040; static final int STATUS_FX_OK = 0; static final int STATUS_FX_EOF = 1; static final int SSH_FXP_INIT = 1; static final int SSH_FXP_VERSION = 2; static final int SSH_FXP_OPEN = 3; static final int SSH_FXP_CLOSE = 4; static final int SSH_FXP_READ = 5; static final int SSH_FXP_WRITE = 6; static final int SSH_FXP_LSTAT = 7; static final int SSH_FXP_FSTAT = 8; static final int SSH_FXP_SETSTAT = 9; static final int SSH_FXP_FSETSTAT = 10; static final int SSH_FXP_OPENDIR = 11; static final int SSH_FXP_READDIR = 12; static final int SSH_FXP_REMOVE = 13; static final int SSH_FXP_MKDIR = 14; static final int SSH_FXP_RMDIR = 15; static final int SSH_FXP_REALPATH = 16; static final int SSH_FXP_STAT = 17; static final int SSH_FXP_RENAME = 18; static final int SSH_FXP_READLINK = 19; static final int SSH_FXP_SYMLINK = 20; static final int SSH_FXP_STATUS = 101; static final int SSH_FXP_HANDLE = 102; static final int SSH_FXP_DATA = 103; static final int SSH_FXP_NAME = 104; static final int SSH_FXP_ATTRS = 105; static final int SSH_FXP_EXTENDED = 200; static final int SSH_FXP_EXTENDED_REPLY = 201; public static int MAX_VERSION = 4; int this_MAX_VERSION = 4; int version = -1; int serverVersion = -1; UnsignedInteger32 requestId = new UnsignedInteger32(0); Hashtable responses = new Hashtable(); SftpThreadSynchronizer sync = new SftpThreadSynchronizer(); Hashtable extensions = new Hashtable(); /** * @throws SshException */ public SftpSubsystemChannel(SshSession session) throws SshException { super(session); this.this_MAX_VERSION = MAX_VERSION; } /** * @throws SshException */ public SftpSubsystemChannel(SshSession session, int Max_Version) throws SshException { super(session); setThisMaxSftpVersion(Max_Version); } /** * Sets the maximum SFTP protocol version to use, this should be <=4. * * @param MAX_VERSION */ public static void setMaxSftpVersion(int MAX_VERSION) { SftpSubsystemChannel.MAX_VERSION = MAX_VERSION; } /** * Sets the maximum SFTP protocol version to use for this instance, this * should be <=4. * * @param MAX_VERSION */ public void setThisMaxSftpVersion(int MAX_VERSION) { this.this_MAX_VERSION = MAX_VERSION; } /** * When called after the initialize method this * will return the version in operation for this sftp session. * * @return int */ public int getVersion() { return version; } /** * Returns the canonical newline convention in use when reading/writing text * files. * * @return String * @throws SftpStatusException */ public byte[] getCanonicalNewline() throws SftpStatusException { if (version <= 3) { throw new SftpStatusException( SftpStatusException.SSH_FX_OP_UNSUPPORTED, "Newline setting not available for SFTP versions <= 3"); } if (!extensions.containsKey("newline")) return "\r\n".getBytes(); return extensions.get("newline"); } /** * Initializes the sftp subsystem and negotiates a version with the server. * This method must be the first method called after the channel has been * opened. This implementation current supports SFTP protocol version 4 and * below. * * @throws SshException * @throws UnsupportedEncodingException */ public void initialize() throws SshException, UnsupportedEncodingException { // Initialize the SFTP subsystem try { Packet packet = createPacket(); packet.write(SSH_FXP_INIT); packet.writeInt(this_MAX_VERSION); sendMessage(packet); byte[] msg = nextMessage(); if (msg[0] != SSH_FXP_VERSION) { close(); throw new SshException( "Unexpected response from SFTP subsystem.", SshException.CHANNEL_FAILURE); } ByteArrayReader bar = new ByteArrayReader(msg); try { bar.skip(1); serverVersion = (int) bar.readInt(); version = Math.min(serverVersion, MAX_VERSION); try { while (bar.available() > 0) { String name = bar.readString(); byte[] data = bar.readBinaryString(); extensions.put(name, data); } } catch (Throwable t) { } } finally { bar.close(); } if (version <= 3) setCharsetEncoding("ISO-8859-1"); else setCharsetEncoding("UTF8"); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(SshException.CHANNEL_FAILURE, ex); } catch (Throwable t) { throw new SshException(SshException.CHANNEL_FAILURE, t); } } public void close() throws IOException { responses.clear(); super.close(); } /** * Allows the default character encoding to be overriden for filename * strings. This method should only be called once the channel has been * initialized, if the version of the protocol is less than or equal to 3 * the encoding is defaulted to latin1 as no encoding is specified by the * protocol. If the version is greater than 3 the default encoding will be * UTF-8. * * @param charset * @throws UnsupportedEncodingException * @throws SshException */ public void setCharsetEncoding(String charset) throws SshException, UnsupportedEncodingException { if (version == -1) throw new SshException( "SFTP Channel must be initialized before setting character set encoding", SshException.BAD_API_USAGE); String test = "123456890"; test.getBytes(charset); CHARSET_ENCODING = charset; } /** * Version 4 of the SFTP protocol allows the server to return its maximum * supported version instead of the actual version to be used. This method * returns the value provided by the server, if the servers version is less * than or equal to 3 then this method will return the protocol number in * use, otherwise it returns the maximum version supported by the server. * * @return int */ public int getServerVersion() { return serverVersion; } /** * Get the current encoding being used for filename Strings. * * @return String */ public String getCharsetEncoding() { return CHARSET_ENCODING; } /** * Does the server support an SFTP extension? This checks the extensions * returned by the server during the SFTP version negotiation. * * @param name * String * @return boolean */ public boolean supportsExtension(String name) { return extensions.containsKey(name); } /** * Get the data value of a supported SFTP extension. Call {@link * supportsExtension(String)} before calling this method to determine if the * extension is available. * * @param name * String * @return String */ public byte[] getExtension(String name) { return extensions.get(name); } /** * Send an extension message and return the response. This is for advanced * use only. * * @param request * String * @param requestData * byte[] * @return SftpMessage * @throws SshException * @throws SftpStatusException */ public SftpMessage sendExtensionMessage(String request, byte[] requestData) throws SshException, SftpStatusException { try { UnsignedInteger32 id = nextRequestId(); Packet packet = createPacket(); packet.write(SSH_FXP_EXTENDED); packet.writeUINT32(id); packet.writeString(request); sendMessage(packet); return getResponse(id); } catch (IOException ex) { throw new SshException(SshException.INTERNAL_ERROR, ex); } } /** * Change the permissions of a file. * * @param file * the file * @param permissions * an integer value containing a file permissions mask * @throws SshException * ,SftpStatusException */ public void changePermissions(SftpFile file, int permissions) throws SftpStatusException, SshException { SftpFileAttributes attrs = new SftpFileAttributes(this, SftpFileAttributes.SSH_FILEXFER_TYPE_UNKNOWN); attrs.setPermissions(new UnsignedInteger32(permissions)); setAttributes(file, attrs); } /** * Change the permissions of a file. * * @param filename * the path to the file. * @param permissions * an integer value containing a file permissions mask. * * @throws SshException * ,SftpStatusException */ public void changePermissions(String filename, int permissions) throws SftpStatusException, SshException { SftpFileAttributes attrs = new SftpFileAttributes(this, SftpFileAttributes.SSH_FILEXFER_TYPE_UNKNOWN); attrs.setPermissions(new UnsignedInteger32(permissions)); setAttributes(filename, attrs); } /** * Change the permissions of a file. * * @param filename * the path to the file. * @param permissions * a string containing the permissions, for example "rw-r--r--" * * @throws SftpStatusException * , SshException */ public void changePermissions(String filename, String permissions) throws SftpStatusException, SshException { SftpFileAttributes attrs = new SftpFileAttributes(this, SftpFileAttributes.SSH_FILEXFER_TYPE_UNKNOWN); attrs.setPermissions(permissions); setAttributes(filename, attrs); } /** * Sets the attributes of a file. * * @param path * the path to the file. * @param attrs * the file attributes. * @throws SftpStatusException * , SshException */ public void setAttributes(String path, SftpFileAttributes attrs) throws SftpStatusException, SshException { try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_SETSTAT); msg.writeInt(requestId.longValue()); msg.writeString(path, CHARSET_ENCODING); msg.write(attrs.toByteArray()); sendMessage(msg); getOKRequestStatus(requestId); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } } /** * Sets the attributes of a file. * * @param file * the file object. * @param attrs * the new attributes. * * @throws SshException */ public void setAttributes(SftpFile file, SftpFileAttributes attrs) throws SftpStatusException, SshException { if (file.getHandle() == null) { throw new SftpStatusException(SftpStatusException.INVALID_HANDLE, "The handle is not an open file handle!"); } try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_FSETSTAT); msg.writeInt(requestId.longValue()); msg.writeBinaryString(file.getHandle()); msg.write(attrs.toByteArray()); sendMessage(msg); getOKRequestStatus(requestId); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Send a write request for an open file but do not wait for the response * from the server. * * @param handle * @param position * @param data * @param off * @param len * @return UnsignedInteger32 * @throws SshException */ public UnsignedInteger32 postWriteRequest(byte[] handle, long position, byte[] data, int off, int len) throws SftpStatusException, SshException { if ((data.length - off) < len) { throw new IndexOutOfBoundsException("Incorrect data array size!"); } try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_WRITE); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); msg.writeUINT64(position); msg.writeBinaryString(data, off, len); sendMessage(msg); return requestId; } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Write a block of data to an open file. * * @param handle * the open file handle. * @param offset * the offset in the file to start writing * @param data * a buffer containing the data to write * @param off * the offset to start in the buffer * @param len * the length of data to write (setting to false will increase * file transfer but may miss errors) * @throws SshException */ public void writeFile(byte[] handle, UnsignedInteger64 offset, byte[] data, int off, int len) throws SftpStatusException, SshException { getOKRequestStatus(postWriteRequest(handle, offset.longValue(), data, off, len)); } /** * Performs an optimized write of a file through asynchronous messaging and * through buffering the local file into memory. * * @param handle * the open file handle to write to * @param blocksize * the block size to send data, should be between 4096 and 65535 * @param outstandingRequests * the maximum number of requests that can be outstanding at any * one time * @param in * the InputStream to read from * @param buffersize * the size of the temporary buffer to read from the InputStream. * Data is buffered into a temporary buffer so that the number of * local filesystem reads is reducted to a minimum. This * increases performance and so the buffer size should be as high * as possible. The default operation, if buffersize <= 0 is to * allocate a buffer the same size as the blocksize, meaning no * buffer optimization is performed. * @param progress * provides progress information, may be null. * @throws SshException */ public void performOptimizedWrite(byte[] handle, int blocksize, int outstandingRequests, java.io.InputStream in, int buffersize, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException { performOptimizedWrite(handle, blocksize, outstandingRequests, in, buffersize, progress, 0); } /** * Performs an optimized write of a file through asynchronous messaging and * through buffering the local file into memory. * * @param handle * the open file handle to write to * @param blocksize * the block size to send data, should be between 4096 and 65535 * @param outstandingRequests * the maximum number of requests that can be outstanding at any * one time * @param in * the InputStream to read from * @param buffersize * the size of the temporary buffer to read from the InputStream. * Data is buffered into a temporary buffer so that the number of * local filesystem reads is reducted to a minimum. This * increases performance and so the buffer size should be as high * as possible. The default operation, if buffersize <= 0 is to * allocate a buffer the same size as the blocksize, meaning no * buffer optimization is performed. * @param progress * provides progress information, may be null. * @param position * the position in the file to start writing to. * @throws SshException */ public void performOptimizedWrite(byte[] handle, int blocksize, int outstandingRequests, java.io.InputStream in, int buffersize, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { try { if (blocksize < 4096) { throw new SshException("Block size cannot be less than 4096", SshException.BAD_API_USAGE); } if (position < 0) throw new SshException( "Position value must be greater than zero!", SshException.BAD_API_USAGE); if (position > 0) { if (progress != null) progress.progressed(position); } if (buffersize <= 0) { buffersize = blocksize; } byte[] buf = new byte[blocksize]; long transfered = position; int buffered = 0; Vector requests = new Vector(); in = new java.io.BufferedInputStream(in, buffersize); UnsignedInteger32 requestId; while (true) { buffered = in.read(buf); if (buffered == -1) break; requests.addElement(postWriteRequest(handle, transfered, buf, 0, buffered)); transfered += buffered; if (progress != null) { if (progress.isCancelled()) throw new TransferCancelledException(); progress.progressed(transfered); } if (requests.size() > outstandingRequests) { requestId = (UnsignedInteger32) requests.elementAt(0); requests.removeElementAt(0); getOKRequestStatus(requestId); } } for (Enumeration e = requests.elements(); e .hasMoreElements();) { getOKRequestStatus(e.nextElement()); } requests.removeAllElements(); } catch (SshIOException ex) { throw ex.getRealException(); } catch (EOFException ex) { // The channel has reached EOF before the transfer could complete so // make sure the channel is closed and throw a status exception try { close(); } catch (SshIOException ex1) { throw ex1.getRealException(); } catch (IOException ex1) { throw new SshException(ex1.getMessage(), SshException.CHANNEL_FAILURE); } throw new SftpStatusException( SftpStatusException.SSH_FX_CONNECTION_LOST, "The SFTP channel terminated unexpectedly"); } catch (IOException ex) { throw new SshException(ex); } catch (OutOfMemoryError ex) { throw new SshException( "Resource Shortage: try reducing the local file buffer size", SshException.BAD_API_USAGE); } } /** * Performs an optimized read of a file through use of asynchronous * messages. The total number of outstanding read requests is configurable. * This should be safe on file objects as the SSH protocol states that file * read operations should return the exact number of bytes requested in each * request. However the server is not required to return the exact number of * bytes on device files and so this method should not be used for device * files. * * @param handle * the open files handle * @param length * the length of the file * @param blocksize * the blocksize to read * @param out * an OutputStream to output the file into * @param outstandingRequests * the maximum number of read requests to * @param progress * @throws SshException */ public void performOptimizedRead(byte[] handle, long length, int blocksize, java.io.OutputStream out, int outstandingRequests, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException { performOptimizedRead(handle, length, blocksize, out, outstandingRequests, progress, 0); } /** * Performs an optimized read of a file through use of asynchronous * messages. The total number of outstanding read requests is configurable. * This should be safe on file objects as the SSH protocol states that file * read operations should return the exact number of bytes requested in each * request. However the server is not required to return the exact number of * bytes on device files and so this method should not be used for device * files. * * @param handle * the open files handle * @param length * the amount of the file file to be read, equal to the file * length when reading the whole file * @param blocksize * the blocksize to read * @param out * an OutputStream to output the file into * @param outstandingRequests * the maximum number of read requests to * @param progress * @param position * the postition from which to start reading the file * @throws SshException */ public void performOptimizedRead(byte[] handle, long length, int blocksize, OutputStream out, int outstandingRequests, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { if (Log.isDebugEnabled()) { Log.debug(this, "Performing optimized read length=" + length + " postion=" + position + " blocksize=" + blocksize + " outstandingRequests=" + outstandingRequests); } if (length <= 0) { // We cannot perform an optimised read on this file since we don't // know its length so // here we assume its very large length = Long.MAX_VALUE; } try { if (blocksize < 1 || blocksize > 32768) { if (Log.isDebugEnabled()) { Log.debug(this, "Blocksize to large for some SFTP servers, reseting to 32K"); } blocksize = 32768; } /** * LDP - Obtain the first block using a synchronous call. We do this * to determine if the server is conforming to the spec and * returning as much data as we have asked for. If not we * reconfigure the block size to the number of bytes returned. */ if (position < 0) { throw new SshException( "Position value must be greater than zero!", SshException.BAD_API_USAGE); } byte[] tmp = new byte[blocksize]; int i = readFile(handle, new UnsignedInteger64(0), tmp, 0, tmp.length); // if i=-1 then eof so return, maybe should throw exception on null // files? if (i == -1) { return; } // if the first block contains required data, write to the output // buffer, // write the portion of tmp needed to out // change position if (i > position) { out.write(tmp, (int) position, (int) (i - position)); length = length - (i - position); position = i; } // if the first block contains the whole portion of the file to be // read, then return if ((position + length) <= i) { return; } // reconfigure the blocksize if necessary if (i < blocksize && length > i) { blocksize = i; } tmp = null; long numBlocks = length / blocksize; long osr = outstandingRequests; if (position > 0) { if (progress != null) progress.progressed(position); } Vector requests = new Vector( outstandingRequests); long offset = position; if (numBlocks < osr) { osr = numBlocks + 1; } if (osr <= 0) { if (Log.isDebugEnabled()) { Log.debug(this, "We calculated zero outstanding requests! numBlocks=" + numBlocks + " outstandingRequests=" + outstandingRequests + " blocksize=" + blocksize + " length=" + length + " position=" + position); } osr = 1; // We need at least one or there will be trouble. } long expected = numBlocks + 2; int completed = 0; long transfered = position; // Fire an initial round of requests for (i = 0; i < osr; i++) { if (Log.isDebugEnabled()) { Log.debug(this, "Posting request for file offset " + offset); } requests.addElement(postReadRequest(handle, offset, blocksize)); offset += blocksize; if (progress != null && progress.isCancelled()) { throw new TransferCancelledException(); } } UnsignedInteger32 requestId; int dataLen; while (true) { requestId = (UnsignedInteger32) requests.elementAt(0); requests.removeElementAt(0); SftpMessage bar = getResponse(requestId); if (bar.getType() == SSH_FXP_DATA) { dataLen = (int) bar.readInt(); // tmp = bar.readBinaryString(); if (Log.isDebugEnabled()) { Log.debug(this, "Get " + dataLen + " bytes of data"); } out.write(bar.array(), bar.getPosition(), dataLen); completed++; bar.dispose(); if (progress != null) { progress.progressed(transfered += dataLen); } } else if (bar.getType() == SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (status == SftpStatusException.SSH_FX_EOF) { if (Log.isDebugEnabled()) { Log.debug(this, "Received file EOF"); } return; } if (version >= 3) { String desc = bar.readString().trim(); if (Log.isDebugEnabled()) { Log.debug(this, "Received status " + desc); } throw new SftpStatusException(status, desc); } if (Log.isDebugEnabled()) { Log.debug(this, "Received status " + status); } throw new SftpStatusException(status); } else { close(); throw new SshException( "The server responded with an unexpected message", SshException.CHANNEL_FAILURE); } /** * If the file length is incorrect we could be stuck in an * endless loop so we check for an empty request list. This * could only happen if the file length is incorrect. */ if (requests.isEmpty() || completed + requests.size() < expected) { if (Log.isDebugEnabled()) { Log.debug(this, "Posting request for file offset " + offset); } requests.addElement(postReadRequest(handle, offset, blocksize)); offset += blocksize; } if (progress != null && progress.isCancelled()) { throw new TransferCancelledException(); } } } catch (SshIOException ex) { throw ex.getRealException(); } catch (EOFException ex) { if (Log.isDebugEnabled()) { Log.debug(this, "Channel has reached EOF", ex); } // The channel has reached EOF before the transfer could complete so // make sure the channel is closed and throw a status exception try { close(); } catch (SshIOException ex1) { throw ex1.getRealException(); } catch (IOException ex1) { throw new SshException(ex1.getMessage(), SshException.CHANNEL_FAILURE); } throw new SftpStatusException( SftpStatusException.SSH_FX_CONNECTION_LOST, "The SFTP channel terminated unexpectedly"); } catch (IOException ex) { throw new SshException(ex); } } /** * Perform a synchronous read of a file from the remote file system. This * implementation waits for acknowledgement of every data packet before * requesting additional data. * * @param handle * @param blocksize * @param out * @param progress * @param position * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public void performSynchronousRead(byte[] handle, int blocksize, OutputStream out, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { if (Log.isDebugEnabled()) { Log.debug(this, "Performing synchronous read postion=" + position + " blocksize=" + blocksize); } if (blocksize < 1 || blocksize > 32768) { if (Log.isDebugEnabled()) { Log.debug(this, "Blocksize to large for some SFTP servers, reseting to 32K"); } blocksize = 32768; } if (position < 0) { throw new SshException("Position value must be greater than zero!", SshException.BAD_API_USAGE); } byte[] tmp = new byte[blocksize]; int read; UnsignedInteger64 offset = new UnsignedInteger64(position); if (position > 0) { if (progress != null) progress.progressed(position); } try { while ((read = readFile(handle, offset, tmp, 0, tmp.length)) > -1) { if (progress != null && progress.isCancelled()) { throw new TransferCancelledException(); } out.write(tmp, 0, read); offset = UnsignedInteger64.add(offset, read); if (progress != null) progress.progressed(offset.longValue()); } } catch (IOException e) { throw new SshException(e); } } /** * Post a read request to the server and return the request id; this is used * to optimize file downloads. In normal operation the files are transfered * by using a synchronous set of requests, however this slows the download * as the client has to wait for the servers response before sending another * request. * * @param handle * @param offset * @param len * @return UnsignedInteger32 * @throws SshException */ public UnsignedInteger32 postReadRequest(byte[] handle, long offset, int len) throws SftpStatusException, SshException { try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_READ); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); msg.writeUINT64(offset); msg.writeInt(len); sendMessage(msg); return requestId; } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Read a block of data from an open file. * * @param handle * the open file handle * @param offset * the offset to start reading in the file * @param output * a buffer to write the returned data to * @param off * the starting offset in the output buffer * @param len * the length of data to read * @return int * @throws SshException */ public int readFile(byte[] handle, UnsignedInteger64 offset, byte[] output, int off, int len) throws SftpStatusException, SshException { try { if ((output.length - off) < len) { throw new IndexOutOfBoundsException( "Output array size is smaller than read length!"); } UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_READ); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); msg.write(offset.toByteArray()); msg.writeInt(len); sendMessage(msg); SftpMessage bar = getResponse(requestId); if (bar.getType() == SSH_FXP_DATA) { byte[] msgdata = bar.readBinaryString(); System.arraycopy(msgdata, 0, output, off, msgdata.length); return msgdata.length; } else if (bar.getType() == SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (status == SftpStatusException.SSH_FX_EOF) return -1; if (version >= 3) { String desc = bar.readString().trim(); throw new SftpStatusException(status, desc); } throw new SftpStatusException(status); } else { close(); throw new SshException( "The server responded with an unexpected message", SshException.CHANNEL_FAILURE); } } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Utility method to obtain an {@link SftpFile} instance for a given path. * * @param path * @return SftpFile * @throws SftpStatusException * @throws SshException */ public SftpFile getFile(String path) throws SftpStatusException, SshException { String absolute = getAbsolutePath(path); SftpFile file = new SftpFile(absolute, getAttributes(absolute)); file.sftp = this; return file; } /** * Get the absolute path of a file. * * @param file * @return String * @throws SshException */ public String getAbsolutePath(SftpFile file) throws SftpStatusException, SshException { return getAbsolutePath(file.getFilename()); } /** * Create a symbolic link. * * @param targetpath * the symbolic link to create * @param linkpath * the path to which the symbolic link points * @throws SshException * if the remote SFTP version is < 3 an exception is thrown as * this feature is not supported by previous versions of the * protocol. */ public void createSymbolicLink(String targetpath, String linkpath) throws SftpStatusException, SshException { if (version < 3) { throw new SftpStatusException( SftpStatusException.SSH_FX_OP_UNSUPPORTED, "Symbolic links are not supported by the server SFTP version " + String.valueOf(version)); } try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_SYMLINK); msg.writeInt(requestId.longValue()); msg.writeString(linkpath, CHARSET_ENCODING); msg.writeString(targetpath, CHARSET_ENCODING); sendMessage(msg); getOKRequestStatus(requestId); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Get the target path of a symbolic link. * * @param linkpath * @return String * @throws SshException * if the remote SFTP version is < 3 an exception is thrown as * this feature is not supported by previous versions of the * protocol. */ public String getSymbolicLinkTarget(String linkpath) throws SftpStatusException, SshException { if (version < 3) { throw new SftpStatusException( SftpStatusException.SSH_FX_OP_UNSUPPORTED, "Symbolic links are not supported by the server SFTP version " + String.valueOf(version)); } try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_READLINK); msg.writeInt(requestId.longValue()); msg.writeString(linkpath, CHARSET_ENCODING); sendMessage(msg); SftpFile[] files = extractFiles(getResponse(requestId), null); return files[0].getAbsolutePath(); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Gets the users default directory. * * @return String * @throws SshException */ public String getDefaultDirectory() throws SftpStatusException, SshException { return getAbsolutePath(""); } /** * Get the absolute path of a file. * * @param path * @return String * @throws SshException */ public String getAbsolutePath(String path) throws SftpStatusException, SshException { try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_REALPATH); msg.writeInt(requestId.longValue()); msg.writeString(path, CHARSET_ENCODING); sendMessage(msg); SftpMessage bar = getResponse(requestId); if (bar.getType() == SSH_FXP_NAME) { SftpFile[] files = extractFiles(bar, null); if (files.length != 1) { close(); throw new SshException( "Server responded to SSH_FXP_REALPATH with too many files!", SshException.CHANNEL_FAILURE); } return files[0].getAbsolutePath(); } else if (bar.getType() == SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (version >= 3) { String desc = bar.readString().trim(); throw new SftpStatusException(status, desc); } throw new SftpStatusException(status); } else { close(); throw new SshException( "The server responded with an unexpected message", SshException.CHANNEL_FAILURE); } } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** *

* List the children of a directory. *

*

* To use this method first open a directory with the openDirectory method and * then create a Vector to store the results. To retrieve the results keep * calling this method until it returns -1 which indicates no more results * will be returned.

* *
	 * SftpFile dir = sftp.openDirectory("code/foobar");
	 * Vector results = new Vector();
	 * while (sftp.listChildren(dir, results) > -1)
	 * 	;
	 * sftp.closeFile(dir);
	 * 
* *
* *

* * @param file * @param children * @return int * @throws SftpStatusException * , SshException */ public int listChildren(SftpFile file, Vector children) throws SftpStatusException, SshException { if (file.isDirectory()) { if (file.getHandle() == null) { file = openDirectory(file.getAbsolutePath()); if (file.getHandle() == null) { throw new SftpStatusException( SftpStatusException.SSH_FX_FAILURE, "Failed to open directory"); } } } else { throw new SshException("Cannot list children for this file object", SshException.BAD_API_USAGE); } try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_READDIR); msg.writeInt(requestId.longValue()); msg.writeBinaryString(file.getHandle()); sendMessage(msg); ; SftpMessage bar = getResponse(requestId); if (bar.getType() == SSH_FXP_NAME) { SftpFile[] files = extractFiles(bar, file.getAbsolutePath()); for (int i = 0; i < files.length; i++) { children.addElement(files[i]); } return files.length; } else if (bar.getType() == SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (status == SftpStatusException.SSH_FX_EOF) { return -1; } if (version >= 3) { String desc = bar.readString().trim(); throw new SftpStatusException(status, desc); } throw new SftpStatusException(status); } else { close(); throw new SshException( "The server responded with an unexpected message", SshException.CHANNEL_FAILURE); } } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } SftpFile[] extractFiles(SftpMessage bar, String parent) throws SshException { try { if (parent != null && !parent.endsWith("/")) { parent += "/"; } int count = (int) bar.readInt(); SftpFile[] files = new SftpFile[count]; String shortname; String longname = null; for (int i = 0; i < files.length; i++) { shortname = bar.readString(CHARSET_ENCODING); if (version <= 3) { // read and throw away the longname as don't use it but need // to read it out of the bar to advance the position. longname = bar.readString(CHARSET_ENCODING); } files[i] = new SftpFile(parent != null ? parent + shortname : shortname, new SftpFileAttributes(this, bar)); files[i].longname = longname; // Work out username/group from long name if (longname != null && version <= 3) { try { StringTokenizer t = new StringTokenizer(longname); t.nextToken(); t.nextToken(); String username = t.nextToken(); String group = t.nextToken(); files[i].getAttributes().setUsername(username); files[i].getAttributes().setGroup(group); } catch (Exception e) { } } files[i].setSFTPSubsystem(this); } return files; } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Recurse through a hierarchy of directories creating them as necessary. * * @param path * @throws SftpStatusException * , SshException */ public void recurseMakeDirectory(String path) throws SftpStatusException, SshException { SftpFile file; if (path.trim().length() > 0) { try { file = openDirectory(path); file.close(); } catch (SshException ioe) { int idx = 0; do { idx = path.indexOf('/', idx); String tmp = (idx > -1 ? path.substring(0, idx + 1) : path); try { file = openDirectory(tmp); file.close(); } catch (SshException ioe7) { makeDirectory(tmp); } } while (idx > -1); } } } /** * Open a file. * * @param absolutePath * @param flags * @return SftpFile * @throws SftpStatusException * , SshException */ public SftpFile openFile(String absolutePath, int flags) throws SftpStatusException, SshException { return openFile(absolutePath, flags, new SftpFileAttributes(this, SftpFileAttributes.SSH_FILEXFER_TYPE_UNKNOWN)); } /** * Open a file. * * @param absolutePath * @param flags * @param attrs * @return SftpFile * @throws SftpStatusException * , SshException */ public SftpFile openFile(String absolutePath, int flags, SftpFileAttributes attrs) throws SftpStatusException, SshException { if (attrs == null) { attrs = new SftpFileAttributes(this, SftpFileAttributes.SSH_FILEXFER_TYPE_UNKNOWN); } try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_OPEN); msg.writeInt(requestId.longValue()); msg.writeString(absolutePath, CHARSET_ENCODING); msg.writeInt(flags); msg.write(attrs.toByteArray()); sendMessage(msg); byte[] handle = getHandleResponse(requestId); SftpFile file = new SftpFile(absolutePath, null); file.setHandle(handle); file.setSFTPSubsystem(this); EventServiceImplementation.getInstance().fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SFTP_FILE_OPENED, true)).addAttribute( J2SSHEventCodes.ATTRIBUTE_FILE_NAME, file.getAbsolutePath())); return file; } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } @SuppressWarnings("unused") private String getSafeHandle(byte[] handle) { return Base64.encodeBytes(handle, 0, handle.length, true); } @SuppressWarnings("unused") private byte[] getSafeHandle(String handle) { return Base64.decode(handle); } /** * Open a directory. * * @param path * @return sftpfile * @throws SftpStatusException * , SshException */ public SftpFile openDirectory(String path) throws SftpStatusException, SshException { String absolutePath = getAbsolutePath(path); SftpFileAttributes attrs = getAttributes(absolutePath); if (!attrs.isDirectory()) { throw new SftpStatusException(SftpStatusException.SSH_FX_FAILURE, path + " is not a directory"); } try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_OPENDIR); msg.writeInt(requestId.longValue()); msg.writeString(path, CHARSET_ENCODING); sendMessage(msg); byte[] handle = getHandleResponse(requestId); SftpFile file = new SftpFile(absolutePath, attrs); file.setHandle(handle); file.setSFTPSubsystem(this); return file; } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } void closeHandle(byte[] handle) throws SftpStatusException, SshException { try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_CLOSE); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); sendMessage(msg); getOKRequestStatus(requestId); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Close a file or directory. * * @param file * @throws SftpStatusException * , SshException */ public void closeFile(SftpFile file) throws SftpStatusException, SshException { if (file.getHandle() != null) { closeHandle(file.getHandle()); EventServiceImplementation.getInstance().fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SFTP_FILE_CLOSED, true)).addAttribute( J2SSHEventCodes.ATTRIBUTE_FILE_NAME, file.getAbsolutePath())); file.setHandle(null); } } /** * Remove an empty directory. * * @param path * @throws SftpStatusException * , SshException */ public void removeDirectory(String path) throws SftpStatusException, SshException { try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_RMDIR); msg.writeInt(requestId.longValue()); msg.writeString(path, CHARSET_ENCODING); sendMessage(msg); getOKRequestStatus(requestId); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } EventServiceImplementation.getInstance().fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SFTP_DIRECTORY_DELETED, true)).addAttribute( J2SSHEventCodes.ATTRIBUTE_DIRECTORY_PATH, path)); } /** * Remove a file. * * @param filename * @throws SftpStatusException * , SshException */ public void removeFile(String filename) throws SftpStatusException, SshException { try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_REMOVE); msg.writeInt(requestId.longValue()); msg.writeString(filename, CHARSET_ENCODING); sendMessage(msg); getOKRequestStatus(requestId); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } EventServiceImplementation.getInstance() .fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SFTP_FILE_DELETED, true)) .addAttribute( J2SSHEventCodes.ATTRIBUTE_FILE_NAME, filename)); } /** * Rename an existing file. * * @param oldpath * @param newpath * @throws SftpStatusException * , SshException */ public void renameFile(String oldpath, String newpath) throws SftpStatusException, SshException { if (version < 2) { throw new SftpStatusException( SftpStatusException.SSH_FX_OP_UNSUPPORTED, "Renaming files is not supported by the server SFTP version " + String.valueOf(version)); } try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_RENAME); msg.writeInt(requestId.longValue()); msg.writeString(oldpath, CHARSET_ENCODING); msg.writeString(newpath, CHARSET_ENCODING); sendMessage(msg); getOKRequestStatus(requestId); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } EventServiceImplementation .getInstance() .fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SFTP_FILE_RENAMED, true)) .addAttribute( J2SSHEventCodes.ATTRIBUTE_FILE_NAME, oldpath) .addAttribute( J2SSHEventCodes.ATTRIBUTE_FILE_NEW_NAME, newpath)); } /** * Get the attributes of a file. This method follows symbolic links * * @param path * @return SftpFileAttributes * @throws SshException */ public SftpFileAttributes getAttributes(String path) throws SftpStatusException, SshException { return getAttributes(path, SSH_FXP_STAT); } /** * Get the attributes of a file. This method does not follow symbolic links * so will return the attributes of an actual link, not its target. * * @param path * @return * @throws SftpStatusException * @throws SshException */ public SftpFileAttributes getLinkAttributes(String path) throws SftpStatusException, SshException { return getAttributes(path, SSH_FXP_LSTAT); } protected SftpFileAttributes getAttributes(String path, int messageId) throws SftpStatusException, SshException { try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(messageId); msg.writeInt(requestId.longValue()); msg.writeString(path, CHARSET_ENCODING); if (version > 3) { msg.writeInt(SftpFileAttributes.SSH_FILEXFER_ATTR_SIZE | SftpFileAttributes.SSH_FILEXFER_ATTR_PERMISSIONS | SftpFileAttributes.SSH_FILEXFER_ATTR_ACCESSTIME | SftpFileAttributes.SSH_FILEXFER_ATTR_CREATETIME | SftpFileAttributes.SSH_FILEXFER_ATTR_MODIFYTIME | SftpFileAttributes.SSH_FILEXFER_ATTR_ACL | SftpFileAttributes.SSH_FILEXFER_ATTR_OWNERGROUP | SftpFileAttributes.SSH_FILEXFER_ATTR_SUBSECOND_TIMES | SftpFileAttributes.SSH_FILEXFER_ATTR_EXTENDED); } sendMessage(msg); SftpMessage bar = getResponse(requestId); return extractAttributes(bar); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } SftpFileAttributes extractAttributes(SftpMessage bar) throws SftpStatusException, SshException { try { if (bar.getType() == SSH_FXP_ATTRS) { return new SftpFileAttributes(this, bar); } else if (bar.getType() == SSH_FXP_STATUS) { int status = (int) bar.readInt(); // Only read the message string if the version is >= 3 if (version >= 3) { String msg = bar.readString().trim(); throw new SftpStatusException(status, msg); } throw new SftpStatusException(status); } else { close(); throw new SshException( "The server responded with an unexpected message.", SshException.CHANNEL_FAILURE); } } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Get the attributes of a file. * * @param file * @return SftpFileAttributes * @throws SftpStatusException * , SshException */ public SftpFileAttributes getAttributes(SftpFile file) throws SftpStatusException, SshException { try { if (file.getHandle() == null) { return getAttributes(file.getAbsolutePath()); } UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_FSTAT); msg.writeInt(requestId.longValue()); msg.writeBinaryString(file.getHandle()); if (version > 3) { msg.writeInt(SftpFileAttributes.SSH_FILEXFER_ATTR_SIZE | SftpFileAttributes.SSH_FILEXFER_ATTR_PERMISSIONS | SftpFileAttributes.SSH_FILEXFER_ATTR_ACCESSTIME | SftpFileAttributes.SSH_FILEXFER_ATTR_CREATETIME | SftpFileAttributes.SSH_FILEXFER_ATTR_MODIFYTIME | SftpFileAttributes.SSH_FILEXFER_ATTR_ACL | SftpFileAttributes.SSH_FILEXFER_ATTR_OWNERGROUP | SftpFileAttributes.SSH_FILEXFER_ATTR_SUBSECOND_TIMES | SftpFileAttributes.SSH_FILEXFER_ATTR_EXTENDED); } sendMessage(msg); return extractAttributes(getResponse(requestId)); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Make a directory. If the directory exists this method will throw an * exception. * * @param path * @throws SftpStatusException * , SshException */ public void makeDirectory(String path) throws SftpStatusException, SshException { makeDirectory(path, new SftpFileAttributes(this, SftpFileAttributes.SSH_FILEXFER_TYPE_DIRECTORY)); } /** * Make a directory. If the directory exists this method will throw an * exception. * * @param path * @param attrs * @throws SftpStatusException * , SshException */ public void makeDirectory(String path, SftpFileAttributes attrs) throws SftpStatusException, SshException { try { UnsignedInteger32 requestId = nextRequestId(); Packet msg = createPacket(); msg.write(SSH_FXP_MKDIR); msg.writeInt(requestId.longValue()); msg.writeString(path, CHARSET_ENCODING); msg.write(attrs.toByteArray()); sendMessage(msg); getOKRequestStatus(requestId); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } byte[] getHandleResponse(UnsignedInteger32 requestId) throws SftpStatusException, SshException { try { SftpMessage bar = getResponse(requestId); if (bar.getType() == SSH_FXP_HANDLE) { return bar.readBinaryString(); } else if (bar.getType() == SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (version >= 3) { String msg = bar.readString().trim(); throw new SftpStatusException(status, msg); } throw new SftpStatusException(status); } else { close(); throw new SshException( "The server responded with an unexpected message!", SshException.CHANNEL_FAILURE); } } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Verify that an OK status has been returned for a request id. * * @param requestId * @throws SftpStatusException * , SshException */ public void getOKRequestStatus(UnsignedInteger32 requestId) throws SftpStatusException, SshException { try { SftpMessage bar = getResponse(requestId); if (bar.getType() == SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (status == SftpStatusException.SSH_FX_OK) { return; } if (version >= 3) { String msg = bar.readString().trim(); throw new SftpStatusException(status, msg); } throw new SftpStatusException(status); } close(); throw new SshException( "The server responded with an unexpected message!", SshException.CHANNEL_FAILURE); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } SftpMessage getResponse(UnsignedInteger32 requestId) throws SshException { SftpMessage msg; MessageHolder holder = new MessageHolder(); while (holder.msg == null) { try { // Read the next response message if (sync.requestBlock(requestId, holder)) { msg = new SftpMessage(nextMessage()); responses.put(new UnsignedInteger32(msg.getMessageId()), msg); } } catch (InterruptedException e) { try { close(); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex1) { throw new SshException(ex1.getMessage(), SshException.CHANNEL_FAILURE); } throw new SshException("The thread was interrupted", SshException.CHANNEL_FAILURE); } catch (IOException ex) { throw new SshException(SshException.INTERNAL_ERROR, ex); } finally { sync.releaseBlock(); } } return (SftpMessage) responses.remove(requestId); } UnsignedInteger32 nextRequestId() { requestId = UnsignedInteger32.add(requestId, 1); return requestId; } class SftpThreadSynchronizer { boolean isBlocking = false; public boolean requestBlock(UnsignedInteger32 requestId, MessageHolder holder) throws InterruptedException { if (responses.containsKey(requestId)) { holder.msg = (Message) responses.get(requestId); return false; } synchronized (SftpThreadSynchronizer.this) { boolean canBlock = !isBlocking; if (responses.containsKey(requestId)) { holder.msg = (Message) responses.get(requestId); return false; } if (canBlock) { isBlocking = true; } else { wait(); } return canBlock; } } public synchronized void releaseBlock() { isBlocking = false; notifyAll(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy