You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2018/04/16 11:47:54 UTC

[07/30] mina-sshd git commit: [SSHD-815] Extract SFTP in its own module

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultGroupPrincipal.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultGroupPrincipal.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultGroupPrincipal.java
new file mode 100644
index 0000000..acf3118
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultGroupPrincipal.java
@@ -0,0 +1,32 @@
+/*
+ * 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.nio.file.attribute.GroupPrincipal;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultGroupPrincipal extends PrincipalBase implements GroupPrincipal {
+
+    public DefaultGroupPrincipal(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultUserPrincipal.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultUserPrincipal.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultUserPrincipal.java
new file mode 100644
index 0000000..d71d772
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultUserPrincipal.java
@@ -0,0 +1,32 @@
+/*
+ * 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.nio.file.attribute.UserPrincipal;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultUserPrincipal extends PrincipalBase implements UserPrincipal {
+
+    public DefaultUserPrincipal(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
new file mode 100644
index 0000000..0ae60cf
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
@@ -0,0 +1,109 @@
+/*
+ * 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.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DirectoryHandle extends Handle implements Iterator<Path> {
+
+    private boolean done;
+    private boolean sendDotDot = true;
+    private boolean sendDot = true;
+    // the directory should be read once at "open directory"
+    private DirectoryStream<Path> ds;
+    private Iterator<Path> fileList;
+
+    public DirectoryHandle(SftpSubsystem subsystem, Path dir, String handle) throws IOException {
+        super(dir, handle);
+        signalHandleOpening(subsystem);
+
+        SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
+        ServerSession session = subsystem.getServerSession();
+        ds = accessor.openDirectory(session, subsystem, dir, handle);
+
+        Path parent = dir.getParent();
+        if (parent == null) {
+            sendDotDot = false;  // if no parent then no need to send ".."
+        }
+        fileList = ds.iterator();
+
+        try {
+            signalHandleOpen(subsystem);
+        } catch (IOException e) {
+            close();
+            throw e;
+        }
+    }
+
+    public boolean isDone() {
+        return done;
+    }
+
+    public void markDone() {
+        this.done = true;
+        // allow the garbage collector to do the job
+        this.fileList = null;
+    }
+
+    public boolean isSendDot() {
+        return sendDot;
+    }
+
+    public void markDotSent() {
+        sendDot = false;
+    }
+
+    public boolean isSendDotDot() {
+        return sendDotDot;
+    }
+
+    public void markDotDotSent() {
+        sendDotDot = false;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return fileList.hasNext();
+    }
+
+    @Override
+    public Path next() {
+        return fileList.next();
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("Not allowed to remove " + toString());
+    }
+
+    @Override
+    public void close() throws IOException {
+        super.close();
+        markDone(); // just making sure
+        ds.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
new file mode 100644
index 0000000..b499524
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
@@ -0,0 +1,270 @@
+/*
+ * 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.nio.ByteBuffer;
+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;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+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;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class FileHandle extends Handle {
+    private final int access;
+    private final SeekableByteChannel fileChannel;
+    private final List<FileLock> locks = new ArrayList<>();
+    private final SftpSubsystem subsystem;
+    private final Set<StandardOpenOption> openOptions;
+    private final Collection<FileAttribute<?>> fileAttributes;
+
+    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;
+        this.openOptions = Collections.unmodifiableSet(getOpenOptions(flags, access));
+        this.fileAttributes = Collections.unmodifiableCollection(toFileAttributes(attrs));
+        signalHandleOpening(subsystem);
+
+        FileAttribute<?>[] fileAttrs = GenericUtils.isEmpty(fileAttributes)
+                ? IoUtils.EMPTY_FILE_ATTRIBUTES
+                : fileAttributes.toArray(new FileAttribute<?>[fileAttributes.size()]);
+        SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
+        ServerSession session = subsystem.getServerSession();
+        SeekableByteChannel channel;
+        try {
+            channel = accessor.openFile(session, subsystem, file, handle, openOptions, fileAttrs);
+        } catch (UnsupportedOperationException e) {
+            channel = accessor.openFile(session, subsystem, file, handle, openOptions, IoUtils.EMPTY_FILE_ATTRIBUTES);
+            subsystem.doSetAttributes(file, attrs);
+        }
+        this.fileChannel = channel;
+
+        try {
+            signalHandleOpen(subsystem);
+        } catch (IOException e) {
+            close();
+            throw e;
+        }
+    }
+
+    public final Set<StandardOpenOption> getOpenOptions() {
+        return openOptions;
+    }
+
+    public final Collection<FileAttribute<?>> getFileAttributes() {
+        return fileAttributes;
+    }
+
+    public final SeekableByteChannel getFileChannel() {
+        return fileChannel;
+    }
+
+    public int getAccessMask() {
+        return access;
+    }
+
+    public boolean isOpenAppend() {
+        return SftpConstants.ACE4_APPEND_DATA == (getAccessMask() & SftpConstants.ACE4_APPEND_DATA);
+    }
+
+    public int read(byte[] data, long offset) throws IOException {
+        return read(data, 0, data.length, offset);
+    }
+
+    public int read(byte[] data, int doff, int length, long offset) throws IOException {
+        SeekableByteChannel channel = getFileChannel();
+        channel = channel.position(offset);
+        return channel.read(ByteBuffer.wrap(data, doff, length));
+    }
+
+    public void append(byte[] data) throws IOException {
+        append(data, 0, data.length);
+    }
+
+    public void append(byte[] data, int doff, int length) throws IOException {
+        SeekableByteChannel channel = getFileChannel();
+        write(data, doff, length, channel.size());
+    }
+
+    public void write(byte[] data, long offset) throws IOException {
+        write(data, 0, data.length, offset);
+    }
+
+    public void write(byte[] data, int doff, int length, long offset) throws IOException {
+        SeekableByteChannel channel = getFileChannel();
+        channel = channel.position(offset);
+        channel.write(ByteBuffer.wrap(data, doff, length));
+    }
+
+    @Override
+    public void close() throws IOException {
+        super.close();
+
+        SeekableByteChannel channel = getFileChannel();
+        if (channel.isOpen()) {
+            channel.close();
+        }
+    }
+
+    public void lock(long offset, long length, int mask) throws IOException {
+        SeekableByteChannel channel = getFileChannel();
+        long size = (length == 0L) ? channel.size() - offset : length;
+        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));
+        }
+
+        synchronized (locks) {
+            locks.add(lock);
+        }
+    }
+
+    public void unlock(long offset, long length) throws IOException {
+        SeekableByteChannel channel = getFileChannel();
+        long size = (length == 0L) ? channel.size() - offset : length;
+        FileLock lock = null;
+        for (Iterator<FileLock> iterator = locks.iterator(); iterator.hasNext();) {
+            FileLock l = iterator.next();
+            if ((l.position() == offset) && (l.size() == size)) {
+                iterator.remove();
+                lock = l;
+                break;
+            }
+        }
+        if (lock == null) {
+            throw new SftpException(SftpConstants.SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK,
+                    "No matching lock found on range [" + offset + "-" + (offset + length));
+        }
+
+        lock.release();
+    }
+
+    public static Collection<FileAttribute<?>> toFileAttributes(Map<String, Object> attrs) {
+        if (GenericUtils.isEmpty(attrs)) {
+            return Collections.emptyList();
+        }
+
+        Collection<FileAttribute<?>> attributes = null;
+        // Cannot use forEach because the referenced attributes variable is not effectively final
+        for (Map.Entry<String, Object> attr : attrs.entrySet()) {
+            FileAttribute<?> fileAttr = toFileAttribute(attr.getKey(), attr.getValue());
+            if (fileAttr == null) {
+                continue;
+            }
+            if (attributes == null) {
+                attributes = new LinkedList<>();
+            }
+            attributes.add(fileAttr);
+        }
+
+        return (attributes == null) ? Collections.emptyList() : attributes;
+    }
+
+    public static FileAttribute<?> toFileAttribute(String key, Object val) {
+        // Some ignored attributes sent by the SFTP client
+        if ("isOther".equals(key)) {
+            if ((Boolean) val) {
+                throw new IllegalArgumentException("Not allowed to use " + key + "=" + val);
+            }
+            return null;
+        } else if ("isRegular".equals(key)) {
+            if (!(Boolean) val) {
+                throw new IllegalArgumentException("Not allowed to use " + key + "=" + val);
+            }
+            return null;
+        }
+
+        return new FileAttribute<Object>() {
+            private final String s = key + "=" + val;
+
+            @Override
+            public String name() {
+                return key;
+            }
+
+            @Override
+            public Object value() {
+                return val;
+            }
+
+            @Override
+            public String toString() {
+                return s;
+            }
+        };
+    }
+
+    public static Set<StandardOpenOption> getOpenOptions(int flags, int access) {
+        Set<StandardOpenOption> options = EnumSet.noneOf(StandardOpenOption.class);
+        if (((access & SftpConstants.ACE4_READ_DATA) != 0) || ((access & SftpConstants.ACE4_READ_ATTRIBUTES) != 0)) {
+            options.add(StandardOpenOption.READ);
+        }
+        if (((access & SftpConstants.ACE4_WRITE_DATA) != 0) || ((access & SftpConstants.ACE4_WRITE_ATTRIBUTES) != 0)) {
+            options.add(StandardOpenOption.WRITE);
+        }
+
+        int accessDisposition = flags & SftpConstants.SSH_FXF_ACCESS_DISPOSITION;
+        switch (accessDisposition) {
+            case SftpConstants.SSH_FXF_CREATE_NEW:
+                options.add(StandardOpenOption.CREATE_NEW);
+                break;
+            case SftpConstants.SSH_FXF_CREATE_TRUNCATE:
+                options.add(StandardOpenOption.CREATE);
+                options.add(StandardOpenOption.TRUNCATE_EXISTING);
+                break;
+            case SftpConstants.SSH_FXF_OPEN_EXISTING:
+                break;
+            case SftpConstants.SSH_FXF_OPEN_OR_CREATE:
+                options.add(StandardOpenOption.CREATE);
+                break;
+            case SftpConstants.SSH_FXF_TRUNCATE_EXISTING:
+                options.add(StandardOpenOption.TRUNCATE_EXISTING);
+                break;
+            default:    // ignored
+        }
+        if ((flags & SftpConstants.SSH_FXF_APPEND_DATA) != 0) {
+            options.add(StandardOpenOption.APPEND);
+        }
+
+        return options;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
new file mode 100644
index 0000000..a860eec
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
@@ -0,0 +1,79 @@
+/*
+ * 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.nio.file.Path;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class Handle implements java.nio.channels.Channel {
+    private final AtomicBoolean closed = new AtomicBoolean(false);
+    private final Path file;
+    private final String handle;
+
+    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);
+    }
+
+    protected void signalHandleOpening(SftpSubsystem subsystem) throws IOException {
+        SftpEventListener listener = subsystem.getSftpEventListenerProxy();
+        ServerSession session = subsystem.getServerSession();
+        listener.opening(session, handle, this);
+    }
+
+    protected void signalHandleOpen(SftpSubsystem subsystem) throws IOException {
+        SftpEventListener listener = subsystem.getSftpEventListenerProxy();
+        ServerSession session = subsystem.getServerSession();
+        listener.open(session, handle, this);
+    }
+
+    public Path getFile() {
+        return file;
+    }
+
+    public String getFileHandle() {
+        return handle;
+    }
+
+    @Override
+    public boolean isOpen() {
+        return !closed.get();
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (!closed.getAndSet(true)) {
+            //noinspection UnnecessaryReturnStatement
+            return; // debug breakpoint
+        }
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toString(getFile());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/InvalidHandleException.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/InvalidHandleException.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/InvalidHandleException.java
new file mode 100644
index 0000000..af7b147
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/InvalidHandleException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class InvalidHandleException extends IOException {
+    private static final long serialVersionUID = -1686077114375131889L;
+
+    public InvalidHandleException(String handle, Handle h, Class<? extends Handle> expected) {
+        super(handle + "[" + h + "] is not a " + expected.getSimpleName());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/PrincipalBase.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/PrincipalBase.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/PrincipalBase.java
new file mode 100644
index 0000000..310c3b4
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/PrincipalBase.java
@@ -0,0 +1,65 @@
+/*
+ * 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.security.Principal;
+import java.util.Objects;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PrincipalBase implements Principal {
+
+    private final String name;
+
+    public PrincipalBase(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("name is null");
+        }
+        this.name = name;
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if ((o == null) || (getClass() != o.getClass())) {
+            return false;
+        }
+
+        Principal that = (Principal) o;
+        return Objects.equals(getName(), that.getName());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(getName());
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpErrorStatusDataHandler.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpErrorStatusDataHandler.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpErrorStatusDataHandler.java
new file mode 100644
index 0000000..1498ba2
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpErrorStatusDataHandler.java
@@ -0,0 +1,83 @@
+/*
+ * 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 org.apache.sshd.common.subsystem.sftp.SftpHelper;
+
+/**
+ * Invoked in order to format failed commands messages
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpErrorStatusDataHandler {
+    SftpErrorStatusDataHandler DEFAULT = new SftpErrorStatusDataHandler() {
+        @Override
+        public String toString() {
+            return SftpErrorStatusDataHandler.class.getSimpleName() + "[DEFAULT]";
+        }
+    };
+
+    /**
+     * @param sftpSubsystem The SFTP subsystem instance
+     * @param id The command identifier
+     * @param e Thrown exception
+     * @param cmd The command that was attempted
+     * @param args The relevant command arguments - <B>Note:</B> provided only for
+     * <U>logging</U> purposes and subject to type and/or order change at any version
+     * @return The relevant sub-status to send as failure indication for the failed command
+     * @see SftpHelper#resolveSubstatus(Throwable)
+     */
+    default int resolveSubStatus(SftpSubsystemEnvironment sftpSubsystem, int id, Throwable e, int cmd, Object... args) {
+        return SftpHelper.resolveSubstatus(e);
+    }
+
+    /**
+     * @param sftpSubsystem The SFTP subsystem instance
+     * @param id The command identifier
+     * @param e Thrown exception
+     * @param subStatus The sub-status code obtained from invocation of
+     * {@link #resolveSubStatus(SftpSubsystemEnvironment, int, Throwable, int, Object...) resolveSubStatus}
+     * @param cmd The command that was attempted
+     * @param args The relevant command arguments - <B>Note:</B> provided only for
+     * <U>logging</U> purposes and subject to type and/or order change at any version
+     * @return The human readable text message that explains the failure reason
+     * @see SftpHelper#resolveStatusMessage(int)
+     */
+    default String resolveErrorMessage(
+            SftpSubsystemEnvironment sftpSubsystem, int id, Throwable e, int subStatus, int cmd, Object... args) {
+        return SftpHelper.resolveStatusMessage(subStatus);
+    }
+
+    /**
+     * @param sftpSubsystem The SFTP subsystem instance
+     * @param id The command identifier
+     * @param e Thrown exception
+     * @param subStatus The sub-status code obtained from invocation of
+     * {@link #resolveSubStatus(SftpSubsystemEnvironment, int, Throwable, int, Object...) resolveSubStatus}
+     * @param cmd The command that was attempted
+     * @param args The relevant command arguments - <B>Note:</B> provided only for
+     * <U>logging</U> purposes and subject to type and/or order change at any version
+     * @return The error message language tag - recommend returning empty string
+     */
+    default String resolveErrorLanguage(
+            SftpSubsystemEnvironment sftpSubsystem, int id, Throwable e, int subStatus, int cmd, Object... args) {
+        return "";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
new file mode 100644
index 0000000..c518af3
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
@@ -0,0 +1,396 @@
+/*
+ * 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.nio.file.CopyOption;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.sshd.common.util.SshdEventListener;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * Can be used register for SFTP events. <B>Note:</B> it does not expose
+ * the entire set of available SFTP commands and responses (e.g., no reports
+ * for initialization, extensions, parameters re-negotiation, etc...);
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpEventListener extends SshdEventListener {
+    /**
+     * Called when the SFTP protocol has been initialized
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param version The negotiated SFTP version
+     */
+    default void initialized(ServerSession session, int version) {
+        // ignored
+    }
+
+    /**
+     * Called when subsystem is destroyed since it was closed
+     *
+     * @param session The associated {@link ServerSession}
+     */
+    default void destroying(ServerSession session) {
+        // ignored
+    }
+
+    /**
+     * Specified file / directory is being opened
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file / directory
+     * @param localHandle  The associated file / directory {@link Handle}
+     * @throws IOException If failed to handle the call
+     */
+    default void opening(ServerSession session, String remoteHandle, Handle localHandle)
+            throws IOException {
+        // ignored
+    }
+
+    /**
+     * Specified file / directory has been opened
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file / directory
+     * @param localHandle  The associated file / directory {@link Handle}
+     * @throws IOException If failed to handle the call
+     */
+    default void open(ServerSession session, String remoteHandle, Handle localHandle)
+            throws IOException {
+        // ignored
+    }
+
+    /**
+     * Result of reading entries from a directory - <B>Note:</B> it may be a
+     * <U>partial</U> result if the directory contains more entries than can
+     * be accommodated in the response
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the directory
+     * @param localHandle  The associated {@link DirectoryHandle}
+     * @param entries      A {@link Map} of the listed entries - key = short name,
+     *                     value = {@link Path} of the sub-entry
+     * @throws IOException If failed to handle the call
+     */
+    default void read(ServerSession session, String remoteHandle, DirectoryHandle localHandle, Map<String, Path> entries)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Preparing to read from a file
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file
+     * @param localHandle  The associated {@link FileHandle}
+     * @param offset       Offset in file from which to read
+     * @param data         Buffer holding the read data
+     * @param dataOffset   Offset of read data in buffer
+     * @param dataLen      Requested read length
+     * @throws IOException If failed to handle the call
+     */
+    default void reading(ServerSession session, String remoteHandle, FileHandle localHandle,
+            long offset, byte[] data, int dataOffset, int dataLen) throws IOException {
+        // ignored
+    }
+
+    /**
+     * Result of reading from a file
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file
+     * @param localHandle  The associated {@link FileHandle}
+     * @param offset       Offset in file from which to read
+     * @param data         Buffer holding the read data
+     * @param dataOffset   Offset of read data in buffer
+     * @param dataLen      Requested read length
+     * @param readLen      Actual read length - negative if thrown exception provided
+     * @param thrown       Non-{@code null} if read failed due to this exception
+     * @throws IOException If failed to handle the call
+     */
+    default void read(ServerSession session, String remoteHandle, FileHandle localHandle,
+              long offset, byte[] data, int dataOffset, int dataLen, int readLen, Throwable thrown)
+                      throws IOException {
+                          // ignored
+    }
+
+    /**
+     * Preparing to write to file
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file
+     * @param localHandle  The associated {@link FileHandle}
+     * @param offset       Offset in file to which to write
+     * @param data         Buffer holding the written data
+     * @param dataOffset   Offset of write data in buffer
+     * @param dataLen      Requested write length
+     * @throws IOException If failed to handle the call
+     */
+    default void writing(ServerSession session, String remoteHandle, FileHandle localHandle,
+               long offset, byte[] data, int dataOffset, int dataLen)
+                       throws IOException {
+                           // ignored
+    }
+
+    /**
+     * Finished to writing to file
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file
+     * @param localHandle  The associated {@link FileHandle}
+     * @param offset       Offset in file to which to write
+     * @param data         Buffer holding the written data
+     * @param dataOffset   Offset of write data in buffer
+     * @param dataLen      Requested write length
+     * @param thrown       The reason for failing to write - {@code null} if successful
+     * @throws IOException If failed to handle the call
+     */
+    default void written(ServerSession session, String remoteHandle, FileHandle localHandle,
+               long offset, byte[] data, int dataOffset, int dataLen, Throwable thrown)
+                       throws IOException {
+                           // ignored
+    }
+
+    /**
+     * Called <U>prior</U> to blocking a file section
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file
+     * @param localHandle  The associated {@link FileHandle}
+     * @param offset       Offset in file for locking
+     * @param length       Section size for locking
+     * @param mask         Lock mask flags - see {@code SSH_FXP_BLOCK} message
+     * @throws IOException If failed to handle the call
+     * @see #blocked(ServerSession, String, FileHandle, long, long, int, Throwable)
+     */
+    default void blocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>after</U> blocking a file section
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file
+     * @param localHandle  The associated {@link FileHandle}
+     * @param offset       Offset in file for locking
+     * @param length       Section size for locking
+     * @param mask         Lock mask flags - see {@code SSH_FXP_BLOCK} message
+     * @param thrown       If not-{@code null} then the reason for the failure to execute
+     * @throws IOException If failed to handle the call
+     */
+    default void blocked(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask, Throwable thrown)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>prior</U> to un-blocking a file section
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file
+     * @param localHandle  The associated {@link FileHandle}
+     * @param offset       Offset in file for un-locking
+     * @param length       Section size for un-locking
+     * @throws IOException If failed to handle the call
+     */
+    default void unblocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>prior</U> to un-blocking a file section
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file
+     * @param localHandle  The associated {@link FileHandle}
+     * @param offset       Offset in file for un-locking
+     * @param length       Section size for un-locking
+     * @param thrown       If not-{@code null} then the reason for the failure to execute
+     * @throws IOException If failed to handle the call
+     */
+    default void unblocked(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, Throwable thrown)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Specified file / directory has been closed
+     *
+     * @param session      The {@link ServerSession} through which the request was handled
+     * @param remoteHandle The (opaque) assigned handle for the file / directory
+     * @param localHandle  The associated file / directory {@link Handle}
+     */
+    default void close(ServerSession session, String remoteHandle, Handle localHandle) {
+        // ignored
+    }
+
+    /**
+     * Called <U>prior</U> to creating a directory
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param path    Directory {@link Path} to be created
+     * @param attrs   Requested associated attributes to set
+     * @throws IOException If failed to handle the call
+     * @see #created(ServerSession, Path, Map, Throwable)
+     */
+    default void creating(ServerSession session, Path path, Map<String, ?> attrs)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>after</U> creating a directory
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param path    Directory {@link Path} to be created
+     * @param attrs   Requested associated attributes to set
+     * @param thrown  If not-{@code null} then the reason for the failure to execute
+     * @throws IOException If failed to handle the call
+     */
+    default void created(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>prior</U> to renaming a file / directory
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param srcPath The source {@link Path}
+     * @param dstPath The target {@link Path}
+     * @param opts    The resolved renaming options
+     * @throws IOException If failed to handle the call
+     * @see #moved(ServerSession, Path, Path, Collection, Throwable)
+     */
+    default void moving(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>after</U> renaming a file / directory
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param srcPath The source {@link Path}
+     * @param dstPath The target {@link Path}
+     * @param opts    The resolved renaming options
+     * @param thrown  If not-{@code null} then the reason for the failure to execute
+     * @throws IOException If failed to handle the call
+     */
+    default void moved(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts, Throwable thrown)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>prior</U> to removing a file / directory
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param path    The {@link Path} about to be removed
+     * @throws IOException If failed to handle the call
+     * @see #removed(ServerSession, Path, Throwable)
+     */
+    default void removing(ServerSession session, Path path) throws IOException {
+        // ignored
+    }
+
+    /**
+     * Called <U>after</U> a file / directory has been removed
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param path    The {@link Path} to be removed
+     * @param thrown  If not-{@code null} then the reason for the failure to execute
+     * @throws IOException If failed to handle the call
+     */
+    default void removed(ServerSession session, Path path, Throwable thrown) throws IOException {
+        // ignored
+    }
+
+    /**
+     * Called <U>prior</U> to creating a link
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param source  The source {@link Path}
+     * @param target  The target {@link Path}
+     * @param symLink {@code true} = symbolic link
+     * @throws IOException If failed to handle the call
+     * @see #linked(ServerSession, Path, Path, boolean, Throwable)
+     */
+    default void linking(ServerSession session, Path source, Path target, boolean symLink)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>after</U> creating a link
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param source  The source {@link Path}
+     * @param target  The target {@link Path}
+     * @param symLink {@code true} = symbolic link
+     * @param thrown  If not-{@code null} then the reason for the failure to execute
+     * @throws IOException If failed to handle the call
+     */
+    default void linked(ServerSession session, Path source, Path target, boolean symLink, Throwable thrown)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>prior</U> to modifying the attributes of a file / directory
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param path    The file / directory {@link Path} to be modified
+     * @param attrs   The attributes {@link Map} - names and values depend on the
+     *                O/S, view, type, etc...
+     * @throws IOException If failed to handle the call
+     * @see #modifiedAttributes(ServerSession, Path, Map, Throwable)
+     */
+    default void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs)
+            throws IOException {
+                // ignored
+    }
+
+    /**
+     * Called <U>after</U> modifying the attributes of a file / directory
+     *
+     * @param session The {@link ServerSession} through which the request was handled
+     * @param path    The file / directory {@link Path} to be modified
+     * @param attrs   The attributes {@link Map} - names and values depend on the
+     *                O/S, view, type, etc...
+     * @param thrown  If not-{@code null} then the reason for the failure to execute
+     * @throws IOException If failed to handle the call
+     */
+    default void modifiedAttributes(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown)
+            throws IOException {
+                // ignored
+    }
+
+    static <L extends SftpEventListener> L validateListener(L listener) {
+        return SshdEventListener.validateListener(listener, SftpEventListener.class.getSimpleName());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java
new file mode 100644
index 0000000..3f91033
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java
@@ -0,0 +1,48 @@
+/*
+ * 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:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpEventListenerManager {
+    /**
+     * @return An instance representing <U>all</U> the currently
+     * registered listeners. Any method invocation is <U>replicated</U>
+     * to the actually registered listeners
+     */
+    SftpEventListener getSftpEventListenerProxy();
+
+    /**
+     * Register a listener instance
+     *
+     * @param listener The {@link SftpEventListener} instance to add - never {@code null}
+     * @return {@code true} if listener is a previously un-registered one
+     */
+    boolean addSftpEventListener(SftpEventListener listener);
+
+    /**
+     * Remove a listener instance
+     *
+     * @param listener The {@link SftpEventListener} instance to remove - never {@code null}
+     * @return {@code true} if listener is a (removed) registered one
+     */
+    boolean removeSftpEventListener(SftpEventListener listener);
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
new file mode 100644
index 0000000..86aa670
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
@@ -0,0 +1,155 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.FileInfoExtractor;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpFileSystemAccessor {
+    List<String> DEFAULT_UNIX_VIEW = Collections.singletonList("unix:*");
+
+    /**
+     * A {@link Map} of {@link FileInfoExtractor}s to be used to complete
+     * attributes that are deemed important enough to warrant an extra
+     * effort if not accessible via the file system attributes views
+     */
+    Map<String, FileInfoExtractor<?>> FILEATTRS_RESOLVERS =
+            GenericUtils.<String, FileInfoExtractor<?>>mapBuilder(String.CASE_INSENSITIVE_ORDER)
+                .put("isRegularFile", FileInfoExtractor.ISREG)
+                .put("isDirectory", FileInfoExtractor.ISDIR)
+                .put("isSymbolicLink", FileInfoExtractor.ISSYMLINK)
+                .put("permissions", FileInfoExtractor.PERMISSIONS)
+                .put("size", FileInfoExtractor.SIZE)
+                .put("lastModifiedTime", FileInfoExtractor.LASTMODIFIED)
+                .immutable();
+
+    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/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
new file mode 100644
index 0000000..616f9ce
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
@@ -0,0 +1,29 @@
+/*
+ * 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:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpFileSystemAccessorManager {
+    SftpFileSystemAccessor getFileSystemAccessor();
+
+    void setFileSystemAccessor(SftpFileSystemAccessor accessor);
+}