/*
* Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
* Please refer to the LICENSE.txt for licensing details.
*/
package ch.ethz.ssh2;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import ch.ethz.ssh2.channel.Channel;
import ch.ethz.ssh2.log.Logger;
import ch.ethz.ssh2.packets.TypesReader;
import ch.ethz.ssh2.packets.TypesWriter;
import ch.ethz.ssh2.sftp.AttribFlags;
import ch.ethz.ssh2.sftp.ErrorCodes;
import ch.ethz.ssh2.sftp.Packet;
/**
* A SFTPv3Client
represents a SFTP (protocol version 3)
* client connection tunnelled over a SSH-2 connection. This is a very simple
* (synchronous) implementation.
*
null
to use the platform's
* default encoding.
* @throws IOException
* @see #getCharset()
*/
public void setCharset(String charset) throws IOException
{
if (charset == null)
{
charsetName = charset;
return;
}
try
{
Charset.forName(charset);
}
catch (UnsupportedCharsetException e)
{
throw (IOException) new IOException("This charset is not supported").initCause(e);
}
charsetName = charset;
}
/**
* The currently used charset for filename encoding/decoding.
*
* @return The name of the charset (null
if the platform's default charset is being used)
* @see #setCharset(String)
*/
public String getCharset()
{
return charsetName;
}
private void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException
{
if (handle.client != this)
{
throw new IOException("The file handle was created with another SFTPv3FileHandle instance.");
}
if (handle.isClosed)
{
throw new IOException("The file handle is closed.");
}
}
private void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException
{
listener.write(Packet.forName(type));
int msglen = len + 1;
if (type != Packet.SSH_FXP_INIT)
{
msglen += 4;
}
os.write(msglen >> 24);
os.write(msglen >> 16);
os.write(msglen >> 8);
os.write(msglen);
os.write(type);
if (type != Packet.SSH_FXP_INIT)
{
os.write(requestId >> 24);
os.write(requestId >> 16);
os.write(requestId >> 8);
os.write(requestId);
}
os.write(msg, off, len);
os.flush();
}
private void sendMessage(int type, int requestId, byte[] msg) throws IOException
{
sendMessage(type, requestId, msg, 0, msg.length);
}
private void readBytes(byte[] buff, int pos, int len) throws IOException
{
while (len > 0)
{
int count = is.read(buff, pos, len);
if (count < 0)
{
throw new IOException("Unexpected end of sftp stream.");
}
if ((count == 0) || (count > len))
{
throw new IOException("Underlying stream implementation is bogus!");
}
len -= count;
pos += count;
}
}
/**
* Read a message and guarantee that the contents is not larger than
* maxlen
bytes.
*
* Note: receiveMessage(34000) actually means that the message may be up to 34004
* bytes (the length attribute preceeding the contents is 4 bytes).
*
* @param maxlen
* @return the message contents
* @throws IOException
*/
private byte[] receiveMessage(int maxlen) throws IOException
{
byte[] msglen = new byte[4];
readBytes(msglen, 0, 4);
int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
if ((len > maxlen) || (len <= 0))
{
throw new IOException("Illegal sftp packet len: " + len);
}
byte[] msg = new byte[len];
readBytes(msg, 0, len);
return msg;
}
private int generateNextRequestID()
{
synchronized (this)
{
return next_request_id++;
}
}
private void closeHandle(byte[] handle) throws IOException
{
int req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(handle, 0, handle.length);
sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
expectStatusOKMessage(req_id);
}
private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException
{
/*
* uint32 flags
* uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
* uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
* uint32 gid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
* uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
* uint32 atime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
* uint32 mtime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
* uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
* string extended_type
* string extended_data
* ... more extended data (extended_type - extended_data pairs),
* so that number of pairs equals extended_count
*/
SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
int flags = tr.readUINT32();
if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0)
{
log.debug("SSH_FILEXFER_ATTR_SIZE");
fa.size = tr.readUINT64();
}
if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0)
{
log.debug("SSH_FILEXFER_ATTR_V3_UIDGID");
fa.uid = tr.readUINT32();
fa.gid = tr.readUINT32();
}
if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0)
{
log.debug("SSH_FILEXFER_ATTR_PERMISSIONS");
fa.permissions = tr.readUINT32();
}
if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0)
{
log.debug("SSH_FILEXFER_ATTR_V3_ACMODTIME");
fa.atime = tr.readUINT32();
fa.mtime = tr.readUINT32();
}
if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0)
{
int count = tr.readUINT32();
log.debug("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")");
/* Read it anyway to detect corrupt packets */
while (count > 0)
{
tr.readByteString();
tr.readByteString();
count--;
}
}
return fa;
}
/**
* Retrieve the file attributes of an open file.
*
* @param handle a SFTPv3FileHandle handle.
* @return a SFTPv3FileAttributes object.
* @throws IOException
*/
public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException
{
checkHandleValidAndOpen(handle);
int req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
log.debug("Sending SSH_FXP_FSTAT...");
sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
int rep_id = tr.readUINT32();
if (rep_id != req_id)
{
throw new IOException("The server sent an invalid id field.");
}
if (t == Packet.SSH_FXP_ATTRS)
{
return readAttrs(tr);
}
if (t != Packet.SSH_FXP_STATUS)
{
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
int errorCode = tr.readUINT32();
String errorMessage = tr.readString();
listener.read(errorMessage);
throw new SFTPException(errorMessage, errorCode);
}
private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException
{
int req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(path, charsetName);
log.debug("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
sendMessage(statMethod, req_id, tw.getBytes());
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
int rep_id = tr.readUINT32();
if (rep_id != req_id)
{
throw new IOException("The server sent an invalid id field.");
}
if (t == Packet.SSH_FXP_ATTRS)
{
return readAttrs(tr);
}
if (t != Packet.SSH_FXP_STATUS)
{
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
int errorCode = tr.readUINT32();
String errorMessage = tr.readString();
listener.read(errorMessage);
throw new SFTPException(errorMessage, errorCode);
}
/**
* Retrieve the file attributes of a file. This method
* follows symbolic links on the server.
*
* @param path See the {@link SFTPv3Client comment} for the class for more details.
* @return a SFTPv3FileAttributes object.
* @throws IOException
* @see #lstat(String)
*/
public SFTPv3FileAttributes stat(String path) throws IOException
{
return statBoth(path, Packet.SSH_FXP_STAT);
}
/**
* Retrieve the file attributes of a file. This method
* does NOT follow symbolic links on the server.
*
* @param path See the {@link SFTPv3Client comment} for the class for more details.
* @return a SFTPv3FileAttributes object.
* @throws IOException
* @see #stat(String)
*/
public SFTPv3FileAttributes lstat(String path) throws IOException
{
return statBoth(path, Packet.SSH_FXP_LSTAT);
}
/**
* Read the target of a symbolic link. Note: OpenSSH (as of version 4.4) gets very upset
* (SSH_FX_BAD_MESSAGE error) if you want to read the target of a file that is not a
* symbolic link. Better check first with {@link #lstat(String)}.
*
* @param path See the {@link SFTPv3Client comment} for the class for more details.
* @return The target of the link.
* @throws IOException
*/
public String readLink(String path) throws IOException
{
int req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(path, charsetName);
log.debug("Sending SSH_FXP_READLINK...");
sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
int rep_id = tr.readUINT32();
if (rep_id != req_id)
{
throw new IOException("The server sent an invalid id field.");
}
if (t == Packet.SSH_FXP_NAME)
{
int count = tr.readUINT32();
if (count != 1)
{
throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
}
return tr.readString(charsetName);
}
if (t != Packet.SSH_FXP_STATUS)
{
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
int errorCode = tr.readUINT32();
String errorMessage = tr.readString();
listener.read(errorMessage);
throw new SFTPException(errorMessage, errorCode);
}
private void expectStatusOKMessage(int id) throws IOException
{
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
int rep_id = tr.readUINT32();
if (rep_id != id)
{
throw new IOException("The server sent an invalid id field.");
}
if (t != Packet.SSH_FXP_STATUS)
{
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
int errorCode = tr.readUINT32();
if (errorCode == ErrorCodes.SSH_FX_OK)
{
return;
}
String errorMessage = tr.readString();
listener.read(errorMessage);
throw new SFTPException(errorMessage, errorCode);
}
/**
* Modify the attributes of a file. Used for operations such as changing
* the ownership, permissions or access times, as well as for truncating a file.
*
* @param path See the {@link SFTPv3Client comment} for the class for more details.
* @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
* made to the attributes of the file. Empty fields will be ignored.
* @throws IOException
*/
public void setstat(String path, SFTPv3FileAttributes attr) throws IOException
{
int req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(path, charsetName);
tw.writeBytes(createAttrs(attr));
log.debug("Sending SSH_FXP_SETSTAT...");
sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());
expectStatusOKMessage(req_id);
}
/**
* Modify the attributes of a file. Used for operations such as changing
* the ownership, permissions or access times, as well as for truncating a file.
*
* @param handle a SFTPv3FileHandle handle
* @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
* made to the attributes of the file. Empty fields will be ignored.
* @throws IOException
*/
public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException
{
checkHandleValidAndOpen(handle);
int req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
tw.writeBytes(createAttrs(attr));
log.debug("Sending SSH_FXP_FSETSTAT...");
sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());
expectStatusOKMessage(req_id);
}
/**
* Create a symbolic link on the server. Creates a link "src" that points
* to "target".
*
* @param src See the {@link SFTPv3Client comment} for the class for more details.
* @param target See the {@link SFTPv3Client comment} for the class for more details.
* @throws IOException
*/
public void createSymlink(String src, String target) throws IOException
{
int req_id = generateNextRequestID();
/* Either I am too stupid to understand the SFTP draft
* or the OpenSSH guys changed the semantics of src and target.
*/
TypesWriter tw = new TypesWriter();
tw.writeString(target, charsetName);
tw.writeString(src, charsetName);
log.debug("Sending SSH_FXP_SYMLINK...");
sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
expectStatusOKMessage(req_id);
}
/**
* Have the server canonicalize any given path name to an absolute path.
* This is useful for converting path names containing ".." components or
* relative pathnames without a leading slash into absolute paths.
*
* @param path See the {@link SFTPv3Client comment} for the class for more details.
* @return An absolute path.
* @throws IOException
*/
public String canonicalPath(String path) throws IOException
{
int req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(path, charsetName);
log.debug("Sending SSH_FXP_REALPATH...");
sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
int rep_id = tr.readUINT32();
if (rep_id != req_id)
{
throw new IOException("The server sent an invalid id field.");
}
if (t == Packet.SSH_FXP_NAME)
{
int count = tr.readUINT32();
if (count != 1)
{
throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
}
final String name = tr.readString(charsetName);
listener.read(name);
return name;
}
if (t != Packet.SSH_FXP_STATUS)
{
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
int errorCode = tr.readUINT32();
String errorMessage = tr.readString();
listener.read(errorMessage);
throw new SFTPException(errorMessage, errorCode);
}
private Listclose()
method, you are likely wasting resources.
*/
public void close()
{
sess.close();
}
/**
* List the contents of a directory.
*
* @param dirName See the {@link SFTPv3Client comment} for the class for more details.
* @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
* @throws IOException
*/
public Listnull
to use server defaults. Probably only
* the uid
, gid
and permissions
* (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
* structure make sense. You need only to set those fields where you want
* to override the server's defaults.
* @return a SFTPv3FileHandle handle
* @throws IOException
*/
public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException
{
return openFile(fileName, SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE, attr);
}
/**
* Create a file (truncate it if it already exists) and open it for writing.
* Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
*
* @param fileName See the {@link SFTPv3Client comment} for the class for more details.
* @return a SFTPv3FileHandle handle
* @throws IOException
*/
public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException
{
return createFileTruncate(fileName, null);
}
/**
* reate a file (truncate it if it already exists) and open it for writing.
* You can specify the default attributes of the file (the server may or may
* not respect your wishes).
*
* @param fileName See the {@link SFTPv3Client comment} for the class for more details.
* @param attr may be null
to use server defaults. Probably only
* the uid
, gid
and permissions
* (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
* structure make sense. You need only to set those fields where you want
* to override the server's defaults.
* @return a SFTPv3FileHandle handle
* @throws IOException
*/
public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException
{
return openFile(fileName, SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_WRITE, attr);
}
private byte[] createAttrs(SFTPv3FileAttributes attr)
{
TypesWriter tw = new TypesWriter();
int attrFlags = 0;
if (attr == null)
{
tw.writeUINT32(0);
}
else
{
if (attr.size != null)
{
attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE;
}
if ((attr.uid != null) && (attr.gid != null))
{
attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;
}
if (attr.permissions != null)
{
attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;
}
if ((attr.atime != null) && (attr.mtime != null))
{
attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;
}
tw.writeUINT32(attrFlags);
if (attr.size != null)
{
tw.writeUINT64(attr.size);
}
if ((attr.uid != null) && (attr.gid != null))
{
tw.writeUINT32(attr.uid);
tw.writeUINT32(attr.gid);
}
if (attr.permissions != null)
{
tw.writeUINT32(attr.permissions);
}
if ((attr.atime != null) && (attr.mtime != null))
{
tw.writeUINT32(attr.atime);
tw.writeUINT32(attr.mtime);
}
}
return tw.getBytes();
}
public SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException
{
int req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(fileName, charsetName);
tw.writeUINT32(flags);
tw.writeBytes(createAttrs(attr));
log.debug("Sending SSH_FXP_OPEN...");
sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
int rep_id = tr.readUINT32();
if (rep_id != req_id)
{
throw new IOException("The server sent an invalid id field.");
}
if (t == Packet.SSH_FXP_HANDLE)
{
log.debug("Got SSH_FXP_HANDLE.");
return new SFTPv3FileHandle(this, tr.readByteString());
}
if (t != Packet.SSH_FXP_STATUS)
{
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
int errorCode = tr.readUINT32();
String errorMessage = tr.readString();
listener.read(errorMessage);
throw new SFTPException(errorMessage, errorCode);
}
/**
* A read is divided into multiple requests sent sequentially before
* reading any status from the server
*/
private static class OutstandingReadRequest
{
int req_id;
/**
* Read offset to request on server starting at the file offset for the first request.
*/
long serverOffset;
/**
* Length of requested data
*/
int len;
/**
* Offset in destination buffer
*/
int dstOffset;
/**
* Temporary buffer
*/
byte[] buffer;
}
private void sendReadRequest(int id, SFTPv3FileHandle handle, long offset, int len) throws IOException
{
TypesWriter tw = new TypesWriter();
tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
tw.writeUINT64(offset);
tw.writeUINT32(len);
log.debug("Sending SSH_FXP_READ (" + id + ") " + offset + "/" + len);
sendMessage(Packet.SSH_FXP_READ, id, tw.getBytes());
}
/**
* Parallel read requests maximum size.
*/
private static final int DEFAULT_MAX_PARALLELISM = 64;
/**
* Parallel read requests.
*/
private int parallelism = DEFAULT_MAX_PARALLELISM;
/**
* @param parallelism
*/
public void setRequestParallelism(int parallelism)
{
this.parallelism = Math.min(parallelism, DEFAULT_MAX_PARALLELISM);
}
/**
* Mapping request ID to request.
*/
Maplen
),
* and return them.-1
is returned.
* EOF
* @throws IOException
*/
public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException
{
boolean errorOccured = false;
checkHandleValidAndOpen(handle);
int remaining = len * parallelism;
int clientOffset = dstoff;
long serverOffset = fileOffset;
for (OutstandingReadRequest r : pendingReadQueue.values())
{
// Server offset should take pending requests into account.
serverOffset += r.len;
}
while (true)
{
// Stop if there was an error and no outstanding request
if ((pendingReadQueue.size() == 0) && errorOccured)
{
break;
}
// Send as many requests as we are allowed to
while (pendingReadQueue.size() < parallelism)
{
if (errorOccured)
{
break;
}
// Send the next read request
OutstandingReadRequest req = new OutstandingReadRequest();
req.req_id = generateNextRequestID();
req.serverOffset = serverOffset;
req.len = (remaining > len) ? len : remaining;
req.buffer = dst;
req.dstOffset = dstoff;
serverOffset += req.len;
clientOffset += req.len;
remaining -= req.len;
sendReadRequest(req.req_id, handle, req.serverOffset, req.len);
pendingReadQueue.put(req.req_id, req);
}
if (pendingReadQueue.size() == 0)
{
break;
}
// Receive a single answer
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
// Search the pending queue
OutstandingReadRequest req = pendingReadQueue.remove(tr.readUINT32());
if (null == req)
{
throw new IOException("The server sent an invalid id field.");
}
// Evaluate the answer
if (t == Packet.SSH_FXP_STATUS)
{
/* In any case, stop sending more packets */
int code = tr.readUINT32();
String msg = tr.readString();
listener.read(msg);
if (log.isDebugEnabled())
{
String[] desc = ErrorCodes.getDescription(code);
log.debug("Got SSH_FXP_STATUS (" + req.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
}
// Flag to read all pending requests but don't send any more.
errorOccured = true;
if (pendingReadQueue.isEmpty())
{
if (ErrorCodes.SSH_FX_EOF == code)
{
return -1;
}
throw new SFTPException(msg, code);
}
}
else if (t == Packet.SSH_FXP_DATA)
{
// OK, collect data
int readLen = tr.readUINT32();
if ((readLen < 0) || (readLen > req.len))
{
throw new IOException("The server sent an invalid length field in a SSH_FXP_DATA packet.");
}
if (log.isDebugEnabled())
{
log.debug("Got SSH_FXP_DATA (" + req.req_id + ") " + req.serverOffset + "/" + readLen
+ " (requested: " + req.len + ")");
}
// Read bytes into buffer
tr.readBytes(req.buffer, req.dstOffset, readLen);
if (readLen < req.len)
{
/* Send this request packet again to request the remaing data in this slot. */
req.req_id = generateNextRequestID();
req.serverOffset += readLen;
req.len -= readLen;
log.debug("Requesting again: " + req.serverOffset + "/" + req.len);
sendReadRequest(req.req_id, handle, req.serverOffset, req.len);
pendingReadQueue.put(req.req_id, req);
}
return readLen;
}
else
{
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
}
// Should never reach here.
throw new SFTPException("No EOF reached", -1);
}
/**
* A read is divided into multiple requests sent sequentially before
* reading any status from the server
*/
private static class OutstandingStatusRequest
{
int req_id;
}
/**
* Mapping request ID to request.
*/
Maplen
> 32768, then the write operation will
* be split into multiple writes.
*
* @param handle a SFTPv3FileHandle handle.
* @param fileOffset offset (in bytes) in the file.
* @param src the source byte array.
* @param srcoff offset in the source byte array.
* @param len how many bytes to write.
* @throws IOException
*/
public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException
{
checkHandleValidAndOpen(handle);
// Send the next write request
OutstandingStatusRequest req = new OutstandingStatusRequest();
req.req_id = generateNextRequestID();
TypesWriter tw = new TypesWriter();
tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
tw.writeUINT64(fileOffset);
tw.writeString(src, srcoff, len);
log.debug("Sending SSH_FXP_WRITE...");
sendMessage(Packet.SSH_FXP_WRITE, req.req_id, tw.getBytes());
pendingStatusQueue.put(req.req_id, req);
// Only read next status if parallelism reached
while (pendingStatusQueue.size() >= parallelism)
{
this.readStatus();
}
}
private void readStatus() throws IOException
{
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
// Search the pending queue
OutstandingStatusRequest status = pendingStatusQueue.remove(tr.readUINT32());
if (null == status)
{
throw new IOException("The server sent an invalid id field.");
}
// Evaluate the answer
if (t == Packet.SSH_FXP_STATUS)
{
// In any case, stop sending more packets
int code = tr.readUINT32();
if (log.isDebugEnabled())
{
String[] desc = ErrorCodes.getDescription(code);
log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
}
if (code == ErrorCodes.SSH_FX_OK)
{
return;
}
String msg = tr.readString();
listener.read(msg);
throw new SFTPException(msg, code);
}
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
private void readPendingReadStatus() throws IOException
{
byte[] resp = receiveMessage(34000);
TypesReader tr = new TypesReader(resp);
int t = tr.readByte();
listener.read(Packet.forName(t));
// Search the pending queue
OutstandingReadRequest status = pendingReadQueue.remove(tr.readUINT32());
if (null == status)
{
throw new IOException("The server sent an invalid id field.");
}
// Evaluate the answer
if (t == Packet.SSH_FXP_STATUS)
{
// In any case, stop sending more packets
int code = tr.readUINT32();
if (log.isDebugEnabled())
{
String[] desc = ErrorCodes.getDescription(code);
log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
}
if (code == ErrorCodes.SSH_FX_OK)
{
return;
}
if (code == ErrorCodes.SSH_FX_EOF)
{
return;
}
String msg = tr.readString();
listener.read(msg);
throw new SFTPException(msg, code);
}
throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
}
/**
* Close a file.
*
* @param handle a SFTPv3FileHandle handle
* @throws IOException
*/
public void closeFile(SFTPv3FileHandle handle) throws IOException
{
try
{
while (!pendingReadQueue.isEmpty())
{
this.readPendingReadStatus();
}
while (!pendingStatusQueue.isEmpty())
{
this.readStatus();
}
if (!handle.isClosed)
{
closeHandle(handle.fileHandle);
}
}
finally
{
handle.isClosed = true;
}
}
}