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

com.sshtools.client.sftp.SftpHandle Maven / Gradle / Ivy

package com.sshtools.client.sftp;

/*-
 * #%L
 * Client API
 * %%
 * Copyright (C) 2002 - 2024 JADAPTIVE Limited
 * %%
 * This program 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.
 * 
 * This program 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 Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

import com.sshtools.client.tasks.FileTransferProgress;
import com.sshtools.common.events.Event;
import com.sshtools.common.events.EventCodes;
import com.sshtools.common.events.EventServiceImplementation;
import com.sshtools.common.logger.Log;
import com.sshtools.common.sftp.SftpFileAttributes;
import com.sshtools.common.sftp.SftpStatusException;
import com.sshtools.common.ssh.Packet;
import com.sshtools.common.ssh.SshException;
import com.sshtools.common.ssh.SshIOException;
import com.sshtools.common.util.ByteArrayWriter;
import com.sshtools.common.util.IOUtils;
import com.sshtools.common.util.UnsignedInteger32;
import com.sshtools.common.util.UnsignedInteger64;

public final class SftpHandle implements Closeable {

	private final byte[] handle;
	private final SftpChannel sftp;
	private final SftpFile file;

	private volatile boolean closed;
	private volatile boolean performVerification = false;
	
	SftpHandle(byte[] handle, SftpChannel sftp, SftpFile file) {
		super();
		this.handle = handle;
		this.sftp = sftp;
		this.file = file;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(handle);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		SftpHandle other = (SftpHandle) obj;
		return Arrays.equals(handle, other.handle);
	}

	/**
	 * Get the file associated with this handle.
	 * 
	 * @return file
	 */
	public SftpFile getFile() {
		return file;
	}

	/**
	 * Lock this file. Requires version 6 SFTP support.
	 * 
	 * @return closeable to release lock
	 * @throws SshException 
	 * @throws SftpStatusException 
	 */
	public Closeable lock() throws SftpStatusException, SshException {
		return lock(0, 0, SftpChannel.SSH_FXF_ACCESS_BLOCK_READ | SftpChannel.SSH_FXF_ACCESS_BLOCK_WRITE | SftpChannel.SSH_FXF_ACCESS_BLOCK_DELETE);
	}

	/**
	 * Lock this file. Requires version 6 SFTP support.  lockFlags
	 * may be a mask of {@link SftpChannel#SSH_FXF_ACCESS_BLOCK_READ}, {@link SftpChannel#SSH_FXF_ACCESS_BLOCK_WRITE},
	 * {@link SftpChannel#SSH_FXF_ACCESS_BLOCK_DELETE}, {@link SftpChannel#SSH_FXF_ACCESS_BLOCK_ADVISORY}.
	 *
	 * @param lockFlags
	 * @return closeable to release lock
	 * @throws SshException 
	 * @throws SftpStatusException 
	 */
	public Closeable lock(int lockFlags) throws SftpStatusException, SshException {
		return lock(0, 0, lockFlags);
	}

	/**
	 * Lock this file or a region of it. Requires version 6 SFTP support. lockFlags
	 * may be a mask of {@link SftpChannel#SSH_FXF_ACCESS_BLOCK_READ}, {@link SftpChannel#SSH_FXF_ACCESS_BLOCK_WRITE},
	 * {@link SftpChannel#SSH_FXF_ACCESS_BLOCK_DELETE}, {@link SftpChannel#SSH_FXF_ACCESS_BLOCK_ADVISORY}.
	 * 
	 * @param length 
	 * @param offset 
	 * @param lockflags 
	 * 
	 * @return closeable to release lock
	 * @throws SshException 
	 * @throws SftpStatusException 
	 */
	@SuppressWarnings("removal")
	public Closeable lock(long offset, long length, int lockflags) throws SftpStatusException, SshException {
		sftp.lockFile(handle, offset, length, lockflags);
		return new Closeable() {
			@Override
			public void close() throws IOException {
				try {
					sftp.unlockFile(handle, offset, length);
				} catch (SftpStatusException | SshException e) {
					throw new IOException(e);
				}
			}
		};
	}

	public void copyTo(SftpHandle destinationHandle, UnsignedInteger64 fromOffset, UnsignedInteger64 length,
			UnsignedInteger64 toOffset)
			throws SftpStatusException, SshException, IOException {

		if (!isOpen() || !destinationHandle.isOpen()) {
			throw new SftpStatusException(SftpStatusException.SSH_FX_INVALID_HANDLE,
					"source and desintation files must be open");
		}

		try (ByteArrayWriter msg = new ByteArrayWriter()) {
			msg.writeBinaryString(handle);
			msg.writeUINT64(fromOffset);
			msg.writeUINT64(length);
			msg.writeBinaryString(destinationHandle.getHandle());
			msg.writeUINT64(toOffset);

			sftp.getOKRequestStatus(
					sftp.sendExtensionMessage("copy-data", msg.toByteArray()), 
					file.getAbsolutePath());

		}
	}

	/**
	 * 

* Read bytes directly from this file. This is a low-level operation, you may * only need to use {@link SftpClientTask#get(String)} methods instead if you * just want to download files. *

* * @param offset offset in remote file to read from * @param output output buffer to place read bytes in * @param outputOffset offset in output buffer to write bytes to * @param len number of bytes to read * @return int number of bytes read * * @throws SftpStatusException * @throws SshException */ public int read(long offset, byte[] output, int outputOffset, int len) throws SftpStatusException, SshException { checkValidHandle(); return readFile(new UnsignedInteger64(offset), output, outputOffset, len); } /** *

* Write bytes directly to this file. This is a low-level operation, you may * only need to use {@link SftpClientTask#put(String)} methods instead if you * just want to upload files. *

* * @param offset offset in remote file to write to * @param input input buffer to retrieve bytes from to write * @param inputOffset offset in output buffer to write bytes to * @param len number of bytes to write * * @throws SftpStatusException * @throws SshException */ public void write(long offset, byte[] input, int inputOffset, int len) throws SftpStatusException, SshException { checkValidHandle(); sftp.writeFile(handle, new UnsignedInteger64(offset), input, inputOffset, len); } /** * Determine whether the file is open. * * @return boolean */ public boolean isOpen() { return !closed; } /** * Get the open file handle * * @return byte[] */ public byte[] getHandle() { return handle; } /** * Close the handle. * * @throws SshException * @throws SftpStatusException */ @Override public void close() throws IOException { if (!closed) { closed = true; try { UnsignedInteger32 requestId = sftp.nextRequestId(); Packet msg = sftp.createPacket(); msg.write(SftpChannel.SSH_FXP_CLOSE); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); if(Log.isDebugEnabled()) { Log.debug("Sending SSH_FXP_CLOSE for {} requestId={}", file.getFilename(), requestId); } sftp.sendMessage(msg); sftp.getOKRequestStatus(requestId, file.getAbsolutePath()); } catch (SshException | SshIOException | SftpStatusException ex) { throw new IOException("Failed to close handle.", ex); } finally { sftp.handles.remove(handle); } EventServiceImplementation.getInstance() .fireEvent((new Event(this, EventCodes.EVENT_SFTP_FILE_CLOSED, true)) .addAttribute(EventCodes.ATTRIBUTE_FILE_NAME, file == null ? "" : file.getAbsolutePath())); } } /** *

* 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 handle * @param children * @return int * @throws SftpStatusException , SshException */ public int listChildren(List children) throws SftpStatusException, SshException { checkValidHandle(); try { UnsignedInteger32 requestId = sftp.nextRequestId(); Packet msg = sftp.createPacket(); msg.write(SftpChannel.SSH_FXP_READDIR); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); sftp.sendMessage(msg); if(Log.isDebugEnabled()) { Log.debug("Sending SSH_FXP_READDIR for {} requestId={}", file.getFilename(), requestId); } SftpMessage bar = sftp.getResponse(requestId); try { if (bar.getType() == SftpChannel.SSH_FXP_NAME) { if (Log.isDebugEnabled()) { Log.debug("Received results"); } SftpFile[] files = sftp.extractFiles(bar, file.getAbsolutePath()); if (Log.isDebugEnabled()) { Log.debug("There are {} results in this packet", files.length); } for (int i = 0; i < files.length; i++) { children.add(files[i]); } return files.length; } else if (bar.getType() == SftpChannel.SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (Log.isDebugEnabled()) { Log.debug("Received status {}", status); } if (status == SftpStatusException.SSH_FX_EOF) { return -1; } if (sftp.version >= 3) { String desc = bar.readString(); throw new SftpStatusException(status, desc); } throw new SftpStatusException(status); } else { close(); throw new SshException("The server responded with an unexpected message", SshException.CHANNEL_FAILURE); } } finally { bar.release(); } } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Sets the attributes of a file. * * @param handle the file object. * @param attrs the new attributes. * * @throws SshException */ public void setAttributes(SftpFileAttributes attrs) throws SftpStatusException, SshException { checkValidHandle(); try { UnsignedInteger32 requestId = sftp.nextRequestId(); Packet msg = sftp.createPacket(); msg.write(SftpChannel.SSH_FXP_FSETSTAT); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); msg.write(attrs.toByteArray(sftp.getVersion())); if(Log.isDebugEnabled()) { Log.debug("Sending SSH_FXP_FSETSTAT for {} requestId=", file.getFilename(), requestId); } sftp.sendMessage(msg); sftp.getOKRequestStatus(requestId, file.getAbsolutePath()); } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Get the attributes of a file. * * @param handle * @return SftpFileAttributes * @throws SftpStatusException , SshException */ public SftpFileAttributes getAttributes() throws SftpStatusException, SshException { checkValidHandle(); try { UnsignedInteger32 requestId = sftp.nextRequestId(); Packet msg = sftp.createPacket(); msg.write(SftpChannel.SSH_FXP_FSTAT); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); if (sftp.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); } if(Log.isDebugEnabled()) { Log.debug("Sending SSH_FXP_FSTAT for {} requestId=", file.getFilename(), requestId); } sftp.sendMessage(msg); SftpMessage attrMessage = sftp.getResponse(requestId); try { return sftp.extractAttributes(attrMessage, getFile().getFilename(), requestId); } finally { attrMessage.release(); } } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * Performs an optimized write of a file through asynchronous messaging and * through buffering the local file into memory. * * @param blocksize the block size to send data, should be between * 4096 and 65536 * @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(String filename, int blocksize, int maxAsyncRequests, java.io.InputStream in, int buffersize, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { checkValidHandle(); long started = System.currentTimeMillis(); long transfered = position; try { if (blocksize > 0 && blocksize < 4096) { throw new SshException("Block size cannot be less than 4096", SshException.BAD_API_USAGE); } if (blocksize <= 0 || blocksize > 65536) { blocksize = sftp.getSession().getMaximumRemotePacketLength() - 13; } else if (blocksize + 13 > sftp.getSession().getMaxiumRemotePacketSize()) { blocksize = sftp.getSession().getMaximumRemotePacketLength() - 13; } int calculatedRequestsMax = (int) ((sftp.getSession().getRemoteWindow().longValue() * 0.9D) / blocksize); if (maxAsyncRequests <= 0) { maxAsyncRequests = calculatedRequestsMax; } System.setProperty("maverick.write.optimizedBlock", String.valueOf(blocksize)); System.setProperty("maverick.write.asyncRequestsMax", String.valueOf(maxAsyncRequests)); if (Log.isTraceEnabled()) { Log.trace("Performing optimized write length=" + in.available() + " postion=" + position + " blocksize=" + blocksize + " maxAsyncRequests=" + maxAsyncRequests); } 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]; int buffered = 0; buffered = in.read(buf); if (buffered != -1) { long time = System.currentTimeMillis(); sftp.writeFile(handle, new UnsignedInteger64(position), buf, 0, buffered); time = System.currentTimeMillis() - time; System.setProperty("maverick.write.blockRoundtrip", String.valueOf(time)); transfered += buffered; if (progress != null) { if (progress.isCancelled()) throw new TransferCancelledException(); progress.progressed(transfered); } List requests = new ArrayList(); // BufferedInputStream is not in J2ME, whatever type of input stream // has been passed in can be used in conjunction with the abstract // InputStream class. in = new BufferedInputStream(in, buffersize); while (true) { buffered = in.read(buf); if (buffered == -1) break; requests.add(postWriteRequest(transfered, buf, 0, buffered)); transfered += buffered; if (progress != null) { if (progress.isCancelled()) throw new TransferCancelledException(); progress.progressed(transfered); } if (requests.size() > maxAsyncRequests) { UnsignedInteger32 requestId = (UnsignedInteger32) requests.remove(0); sftp.getOKRequestStatus(requestId, file.getAbsolutePath()); } } while (requests.size() > 0) { sftp.getOKRequestStatus(requests.remove(0), file.getAbsolutePath()); } } } catch (IOException ex) { throw new TransferCancelledException(); } catch (OutOfMemoryError ex) { throw new SshException("Resource Shortage: try reducing the local file buffer size", SshException.BAD_API_USAGE); } finally { long finished = System.currentTimeMillis(); long transferTime = finished - started; double seconds = transferTime > 1000 ? transferTime / 1000 : 1D; if (Log.isInfoEnabled()) { if (transfered > 0) { Log.info("Optimized write of {} to {} took {} seconds at {} per second", IOUtils.toByteSize(transfered), filename, seconds, IOUtils.toByteSize(transfered / seconds, 1)); } else { Log.info("Optimized write did not transfer any data"); } } } } /** * 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(UnsignedInteger64 offset, byte[] output, int off, int len) throws SftpStatusException, SshException { checkValidHandle(); try { if ((output.length - off) < len) { throw new IndexOutOfBoundsException("Output array size is smaller than read length!"); } UnsignedInteger32 requestId = sftp.nextRequestId(); Packet msg = sftp.createPacket(); msg.write(SftpChannel.SSH_FXP_READ); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); msg.write(offset.toByteArray()); msg.writeInt(len); if(Log.isDebugEnabled()) { Log.debug("Sending SSH_FXP_READ for {} bytes at position {} for {} requestId={}", len, offset.toString(), file.getFilename(), requestId); } sftp.sendMessage(msg); SftpMessage bar = sftp.getResponse(requestId); try { if (bar.getType() == SftpChannel.SSH_FXP_DATA) { byte[] msgdata = bar.readBinaryString(); System.arraycopy(msgdata, 0, output, off, msgdata.length); if(Log.isDebugEnabled()) { Log.debug("Received SSH_FXP_DATA with {} bytes at position {} for {} requestId={}", msgdata.length, offset.toString(), file.getFilename(), requestId); } return msgdata.length; } else if (bar.getType() == SftpChannel.SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (status == SftpStatusException.SSH_FX_EOF) return -1; if (sftp.getVersion() >= 3) { String desc = bar.readString(); throw new SftpStatusException(status, desc); } throw new SftpStatusException(status); } else { close(); throw new SshException("The server responded with an unexpected message", SshException.CHANNEL_FAILURE); } } finally { bar.release(); } } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * 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(long offset, int len) throws SftpStatusException, SshException { checkValidHandle(); try { UnsignedInteger32 requestId = sftp.nextRequestId(); Packet msg = sftp.createPacket(); msg.write(SftpChannel.SSH_FXP_READ); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); msg.writeUINT64(offset); msg.writeInt(len); if(Log.isDebugEnabled()) { Log.debug("Sending SSH_FXP_READ for {} bytes at position {} for {} requestId={}", len, offset, file.getFilename(), requestId); } sftp.sendMessage(msg); return requestId; } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } /** * 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(long length, int blocksize, OutputStream out, int outstandingRequests, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { checkValidHandle(); long transfered = 0; boolean reachedEOF = false; long started = System.currentTimeMillis(); if (blocksize > 0 && blocksize < 4096) { throw new SshException("Block size cannot be less than 4096", SshException.BAD_API_USAGE); } if (blocksize <= 0 || blocksize > 65536) { blocksize = sftp.getSession().getMaximumLocalPacketLength() - 13; } else if (blocksize + 13 > sftp.getSession().getMaximumLocalPacketLength()) { blocksize = sftp.getSession().getMaximumLocalPacketLength() - 13; } int calculatedRequests = (int) ((sftp.getSession().getMaximumWindowSpace().longValue() * 0.9D) / blocksize); if (outstandingRequests <= 0 || calculatedRequests < outstandingRequests) { outstandingRequests = calculatedRequests / 2; } System.setProperty("maverick.read.optimizedBlock", String.valueOf(blocksize)); System.setProperty("maverick.read.asyncRequests", String.valueOf(outstandingRequests)); if (Log.isTraceEnabled()) { Log.trace("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; } MessageDigest md5 = null; OutputStream originalStream = null; if (performVerification) { try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException ex) { throw new SshException(ex); } originalStream = out; out = new DigestOutputStream(out, md5); } /** * 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); } try { byte[] tmp = new byte[blocksize]; long time = System.currentTimeMillis(); int i = readFile(new UnsignedInteger64(0), tmp, 0, tmp.length); time = System.currentTimeMillis() - time; System.setProperty("maverick.read.blockRoundtrip", String.valueOf(time)); // 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) { try { out.write(tmp, (int) position, (int) (i - position)); } catch (IOException e) { throw new TransferCancelledException(); } length = length - (i - position); transfered += (i - position); if (progress != null) { progress.progressed(transfered); } 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; System.setProperty("maverick.read.optimizedBlock", String.valueOf(blocksize)); } Vector requests = new Vector(outstandingRequests); int osr = calculatedRequests; long offset = position; UnsignedInteger32 requestId; int dataLen; while (true) { while (requests.size() < osr) { if (i > 0 && sftp.getSession().getRemoteWindow().longValue() < 29) { if (Log.isDebugEnabled()) Log.debug("Deferring post requests due to lack of remote window"); break; } if (Log.isTraceEnabled()) Log.trace("Posting request for file offset " + offset); requests.addElement(postReadRequest(offset, blocksize)); offset += blocksize; if (progress != null && progress.isCancelled()) { throw new TransferCancelledException(); } } requestId = (UnsignedInteger32) requests.elementAt(0); requests.removeElementAt(0); SftpMessage bar = sftp.getResponse(requestId); try { if (bar.getType() == SftpChannel.SSH_FXP_DATA) { dataLen = (int) bar.readInt(); if (Log.isTraceEnabled()) Log.trace("Got " + dataLen + " bytes of data"); try { out.write(bar.array(), bar.getPosition(), dataLen); } catch (IOException e) { throw new TransferCancelledException(); } transfered += dataLen; if (progress != null) { progress.progressed(transfered); } } else if (bar.getType() == SftpChannel.SSH_FXP_STATUS) { int status = (int) bar.readInt(); if (status == SftpStatusException.SSH_FX_EOF) { if (Log.isTraceEnabled()) Log.trace("Received file EOF"); reachedEOF = true; // Hack for bad servers return; } if (sftp.version >= 3) { String desc = bar.readString(); if (Log.isTraceEnabled()) Log.trace("Received status " + desc); throw new SftpStatusException(status, desc); } if (Log.isTraceEnabled()) Log.trace("Received status " + status); throw new SftpStatusException(status); } else { throw new SshException("The server responded with an unexpected message", SshException.CHANNEL_FAILURE); } } catch (IOException ex) { throw new SshException("Failed to read expected data from server response", SshException.CHANNEL_FAILURE); } finally { bar.release(); } if (osr < calculatedRequests) { osr++; } } } finally { long finished = System.currentTimeMillis(); long transferTime = finished - started; double seconds = transferTime > 1000 ? transferTime / 1000 : 1D; if (transfered > 0) { Log.info("Optimized read of {} took seconds {} at {} per second", IOUtils.toByteSize(transfered), seconds, IOUtils.toByteSize(transfered / seconds, 1)); } else { Log.info("Optimized read did not transfer any data"); } if (reachedEOF && performVerification && transfered > 0) { try { out.flush(); out.close(); try { originalStream.close(); } catch (IOException e) { } byte[] digest = md5.digest(); ByteArrayWriter baw = new ByteArrayWriter(); try { baw.writeBinaryString(handle); baw.writeUINT64(0); baw.writeUINT64(transfered); baw.writeBinaryString(new byte[0]); SftpMessage reply = sftp.getExtensionResponse( sftp.sendExtensionMessage("md5-hash-handle", baw.toByteArray()), file.getAbsolutePath()); reply.readString(); byte[] remoteDigest = reply.readBinaryString(); if (!Arrays.equals(digest, remoteDigest)) { throw new SshException("Remote file digest does not match local digest", SshException.POSSIBLE_CORRUPT_FILE); } } finally { baw.close(); } } catch (IOException e) { Log.error("Error processing remote digest", e); } catch (SftpStatusException e) { if (e.getStatus() == SftpStatusException.SSH_FX_OP_UNSUPPORTED) { performVerification = false; } else { Log.error("Could not verify file", e); } } catch (SshException e) { if (reachedEOF && e.getReason() == SshException.POSSIBLE_CORRUPT_FILE) { throw e; } else if (!reachedEOF) { throw e; } } } } } /** * 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. *

* Deprecated. Filename argument irrelevant. * * @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 * @deprecated * @see #performOptimizedRead(long, int, OutputStream, int, FileTransferProgress, long) */ @Deprecated(since = "3.1.0", forRemoval = true) public void performOptimizedRead(String filename, long length, int blocksize, OutputStream out, int outstandingRequests, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { performOptimizedRead(length, blocksize, out, outstandingRequests, progress, position); } /** * 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(int blocksize, OutputStream out, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { checkValidHandle(); if (Log.isTraceEnabled()) Log.trace("Performing synchronous read postion=" + position + " blocksize=" + blocksize); if (blocksize < 1 || blocksize > 65536) { blocksize = sftp.getSession().getMaximumRemotePacketLength() - 13; } else if (blocksize + 13 < sftp.getSession().getMaxiumRemotePacketSize()) { blocksize = sftp.getSession().getMaximumLocalPacketLength() - 13; } if (Log.isInfoEnabled()) { Log.info("Optimised block size will be {}", blocksize); } 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(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); } } /** * 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(long position, byte[] data, int off, int len) throws SftpStatusException, SshException { checkValidHandle(); if ((data.length - off) < len) { throw new IndexOutOfBoundsException("Incorrect data array size!"); } try { UnsignedInteger32 requestId = sftp.nextRequestId(); Packet msg = sftp.createPacket(); msg.write(SftpChannel.SSH_FXP_WRITE); msg.writeInt(requestId.longValue()); msg.writeBinaryString(handle); msg.writeUINT64(position); msg.writeBinaryString(data, off, len); if(Log.isDebugEnabled()) { Log.debug("Sending SSH_FXP_WRITE with {} bytes at position {} for {} requestId={}", len, position, file.getFilename(), requestId); } sftp.sendMessage(msg); return requestId; } catch (SshIOException ex) { throw ex.getRealException(); } catch (IOException ex) { throw new SshException(ex); } } SftpChannel getSFTPChannel() { return sftp; } private void checkValidHandle() throws SftpStatusException { if (closed) { throw new SftpStatusException(SftpStatusException.INVALID_HANDLE, "The handle is not an open file handle!"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy