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> + <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);