Repository: mina-sshd
Updated Branches:
  refs/heads/master 8c9fe09eb -> 1448b79ba


[SSHD-702] Add support for an SftpFileChannelOpener


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/1448b79b
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/1448b79b
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/1448b79b

Branch: refs/heads/master
Commit: 1448b79ba5848c8cb12bf87c8a1d6369b514fafa
Parents: 8c9fe09
Author: Lyor Goldstein <[email protected]>
Authored: Sun Oct 2 12:39:45 2016 +0300
Committer: Lyor Goldstein <[email protected]>
Committed: Sun Oct 2 12:39:45 2016 +0300

----------------------------------------------------------------------
 README.md                                       |  15 +++
 .../server/subsystem/sftp/DirectoryHandle.java  |  14 +-
 .../sshd/server/subsystem/sftp/FileHandle.java  |  40 +++---
 .../sshd/server/subsystem/sftp/Handle.java      |  14 +-
 .../subsystem/sftp/SftpFileSystemAccessor.java  | 133 +++++++++++++++++++
 .../sftp/SftpFileSystemAccessorManager.java     |  28 ++++
 .../server/subsystem/sftp/SftpSubsystem.java    |  33 +++--
 .../subsystem/sftp/SftpSubsystemFactory.java    |  37 ++++--
 .../sshd/client/subsystem/sftp/SftpTest.java    |  94 +++++++++++++
 .../client/subsystem/sftp/SftpVersionsTest.java |   4 +-
 .../SpaceAvailableExtensionImplTest.java        |   2 +-
 .../openssh/helpers/OpenSSHExtensionsTest.java  |   2 +-
 12 files changed, 369 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 7c1cdf4..290fc68 100644
--- a/README.md
+++ b/README.md
@@ -406,6 +406,7 @@ the version, and all we can do at the server is require a 
**specific** version v
 configuration key. For more advanced restrictions on needs to sub-class 
`SftpSubSystem` and provide a non-default
 `SftpSubsystemFactory` that uses the sub-classed code.
 
+
 ### Using `SftpFileSystemProvider` to create an `SftpFileSystem`
 
 
@@ -495,6 +496,20 @@ configuration keys and values.
 
 ```
 
+#### Tracking accessed location via `SftpFileSystemAccessor`
+
+One can override the default `SftpFileSystemAccessor` and thus be able to 
track all opened files and folders
+throughout the SFTP server subsystem code. The accessor is 
registered/overwritten in via the `SftpSubSystemFactory`:
+
+```java
+
+    SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder()
+        .withFileSystemAccessor(new MySftpFileSystemAccessor())
+        .build();
+    server.setSubsystemFactories(Collections.singletonList(factory));
+
+```
+
 
 ### Supported SFTP extensions
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
index 0319f6a..82e691e 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
@@ -20,10 +20,11 @@ package org.apache.sshd.server.subsystem.sftp;
 
 import java.io.IOException;
 import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Iterator;
 
+import org.apache.sshd.server.session.ServerSession;
+
 /**
  * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
  */
@@ -36,11 +37,14 @@ public class DirectoryHandle extends Handle implements 
Iterator<Path> {
     private DirectoryStream<Path> ds;
     private Iterator<Path> fileList;
 
-    public DirectoryHandle(Path file) throws IOException {
-        super(file);
-        ds = Files.newDirectoryStream(file);
+    public DirectoryHandle(SftpSubsystem subsystem, Path dir, String handle) 
throws IOException {
+        super(dir, handle);
+
+        SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
+        ServerSession session = subsystem.getServerSession();
+        ds = accessor.openDirectory(session, subsystem, dir, handle);
 
-        Path parent = file.getParent();
+        Path parent = dir.getParent();
         if (parent == null) {
             sendDotDot = false;  // if no parent then no need to send ".."
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
index 075665a..fe3aa1e 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
@@ -20,8 +20,8 @@ package org.apache.sshd.server.subsystem.sftp;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
+import java.nio.channels.SeekableByteChannel;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.FileAttribute;
@@ -32,12 +32,14 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.subsystem.sftp.SftpException;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.session.ServerSession;
 
 
 /**
@@ -46,11 +48,13 @@ import org.apache.sshd.common.util.io.IoUtils;
 public class FileHandle extends Handle {
 
     private final int access;
-    private final FileChannel fileChannel;
+    private final SeekableByteChannel fileChannel;
     private final List<FileLock> locks = new ArrayList<>();
+    private final SftpSubsystem subsystem;
 
-    public FileHandle(SftpSubsystem sftpSubsystem, Path file, int flags, int 
access, Map<String, Object> attrs) throws IOException {
-        super(file);
+    public FileHandle(SftpSubsystem subsystem, Path file, String handle, int 
flags, int access, Map<String, Object> attrs) throws IOException {
+        super(file, handle);
+        this.subsystem = Objects.requireNonNull(subsystem, "No subsystem 
instance provided");
         this.access = access;
 
         Set<StandardOpenOption> options = 
EnumSet.noneOf(StandardOpenOption.class);
@@ -99,17 +103,19 @@ public class FileHandle extends Handle {
         FileAttribute<?>[] fileAttrs = GenericUtils.isEmpty(attributes)
                 ? IoUtils.EMPTY_FILE_ATTRIBUTES
                 : attributes.toArray(new FileAttribute<?>[attributes.size()]);
-        FileChannel channel;
+        SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
+        ServerSession session = subsystem.getServerSession();
+        SeekableByteChannel channel;
         try {
-            channel = FileChannel.open(file, options, fileAttrs);
+            channel = accessor.openFile(session, subsystem, file, handle, 
options, fileAttrs);
         } catch (UnsupportedOperationException e) {
-            channel = FileChannel.open(file, options);
-            sftpSubsystem.doSetAttributes(file, attrs);
+            channel = accessor.openFile(session, subsystem, file, handle, 
options, IoUtils.EMPTY_FILE_ATTRIBUTES);
+            subsystem.doSetAttributes(file, attrs);
         }
         this.fileChannel = channel;
     }
 
-    public final FileChannel getFileChannel() {
+    public final SeekableByteChannel getFileChannel() {
         return fileChannel;
     }
 
@@ -126,7 +132,7 @@ public class FileHandle extends Handle {
     }
 
     public int read(byte[] data, int doff, int length, long offset) throws 
IOException {
-        FileChannel channel = getFileChannel();
+        SeekableByteChannel channel = getFileChannel();
         channel.position(offset);
 
         long size = channel.size();
@@ -149,7 +155,7 @@ public class FileHandle extends Handle {
     }
 
     public void append(byte[] data, int doff, int length) throws IOException {
-        FileChannel channel = getFileChannel();
+        SeekableByteChannel channel = getFileChannel();
         write(data, doff, length, channel.size());
     }
 
@@ -158,7 +164,7 @@ public class FileHandle extends Handle {
     }
 
     public void write(byte[] data, int doff, int length, long offset) throws 
IOException {
-        FileChannel channel = getFileChannel();
+        SeekableByteChannel channel = getFileChannel();
         channel.position(offset);
         channel.write(ByteBuffer.wrap(data, doff, length));
     }
@@ -167,16 +173,18 @@ public class FileHandle extends Handle {
     public void close() throws IOException {
         super.close();
 
-        FileChannel channel = getFileChannel();
+        SeekableByteChannel channel = getFileChannel();
         if (channel.isOpen()) {
             channel.close();
         }
     }
 
     public void lock(long offset, long length, int mask) throws IOException {
-        FileChannel channel = getFileChannel();
+        SeekableByteChannel channel = getFileChannel();
         long size = (length == 0L) ? channel.size() - offset : length;
-        FileLock lock = channel.tryLock(offset, size, false);
+        SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
+        ServerSession session = subsystem.getServerSession();
+        FileLock lock = accessor.tryLock(session, subsystem, getFile(), 
getFileHandle(), channel, offset, size, false);
         if (lock == null) {
             throw new 
SftpException(SftpConstants.SSH_FX_BYTE_RANGE_LOCK_REFUSED,
                 "Overlapping lock held by another program on range [" + offset 
+ "-" + (offset + length));
@@ -188,7 +196,7 @@ public class FileHandle extends Handle {
     }
 
     public void unlock(long offset, long length) throws IOException {
-        FileChannel channel = getFileChannel();
+        SeekableByteChannel channel = getFileChannel();
         long size = (length == 0L) ? channel.size() - offset : length;
         FileLock lock = null;
         for (Iterator<FileLock> iterator = locks.iterator(); 
iterator.hasNext();) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
index 4b9547d..ad166a9 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
@@ -23,21 +23,29 @@ import java.nio.file.Path;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.sshd.common.util.ValidateUtils;
+
 /**
  * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
  */
 public abstract class Handle implements java.nio.channels.Channel {
     private final AtomicBoolean closed = new AtomicBoolean(false);
-    private Path file;
+    private final Path file;
+    private final String handle;
 
-    protected Handle(Path file) {
-        this.file = file;
+    protected Handle(Path file, String handle) {
+        this.file = Objects.requireNonNull(file, "No local file path");
+        this.handle = ValidateUtils.checkNotNullAndNotEmpty(handle, "No 
assigned handle for %s", file);
     }
 
     public Path getFile() {
         return file;
     }
 
+    public String getFileHandle() {
+        return handle;
+    }
+
     @Override
     public boolean isOpen() {
         return !closed.get();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
new file mode 100644
index 0000000..8d4499e
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
@@ -0,0 +1,133 @@
+/*
+ * 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.server.subsystem.sftp;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.nio.channels.Channel;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileAttribute;
+import java.util.Set;
+
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public interface SftpFileSystemAccessor {
+    SftpFileSystemAccessor DEFAULT = new SftpFileSystemAccessor() {
+        @Override
+        public String toString() {
+            return SftpFileSystemAccessor.class.getSimpleName() + "[DEFAULT]";
+        }
+    };
+
+    /**
+     * Called whenever a new file is opened
+     *
+     * @param session The {@link ServerSession} through which the request was 
received
+     * @param subsystem The SFTP subsystem instance that manages the session
+     * @param file The requested <U>local</U> file {@link Path}
+     * @param handle The assigned file handle through which the remote peer 
references this file.
+     * May be {@code null}/empty if the request is due to some internal 
functionality
+     * instead of due to peer requesting a handle to a file.
+     * @param options The requested {@link OpenOption}s
+     * @param attrs The requested {@link FileAttribute}s
+     * @return The opened {@link SeekableByteChannel}
+     * @throws IOException If failed to open
+     */
+    default SeekableByteChannel openFile(
+            ServerSession session, SftpEventListenerManager subsystem,
+            Path file, String handle, Set<? extends OpenOption> options, 
FileAttribute<?>... attrs)
+                    throws IOException {
+        return FileChannel.open(file, options, attrs);
+    }
+
+    /**
+     * Called when locking a section of a file is requested
+     *
+     * @param session The {@link ServerSession} through which the request was 
received
+     * @param subsystem The SFTP subsystem instance that manages the session
+     * @param file The requested <U>local</U> file {@link Path}
+     * @param handle The assigned file handle through which the remote peer 
references this file
+     * @param channel The original {@link Channel} that was returned by {@link 
#openFile(ServerSession, SftpEventListenerManager, Path, String, Set, 
FileAttribute...)}
+     * @param position The position at which the locked region is to start - 
must be non-negative
+     * @param size The size of the locked region; must be non-negative, and 
the sum
+     * <tt>position</tt>&nbsp;+&nbsp;<tt>size</tt> must be non-negative
+     * @param shared {@code true} to request a shared lock, {@code false} to 
request an exclusive lock
+     * @return A lock object representing the newly-acquired lock, or {@code 
null}
+     * if the lock could not be acquired because another program holds an 
overlapping lock
+     * @throws IOException If failed to honor the request
+     * @see FileChannel#tryLock(long, long, boolean)
+     */
+    default FileLock tryLock(ServerSession session, SftpEventListenerManager 
subsystem,
+            Path file, String handle, Channel channel, long position, long 
size, boolean shared)
+                    throws IOException {
+        if (!(channel instanceof FileChannel)) {
+            throw new StreamCorruptedException("Non file channel to lock: " + 
channel);
+        }
+
+        return ((FileChannel) channel).lock(position, size, shared);
+    }
+
+    /**
+     * Called when file meta-data re-synchronization is required
+     *
+     * @param session The {@link ServerSession} through which the request was 
received
+     * @param subsystem The SFTP subsystem instance that manages the session
+     * @param file The requested <U>local</U> file {@link Path}
+     * @param handle The assigned file handle through which the remote peer 
references this file
+     * @param channel The original {@link Channel} that was returned by {@link 
#openFile(ServerSession, SftpEventListenerManager, Path, String, Set, 
FileAttribute...)}
+     * @throws IOException If failed to execute the request
+     * @see FileChannel#force(boolean)
+     * @see <A 
HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL";>OpenSSH 
-  section 10</A>
+     */
+    default void syncFileData(ServerSession session, SftpEventListenerManager 
subsystem,
+            Path file, String handle, Channel channel)
+                throws IOException {
+        if (!(channel instanceof FileChannel)) {
+            throw new StreamCorruptedException("Non file channel to sync: " + 
channel);
+        }
+
+        ((FileChannel) channel).force(true);
+    }
+
+    /**
+     * Called when a new directory stream is requested
+     *
+     * @param session The {@link ServerSession} through which the request was 
received
+     * @param subsystem The SFTP subsystem instance that manages the session
+     * @param dir The requested <U>local</U> directory
+     * @param handle The assigned directory handle through which the remote 
peer references this directory
+     * @return The opened {@link DirectoryStream}
+     * @throws IOException If failed to open
+     */
+    default DirectoryStream<Path> openDirectory(
+            ServerSession session, SftpEventListenerManager subsystem, Path 
dir, String handle)
+                    throws IOException {
+        return Files.newDirectoryStream(dir);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
new file mode 100644
index 0000000..9619794
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.subsystem.sftp;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public interface SftpFileSystemAccessorManager {
+    SftpFileSystemAccessor getFileSystemAccessor();
+    void setFileSystemAccessor(SftpFileSystemAccessor accessor);
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index 9377172..dc1de62 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -26,7 +26,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.StreamCorruptedException;
 import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.AccessDeniedException;
 import java.nio.file.CopyOption;
@@ -57,6 +57,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -280,6 +281,7 @@ public class SftpSubsystem
     private final Collection<SftpEventListener> sftpEventListeners =
             EventListenerUtils.synchronizedListenersSet();
     private final SftpEventListener sftpEventListenerProxy;
+    private final SftpFileSystemAccessor fileSystemAccessor;
 
     /**
      * @param executorService The {@link ExecutorService} to be used by
@@ -290,9 +292,10 @@ public class SftpSubsystem
      *                        service, which will be shutdown regardless
      * @param policy          The {@link UnsupportedAttributePolicy} to use if 
failed to access
      *                        some local file attributes
+     * @param accessor        The {@link SftpFileSystemAccessor} to use for 
opening files and directories
      * @see ThreadUtils#newSingleThreadExecutor(String)
      */
-    public SftpSubsystem(ExecutorService executorService, boolean 
shutdownOnExit, UnsupportedAttributePolicy policy) {
+    public SftpSubsystem(ExecutorService executorService, boolean 
shutdownOnExit, UnsupportedAttributePolicy policy, SftpFileSystemAccessor 
accessor) {
         if (executorService == null) {
             executors = 
ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
             shutdownExecutor = true;    // we always close the ad-hoc executor 
service
@@ -301,7 +304,8 @@ public class SftpSubsystem
             shutdownExecutor = shutdownOnExit;
         }
 
-        unsupportedAttributePolicy = ValidateUtils.checkNotNull(policy, "No 
policy provided");
+        unsupportedAttributePolicy = Objects.requireNonNull(policy, "No policy 
provided");
+        fileSystemAccessor = Objects.requireNonNull(accessor, "No accessor");
         sftpEventListenerProxy = 
EventListenerUtils.proxyWrapper(SftpEventListener.class, 
getClass().getClassLoader(), sftpEventListeners);
     }
 
@@ -313,6 +317,10 @@ public class SftpSubsystem
         return unsupportedAttributePolicy;
     }
 
+    public final SftpFileSystemAccessor getFileSystemAccessor() {
+        return fileSystemAccessor;
+    }
+
     @Override
     public SftpEventListener getSftpEventListenerProxy() {
         return sftpEventListenerProxy;
@@ -689,8 +697,9 @@ public class SftpSubsystem
         }
 
         FileHandle fileHandle = validateHandle(handle, h, FileHandle.class);
-        FileChannel channel = fileHandle.getFileChannel();
-        channel.force(false);
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        ServerSession session = getServerSession();
+        accessor.syncFileData(session, this, fileHandle.getFile(), 
fileHandle.getFileHandle(), fileHandle.getFileChannel());
     }
 
     protected void doCheckFileHash(Buffer buffer, int id, String targetType) 
throws IOException {
@@ -794,7 +803,8 @@ public class SftpSubsystem
                 ? new byte[Math.min((int) effectiveLength, 
IoUtils.DEFAULT_COPY_SIZE)]
                 : new byte[Math.min((int) effectiveLength, blockSize)];
         ByteBuffer wb = ByteBuffer.wrap(digestBuf);
-        try (FileChannel channel = FileChannel.open(file, 
IoUtils.EMPTY_OPEN_OPTIONS)) {
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        try (SeekableByteChannel channel = 
accessor.openFile(getServerSession(), this, file, "", Collections.emptySet())) {
             channel.position(startOffset);
 
             Digest digest = factory.create();
@@ -948,8 +958,8 @@ public class SftpSubsystem
         ByteBuffer wb = ByteBuffer.wrap(digestBuf);
         boolean hashMatches = false;
         byte[] hashValue = null;
-
-        try (FileChannel channel = FileChannel.open(path, 
StandardOpenOption.READ)) {
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        try (SeekableByteChannel channel = 
accessor.openFile(getServerSession(), this, path, null, 
EnumSet.of(StandardOpenOption.READ))) {
             channel.position(startOffset);
 
             /*
@@ -1878,7 +1888,7 @@ public class SftpSubsystem
             throw new AccessDeniedException("Not readable: " + p);
         } else {
             String handle = generateFileHandle(p);
-            DirectoryHandle dirHandle = new DirectoryHandle(p);
+            DirectoryHandle dirHandle = new DirectoryHandle(this, p, handle);
             SftpEventListener listener = getSftpEventListenerProxy();
             listener.open(getServerSession(), handle, dirHandle);
             handles.put(handle, dirHandle);
@@ -2205,7 +2215,7 @@ public class SftpSubsystem
 
         Path file = resolveFile(path);
         String handle = generateFileHandle(file);
-        FileHandle fileHandle = new FileHandle(this, file, pflags, access, 
attrs);
+        FileHandle fileHandle = new FileHandle(this, file, handle, pflags, 
access, attrs);
         SftpEventListener listener = getSftpEventListenerProxy();
         listener.open(getServerSession(), handle, fileHandle);
         handles.put(handle, fileHandle);
@@ -2985,7 +2995,8 @@ public class SftpSubsystem
             switch (attribute) {
                 case "size": {
                     long newSize = ((Number) value).longValue();
-                    try (FileChannel channel = FileChannel.open(file, 
StandardOpenOption.WRITE)) {
+                    SftpFileSystemAccessor accessor = getFileSystemAccessor();
+                    try (SeekableByteChannel channel = 
accessor.openFile(getServerSession(), this, file, null, 
EnumSet.of(StandardOpenOption.WRITE))) {
                         channel.truncate(newSize);
                     }
                     continue;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
index 3fe2721..f5bd228 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
@@ -19,12 +19,12 @@
 
 package org.apache.sshd.server.subsystem.sftp;
 
+import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ObjectBuilder;
-import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.subsystem.SubsystemFactory;
@@ -32,7 +32,9 @@ import org.apache.sshd.server.subsystem.SubsystemFactory;
 /**
  * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
  */
-public class SftpSubsystemFactory extends AbstractSftpEventListenerManager 
implements SubsystemFactory, ExecutorServiceConfigurer, 
SftpEventListenerManager {
+public class SftpSubsystemFactory
+    extends AbstractSftpEventListenerManager
+    implements SubsystemFactory, ExecutorServiceConfigurer, 
SftpEventListenerManager, SftpFileSystemAccessorManager {
     public static final String NAME = SftpConstants.SFTP_SUBSYSTEM_NAME;
     public static final UnsupportedAttributePolicy DEFAULT_POLICY = 
UnsupportedAttributePolicy.Warn;
 
@@ -40,6 +42,7 @@ public class SftpSubsystemFactory extends 
AbstractSftpEventListenerManager imple
         private ExecutorService executors;
         private boolean shutdownExecutor;
         private UnsupportedAttributePolicy policy = DEFAULT_POLICY;
+        private SftpFileSystemAccessor fileSystemAccessor = 
SftpFileSystemAccessor.DEFAULT;
 
         public Builder() {
             super();
@@ -56,7 +59,12 @@ public class SftpSubsystemFactory extends 
AbstractSftpEventListenerManager imple
         }
 
         public Builder 
withUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
-            policy = ValidateUtils.checkNotNull(p, "No policy");
+            policy = Objects.requireNonNull(p, "No policy");
+            return this;
+        }
+
+        public Builder withFileSystemAccessor(SftpFileSystemAccessor accessor) 
{
+            fileSystemAccessor = Objects.requireNonNull(accessor, "No 
accessor");
             return this;
         }
 
@@ -66,6 +74,7 @@ public class SftpSubsystemFactory extends 
AbstractSftpEventListenerManager imple
             factory.setExecutorService(executors);
             factory.setShutdownOnExit(shutdownExecutor);
             factory.setUnsupportedAttributePolicy(policy);
+            factory.setFileSystemAccessor(fileSystemAccessor);
             GenericUtils.forEach(getRegisteredListeners(), 
factory::addSftpEventListener);
             return factory;
         }
@@ -74,6 +83,7 @@ public class SftpSubsystemFactory extends 
AbstractSftpEventListenerManager imple
     private ExecutorService executors;
     private boolean shutdownExecutor;
     private UnsupportedAttributePolicy policy = DEFAULT_POLICY;
+    private SftpFileSystemAccessor fileSystemAccessor = 
SftpFileSystemAccessor.DEFAULT;
 
     public SftpSubsystemFactory() {
         super();
@@ -91,7 +101,7 @@ public class SftpSubsystemFactory extends 
AbstractSftpEventListenerManager imple
 
     /**
      * @param service The {@link ExecutorService} to be used by the {@link 
SftpSubsystem}
-     *                command when starting execution. If {@code null} then a 
single-threaded ad-hoc service is used.
+     * command when starting execution. If {@code null} then a single-threaded 
ad-hoc service is used.
      */
     @Override
     public void setExecutorService(ExecutorService service) {
@@ -105,7 +115,7 @@ public class SftpSubsystemFactory extends 
AbstractSftpEventListenerManager imple
 
     /**
      * @param shutdownOnExit If {@code true} the {@link 
ExecutorService#shutdownNow()}
-     *                       will be called when subsystem terminates - unless 
it is the ad-hoc service, which
+     * will be called when subsystem terminates - unless it is the ad-hoc 
service, which
      *                       will be shutdown regardless
      */
     @Override
@@ -119,15 +129,26 @@ public class SftpSubsystemFactory extends 
AbstractSftpEventListenerManager imple
 
     /**
      * @param p The {@link UnsupportedAttributePolicy} to use if failed to 
access
-     *          some local file attributes - never {@code null}
+     * some local file attributes - never {@code null}
      */
     public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
-        policy = ValidateUtils.checkNotNull(p, "No policy");
+        policy = Objects.requireNonNull(p, "No policy");
+    }
+
+    @Override
+    public SftpFileSystemAccessor getFileSystemAccessor() {
+        return fileSystemAccessor;
+    }
+
+    @Override
+    public void setFileSystemAccessor(SftpFileSystemAccessor accessor) {
+        fileSystemAccessor = Objects.requireNonNull(accessor, "No accessor");
     }
 
     @Override
     public Command create() {
-        SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), 
isShutdownOnExit(), getUnsupportedAttributePolicy());
+        SftpSubsystem subsystem =
+                new SftpSubsystem(getExecutorService(), isShutdownOnExit(), 
getUnsupportedAttributePolicy(), getFileSystemAccessor());
         GenericUtils.forEach(getRegisteredListeners(), 
subsystem::addSftpEventListener);
         return subsystem;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index d2633d3..007206a 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -29,14 +29,18 @@ import java.net.SocketTimeoutException;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
 import java.nio.file.FileSystem;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileAttribute;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -45,6 +49,7 @@ import java.util.EnumSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.Vector;
@@ -58,7 +63,9 @@ import com.jcraft.jsch.JSch;
 
 import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.OpenMode;
 import 
org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions;
 import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
@@ -92,6 +99,8 @@ import org.apache.sshd.server.subsystem.sftp.DirectoryHandle;
 import org.apache.sshd.server.subsystem.sftp.FileHandle;
 import org.apache.sshd.server.subsystem.sftp.Handle;
 import org.apache.sshd.server.subsystem.sftp.SftpEventListener;
+import org.apache.sshd.server.subsystem.sftp.SftpEventListenerManager;
+import org.apache.sshd.server.subsystem.sftp.SftpFileSystemAccessor;
 import org.apache.sshd.server.subsystem.sftp.SftpSubsystem;
 import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
 import org.apache.sshd.util.test.SimpleUserInfo;
@@ -516,6 +525,91 @@ public class SftpTest extends 
AbstractSftpClientTestSupport {
     }
 
     @Test
+    public void testSftpFileSystemAccessor() throws Exception {
+        List<NamedFactory<Command>> factories = sshd.getSubsystemFactories();
+        assertEquals("Mismatched subsystem factories count", 1, 
GenericUtils.size(factories));
+
+        NamedFactory<Command> f = factories.get(0);
+        assertObjectInstanceOf("Not an SFTP subsystem factory", 
SftpSubsystemFactory.class, f);
+
+        SftpSubsystemFactory factory = (SftpSubsystemFactory) f;
+        SftpFileSystemAccessor accessor = factory.getFileSystemAccessor();
+        try {
+            AtomicReference<Path> fileHolder = new AtomicReference<>();
+            AtomicReference<Path> dirHolder = new AtomicReference<>();
+            factory.setFileSystemAccessor(new SftpFileSystemAccessor() {
+                @Override
+                public SeekableByteChannel openFile(ServerSession session, 
SftpEventListenerManager subsystem, Path file,
+                        String handle, Set<? extends OpenOption> options, 
FileAttribute<?>... attrs)
+                                throws IOException {
+                    fileHolder.set(file);
+                    return SftpFileSystemAccessor.super.openFile(session, 
subsystem, file, handle, options, attrs);
+                }
+
+                @Override
+                public DirectoryStream<Path> openDirectory(
+                        ServerSession session, SftpEventListenerManager 
subsystem, Path dir, String handle) throws IOException {
+                    dirHolder.set(dir);
+                    return SftpFileSystemAccessor.super.openDirectory(session, 
subsystem, dir, handle);
+                }
+
+                @Override
+                public String toString() {
+                    return SftpFileSystemAccessor.class.getSimpleName() + "[" 
+ getCurrentTestName() + "]";
+                }
+            });
+
+            Path targetPath = detectTargetFolder();
+            Path parentPath = targetPath.getParent();
+            Path localFile = Utils.resolve(targetPath, 
SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), 
getCurrentTestName());
+            Files.createDirectories(localFile.getParent());
+            byte[] expected = (getClass().getName() + "#" + 
getCurrentTestName() + "[" + localFile + "]").getBytes(StandardCharsets.UTF_8);
+            Files.write(localFile, expected, StandardOpenOption.CREATE);
+            try (ClientSession session = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                try (SftpClient sftp = session.createSftpClient()) {
+                    byte[] actual = new byte[expected.length];
+                    try (InputStream stream = 
sftp.read(Utils.resolveRelativeRemotePath(parentPath, localFile), 
OpenMode.Read)) {
+                        IoUtils.readFully(stream, actual);
+                    }
+
+                    Path remoteFile = fileHolder.getAndSet(null);
+                    assertNotNull("No remote file holder value", remoteFile);
+                    assertEquals("Mismatched opened local files", 
localFile.toFile(), remoteFile.toFile());
+                    assertArrayEquals("Mismatched retrieved file contents", 
expected, actual);
+
+                    Path localParent = localFile.getParent();
+                    String localName = 
Objects.toString(localFile.getFileName(), null);
+                    try (CloseableHandle handle = 
sftp.openDir(Utils.resolveRelativeRemotePath(parentPath, localParent))) {
+                        List<DirEntry> entries = sftp.readDir(handle);
+                        Path remoteParent = dirHolder.getAndSet(null);
+                        assertNotNull("No remote folder holder value", 
remoteParent);
+                        assertEquals("Mismatched opened folder", 
localParent.toFile(), remoteParent.toFile());
+                        assertFalse("No dir entries", 
GenericUtils.isEmpty(entries));
+
+                        for (DirEntry de : entries) {
+                            Attributes attrs = de.getAttributes();
+                            if (!attrs.isRegularFile()) {
+                                continue;
+                            }
+
+                            if (localName.equals(de.getFilename())) {
+                                return;
+                            }
+                        }
+
+                        fail("Cannot find listing of " + localName);
+                    }
+                }
+            }
+        } finally {
+            factory.setFileSystemAccessor(accessor);    // restore original
+        }
+    }
+
+    @Test
     @SuppressWarnings({"checkstyle:anoninnerlength", 
"checkstyle:methodlength"})
     public void testClient() throws Exception {
         List<NamedFactory<Command>> factories = sshd.getSubsystemFactories();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
index 58d2eed..ce911c1 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
@@ -196,7 +196,7 @@ public class SftpVersionsTest extends 
AbstractSftpClientTestSupport {
         SftpSubsystemFactory factory = new SftpSubsystemFactory() {
             @Override
             public Command create() {
-                SftpSubsystem subsystem = new 
SftpSubsystem(getExecutorService(), isShutdownOnExit(), 
getUnsupportedAttributePolicy()) {
+                SftpSubsystem subsystem = new 
SftpSubsystem(getExecutorService(), isShutdownOnExit(), 
getUnsupportedAttributePolicy(), getFileSystemAccessor()) {
                     @Override
                     protected Map<String, Object> resolveFileAttributes(Path 
file, int flags, LinkOption... options) throws IOException {
                         Map<String, Object> attrs = 
super.resolveFileAttributes(file, flags, options);
@@ -314,7 +314,7 @@ public class SftpVersionsTest extends 
AbstractSftpClientTestSupport {
         SftpSubsystemFactory factory = new SftpSubsystemFactory() {
             @Override
             public Command create() {
-                SftpSubsystem subsystem = new 
SftpSubsystem(getExecutorService(), isShutdownOnExit(), 
getUnsupportedAttributePolicy()) {
+                SftpSubsystem subsystem = new 
SftpSubsystem(getExecutorService(), isShutdownOnExit(), 
getUnsupportedAttributePolicy(), getFileSystemAccessor()) {
                     @Override
                     protected Map<String, Object> resolveFileAttributes(Path 
file, int flags, LinkOption... options) throws IOException {
                         Map<String, Object> attrs = 
super.resolveFileAttributes(file, flags, options);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java
index 9fb6775..3470adb 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java
@@ -71,7 +71,7 @@ public class SpaceAvailableExtensionImplTest extends 
AbstractSftpClientTestSuppo
         sshd.setSubsystemFactories(Collections.singletonList(new 
SftpSubsystemFactory() {
             @Override
             public Command create() {
-                return new SftpSubsystem(getExecutorService(), 
isShutdownOnExit(), getUnsupportedAttributePolicy()) {
+                return new SftpSubsystem(getExecutorService(), 
isShutdownOnExit(), getUnsupportedAttributePolicy(), getFileSystemAccessor()) {
                     @Override
                     protected SpaceAvailableExtensionInfo doSpaceAvailable(int 
id, String path) throws IOException {
                         if (!queryPath.equals(path)) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java
index 8557375..8799b14 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java
@@ -124,7 +124,7 @@ public class OpenSSHExtensionsTest extends 
AbstractSftpClientTestSupport {
         sshd.setSubsystemFactories(Collections.singletonList(new 
SftpSubsystemFactory() {
             @Override
             public Command create() {
-                return new SftpSubsystem(getExecutorService(), 
isShutdownOnExit(), getUnsupportedAttributePolicy()) {
+                return new SftpSubsystem(getExecutorService(), 
isShutdownOnExit(), getUnsupportedAttributePolicy(), getFileSystemAccessor()) {
                     @Override
                     protected List<OpenSSHExtension> 
resolveOpenSSHExtensions(ServerSession session) {
                         List<OpenSSHExtension> original = 
super.resolveOpenSSHExtensions(session);

Reply via email to