| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.sshd.client.subsystem.sftp; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.nio.file.attribute.FileTime; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.sshd.client.channel.ClientChannel; |
| import org.apache.sshd.client.subsystem.AbstractSubsystemClient; |
| import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions; |
| import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension; |
| import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtensionFactory; |
| import org.apache.sshd.common.SshException; |
| import org.apache.sshd.common.channel.Channel; |
| import org.apache.sshd.common.subsystem.sftp.SftpConstants; |
| import org.apache.sshd.common.subsystem.sftp.SftpException; |
| import org.apache.sshd.common.subsystem.sftp.SftpHelper; |
| import org.apache.sshd.common.subsystem.sftp.SftpUniversalOwnerAndGroup; |
| import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils; |
| import org.apache.sshd.common.util.GenericUtils; |
| import org.apache.sshd.common.util.ValidateUtils; |
| import org.apache.sshd.common.util.buffer.Buffer; |
| import org.apache.sshd.common.util.buffer.ByteArrayBuffer; |
| |
| |
| /** |
| * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> |
| */ |
| public abstract class AbstractSftpClient extends AbstractSubsystemClient implements SftpClient, RawSftpClient { |
| private final Attributes fileOpenAttributes = new Attributes(); |
| private final AtomicReference<Map<String, Object>> parsedExtensionsHolder = new AtomicReference<>(null); |
| |
| protected AbstractSftpClient() { |
| fileOpenAttributes.setType(SftpConstants.SSH_FILEXFER_TYPE_REGULAR); |
| } |
| |
| @Override |
| public Channel getChannel() { |
| return getClientChannel(); |
| } |
| |
| @Override |
| public String getName() { |
| return SftpConstants.SFTP_SUBSYSTEM_NAME; |
| } |
| |
| @Override |
| public CloseableHandle open(String path) throws IOException { |
| return open(path, Collections.<OpenMode>emptySet()); |
| } |
| |
| @Override |
| public CloseableHandle open(String path, OpenMode... options) throws IOException { |
| return open(path, GenericUtils.of(options)); |
| } |
| |
| @Override |
| public void rename(String oldPath, String newPath) throws IOException { |
| rename(oldPath, newPath, Collections.<CopyMode>emptySet()); |
| } |
| |
| @Override |
| public void rename(String oldPath, String newPath, CopyMode... options) throws IOException { |
| rename(oldPath, newPath, GenericUtils.of(options)); |
| } |
| |
| @Override |
| public InputStream read(String path) throws IOException { |
| return read(path, DEFAULT_READ_BUFFER_SIZE); |
| } |
| |
| @Override |
| public InputStream read(String path, int bufferSize) throws IOException { |
| return read(path, bufferSize, EnumSet.of(OpenMode.Read)); |
| } |
| |
| @Override |
| public InputStream read(String path, OpenMode... mode) throws IOException { |
| return read(path, DEFAULT_READ_BUFFER_SIZE, mode); |
| } |
| |
| @Override |
| public InputStream read(String path, int bufferSize, OpenMode... mode) throws IOException { |
| return read(path, bufferSize, GenericUtils.of(mode)); |
| } |
| |
| @Override |
| public InputStream read(String path, Collection<OpenMode> mode) throws IOException { |
| return read(path, DEFAULT_READ_BUFFER_SIZE, mode); |
| } |
| |
| @Override |
| public OutputStream write(String path) throws IOException { |
| return write(path, DEFAULT_WRITE_BUFFER_SIZE); |
| } |
| |
| @Override |
| public OutputStream write(String path, int bufferSize) throws IOException { |
| return write(path, bufferSize, EnumSet.of(OpenMode.Write, OpenMode.Create, OpenMode.Truncate)); |
| } |
| |
| @Override |
| public OutputStream write(String path, OpenMode... mode) throws IOException { |
| return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode); |
| } |
| |
| @Override |
| public OutputStream write(String path, Collection<OpenMode> mode) throws IOException { |
| return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode); |
| } |
| |
| @Override |
| public OutputStream write(String path, int bufferSize, OpenMode... mode) throws IOException { |
| return write(path, bufferSize, GenericUtils.of(mode)); |
| } |
| |
| @Override |
| public void write(Handle handle, long fileOffset, byte[] src) throws IOException { |
| write(handle, fileOffset, src, 0, src.length); |
| } |
| |
| @Override |
| public void symLink(String linkPath, String targetPath) throws IOException { |
| link(linkPath, targetPath, true); |
| } |
| |
| @Override |
| public <E extends SftpClientExtension> E getExtension(Class<? extends E> extensionType) { |
| Object instance = getExtension(BuiltinSftpClientExtensions.fromType(extensionType)); |
| if (instance == null) { |
| return null; |
| } else { |
| return extensionType.cast(instance); |
| } |
| } |
| |
| @Override |
| public SftpClientExtension getExtension(String extensionName) { |
| return getExtension(BuiltinSftpClientExtensions.fromName(extensionName)); |
| } |
| |
| protected SftpClientExtension getExtension(SftpClientExtensionFactory factory) { |
| if (factory == null) { |
| return null; |
| } |
| |
| Map<String, byte[]> extensions = getServerExtensions(); |
| Map<String, Object> parsed = getParsedServerExtensions(extensions); |
| return factory.create(this, this, extensions, parsed); |
| } |
| |
| protected Map<String, Object> getParsedServerExtensions() { |
| return getParsedServerExtensions(getServerExtensions()); |
| } |
| |
| protected Map<String, Object> getParsedServerExtensions(Map<String, byte[]> extensions) { |
| Map<String, Object> parsed = parsedExtensionsHolder.get(); |
| if (parsed == null) { |
| parsed = ParserUtils.parse(extensions); |
| if (parsed == null) { |
| parsed = Collections.emptyMap(); |
| } |
| parsedExtensionsHolder.set(parsed); |
| } |
| |
| return parsed; |
| } |
| |
| /** |
| * Sends the specified command, waits for the response and then invokes {@link #checkResponseStatus(int, Buffer)} |
| * @param cmd The command to send |
| * @param request The request {@link Buffer} |
| * @throws IOException If failed to send, receive or check the returned status |
| * @see #send(int, Buffer) |
| * @see #receive(int) |
| * @see #checkResponseStatus(int, Buffer) |
| */ |
| protected void checkCommandStatus(int cmd, Buffer request) throws IOException { |
| int reqId = send(cmd, request); |
| Buffer response = receive(reqId); |
| checkResponseStatus(cmd, response); |
| } |
| |
| /** |
| * Checks if the incoming response is an {@code SSH_FXP_STATUS} one, |
| * and if so whether the substatus is {@code SSH_FX_OK}. |
| * |
| * @param cmd The sent command opcode |
| * @param buffer The received response {@link Buffer} |
| * @throws IOException If response does not carry a status or carries |
| * a bad status code |
| * @see #checkResponseStatus(int, int, int, String, String) |
| */ |
| protected void checkResponseStatus(int cmd, Buffer buffer) throws IOException { |
| int length = buffer.getInt(); |
| int type = buffer.getUByte(); |
| int id = buffer.getInt(); |
| if (type == SftpConstants.SSH_FXP_STATUS) { |
| int substatus = buffer.getInt(); |
| String msg = buffer.getString(); |
| String lang = buffer.getString(); |
| checkResponseStatus(cmd, id, substatus, msg, lang); |
| } else { |
| handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_STATUS, id, type, length, buffer); |
| } |
| } |
| |
| /** |
| * @param cmd The sent command opcode |
| * @param id The request id |
| * @param substatus The sub-status value |
| * @param msg The message |
| * @param lang The language |
| * @throws IOException if the sub-status is not {@code SSH_FX_OK} |
| * @see #throwStatusException(int, int, int, String, String) |
| */ |
| protected void checkResponseStatus(int cmd, int id, int substatus, String msg, String lang) throws IOException { |
| if (log.isTraceEnabled()) { |
| log.trace("checkResponseStatus({})[id={}] cmd={} status={} lang={} msg={}", |
| getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), |
| SftpConstants.getStatusName(substatus), lang, msg); |
| } |
| |
| if (substatus != SftpConstants.SSH_FX_OK) { |
| throwStatusException(cmd, id, substatus, msg, lang); |
| } |
| } |
| |
| protected void throwStatusException(int cmd, int id, int substatus, String msg, String lang) throws IOException { |
| throw new SftpException(substatus, msg); |
| } |
| |
| /** |
| * @param cmd Command to be sent |
| * @param request The {@link Buffer} containing the request |
| * @return The received handle identifier |
| * @throws IOException If failed to send/receive or process the response |
| * @see #send(int, Buffer) |
| * @see #receive(int) |
| * @see #checkHandleResponse(int, Buffer) |
| */ |
| protected byte[] checkHandle(int cmd, Buffer request) throws IOException { |
| int reqId = send(cmd, request); |
| Buffer response = receive(reqId); |
| return checkHandleResponse(cmd, response); |
| } |
| |
| protected byte[] checkHandleResponse(int cmd, Buffer buffer) throws IOException { |
| int length = buffer.getInt(); |
| int type = buffer.getUByte(); |
| int id = buffer.getInt(); |
| if (type == SftpConstants.SSH_FXP_HANDLE) { |
| return ValidateUtils.checkNotNullAndNotEmpty(buffer.getBytes(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY); |
| } |
| |
| if (type == SftpConstants.SSH_FXP_STATUS) { |
| int substatus = buffer.getInt(); |
| String msg = buffer.getString(); |
| String lang = buffer.getString(); |
| if (log.isTraceEnabled()) { |
| log.trace("checkHandleResponse({})[id={}] {} - status: {} [{}] {}", |
| getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), |
| SftpConstants.getStatusName(substatus), lang, msg); |
| } |
| throwStatusException(cmd, id, substatus, msg, lang); |
| } |
| |
| return handleUnexpectedHandlePacket(cmd, id, type, length, buffer); |
| } |
| |
| protected byte[] handleUnexpectedHandlePacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { |
| handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_HANDLE, id, type, length, buffer); |
| throw new SshException("No handling for unexpected handle packet id=" + id |
| + ", type=" + SftpConstants.getCommandMessageName(type) + ", length=" + length); |
| } |
| |
| /** |
| * @param cmd Command to be sent |
| * @param request Request {@link Buffer} |
| * @return The decoded response {@code Attributes} |
| * @throws IOException If failed to send/receive or process the response |
| * @see #send(int, Buffer) |
| * @see #receive(int) |
| * @see #checkAttributesResponse(int, Buffer) |
| */ |
| protected Attributes checkAttributes(int cmd, Buffer request) throws IOException { |
| int reqId = send(cmd, request); |
| Buffer response = receive(reqId); |
| return checkAttributesResponse(cmd, response); |
| } |
| |
| protected Attributes checkAttributesResponse(int cmd, Buffer buffer) throws IOException { |
| int length = buffer.getInt(); |
| int type = buffer.getUByte(); |
| int id = buffer.getInt(); |
| if (type == SftpConstants.SSH_FXP_ATTRS) { |
| return readAttributes(buffer); |
| } |
| |
| if (type == SftpConstants.SSH_FXP_STATUS) { |
| int substatus = buffer.getInt(); |
| String msg = buffer.getString(); |
| String lang = buffer.getString(); |
| if (log.isTraceEnabled()) { |
| log.trace("checkAttributesResponse()[id={}] {} - status: {} [{}] {}", |
| getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), |
| SftpConstants.getStatusName(substatus), lang, msg); |
| } |
| throwStatusException(cmd, id, substatus, msg, lang); |
| } |
| |
| return handleUnexpectedAttributesPacket(cmd, id, type, length, buffer); |
| } |
| |
| protected Attributes handleUnexpectedAttributesPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { |
| IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_ATTRS, id, type, length, buffer); |
| if (err != null) { |
| throw err; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @param cmd Command to be sent |
| * @param request The request {@link Buffer} |
| * @return The retrieved name |
| * @throws IOException If failed to send/receive or process the response |
| * @see #send(int, Buffer) |
| * @see #receive(int) |
| * @see #checkOneNameResponse(int, Buffer) |
| */ |
| protected String checkOneName(int cmd, Buffer request) throws IOException { |
| int reqId = send(cmd, request); |
| Buffer response = receive(reqId); |
| return checkOneNameResponse(cmd, response); |
| } |
| |
| protected String checkOneNameResponse(int cmd, Buffer buffer) throws IOException { |
| int length = buffer.getInt(); |
| int type = buffer.getUByte(); |
| int id = buffer.getInt(); |
| if (type == SftpConstants.SSH_FXP_NAME) { |
| int len = buffer.getInt(); |
| if (len != 1) { |
| throw new SshException("SFTP error: received " + len + " names instead of 1"); |
| } |
| String name = buffer.getString(); |
| String longName = null; |
| int version = getVersion(); |
| if (version == SftpConstants.SFTP_V3) { |
| longName = buffer.getString(); |
| } |
| |
| Attributes attrs = readAttributes(buffer); |
| Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version); |
| // TODO decide what to do if not-null and not TRUE |
| if (log.isTraceEnabled()) { |
| log.trace("checkOneNameResponse({})[id={}] {} ({})[{}] eol={}: {}", |
| getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), |
| name, longName, indicator, attrs); |
| } |
| return name; |
| } |
| |
| if (type == SftpConstants.SSH_FXP_STATUS) { |
| int substatus = buffer.getInt(); |
| String msg = buffer.getString(); |
| String lang = buffer.getString(); |
| if (log.isTraceEnabled()) { |
| log.trace("checkOneNameResponse({})[id={}] {} status: {} [{}] {}", |
| getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), |
| SftpConstants.getStatusName(substatus), lang, msg); |
| } |
| |
| throwStatusException(cmd, id, substatus, msg, lang); |
| } |
| |
| return handleUnknownOneNamePacket(cmd, id, type, length, buffer); |
| } |
| |
| protected String handleUnknownOneNamePacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { |
| IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_NAME, id, type, length, buffer); |
| if (err != null) { |
| throw err; |
| } |
| |
| return null; |
| } |
| |
| protected Attributes readAttributes(Buffer buffer) throws IOException { |
| Attributes attrs = new Attributes(); |
| int flags = buffer.getInt(); |
| int version = getVersion(); |
| if (version == SftpConstants.SFTP_V3) { |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) { |
| attrs.setSize(buffer.getLong()); |
| } |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_UIDGID) != 0) { |
| attrs.owner(buffer.getInt(), buffer.getInt()); |
| } |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { |
| int perms = buffer.getInt(); |
| attrs.setPermissions(perms); |
| attrs.setType(SftpHelper.permissionsToFileType(perms)); |
| } |
| |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME) != 0) { |
| attrs.setAccessTime(SftpHelper.readTime(buffer, version, flags)); |
| attrs.setModifyTime(SftpHelper.readTime(buffer, version, flags)); |
| } |
| } else if (version >= SftpConstants.SFTP_V4) { |
| attrs.setType(buffer.getUByte()); |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) { |
| attrs.setSize(buffer.getLong()); |
| } |
| |
| if ((version >= SftpConstants.SFTP_V6) && ((flags & SftpConstants.SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0)) { |
| @SuppressWarnings("unused") |
| long allocSize = buffer.getLong(); // TODO handle allocation size |
| } |
| |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) { |
| attrs.setOwner(buffer.getString()); |
| attrs.setGroup(buffer.getString()); |
| } |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { |
| attrs.setPermissions(buffer.getInt()); |
| } |
| |
| // update the permissions according to the type |
| int perms = attrs.getPermissions(); |
| perms |= SftpHelper.fileTypeToPermission(attrs.getType()); |
| attrs.setPermissions(perms); |
| |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) { |
| attrs.setAccessTime(SftpHelper.readTime(buffer, version, flags)); |
| } |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_CREATETIME) != 0) { |
| attrs.setCreateTime(SftpHelper.readTime(buffer, version, flags)); |
| } |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) { |
| attrs.setModifyTime(SftpHelper.readTime(buffer, version, flags)); |
| } |
| if ((version >= SftpConstants.SFTP_V6) && (flags & SftpConstants.SSH_FILEXFER_ATTR_CTIME) != 0) { |
| @SuppressWarnings("unused") |
| FileTime attrsChangedTime = SftpHelper.readTime(buffer, version, flags); // TODO the last time the file attributes were changed |
| } |
| |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACL) != 0) { |
| attrs.setAcl(SftpHelper.readACLs(buffer, version)); |
| } |
| |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_BITS) != 0) { |
| @SuppressWarnings("unused") |
| int bits = buffer.getInt(); |
| @SuppressWarnings("unused") |
| int valid = 0xffffffff; |
| if (version >= SftpConstants.SFTP_V6) { |
| valid = buffer.getInt(); |
| } |
| // TODO: handle attrib bits |
| } |
| |
| if (version >= SftpConstants.SFTP_V6) { |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_TEXT_HINT) != 0) { |
| @SuppressWarnings("unused") |
| boolean text = buffer.getBoolean(); // TODO: handle text |
| } |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_MIME_TYPE) != 0) { |
| @SuppressWarnings("unused") |
| String mimeType = buffer.getString(); // TODO: handle mime-type |
| } |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_LINK_COUNT) != 0) { |
| @SuppressWarnings("unused") |
| int nlink = buffer.getInt(); // TODO: handle link-count |
| } |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) { |
| @SuppressWarnings("unused") |
| String untranslated = buffer.getString(); // TODO: handle untranslated-name |
| } |
| } |
| } else { |
| throw new IllegalStateException("readAttributes - unsupported version: " + version); |
| } |
| |
| if ((flags & SftpConstants.SSH_FILEXFER_ATTR_EXTENDED) != 0) { |
| attrs.setExtensions(SftpHelper.readExtensions(buffer)); |
| } |
| |
| return attrs; |
| } |
| |
| protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException { |
| int version = getVersion(); |
| int flagsMask = 0; |
| Collection<Attribute> flags = ValidateUtils.checkNotNull(attributes, "No attributes").getFlags(); |
| if (version == SftpConstants.SFTP_V3) { |
| for (Attribute a : flags) { |
| switch (a) { |
| case Size: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_SIZE; |
| break; |
| case UidGid: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_UIDGID; |
| break; |
| case Perms: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS; |
| break; |
| case AccessTime: |
| if (flags.contains(Attribute.ModifyTime)) { |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME; |
| } |
| break; |
| case ModifyTime: |
| if (flags.contains(Attribute.AccessTime)) { |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME; |
| } |
| break; |
| case Extensions: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_EXTENDED; |
| break; |
| default: // do nothing |
| } |
| } |
| buffer.putInt(flagsMask); |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) { |
| buffer.putLong(attributes.getSize()); |
| } |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_UIDGID) != 0) { |
| buffer.putInt(attributes.getUserId()); |
| buffer.putInt(attributes.getGroupId()); |
| } |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { |
| buffer.putInt(attributes.getPermissions()); |
| } |
| |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME) != 0) { |
| SftpHelper.writeTime(buffer, version, flagsMask, attributes.getAccessTime()); |
| SftpHelper.writeTime(buffer, version, flagsMask, attributes.getModifyTime()); |
| } |
| } else if (version >= SftpConstants.SFTP_V4) { |
| for (Attribute a : flags) { |
| switch (a) { |
| case Size: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_SIZE; |
| break; |
| case OwnerGroup: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP; |
| break; |
| case Perms: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS; |
| break; |
| case AccessTime: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME; |
| break; |
| case ModifyTime: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME; |
| break; |
| case CreateTime: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_CREATETIME; |
| break; |
| case Acl: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACL; |
| break; |
| case Extensions: |
| flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_EXTENDED; |
| break; |
| default: // do nothing |
| } |
| } |
| buffer.putInt(flagsMask); |
| buffer.putByte((byte) attributes.getType()); |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) { |
| buffer.putLong(attributes.getSize()); |
| } |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) { |
| String owner = attributes.getOwner(); |
| buffer.putString(GenericUtils.isEmpty(owner) ? SftpUniversalOwnerAndGroup.Owner.getName() : owner); |
| |
| String group = attributes.getGroup(); |
| buffer.putString(GenericUtils.isEmpty(group) ? SftpUniversalOwnerAndGroup.Group.getName() : group); |
| } |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { |
| buffer.putInt(attributes.getPermissions()); |
| } |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) { |
| SftpHelper.writeTime(buffer, version, flagsMask, attributes.getAccessTime()); |
| } |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_CREATETIME) != 0) { |
| SftpHelper.writeTime(buffer, version, flagsMask, attributes.getCreateTime()); |
| } |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) { |
| SftpHelper.writeTime(buffer, version, flagsMask, attributes.getModifyTime()); |
| } |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACL) != 0) { |
| SftpHelper.writeACLs(buffer, version, attributes.getAcl()); |
| } |
| |
| // TODO: for v6+ add CTIME (see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-21) |
| } else { |
| throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version); |
| } |
| |
| if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_EXTENDED) != 0) { |
| SftpHelper.writeExtensions(buffer, attributes.getExtensions()); |
| } |
| } |
| |
| @Override |
| public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("open(" + path + ")[" + options + "] client is closed"); |
| } |
| |
| /* |
| * Be consistent with FileChannel#open - if no mode specified then READ is assumed |
| */ |
| if (GenericUtils.isEmpty(options)) { |
| options = EnumSet.of(OpenMode.Read); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); |
| buffer.putString(path); |
| int version = getVersion(); |
| int mode = 0; |
| if (version == SftpConstants.SFTP_V3) { |
| for (OpenMode m : options) { |
| switch (m) { |
| case Read: |
| mode |= SftpConstants.SSH_FXF_READ; |
| break; |
| case Write: |
| mode |= SftpConstants.SSH_FXF_WRITE; |
| break; |
| case Append: |
| mode |= SftpConstants.SSH_FXF_APPEND; |
| break; |
| case Create: |
| mode |= SftpConstants.SSH_FXF_CREAT; |
| break; |
| case Truncate: |
| mode |= SftpConstants.SSH_FXF_TRUNC; |
| break; |
| case Exclusive: |
| mode |= SftpConstants.SSH_FXF_EXCL; |
| break; |
| default: // do nothing |
| } |
| } |
| } else { |
| if (version >= SftpConstants.SFTP_V5) { |
| int access = 0; |
| if (options.contains(OpenMode.Read)) { |
| access |= SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES; |
| } |
| if (options.contains(OpenMode.Write)) { |
| access |= SftpConstants.ACE4_WRITE_DATA | SftpConstants.ACE4_WRITE_ATTRIBUTES; |
| } |
| if (options.contains(OpenMode.Append)) { |
| access |= SftpConstants.ACE4_APPEND_DATA; |
| } |
| buffer.putInt(access); |
| } |
| |
| if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) { |
| mode |= SftpConstants.SSH_FXF_CREATE_NEW; |
| } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) { |
| mode |= SftpConstants.SSH_FXF_CREATE_TRUNCATE; |
| } else if (options.contains(OpenMode.Create)) { |
| mode |= SftpConstants.SSH_FXF_OPEN_OR_CREATE; |
| } else if (options.contains(OpenMode.Truncate)) { |
| mode |= SftpConstants.SSH_FXF_TRUNCATE_EXISTING; |
| } else { |
| mode |= SftpConstants.SSH_FXF_OPEN_EXISTING; |
| } |
| } |
| buffer.putInt(mode); |
| writeAttributes(buffer, fileOpenAttributes); |
| |
| CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPEN, buffer)); |
| if (log.isTraceEnabled()) { |
| log.trace("open({})[{}] options={}: {}", getClientSession(), path, options, handle); |
| } |
| return handle; |
| } |
| |
| @Override |
| public void close(Handle handle) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("close(" + handle + ") client is closed"); |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("close({}) {}", getClientSession(), handle); |
| } |
| |
| byte[] id = ValidateUtils.checkNotNull(handle, "No handle").getIdentifier(); |
| Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */, false); |
| buffer.putBytes(id); |
| checkCommandStatus(SftpConstants.SSH_FXP_CLOSE, buffer); |
| } |
| |
| @Override |
| public void remove(String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("remove(" + path + ") client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("remove({}) {}", getClientSession(), path); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); |
| buffer.putString(path); |
| checkCommandStatus(SftpConstants.SSH_FXP_REMOVE, buffer); |
| } |
| |
| @Override |
| public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("rename({}) {} => {}", getClientSession(), oldPath, newPath); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */, false); |
| buffer.putString(oldPath); |
| buffer.putString(newPath); |
| |
| int numOptions = GenericUtils.size(options); |
| int version = getVersion(); |
| if (version >= SftpConstants.SFTP_V5) { |
| int opts = 0; |
| if (numOptions > 0) { |
| for (CopyMode opt : options) { |
| switch (opt) { |
| case Atomic: |
| opts |= SftpConstants.SSH_FXP_RENAME_ATOMIC; |
| break; |
| case Overwrite: |
| opts |= SftpConstants.SSH_FXP_RENAME_OVERWRITE; |
| break; |
| default: // do nothing |
| } |
| } |
| } |
| buffer.putInt(opts); |
| } else if (numOptions > 0) { |
| throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")" |
| + " - copy options can not be used with this SFTP version: " + options); |
| } |
| checkCommandStatus(SftpConstants.SSH_FXP_RENAME, buffer); |
| } |
| |
| @Override // TODO make this a default method in Java 8 |
| public int read(Handle handle, long fileOffset, byte[] dst) throws IOException { |
| return read(handle, fileOffset, dst, null); |
| } |
| |
| @Override // TODO make this a default method in Java 8 |
| public int read(Handle handle, long fileOffset, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException { |
| return read(handle, fileOffset, dst, 0, dst.length, eofSignalled); |
| } |
| |
| @Override // TODO make this a default method in Java 8 |
| public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException { |
| return read(handle, fileOffset, dst, dstOffset, len, null); |
| } |
| |
| @Override |
| public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len, AtomicReference<Boolean> eofSignalled) throws IOException { |
| if (eofSignalled != null) { |
| eofSignalled.set(null); |
| } |
| |
| if (!isOpen()) { |
| throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed"); |
| } |
| |
| byte[] id = ValidateUtils.checkNotNull(handle, "No handle").getIdentifier(); |
| Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */, false); |
| buffer.putBytes(id); |
| buffer.putLong(fileOffset); |
| buffer.putInt(len); |
| return checkData(SftpConstants.SSH_FXP_READ, buffer, dstOffset, dst, eofSignalled); |
| } |
| |
| protected int checkData(int cmd, Buffer request, int dstOffset, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException { |
| if (eofSignalled != null) { |
| eofSignalled.set(null); |
| } |
| int reqId = send(cmd, request); |
| Buffer response = receive(reqId); |
| return checkDataResponse(cmd, response, dstOffset, dst, eofSignalled); |
| } |
| |
| protected int checkDataResponse(int cmd, Buffer buffer, int dstoff, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException { |
| if (eofSignalled != null) { |
| eofSignalled.set(null); |
| } |
| |
| int length = buffer.getInt(); |
| int type = buffer.getUByte(); |
| int id = buffer.getInt(); |
| if (type == SftpConstants.SSH_FXP_DATA) { |
| int len = buffer.getInt(); |
| buffer.getRawBytes(dst, dstoff, len); |
| Boolean indicator = SftpHelper.getEndOfFileIndicatorValue(buffer, getVersion()); |
| if (log.isTraceEnabled()) { |
| log.trace("checkDataResponse({}][id={}] {} offset={}, len={}, EOF={}", |
| getClientChannel(), SftpConstants.getCommandMessageName(cmd), |
| id, dstoff, len, indicator); |
| } |
| if (eofSignalled != null) { |
| eofSignalled.set(indicator); |
| } |
| |
| return len; |
| } |
| |
| if (type == SftpConstants.SSH_FXP_STATUS) { |
| int substatus = buffer.getInt(); |
| String msg = buffer.getString(); |
| String lang = buffer.getString(); |
| if (log.isTraceEnabled()) { |
| log.trace("checkDataResponse({})[id={}] {} status: {} [{}] {}", |
| getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), |
| SftpConstants.getStatusName(substatus), lang, msg); |
| } |
| |
| if (substatus == SftpConstants.SSH_FX_EOF) { |
| return -1; |
| } |
| |
| throwStatusException(cmd, id, substatus, msg, lang); |
| } |
| |
| return handleUnknownDataPacket(cmd, id, type, length, buffer); |
| } |
| |
| protected int handleUnknownDataPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { |
| IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_DATA, id, type, length, buffer); |
| if (err != null) { |
| throw err; |
| } |
| |
| return 0; |
| } |
| |
| @Override |
| public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException { |
| // do some bounds checking first |
| if ((fileOffset < 0) || (srcOffset < 0) || (len < 0)) { |
| throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters " |
| + " are non-negative values: file-offset=" + fileOffset |
| + ", src-offset=" + srcOffset + ", len=" + len); |
| } |
| if ((srcOffset + len) > src.length) { |
| throw new IllegalArgumentException("write(" + handle + ")" |
| + " cannot read bytes " + srcOffset + " to " + (srcOffset + len) |
| + " when array is only of length " + src.length); |
| } |
| if (!isOpen()) { |
| throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed"); |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("write({}) handle={}, file-offset={}, buf-offset={}, len={}", |
| getClientChannel(), handle, fileOffset, srcOffset, len); |
| } |
| |
| byte[] id = ValidateUtils.checkNotNull(handle, "No handle").getIdentifier(); |
| Buffer buffer = new ByteArrayBuffer(id.length + len + Long.SIZE /* some extra fields */, false); |
| buffer.putBytes(id); |
| buffer.putLong(fileOffset); |
| buffer.putBytes(src, srcOffset, len); |
| checkCommandStatus(SftpConstants.SSH_FXP_WRITE, buffer); |
| } |
| |
| @Override |
| public void mkdir(String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("mkdir(" + path + ") client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("mkdir({}) {}", getClientSession(), path); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); |
| buffer.putString(path); |
| buffer.putInt(0); |
| |
| int version = getVersion(); |
| if (version != SftpConstants.SFTP_V3) { |
| buffer.putByte((byte) 0); |
| } |
| |
| checkCommandStatus(SftpConstants.SSH_FXP_MKDIR, buffer); |
| } |
| |
| @Override |
| public void rmdir(String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("rmdir(" + path + ") client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("rmdir({}) {}", getClientSession(), path); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); |
| buffer.putString(path); |
| checkCommandStatus(SftpConstants.SSH_FXP_RMDIR, buffer); |
| } |
| |
| @Override |
| public CloseableHandle openDir(String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("openDir(" + path + ") client is closed"); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); |
| buffer.putString(path); |
| |
| CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPENDIR, buffer)); |
| if (log.isTraceEnabled()) { |
| log.trace("openDir({})[{}}: {}", getClientSession(), path, handle); |
| } |
| |
| return handle; |
| } |
| |
| @Override // TODO in JDK-8 make this a default method |
| public List<DirEntry> readDir(Handle handle) throws IOException { |
| return readDir(handle, null); |
| } |
| |
| @Override |
| public List<DirEntry> readDir(Handle handle, AtomicReference<Boolean> eolIndicator) throws IOException { |
| if (eolIndicator != null) { |
| eolIndicator.set(null); // assume unknown information |
| } |
| if (!isOpen()) { |
| throw new IOException("readDir(" + handle + ") client is closed"); |
| } |
| |
| byte[] id = ValidateUtils.checkNotNull(handle, "No handle").getIdentifier(); |
| Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* some extra fields */, false); |
| buffer.putBytes(id); |
| |
| int cmdId = send(SftpConstants.SSH_FXP_READDIR, buffer); |
| Buffer response = receive(cmdId); |
| return checkDirResponse(SftpConstants.SSH_FXP_READDIR, response, eolIndicator); |
| } |
| |
| protected List<DirEntry> checkDirResponse(int cmd, Buffer buffer, AtomicReference<Boolean> eolIndicator) throws IOException { |
| if (eolIndicator != null) { |
| eolIndicator.set(null); // assume unknown |
| } |
| |
| int length = buffer.getInt(); |
| int type = buffer.getUByte(); |
| int id = buffer.getInt(); |
| if (type == SftpConstants.SSH_FXP_NAME) { |
| int len = buffer.getInt(); |
| int version = getVersion(); |
| ClientChannel channel = getClientChannel(); |
| if (log.isDebugEnabled()) { |
| log.debug("checkDirResponse({}}[id={}] reading {} entries", channel, id, len); |
| } |
| |
| List<DirEntry> entries = new ArrayList<DirEntry>(len); |
| for (int i = 0; i < len; i++) { |
| String name = buffer.getString(); |
| String longName = (version == SftpConstants.SFTP_V3) ? buffer.getString() : null; |
| Attributes attrs = readAttributes(buffer); |
| if (log.isTraceEnabled()) { |
| log.trace("checkDirResponse({})[id={}][{}] ({})[{}]: {}", |
| channel, id, i, name, longName, attrs); |
| } |
| |
| entries.add(new DirEntry(name, longName, attrs)); |
| } |
| |
| Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version); |
| if (eolIndicator != null) { |
| eolIndicator.set(indicator); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("checkDirResponse({}}[id={}] read count={}, eol={}", channel, entries.size(), indicator); |
| } |
| return entries; |
| } |
| |
| if (type == SftpConstants.SSH_FXP_STATUS) { |
| int substatus = buffer.getInt(); |
| String msg = buffer.getString(); |
| String lang = buffer.getString(); |
| if (log.isTraceEnabled()) { |
| log.trace("checkDirResponse({})[id={}] - status: {} [{}] {}", |
| getClientChannel(), id, SftpConstants.getStatusName(substatus), lang, msg); |
| } |
| |
| if (substatus == SftpConstants.SSH_FX_EOF) { |
| return null; |
| } |
| |
| throwStatusException(cmd, id, substatus, msg, lang); |
| } |
| |
| return handleUnknownDirListingPacket(cmd, id, type, length, buffer); |
| } |
| |
| protected List<DirEntry> handleUnknownDirListingPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { |
| IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_NAME, id, type, length, buffer); |
| if (err != null) { |
| throw err; |
| } |
| return Collections.emptyList(); |
| } |
| |
| protected IOException handleUnexpectedPacket(int cmd, int expected, int id, int type, int length, Buffer buffer) throws IOException { |
| throw new SshException("Unexpected SFTP packet received while awaiting " + SftpConstants.getCommandMessageName(expected) |
| + " response to " + SftpConstants.getCommandMessageName(cmd) |
| + ": type=" + SftpConstants.getCommandMessageName(type) + ", id=" + id + ", length=" + length); |
| } |
| |
| @Override |
| public String canonicalPath(String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("canonicalPath(" + path + ") client is closed"); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false); |
| buffer.putString(path); |
| return checkOneName(SftpConstants.SSH_FXP_REALPATH, buffer); |
| } |
| |
| @Override |
| public Attributes stat(String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("stat(" + path + ") client is closed"); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false); |
| buffer.putString(path); |
| |
| int version = getVersion(); |
| if (version >= SftpConstants.SFTP_V4) { |
| buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL); |
| } |
| |
| return checkAttributes(SftpConstants.SSH_FXP_STAT, buffer); |
| } |
| |
| @Override |
| public Attributes lstat(String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("lstat(" + path + ") client is closed"); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false); |
| buffer.putString(path); |
| |
| int version = getVersion(); |
| if (version >= SftpConstants.SFTP_V4) { |
| buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL); |
| } |
| |
| return checkAttributes(SftpConstants.SSH_FXP_LSTAT, buffer); |
| } |
| |
| @Override |
| public Attributes stat(Handle handle) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("stat(" + handle + ") client is closed"); |
| } |
| |
| byte[] id = ValidateUtils.checkNotNull(handle, "No handle").getIdentifier(); |
| Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* a bit extra */, false); |
| buffer.putBytes(id); |
| |
| int version = getVersion(); |
| if (version >= SftpConstants.SFTP_V4) { |
| buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL); |
| } |
| |
| return checkAttributes(SftpConstants.SSH_FXP_FSTAT, buffer); |
| } |
| |
| @Override |
| public void setStat(String path, Attributes attributes) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("setStat({})[{}]: {}", getClientSession(), path, attributes); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(); |
| buffer.putString(path); |
| writeAttributes(buffer, attributes); |
| checkCommandStatus(SftpConstants.SSH_FXP_SETSTAT, buffer); |
| } |
| |
| @Override |
| public void setStat(Handle handle, Attributes attributes) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("setStat({})[{}]: {}", getClientSession(), handle, attributes); |
| } |
| byte[] id = ValidateUtils.checkNotNull(handle, "No handle").getIdentifier(); |
| Buffer buffer = new ByteArrayBuffer(id.length + (2 * Long.SIZE) /* some extras */, false); |
| buffer.putBytes(id); |
| writeAttributes(buffer, attributes); |
| checkCommandStatus(SftpConstants.SSH_FXP_FSETSTAT, buffer); |
| } |
| |
| @Override |
| public String readLink(String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("readLink(" + path + ") client is closed"); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); |
| buffer.putString(path); |
| return checkOneName(SftpConstants.SSH_FXP_READLINK, buffer); |
| } |
| |
| @Override |
| public void link(String linkPath, String targetPath, boolean symbolic) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("link(" + linkPath + " => " + targetPath + ")[symbolic=" + symbolic + "] client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("link({})[symbolic={}] {} => {}", getClientSession(), symbolic, linkPath, targetPath); |
| } |
| |
| Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */, false); |
| int version = getVersion(); |
| if (version < SftpConstants.SFTP_V6) { |
| if (!symbolic) { |
| throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version); |
| } |
| buffer.putString(targetPath); |
| buffer.putString(linkPath); |
| checkCommandStatus(SftpConstants.SSH_FXP_SYMLINK, buffer); |
| } else { |
| buffer.putString(targetPath); |
| buffer.putString(linkPath); |
| buffer.putBoolean(symbolic); |
| checkCommandStatus(SftpConstants.SSH_FXP_LINK, buffer); |
| } |
| } |
| |
| @Override |
| public void lock(Handle handle, long offset, long length, int mask) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("lock({})[{}] offset={}, length={}, mask=0x{}", |
| getClientSession(), handle, offset, length, Integer.toHexString(mask)); |
| } |
| |
| byte[] id = ValidateUtils.checkNotNull(handle, "No handle").getIdentifier(); |
| Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */, false); |
| buffer.putBytes(id); |
| buffer.putLong(offset); |
| buffer.putLong(length); |
| buffer.putInt(mask); |
| checkCommandStatus(SftpConstants.SSH_FXP_BLOCK, buffer); |
| } |
| |
| @Override |
| public void unlock(Handle handle, long offset, long length) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed"); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("unlock({})[{}] offset={}, length={}", getClientSession(), handle, offset, length); |
| } |
| |
| byte[] id = ValidateUtils.checkNotNull(handle, "No handle").getIdentifier(); |
| Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */, false); |
| buffer.putBytes(id); |
| buffer.putLong(offset); |
| buffer.putLong(length); |
| checkCommandStatus(SftpConstants.SSH_FXP_UNBLOCK, buffer); |
| } |
| |
| @Override |
| public Iterable<DirEntry> readDir(final String path) throws IOException { |
| if (!isOpen()) { |
| throw new IOException("readDir(" + path + ") client is closed"); |
| } |
| return new SftpIterableDirEntry(this, path); |
| } |
| |
| @Override |
| public InputStream read(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException { |
| if (bufferSize < MIN_READ_BUFFER_SIZE) { |
| throw new IllegalArgumentException("Insufficient read buffer size: " + bufferSize + ", min.=" + MIN_READ_BUFFER_SIZE); |
| } |
| |
| if (!isOpen()) { |
| throw new IOException("read(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed"); |
| } |
| |
| return new SftpInputStreamWithChannel(this, bufferSize, path, mode); |
| } |
| |
| @Override |
| public OutputStream write(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException { |
| if (bufferSize < MIN_WRITE_BUFFER_SIZE) { |
| throw new IllegalArgumentException("Insufficient write buffer size: " + bufferSize + ", min.=" + MIN_WRITE_BUFFER_SIZE); |
| } |
| |
| if (!isOpen()) { |
| throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed"); |
| } |
| |
| return new SftpOutputStreamWithChannel(this, bufferSize, path, mode); |
| } |
| } |