http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java deleted file mode 100644 index 0788b67..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * 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.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InterruptedIOException; -import java.io.OutputStream; -import java.io.StreamCorruptedException; -import java.net.SocketTimeoutException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import org.apache.sshd.client.channel.ChannelSubsystem; -import org.apache.sshd.client.channel.ClientChannel; -import org.apache.sshd.client.session.ClientSession; -import org.apache.sshd.common.PropertyResolverUtils; -import org.apache.sshd.common.SshException; -import org.apache.sshd.common.subsystem.sftp.SftpConstants; -import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils; -import org.apache.sshd.common.subsystem.sftp.extensions.VersionsParser.Versions; -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.BufferUtils; -import org.apache.sshd.common.util.buffer.ByteArrayBuffer; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public class DefaultSftpClient extends AbstractSftpClient { - private final ClientSession clientSession; - private final ChannelSubsystem channel; - private final Map<Integer, Buffer> messages = new HashMap<>(); - private final AtomicInteger cmdId = new AtomicInteger(100); - private final Buffer receiveBuffer = new ByteArrayBuffer(); - private final byte[] workBuf = new byte[Integer.BYTES]; - private final AtomicInteger versionHolder = new AtomicInteger(0); - private final AtomicBoolean closing = new AtomicBoolean(false); - private final NavigableMap<String, byte[]> extensions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - private final NavigableMap<String, byte[]> exposedExtensions = Collections.unmodifiableNavigableMap(extensions); - private Charset nameDecodingCharset = DEFAULT_NAME_DECODING_CHARSET; - - public DefaultSftpClient(ClientSession clientSession) throws IOException { - this.nameDecodingCharset = PropertyResolverUtils.getCharset(clientSession, NAME_DECODING_CHARSET, DEFAULT_NAME_DECODING_CHARSET); - this.clientSession = Objects.requireNonNull(clientSession, "No client session"); - this.channel = clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME); - this.channel.setOut(new OutputStream() { - private final byte[] singleByte = new byte[1]; - @Override - public void write(int b) throws IOException { - synchronized (singleByte) { - singleByte[0] = (byte) b; - write(singleByte); - } - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - data(b, off, len); - } - }); - this.channel.setErr(new ByteArrayOutputStream(Byte.MAX_VALUE)); - - long initializationTimeout = clientSession.getLongProperty(SFTP_CHANNEL_OPEN_TIMEOUT, DEFAULT_CHANNEL_OPEN_TIMEOUT); - this.channel.open().verify(initializationTimeout); - this.channel.onClose(() -> { - synchronized (messages) { - closing.set(true); - messages.notifyAll(); - } - - if (versionHolder.get() <= 0) { - log.warn("onClose({}) closed before version negotiated", channel); - } - }); - - try { - init(initializationTimeout); - } catch (IOException | RuntimeException e) { - this.channel.close(true); - throw e; - } - } - - @Override - public int getVersion() { - return versionHolder.get(); - } - - @Override - public ClientSession getClientSession() { - return clientSession; - } - - @Override - public ClientChannel getClientChannel() { - return channel; - } - - @Override - public NavigableMap<String, byte[]> getServerExtensions() { - return exposedExtensions; - } - - @Override - public Charset getNameDecodingCharset() { - return nameDecodingCharset; - } - - @Override - public void setNameDecodingCharset(Charset nameDecodingCharset) { - this.nameDecodingCharset = Objects.requireNonNull(nameDecodingCharset, "No charset provided"); - } - - @Override - public boolean isClosing() { - return closing.get(); - } - - @Override - public boolean isOpen() { - return this.channel.isOpen(); - } - - @Override - public void close() throws IOException { - if (isOpen()) { - this.channel.close(false); - } - } - - /** - * Receive binary data - * @param buf The buffer for the incoming data - * @param start Offset in buffer to place the data - * @param len Available space in buffer for the data - * @return Actual size of received data - * @throws IOException If failed to receive incoming data - */ - protected int data(byte[] buf, int start, int len) throws IOException { - Buffer incoming = new ByteArrayBuffer(buf, start, len); - // If we already have partial data, we need to append it to the buffer and use it - if (receiveBuffer.available() > 0) { - receiveBuffer.putBuffer(incoming); - incoming = receiveBuffer; - } - - // Process commands - int rpos = incoming.rpos(); - for (int count = 1; receive(incoming); count++) { - if (log.isTraceEnabled()) { - log.trace("data({}) Processed {} data messages", getClientChannel(), count); - } - } - - int read = incoming.rpos() - rpos; - // Compact and add remaining data - receiveBuffer.compact(); - if (receiveBuffer != incoming && incoming.available() > 0) { - receiveBuffer.putBuffer(incoming); - } - - return read; - } - - /** - * Read SFTP packets from buffer - * - * @param incoming The received {@link Buffer} - * @return {@code true} if data from incoming buffer was processed - * @throws IOException if failed to process the buffer - * @see #process(Buffer) - */ - protected boolean receive(Buffer incoming) throws IOException { - int rpos = incoming.rpos(); - int wpos = incoming.wpos(); - ClientSession session = getClientSession(); - session.resetIdleTimeout(); - - if ((wpos - rpos) > 4) { - int length = incoming.getInt(); - if (length < 5) { - throw new IOException("Illegal sftp packet length: " + length); - } - if ((wpos - rpos) >= (length + 4)) { - incoming.rpos(rpos); - incoming.wpos(rpos + 4 + length); - process(incoming); - incoming.rpos(rpos + 4 + length); - incoming.wpos(wpos); - return true; - } - } - incoming.rpos(rpos); - return false; - } - - /** - * Process an SFTP packet - * - * @param incoming The received {@link Buffer} - * @throws IOException if failed to process the buffer - */ - protected void process(Buffer incoming) throws IOException { - // create a copy of the buffer in case it is being re-used - Buffer buffer = new ByteArrayBuffer(incoming.available() + Long.SIZE, false); - buffer.putBuffer(incoming); - - int rpos = buffer.rpos(); - int length = buffer.getInt(); - int type = buffer.getUByte(); - Integer id = buffer.getInt(); - buffer.rpos(rpos); - - if (log.isTraceEnabled()) { - log.trace("process({}) id={}, type={}, len={}", - getClientChannel(), id, SftpConstants.getCommandMessageName(type), length); - } - - synchronized (messages) { - messages.put(id, buffer); - messages.notifyAll(); - } - } - - @Override - public int send(int cmd, Buffer buffer) throws IOException { - int id = cmdId.incrementAndGet(); - int len = buffer.available(); - if (log.isTraceEnabled()) { - log.trace("send({}) cmd={}, len={}, id={}", - getClientChannel(), SftpConstants.getCommandMessageName(cmd), len, id); - } - - OutputStream dos = channel.getInvertedIn(); - BufferUtils.writeInt(dos, 1 /* cmd */ + Integer.BYTES /* id */ + len, workBuf); - dos.write(cmd & 0xFF); - BufferUtils.writeInt(dos, id, workBuf); - dos.write(buffer.array(), buffer.rpos(), len); - dos.flush(); - return id; - } - - @Override - public Buffer receive(int id) throws IOException { - Integer reqId = id; - synchronized (messages) { - for (int count = 1;; count++) { - if (isClosing() || (!isOpen())) { - throw new SshException("Channel is being closed"); - } - - Buffer buffer = messages.remove(reqId); - if (buffer != null) { - return buffer; - } - - try { - messages.wait(); - } catch (InterruptedException e) { - throw (IOException) new InterruptedIOException("Interrupted while waiting for messages at iteration #" + count).initCause(e); - } - } - } - } - - protected Buffer read() throws IOException { - InputStream dis = channel.getInvertedOut(); - int length = BufferUtils.readInt(dis, workBuf); - // must have at least command + length - if (length < (1 + Integer.BYTES)) { - throw new IllegalArgumentException("Bad length: " + length); - } - - Buffer buffer = new ByteArrayBuffer(length + Integer.BYTES, false); - buffer.putInt(length); - int nb = length; - while (nb > 0) { - int readLen = dis.read(buffer.array(), buffer.wpos(), nb); - if (readLen < 0) { - throw new IllegalArgumentException("Premature EOF while read " + length + " bytes - remaining=" + nb); - } - buffer.wpos(buffer.wpos() + readLen); - nb -= readLen; - } - - return buffer; - } - - protected void init(long initializationTimeout) throws IOException { - ValidateUtils.checkTrue(initializationTimeout > 0L, "Invalid initialization timeout: %d", initializationTimeout); - - // Send init packet - OutputStream dos = channel.getInvertedIn(); - BufferUtils.writeInt(dos, 5 /* total length */, workBuf); - dos.write(SftpConstants.SSH_FXP_INIT); - BufferUtils.writeInt(dos, SftpConstants.SFTP_V6, workBuf); - dos.flush(); - - Buffer buffer; - Integer reqId; - synchronized (messages) { - /* - * We need to use a timeout since if the remote server does not support - * SFTP, we will not know it immediately. This is due to the fact that the - * request for the subsystem does not contain a reply as to its success or - * failure. Thus, the SFTP channel is created by the client, but there is - * no one on the other side to reply - thus the need for the timeout - */ - for (long remainingTimeout = initializationTimeout; (remainingTimeout > 0L) && messages.isEmpty() && (!isClosing()) && isOpen();) { - try { - long sleepStart = System.nanoTime(); - messages.wait(remainingTimeout); - long sleepEnd = System.nanoTime(); - long sleepDuration = sleepEnd - sleepStart; - long sleepMillis = TimeUnit.NANOSECONDS.toMillis(sleepDuration); - if (sleepMillis < 1L) { - remainingTimeout--; - } else { - remainingTimeout -= sleepMillis; - } - } catch (InterruptedException e) { - throw (IOException) new InterruptedIOException("Interrupted init()").initCause(e); - } - } - - if (isClosing() || (!isOpen())) { - throw new EOFException("Closing while await init message"); - } - - if (messages.isEmpty()) { - throw new SocketTimeoutException("No incoming initialization response received within " + initializationTimeout + " msec."); - } - - Collection<Integer> ids = messages.keySet(); - Iterator<Integer> iter = ids.iterator(); - reqId = iter.next(); - buffer = messages.remove(reqId); - } - - int length = buffer.getInt(); - int type = buffer.getUByte(); - int id = buffer.getInt(); - if (log.isTraceEnabled()) { - log.trace("init({}) id={} type={} len={}", - getClientChannel(), id, SftpConstants.getCommandMessageName(type), length); - } - - if (type == SftpConstants.SSH_FXP_VERSION) { - if (id < SftpConstants.SFTP_V3) { - throw new SshException("Unsupported sftp version " + id); - } - versionHolder.set(id); - - if (log.isTraceEnabled()) { - log.trace("init({}) version={}", getClientChannel(), versionHolder); - } - - while (buffer.available() > 0) { - String name = buffer.getString(); - byte[] data = buffer.getBytes(); - if (log.isTraceEnabled()) { - log.trace("init({}) added extension=", getClientChannel(), name); - } - extensions.put(name, data); - } - } else if (type == SftpConstants.SSH_FXP_STATUS) { - int substatus = buffer.getInt(); - String msg = buffer.getString(); - String lang = buffer.getString(); - if (log.isTraceEnabled()) { - log.trace("init({})[id={}] - status: {} [{}] {}", - getClientChannel(), id, SftpConstants.getStatusName(substatus), lang, msg); - } - - throwStatusException(SftpConstants.SSH_FXP_INIT, id, substatus, msg, lang); - } else { - handleUnexpectedPacket(SftpConstants.SSH_FXP_INIT, SftpConstants.SSH_FXP_VERSION, id, type, length, buffer); - } - } - - /** - * @param selector The {@link SftpVersionSelector} to use - ignored if {@code null} - * @return The selected version (may be same as current) - * @throws IOException If failed to negotiate - */ - public int negotiateVersion(SftpVersionSelector selector) throws IOException { - int current = getVersion(); - if (selector == null) { - return current; - } - - Set<Integer> available = GenericUtils.asSortedSet(Collections.singleton(current)); - Map<String, ?> parsed = getParsedServerExtensions(); - Collection<String> extensions = ParserUtils.supportedExtensions(parsed); - if ((GenericUtils.size(extensions) > 0) && extensions.contains(SftpConstants.EXT_VERSION_SELECT)) { - Versions vers = GenericUtils.isEmpty(parsed) ? null : (Versions) parsed.get(SftpConstants.EXT_VERSIONS); - Collection<String> reported = (vers == null) ? null : vers.getVersions(); - if (GenericUtils.size(reported) > 0) { - for (String v : reported) { - if (!available.add(Integer.valueOf(v))) { - continue; // debug breakpoint - } - } - } - } - - int selected = selector.selectVersion(getClientSession(), current, new ArrayList<>(available)); - if (log.isDebugEnabled()) { - log.debug("negotiateVersion({}) current={} {} -> {}", getClientChannel(), current, available, selected); - } - - if (selected == current) { - return current; - } - - if (!available.contains(selected)) { - throw new StreamCorruptedException("Selected version (" + selected + ") not part of available: " + available); - } - - String verVal = String.valueOf(selected); - Buffer buffer = new ByteArrayBuffer(Integer.BYTES + SftpConstants.EXT_VERSION_SELECT.length() // extension name - + Integer.BYTES + verVal.length() + Byte.SIZE, false); - buffer.putString(SftpConstants.EXT_VERSION_SELECT); - buffer.putString(verVal); - checkCommandStatus(SftpConstants.SSH_FXP_EXTENDED, buffer); - versionHolder.set(selected); - return selected; - } -}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java index f3ca4f3..7cada6e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java @@ -28,6 +28,8 @@ import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.UserPrincipal; import java.util.List; +import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView; + /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java index c835e35..c860cb4 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java @@ -496,7 +496,7 @@ public interface SftpClient extends SubsystemClient { private final String longFilename; private final Attributes attributes; - DirEntry(String filename, String longFilename, Attributes attributes) { + public DirEntry(String filename, String longFilename, Attributes attributes) { this.filename = filename; this.longFilename = longFilename; this.attributes = attributes; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java index 8e41729..a282b87 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientCreator.java @@ -61,15 +61,25 @@ public interface SftpClientCreator { */ SftpClient createSftpClient(SftpVersionSelector selector) throws IOException; - FileSystem createSftpFileSystem() throws IOException; + default FileSystem createSftpFileSystem() throws IOException { + return createSftpFileSystem(SftpVersionSelector.CURRENT); + } - FileSystem createSftpFileSystem(int version) throws IOException; + default FileSystem createSftpFileSystem(int version) throws IOException { + return createSftpFileSystem(SftpVersionSelector.fixedVersionSelector(version)); + } - FileSystem createSftpFileSystem(SftpVersionSelector selector) throws IOException; + default FileSystem createSftpFileSystem(SftpVersionSelector selector) throws IOException { + return createSftpFileSystem(selector, SftpClient.DEFAULT_READ_BUFFER_SIZE, SftpClient.DEFAULT_WRITE_BUFFER_SIZE); + } - FileSystem createSftpFileSystem(int readBufferSize, int writeBufferSize) throws IOException; + default FileSystem createSftpFileSystem(int version, int readBufferSize, int writeBufferSize) throws IOException { + return createSftpFileSystem(SftpVersionSelector.fixedVersionSelector(version), readBufferSize, writeBufferSize); + } - FileSystem createSftpFileSystem(int version, int readBufferSize, int writeBufferSize) throws IOException; + default FileSystem createSftpFileSystem(int readBufferSize, int writeBufferSize) throws IOException { + return createSftpFileSystem(SftpVersionSelector.CURRENT, readBufferSize, writeBufferSize); + } FileSystem createSftpFileSystem(SftpVersionSelector selector, int readBufferSize, int writeBufferSize) throws IOException; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java new file mode 100644 index 0000000..15e321f --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java @@ -0,0 +1,51 @@ +/* + * 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.nio.file.FileSystem; + +import org.apache.sshd.client.session.ClientSession; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SftpClientFactory { + /** + * @param session The {@link ClientSession} to which the SFTP client should be attached + * @param selector The {@link SftpVersionSelector} to use in order to negotiate the SFTP version + * @return The created {@link SftpClient} instance + * @throws IOException If failed to create the client + */ + SftpClient createSftpClient(ClientSession session, SftpVersionSelector selector) throws IOException; + + /** + * @param session The {@link ClientSession} to which the SFTP client backing the file system should be attached + * @param selector The {@link SftpVersionSelector} to use in order to negotiate the SFTP version + * @param readBufferSize Default I/O read buffer size + * @param writeBufferSize Default I/O write buffer size + * @return The created {@link FileSystem} instance + * @throws IOException If failed to create the instance + */ + FileSystem createSftpFileSystem( + ClientSession session, SftpVersionSelector selector, int readBufferSize, int writeBufferSize) + throws IOException; +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactoryManager.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactoryManager.java new file mode 100644 index 0000000..02bc5f6 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactoryManager.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SftpClientFactoryManager { + /** + * @return The (never {@code null}) {@link SftpClientFactory} instance + */ + SftpClientFactory getSftpClientFactory(); + + /** + * @param sftpClientFactory The {@link SftpClientFactory} instance to use - if {@code null} + * then an internal default will be used + */ + void setSftpClientFactory(SftpClientFactory sftpClientFactory); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java index f028a5b..1ca0283 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java @@ -42,6 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.sshd.client.channel.ClientChannel; import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.client.session.ClientSessionHolder; +import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpClient; import org.apache.sshd.common.file.util.BaseFileSystem; import org.apache.sshd.common.subsystem.sftp.SftpConstants; import org.apache.sshd.common.util.GenericUtils; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java index 5b2f49d..116282a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java @@ -831,7 +831,7 @@ public class SftpFileSystemProvider extends FileSystemProvider { return map; } - protected SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... options) throws IOException { + public SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... options) throws IOException { SftpFileSystem fs = path.getFileSystem(); try (SftpClient client = fs.getClient()) { try { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java index 13d2119..1fb614c 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java @@ -29,6 +29,7 @@ import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.UserPrincipal; import java.util.Set; +import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView; import org.apache.sshd.common.util.GenericUtils; /** http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpClient.java new file mode 100644 index 0000000..a1d2014 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpClient.java @@ -0,0 +1,1134 @@ +/* + * 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.impl; + +import java.io.IOException; +import java.nio.charset.Charset; +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.Objects; +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.RawSftpClient; +import org.apache.sshd.client.subsystem.sftp.SftpClient; +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:[email protected]">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 <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; + } + + /** + * @param cmd The command that was sent whose response contains the name to be decoded + * @param buf The {@link Buffer} containing the encoded name + * @return The decoded referenced name + */ + protected String getReferencedName(int cmd, Buffer buf) { + Charset cs = getNameDecodingCharset(); + return buf.getString(cs); + } + + /** + * 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 { + //noinspection ThrowableResultOfMethodCallIgnored + 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(cmd, 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 = getReferencedName(cmd, buffer); + String longName = null; + int version = getVersion(); + if (version == SftpConstants.SFTP_V3) { + longName = getReferencedName(cmd, buffer); + } + + Attributes attrs = readAttributes(cmd, 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(int cmd, 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 = getReferencedName(cmd, buffer); // 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 = Objects.requireNonNull(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_V5) { + 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 { + 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 = Objects.requireNonNull(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 + 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 = Objects.requireNonNull(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 = Objects.requireNonNull(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 + 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 = Objects.requireNonNull(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<>(len); + for (int i = 0; i < len; i++) { + String name = getReferencedName(cmd, buffer); + String longName = (version == SftpConstants.SFTP_V3) ? getReferencedName(cmd, buffer) : null; + Attributes attrs = readAttributes(cmd, 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 = Objects.requireNonNull(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 = Objects.requireNonNull(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 = Objects.requireNonNull(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 = Objects.requireNonNull(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); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java new file mode 100644 index 0000000..0fce423 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java @@ -0,0 +1,92 @@ +/* + * 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.impl; + +import java.io.IOException; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttributeView; +import java.util.Objects; + +import org.apache.sshd.client.subsystem.sftp.SftpClient; +import org.apache.sshd.client.subsystem.sftp.SftpFileSystem; +import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider; +import org.apache.sshd.client.subsystem.sftp.SftpPath; +import org.apache.sshd.common.subsystem.sftp.SftpConstants; +import org.apache.sshd.common.subsystem.sftp.SftpException; +import org.apache.sshd.common.util.logging.AbstractLoggingBean; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class AbstractSftpFileAttributeView extends AbstractLoggingBean implements FileAttributeView { + protected final SftpFileSystemProvider provider; + protected final Path path; + protected final LinkOption[] options; + + protected AbstractSftpFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) { + this.provider = Objects.requireNonNull(provider, "No file system provider instance"); + this.path = Objects.requireNonNull(path, "No path"); + this.options = options; + } + + @Override + public String name() { + return "view"; + } + + /** + * @return The underlying {@link SftpFileSystemProvider} used to + * provide the view functionality + */ + public final SftpFileSystemProvider provider() { + return provider; + } + + /** + * @return The referenced view {@link Path} + */ + public final Path getPath() { + return path; + } + + protected SftpClient.Attributes readRemoteAttributes() throws IOException { + return provider.readRemoteAttributes(provider.toSftpPath(path), options); + } + + protected void writeRemoteAttributes(SftpClient.Attributes attrs) throws IOException { + SftpPath p = provider.toSftpPath(path); + SftpFileSystem fs = p.getFileSystem(); + try (SftpClient client = fs.getClient()) { + try { + if (log.isDebugEnabled()) { + log.debug("writeRemoteAttributes({})[{}]: {}", fs, p, attrs); + } + client.setStat(p.toString(), attrs); + } catch (SftpException e) { + if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) { + throw new NoSuchFileException(p.toString()); + } + throw e; + } + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultCloseableHandle.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultCloseableHandle.java new file mode 100644 index 0000000..f6597f3 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultCloseableHandle.java @@ -0,0 +1,66 @@ +/* + * 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.impl; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.sshd.client.subsystem.sftp.SftpClient; +import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class DefaultCloseableHandle extends CloseableHandle { + private final AtomicBoolean open = new AtomicBoolean(true); + private final SftpClient client; + + public DefaultCloseableHandle(SftpClient client, String path, byte[] id) { + super(path, id); + this.client = ValidateUtils.checkNotNull(client, "No client for path=%s", path); + } + + public final SftpClient getSftpClient() { + return client; + } + + @Override + public boolean isOpen() { + return open.get(); + } + + @Override + public void close() throws IOException { + if (open.getAndSet(false)) { + client.close(this); + } + } + + @Override // to avoid Findbugs[EQ_DOESNT_OVERRIDE_EQUALS] + public int hashCode() { + return super.hashCode(); + } + + @Override // to avoid Findbugs[EQ_DOESNT_OVERRIDE_EQUALS] + public boolean equals(Object obj) { + return super.equals(obj); + } +}
