You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2020/05/15 03:53:15 UTC

[mina-sshd] branch master updated: [SSHD-992] Provide more hooks to access SFTP backend file via SftpFileSystemAccessor

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git


The following commit(s) were added to refs/heads/master by this push:
     new 95de1a6  [SSHD-992] Provide more hooks to access SFTP backend file via SftpFileSystemAccessor
95de1a6 is described below

commit 95de1a6614bd451744c3822c533dd2b34022c428
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 15 05:10:15 2020 +0300

    [SSHD-992] Provide more hooks to access SFTP backend file via SftpFileSystemAccessor
---
 CHANGES.md                                         |  15 +-
 .../sftp/AbstractSftpSubsystemHelper.java          | 153 ++++-------
 .../subsystem/sftp/SftpFileSystemAccessor.java     | 289 +++++++++++++++++++--
 .../sshd/server/subsystem/sftp/SftpSubsystem.java  |   7 +-
 .../server/subsystem/sftp/SftpSubsystemProxy.java  |  29 +++
 .../sshd/client/subsystem/sftp/SftpTest.java       |   6 +-
 6 files changed, 379 insertions(+), 120 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 0fbc103..76c083f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -12,6 +12,14 @@
 translated internally into same code flow as if an `SSH_MSH_REQUEST_FAILURE` has
 been received - see [SSHD-968](https://issues.apache.org/jira/browse/SSHD-968).
 
+* Server SFTP subsystem internal code dealing with the local files has been delegated to
+the `SftpFileSystemAccessor` in order to allow easier hooking into the SFTP subsystem.
+
+    * Resolving a local file path for an SFTP remote one
+    * Reading/Writing a file's attribute(s)
+    * Creating files links
+    * Copying / Renaming / Deleting files
+
 ## Minor code helpers
 
 * Handling of debug/ignore/unimplemented messages has been split into `handleXXX` and `doInvokeXXXMsgHandler` methods
@@ -19,6 +27,9 @@ where the former validate the messages and deal with the idle timeout, and the l
 
 * Added overloaded methods that accept a `java.time.Duration` specifier for timeout value.
 
+* The argument representing the SFTP subsystem in invocations to `SftpFileSystemAccessor` has been enhanced to expose
+as much of the available functionality as possible.
+
 ## Behavioral changes and enhancements
 
 * [SSHD-964](https://issues.apache.org/jira/browse/SSHD-964) - Send SSH_MSG_CHANNEL_EOF when tunnel channel being closed.
@@ -35,4 +46,6 @@ where the former validate the messages and deal with the idle timeout, and the l
 
 * [SSHD-660](https://issues.apache.org/jira/browse/SSHD-660) - Added support for server-side signed certificate keys
 
-* [SSHD-984](https://issues.apache.org/jira/browse/SSHD-984) - Utility method to export KeyPair in OpenSSH format
\ No newline at end of file
+* [SSHD-984](https://issues.apache.org/jira/browse/SSHD-984) - Utility method to export KeyPair in OpenSSH format
+
+* [SSHD-992](https://issues.apache.org/jira/browse/SSHD-984) - Provide more hooks into the SFTP server subsystem via SftpFileSystemAccessor
\ No newline at end of file
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java
index 4d2144d..ef644a3 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpSubsystemHelper.java
@@ -23,7 +23,6 @@ import java.io.EOFException;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.StreamCorruptedException;
 import java.nio.ByteBuffer;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.charset.StandardCharsets;
@@ -41,15 +40,10 @@ import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.FileOwnerAttributeView;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFilePermission;
 import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.attribute.UserPrincipalNotFoundException;
 import java.security.Principal;
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
@@ -95,7 +89,6 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.SelectorUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
@@ -110,10 +103,10 @@ import org.apache.sshd.server.session.ServerSession;
 @SuppressWarnings("checkstyle:MethodCount") // TODO split this big class and remove the suppression
 public abstract class AbstractSftpSubsystemHelper
         extends AbstractLoggingBean
-        implements SftpEventListenerManager, SftpSubsystemEnvironment {
+        implements SftpSubsystemProxy {
     /**
      * Whether to automatically follow symbolic links when resolving paths
-     * 
+     *
      * @see #DEFAULT_AUTO_FOLLOW_LINKS
      */
     public static final String AUTO_FOLLOW_LINKS = "sftp-auto-follow-links";
@@ -1181,13 +1174,14 @@ public abstract class AbstractSftpSubsystemHelper
     }
 
     protected String doReadLink(int id, String path) throws IOException {
-        Path f = resolveFile(path);
-        Path t = Files.readSymbolicLink(f);
+        Path link = resolveFile(path);
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        String target = accessor.resolveLinkTarget(getServerSession(), this, link);
         if (log.isDebugEnabled()) {
             log.debug("doReadLink({})[id={}] path={}[{}]: {}",
-                    getServerSession(), id, path, f, t);
+                    getServerSession(), id, path, link, target);
         }
-        return t.toString();
+        return target;
     }
 
     protected void doRename(Buffer buffer, int id) throws IOException {
@@ -1237,10 +1231,8 @@ public abstract class AbstractSftpSubsystemHelper
 
         listener.moving(session, o, n, opts);
         try {
-            Files.move(o, n,
-                    GenericUtils.isEmpty(opts)
-                            ? IoUtils.EMPTY_COPY_OPTIONS
-                            : opts.toArray(new CopyOption[opts.size()]));
+            SftpFileSystemAccessor accessor = getFileSystemAccessor();
+            accessor.renameFile(session, this, o, n, opts);
         } catch (IOException | RuntimeException e) {
             listener.moved(session, o, n, opts, e);
             throw e;
@@ -1308,10 +1300,8 @@ public abstract class AbstractSftpSubsystemHelper
             throws IOException {
         Path src = resolveFile(srcFile);
         Path dst = resolveFile(dstFile);
-        Files.copy(src, dst,
-                GenericUtils.isEmpty(opts)
-                        ? IoUtils.EMPTY_COPY_OPTIONS
-                        : opts.toArray(new CopyOption[opts.size()]));
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        accessor.copyFile(getServerSession(), this, src, dst, opts);
     }
 
     protected void doBlock(Buffer buffer, int id) throws IOException {
@@ -1582,7 +1572,8 @@ public abstract class AbstractSftpSubsystemHelper
 
         listener.removing(session, p, isDirectory);
         try {
-            Files.delete(p);
+            SftpFileSystemAccessor accessor = getFileSystemAccessor();
+            accessor.removeFile(session, this, p, isDirectory);
         } catch (IOException | RuntimeException e) {
             listener.removed(session, p, isDirectory, e);
             throw e;
@@ -1632,7 +1623,8 @@ public abstract class AbstractSftpSubsystemHelper
             SftpEventListener listener = getSftpEventListenerProxy();
             listener.creating(session, p, attrs);
             try {
-                Files.createDirectory(p);
+                SftpFileSystemAccessor accessor = getFileSystemAccessor();
+                accessor.createDirectory(session, this, p);
                 boolean followLinks = resolvePathResolutionFollowLinks(
                         SftpConstants.SSH_FXP_MKDIR, "", p);
                 doSetAttributes(p, attrs, followLinks);
@@ -2370,7 +2362,7 @@ public abstract class AbstractSftpSubsystemHelper
      * Called by {@link #getAttributes(Path, int, LinkOption...)} in order to complete any attributes that could not be
      * retrieved via the supported file system views. These attributes are deemed important so an extra effort is made
      * to provide a value for them
-     * 
+     *
      * @param  file        The {@link Path} location for the required attributes
      * @param  flags       A mask of the original required attributes - ignored by the default implementation
      * @param  current     The {@link Map} of attributes already retrieved - may be {@code null}/empty and/or
@@ -2465,7 +2457,9 @@ public abstract class AbstractSftpSubsystemHelper
             Path file, String view, LinkOption... options)
             throws IOException {
         try {
-            Map<String, ?> attrs = Files.readAttributes(file, view, options);
+            SftpFileSystemAccessor accessor = getFileSystemAccessor();
+            Map<String, ?> attrs = accessor.readFileAttributes(
+                    getServerSession(), this, file, view, options);
             if (GenericUtils.isEmpty(attrs)) {
                 return Collections.emptyNavigableMap();
             }
@@ -2654,7 +2648,7 @@ public abstract class AbstractSftpSubsystemHelper
             Map<String, byte[]> extensions = (Map<String, byte[]>) value;
             setFileExtensions(file, extensions, options);
         } else {
-            Files.setAttribute(file, view + ":" + attribute, value, options);
+            setFileRawViewAttribute(file, view, attribute, value, options);
         }
     }
 
@@ -2670,18 +2664,23 @@ public abstract class AbstractSftpSubsystemHelper
                     getServerSession(), file, view, attribute, value);
         }
 
-        Files.setAttribute(file, view + ":" + attribute, value, options);
+        setFileRawViewAttribute(file, view, attribute, value, options);
+    }
+
+    protected void setFileRawViewAttribute(
+            Path file, String view, String attribute, Object value, LinkOption... options)
+            throws IOException {
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        accessor.setFileAttribute(
+                getServerSession(), this, file, view, attribute, value, options);
     }
 
     protected void setFileOwnership(
             Path file, String attribute, Principal value, LinkOption... options)
             throws IOException {
-        if (value == null) {
-            return;
-        }
-
+        ServerSession serverSession = getServerSession();
         if (log.isDebugEnabled()) {
-            log.debug("setFileOwnership({})[{}] {}={}", getServerSession(), file, attribute, value);
+            log.debug("setFileOwnership({})[{}] {}={}", serverSession, file, attribute, value);
         }
 
         /*
@@ -2690,30 +2689,11 @@ public abstract class AbstractSftpSubsystemHelper
          * To ensure consistent and correct behavior across platforms it is recommended that this method should only be
          * used to set the file owner to a user principal that is not a group.
          */
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
         if ("owner".equalsIgnoreCase(attribute)) {
-            FileOwnerAttributeView view = Files.getFileAttributeView(file, FileOwnerAttributeView.class, options);
-            if (view == null) {
-                throw new UnsupportedOperationException("Owner view not supported for " + file);
-            }
-
-            if (!(value instanceof UserPrincipal)) {
-                throw new StreamCorruptedException(
-                        "Owner is not " + UserPrincipal.class.getSimpleName() + ": " + value.getClass().getSimpleName());
-            }
-
-            view.setOwner((UserPrincipal) value);
+            accessor.setFileOwner(serverSession, this, file, value, options);
         } else if ("group".equalsIgnoreCase(attribute)) {
-            PosixFileAttributeView view = Files.getFileAttributeView(file, PosixFileAttributeView.class, options);
-            if (view == null) {
-                throw new UnsupportedOperationException("POSIX view not supported");
-            }
-
-            if (!(value instanceof GroupPrincipal)) {
-                throw new StreamCorruptedException(
-                        "Group is not " + GroupPrincipal.class.getSimpleName() + ": " + value.getClass().getSimpleName());
-            }
-
-            view.setGroup((GroupPrincipal) value);
+            accessor.setGroupOwner(serverSession, this, file, value, options);
         } else {
             throw new UnsupportedOperationException("Unknown ownership attribute: " + attribute);
         }
@@ -2747,34 +2727,26 @@ public abstract class AbstractSftpSubsystemHelper
     protected void setFilePermissions(
             Path file, Set<PosixFilePermission> perms, LinkOption... options)
             throws IOException {
-        if (OsUtils.isWin32()) {
-            IoUtils.setPermissionsToFile(file.toFile(), perms);
-            return;
-        }
-
-        PosixFileAttributeView view = Files.getFileAttributeView(file, PosixFileAttributeView.class, options);
-        if (view == null) {
-            throw new UnsupportedOperationException("POSIX view not supported for " + file);
-        }
-
+        ServerSession serverSession = getServerSession();
         if (log.isTraceEnabled()) {
-            log.trace("setFilePermissions({})[{}] {}", getServerSession(), file, perms);
+            log.trace("setFilePermissions({})[{}] {}", serverSession, file, perms);
         }
-        view.setPermissions(perms);
+
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        accessor.setFilePermissions(
+                serverSession, this, file, perms, options);
     }
 
     protected void setFileAccessControl(
             Path file, List<AclEntry> acl, LinkOption... options)
             throws IOException {
-        AclFileAttributeView view = Files.getFileAttributeView(file, AclFileAttributeView.class, options);
-        if (view == null) {
-            throw new UnsupportedOperationException("ACL view not supported for " + file);
-        }
-
+        ServerSession serverSession = getServerSession();
         if (log.isTraceEnabled()) {
-            log.trace("setFileAccessControl({})[{}] {}", getServerSession(), file, acl);
+            log.trace("setFileAccessControl({})[{}] {}", serverSession, file, acl);
         }
-        view.setAcl(acl);
+
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        accessor.setFileAccessControl(serverSession, this, file, acl, options);
     }
 
     protected void handleUnsupportedAttributes(Collection<String> attributes) {
@@ -2798,32 +2770,21 @@ public abstract class AbstractSftpSubsystemHelper
     }
 
     protected GroupPrincipal toGroup(Path file, GroupPrincipal name) throws IOException {
-        String groupName = name.toString();
-        FileSystem fileSystem = file.getFileSystem();
-        UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
         try {
-            if (lookupService == null) {
-                throw new UserPrincipalNotFoundException(groupName);
-            }
-            return lookupService.lookupPrincipalByGroupName(groupName);
+            SftpFileSystemAccessor accessor = getFileSystemAccessor();
+            return accessor.resolveGroupOwner(getServerSession(), this, file, name);
         } catch (IOException e) {
-            handleUserPrincipalLookupServiceException(GroupPrincipal.class, groupName, e);
+            handleUserPrincipalLookupServiceException(GroupPrincipal.class, name.toString(), e);
             return null;
         }
     }
 
     protected UserPrincipal toUser(Path file, UserPrincipal name) throws IOException {
-        String username = name.toString();
-        FileSystem fileSystem = file.getFileSystem();
-        UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
         try {
-            if (lookupService == null) {
-                throw new UserPrincipalNotFoundException(username);
-            }
-
-            return lookupService.lookupPrincipalByName(username);
+            SftpFileSystemAccessor accessor = getFileSystemAccessor();
+            return accessor.resolveFileOwner(getServerSession(), this, file, name);
         } catch (IOException e) {
-            handleUserPrincipalLookupServiceException(UserPrincipal.class, username, e);
+            handleUserPrincipalLookupServiceException(UserPrincipal.class, name.toString(), e);
             return null;
         }
     }
@@ -2954,13 +2915,13 @@ public abstract class AbstractSftpSubsystemHelper
      */
     protected Path resolveFile(String remotePath)
             throws IOException, InvalidPathException {
-        Path defaultDir = getDefaultDirectory();
-        String path = SelectorUtils.translateToLocalFileSystemPath(
-                remotePath, '/', defaultDir.getFileSystem());
-        Path p = defaultDir.resolve(path);
+        ServerSession serverSession = getServerSession();
+        SftpFileSystemAccessor accessor = getFileSystemAccessor();
+        Path localPath = accessor.resolveLocalFilePath(
+                serverSession, this, getDefaultDirectory(), remotePath);
         if (log.isTraceEnabled()) {
-            log.trace("resolveFile({}) {} => {}", getServerSession(), remotePath, p);
+            log.trace("resolveFile({}) {} => {}", serverSession, remotePath, localPath);
         }
-        return p;
+        return localPath;
     }
 }
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
index 80a1432..cf98d39 100644
--- 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
@@ -25,19 +25,37 @@ import java.nio.channels.Channel;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
 import java.nio.channels.SeekableByteChannel;
+import java.nio.file.CopyOption;
 import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
 import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
 import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileOwnerAttributeView;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.UserPrincipalNotFoundException;
+import java.security.Principal;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.NavigableMap;
 import java.util.Set;
 
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.SelectorUtils;
 import org.apache.sshd.common.util.io.FileInfoExtractor;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.session.ServerSession;
@@ -77,13 +95,37 @@ public interface SftpFileSystemAccessor {
     };
 
     /**
+     * Invoked in order to resolve remote file paths reference by the client into ones accessible by the server
+     *
+     * @param  session              The {@link ServerSession} through which the request was received
+     * @param  subsystem            The SFTP subsystem instance that manages the session
+     * @param  rootDir              The default root directory used to resolve relative paths - a.k.a. the
+     *                              {@code chroot} location
+     * @param  remotePath           The remote path - separated by '/'
+     * @return                      The local {@link Path}
+     * @throws IOException          If failed to resolve the local path
+     * @throws InvalidPathException If bad local path specification
+     * @see                         org.apache.sshd.server.subsystem.sftp.SftpSubsystemEnvironment#getDefaultDirectory()
+     *                              SftpSubsystemEnvironment#getDefaultDirectory()
+     */
+    default Path resolveLocalFilePath(
+            ServerSession session, SftpSubsystemProxy subsystem, Path rootDir, String remotePath)
+            throws IOException, InvalidPathException {
+        String path = SelectorUtils.translateToLocalFileSystemPath(
+                remotePath, '/', rootDir.getFileSystem());
+        return rootDir.resolve(path);
+    }
+
+    /**
      * 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  fileHandle  The {@link FileHandle} representing the created channel - may be {@code null} if not invoked
      *                     within the context of such a handle (special cases)
-     * @param  file        The requested <U>local</U> file {@link Path}
+     * @param  file        The requested <U>local</U> file {@link Path} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
      * @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.
@@ -93,7 +135,7 @@ public interface SftpFileSystemAccessor {
      * @throws IOException If failed to open
      */
     default SeekableByteChannel openFile(
-            ServerSession session, SftpEventListenerManager subsystem, FileHandle fileHandle,
+            ServerSession session, SftpSubsystemProxy subsystem, FileHandle fileHandle,
             Path file, String handle, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
             throws IOException {
         /*
@@ -114,10 +156,12 @@ public interface SftpFileSystemAccessor {
      * @param  session     The {@link ServerSession} through which the request was received
      * @param  subsystem   The SFTP subsystem instance that manages the session
      * @param  fileHandle  The {@link FileHandle} representing the created channel
-     * @param  file        The requested <U>local</U> file {@link Path}
+     * @param  file        The requested <U>local</U> file {@link Path} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
      * @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, FileHandle, Path, String, Set, FileAttribute...)}
+     *                     {@link #openFile(ServerSession, SftpSubsystemProxy, FileHandle, 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
@@ -129,7 +173,7 @@ public interface SftpFileSystemAccessor {
      */
     @SuppressWarnings("checkstyle:ParameterNumber")
     default FileLock tryLock(
-            ServerSession session, SftpEventListenerManager subsystem, FileHandle fileHandle,
+            ServerSession session, SftpSubsystemProxy subsystem, FileHandle fileHandle,
             Path file, String handle, Channel channel, long position, long size, boolean shared)
             throws IOException {
         if (!(channel instanceof FileChannel)) {
@@ -145,17 +189,19 @@ public interface SftpFileSystemAccessor {
      * @param  session     The {@link ServerSession} through which the request was received
      * @param  subsystem   The SFTP subsystem instance that manages the session
      * @param  fileHandle  The {@link FileHandle} representing the created channel
-     * @param  file        The requested <U>local</U> file {@link Path}
+     * @param  file        The requested <U>local</U> file {@link Path} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
      * @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, FileHandle, Path, String, Set, FileAttribute...)}
+     *                     {@link #openFile(ServerSession, SftpSubsystemProxy, FileHandle, 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, FileHandle fileHandle, Path file, String handle,
+            ServerSession session, SftpSubsystemProxy subsystem, FileHandle fileHandle, Path file, String handle,
             Channel channel)
             throws IOException {
         if (!(channel instanceof FileChannel)) {
@@ -172,15 +218,17 @@ public interface SftpFileSystemAccessor {
      * @param  subsystem   The SFTP subsystem instance that manages the session
      * @param  fileHandle  The {@link FileHandle} representing the created channel - may be {@code null} if not invoked
      *                     within the context of such a handle (special cases)
-     * @param  file        The requested <U>local</U> file {@link Path}
+     * @param  file        The requested <U>local</U> file {@link Path} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
      * @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, FileHandle, Path, String, Set, FileAttribute...)}
+     *                     {@link #openFile(ServerSession, SftpSubsystemProxy, FileHandle, Path, String, Set, FileAttribute...)}
      * @param  options     The original options used to open the channel
      * @throws IOException If failed to execute the request
      */
     default void closeFile(
-            ServerSession session, SftpEventListenerManager subsystem, FileHandle fileHandle,
+            ServerSession session, SftpSubsystemProxy subsystem, FileHandle fileHandle,
             Path file, String handle, Channel channel, Set<? extends OpenOption> options)
             throws IOException {
         if ((channel == null) || (!channel.isOpen())) {
@@ -203,13 +251,15 @@ public interface SftpFileSystemAccessor {
      * @param  session     The {@link ServerSession} through which the request was received
      * @param  subsystem   The SFTP subsystem instance that manages the session
      * @param  dirHandle   The {@link DirectoryHandle} representing the stream
-     * @param  dir         The requested <U>local</U> directory
+     * @param  dir         The requested <U>local</U> directory {@link Path} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
      * @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, DirectoryHandle dirHandle, Path dir, String handle)
+            ServerSession session, SftpSubsystemProxy subsystem, DirectoryHandle dirHandle, Path dir, String handle)
             throws IOException {
         return Files.newDirectoryStream(dir);
     }
@@ -221,13 +271,15 @@ public interface SftpFileSystemAccessor {
      * @param  subsystem   The SFTP subsystem instance that manages the session
      * @param  dirHandle   The {@link DirectoryHandle} representing the stream - may be {@code null} if not invoked
      *                     within the context of such a handle (special cases)
-     * @param  dir         The requested <U>local</U> directory
+     * @param  dir         The requested <U>local</U> directory {@link Path} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
      * @param  handle      The assigned directory handle through which the remote peer references this directory
      * @param  ds          The disposed {@link DirectoryStream}
      * @throws IOException If failed to open
      */
     default void closeDirectory(
-            ServerSession session, SftpEventListenerManager subsystem, DirectoryHandle dirHandle,
+            ServerSession session, SftpSubsystemProxy subsystem, DirectoryHandle dirHandle,
             Path dir, String handle, DirectoryStream<Path> ds)
             throws IOException {
         if (ds == null) {
@@ -236,4 +288,211 @@ public interface SftpFileSystemAccessor {
 
         ds.close();
     }
+
+    /**
+     * Invoked when required to retrieve file attributes for a specific file system view
+     *
+     * @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} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
+     * @param  view        The required view name
+     * @param  options     The access {@link LinkOption}-s
+     * @return             A {@link Map} of all the attributes available for the file in the view
+     * @throws IOException If failed to read the attributes
+     * @see                Files#readAttributes(Path, String, LinkOption...)
+     */
+    default Map<String, ?> readFileAttributes(
+            ServerSession session, SftpSubsystemProxy subsystem,
+            Path file, String view, LinkOption... options)
+            throws IOException {
+        return Files.readAttributes(file, view, options);
+    }
+
+    /**
+     * Sets a view attribute for a local file
+     *
+     * @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} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
+     * @param  view        The required view name
+     * @param  attribute   The attribute name
+     * @param  value       The attribute value
+     * @param  options     The access {@link LinkOption}-s
+     * @throws IOException If failed to set the attribute
+     */
+    default void setFileAttribute(
+            ServerSession session, SftpSubsystemProxy subsystem, Path file,
+            String view, String attribute, Object value, LinkOption... options)
+            throws IOException {
+        if (value == null) {
+            return;
+        }
+
+        Files.setAttribute(file, view + ":" + attribute, value, options);
+    }
+
+    default UserPrincipal resolveFileOwner(
+            ServerSession session, SftpSubsystemProxy subsystem, Path file, UserPrincipal name)
+            throws IOException {
+        FileSystem fileSystem = file.getFileSystem();
+        UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
+        String username = name.toString();
+
+        if (lookupService == null) {
+            throw new UserPrincipalNotFoundException(username);
+        }
+
+        return lookupService.lookupPrincipalByName(username);
+    }
+
+    default void setFileOwner(
+            ServerSession session, SftpSubsystemProxy subsystem, Path file,
+            Principal value, LinkOption... options)
+            throws IOException {
+        if (value == null) {
+            return;
+        }
+
+        FileOwnerAttributeView view = Files.getFileAttributeView(file, FileOwnerAttributeView.class, options);
+        if (view == null) {
+            throw new UnsupportedOperationException("Owner view not supported for " + file);
+        }
+
+        if (!(value instanceof UserPrincipal)) {
+            throw new StreamCorruptedException(
+                    "Owner is not " + UserPrincipal.class.getSimpleName() + ": " + value.getClass().getSimpleName());
+        }
+
+        view.setOwner((UserPrincipal) value);
+    }
+
+    default GroupPrincipal resolveGroupOwner(
+            ServerSession session, SftpSubsystemProxy subsystem, Path file, GroupPrincipal name)
+            throws IOException {
+        FileSystem fileSystem = file.getFileSystem();
+        UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
+        String groupName = name.toString();
+        if (lookupService == null) {
+            throw new UserPrincipalNotFoundException(groupName);
+        }
+        return lookupService.lookupPrincipalByGroupName(groupName);
+
+    }
+
+    default void setGroupOwner(
+            ServerSession session, SftpSubsystemProxy subsystem,
+            Path file, Principal value, LinkOption... options)
+            throws IOException {
+        if (value == null) {
+            return;
+        }
+
+        PosixFileAttributeView view = Files.getFileAttributeView(file, PosixFileAttributeView.class, options);
+        if (view == null) {
+            throw new UnsupportedOperationException("POSIX view not supported");
+        }
+
+        if (!(value instanceof GroupPrincipal)) {
+            throw new StreamCorruptedException(
+                    "Group is not " + GroupPrincipal.class.getSimpleName() + ": " + value.getClass().getSimpleName());
+        }
+
+        view.setGroup((GroupPrincipal) value);
+    }
+
+    default void setFilePermissions(
+            ServerSession session, SftpSubsystemProxy subsystem, Path file,
+            Set<PosixFilePermission> perms, LinkOption... options)
+            throws IOException {
+        if (OsUtils.isWin32()) {
+            IoUtils.setPermissionsToFile(file.toFile(), perms);
+            return;
+        }
+
+        PosixFileAttributeView view = Files.getFileAttributeView(file, PosixFileAttributeView.class, options);
+        if (view == null) {
+            throw new UnsupportedOperationException("POSIX view not supported for " + file);
+        }
+
+        view.setPermissions(perms);
+    }
+
+    default void setFileAccessControl(
+            ServerSession session, SftpSubsystemProxy subsystem,
+            Path file, List<AclEntry> acl, LinkOption... options)
+            throws IOException {
+        AclFileAttributeView view = Files.getFileAttributeView(file, AclFileAttributeView.class, options);
+        if (view == null) {
+            throw new UnsupportedOperationException("ACL view not supported for " + file);
+        }
+
+        view.setAcl(acl);
+    }
+
+    default void createDirectory(
+            ServerSession session, SftpSubsystemProxy subsystem, Path path)
+            throws IOException {
+        Files.createDirectory(path);
+    }
+
+    /**
+     * Invoked in order to create a link to a path
+     *
+     * @param  session     The {@link ServerSession} through which the request was received
+     * @param  subsystem   The SFTP subsystem instance that manages the session
+     * @param  link        The requested <U>link</U> {@link Path} - same one returned by
+     *                     {@link #resolveLocalFilePath(ServerSession, SftpSubsystemProxy, Path, String)
+     *                     resolveLocalFilePath}
+     * @param  existing    The <U>existing</U> {@link Path} that the link should reference
+     * @param  symLink     {@code true} if this should be a symbolic link
+     * @throws IOException If failed to create the link
+     * @see                Files#createLink(Path, Path)
+     * @see                Files#createSymbolicLink(Path, Path, FileAttribute...)
+     */
+    default void createLink(
+            ServerSession session, SftpSubsystemProxy subsystem, Path link, Path existing, boolean symLink)
+            throws IOException {
+        if (symLink) {
+            Files.createSymbolicLink(link, existing);
+        } else {
+            Files.createLink(link, existing);
+        }
+    }
+
+    default String resolveLinkTarget(
+            ServerSession session, SftpSubsystemProxy subsystem, Path link)
+            throws IOException {
+        Path target = Files.readSymbolicLink(link);
+        return target.toString();
+    }
+
+    default void renameFile(
+            ServerSession session, SftpSubsystemProxy subsystem,
+            Path oldPath, Path newPath, Collection<CopyOption> opts)
+            throws IOException {
+        Files.move(oldPath, newPath,
+                GenericUtils.isEmpty(opts)
+                        ? IoUtils.EMPTY_COPY_OPTIONS
+                        : opts.toArray(new CopyOption[opts.size()]));
+    }
+
+    default void copyFile(
+            ServerSession session, SftpSubsystemProxy subsystem,
+            Path src, Path dst, Collection<CopyOption> opts)
+            throws IOException {
+        Files.copy(src, dst,
+                GenericUtils.isEmpty(opts)
+                        ? IoUtils.EMPTY_COPY_OPTIONS
+                        : opts.toArray(new CopyOption[opts.size()]));
+    }
+
+    default void removeFile(
+            ServerSession session, SftpSubsystemProxy subsystem, Path path, boolean isDirectory)
+            throws IOException {
+        Files.delete(path);
+    }
 }
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index 643f02b..4415883 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -362,11 +362,8 @@ public class SftpSubsystem
         SftpEventListener listener = getSftpEventListenerProxy();
         listener.linking(session, link, existing, symLink);
         try {
-            if (symLink) {
-                Files.createSymbolicLink(link, existing);
-            } else {
-                Files.createLink(link, existing);
-            }
+            SftpFileSystemAccessor accessor = getFileSystemAccessor();
+            accessor.createLink(session, this, link, existing, symLink);
         } catch (IOException | RuntimeException e) {
             listener.linked(session, link, existing, symLink, e);
             throw e;
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemProxy.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemProxy.java
new file mode 100644
index 0000000..d224dfc
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemProxy.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 SftpSubsystemProxy
+        extends SftpSubsystemEnvironment,
+        SftpEventListenerManager {
+    // Nothing extra
+}
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index 3078ec2..d5484ad 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -100,10 +100,10 @@ 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.SftpSubsystemEnvironment;
 import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemProxy;
 import org.apache.sshd.util.test.CommonTestSupportUtils;
 import org.apache.sshd.util.test.SimpleUserInfo;
 import org.junit.After;
@@ -629,7 +629,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
             factory.setFileSystemAccessor(new SftpFileSystemAccessor() {
                 @Override
                 public SeekableByteChannel openFile(
-                        ServerSession session, SftpEventListenerManager subsystem, FileHandle fileHandle, Path file,
+                        ServerSession session, SftpSubsystemProxy subsystem, FileHandle fileHandle, Path file,
                         String handle, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
                         throws IOException {
                     fileHolder.set(file);
@@ -639,7 +639,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
 
                 @Override
                 public DirectoryStream<Path> openDirectory(
-                        ServerSession session, SftpEventListenerManager subsystem,
+                        ServerSession session, SftpSubsystemProxy subsystem,
                         DirectoryHandle dirHandle, Path dir, String handle)
                         throws IOException {
                     dirHolder.set(dir);