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 2015/10/22 11:06:12 UTC
mina-sshd git commit: [SSHD-108] Add upload monitoring to sftp
Repository: mina-sshd
Updated Branches:
refs/heads/master e1b8bc40c -> 0c443af5c
[SSHD-108] Add upload monitoring to sftp
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/0c443af5
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/0c443af5
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/0c443af5
Branch: refs/heads/master
Commit: 0c443af5c603cbbe2fd4e65a78c0d59814fd568f
Parents: e1b8bc4
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Thu Oct 22 12:05:54 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Thu Oct 22 12:05:54 2015 +0300
----------------------------------------------------------------------
pom.xml | 2 +-
.../sftp/AbstractSftpEventListenerManager.java | 58 +++
.../server/subsystem/sftp/DirectoryHandle.java | 1 +
.../sshd/server/subsystem/sftp/FileHandle.java | 8 +-
.../sshd/server/subsystem/sftp/Handle.java | 13 +-
.../subsystem/sftp/SftpEventListener.java | 267 ++++++++++++++
.../sftp/SftpEventListenerManager.java | 48 +++
.../server/subsystem/sftp/SftpSubsystem.java | 221 ++++++++++--
.../subsystem/sftp/SftpSubsystemFactory.java | 59 +--
.../sftp/AbstractSftpClientTestSupport.java | 3 +-
.../sshd/client/subsystem/sftp/SftpTest.java | 360 ++++++++++++++++++-
11 files changed, 961 insertions(+), 79 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 311d803..8e504d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -730,7 +730,7 @@
</module>
<!-- <module name="RegexpHeader" /> -->
<module name="FileLength">
- <property name="max" value="3000" />
+ <property name="max" value="3072" />
</module>
<module name="FileTabCharacter">
<property name="eachLine" value="true" />
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerManager.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerManager.java
new file mode 100644
index 0000000..b6d9e28
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerManager.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util.Collection;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.sshd.common.util.EventListenerUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpEventListenerManager implements SftpEventListenerManager {
+ private final Collection<SftpEventListener> sftpEventListeners = new CopyOnWriteArraySet<>();
+ private final SftpEventListener sftpEventListenerProxy;
+
+ protected AbstractSftpEventListenerManager() {
+ sftpEventListenerProxy = EventListenerUtils.proxyWrapper(SftpEventListener.class, getClass().getClassLoader(), sftpEventListeners);
+ }
+
+ public Collection<SftpEventListener> getRegisteredListeners() {
+ return sftpEventListeners;
+ }
+
+ @Override
+ public SftpEventListener getSftpEventListenerProxy() {
+ return sftpEventListenerProxy;
+ }
+
+
+ @Override
+ public boolean addSftpEventListener(SftpEventListener listener) {
+ return sftpEventListeners.add(ValidateUtils.checkNotNull(listener, "No listener"));
+ }
+
+ @Override
+ public boolean removeSftpEventListener(SftpEventListener listener) {
+ return sftpEventListeners.remove(listener);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
index b64bce3..9a813ce 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
@@ -88,6 +88,7 @@ public class DirectoryHandle extends Handle implements Iterator<Path> {
@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/0c443af5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
index 056c513..bd54bdf 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
@@ -111,7 +111,7 @@ public class FileHandle extends Handle {
channel = FileChannel.open(file, options, attributes);
} catch (UnsupportedOperationException e) {
channel = FileChannel.open(file, options);
- sftpSubsystem.setAttributes(file, attrs);
+ sftpSubsystem.doSetAttributes(file, attrs);
}
this.fileChannel = channel;
this.pos = 0;
@@ -170,8 +170,12 @@ public class FileHandle extends Handle {
@Override
public void close() throws IOException {
+ super.close();
+
FileChannel channel = getFileChannel();
- channel.close();
+ if (channel.isOpen()) {
+ channel.close();
+ }
}
public void lock(long offset, long length, int mask) throws IOException {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
index 686cf5d..9253c4a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
@@ -21,11 +21,13 @@ 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;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public abstract class Handle implements java.io.Closeable {
+public abstract class Handle implements java.nio.channels.Channel {
+ private final AtomicBoolean closed = new AtomicBoolean(false);
private Path file;
protected Handle(Path file) {
@@ -37,8 +39,15 @@ public abstract class Handle implements java.io.Closeable {
}
@Override
+ public boolean isOpen() {
+ return !closed.get();
+ }
+
+ @Override
public void close() throws IOException {
- // ignored
+ if (!closed.getAndSet(true)) {
+ return; // debug breakpoint
+ }
}
@Override
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
new file mode 100644
index 0000000..a52f820
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
@@ -0,0 +1,267 @@
+/*
+ * 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.CopyOption;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.EventListener;
+import java.util.Map;
+
+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 EventListener {
+ /**
+ * 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
+ */
+ void initialized(ServerSession session, int version);
+
+ /**
+ * Called when subsystem is destroyed since it was closed
+ *
+ * @param session The associated {@link ServerSession}
+ */
+ void destroying(ServerSession session);
+
+ /**
+ * 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}
+ */
+ void open(ServerSession session, String remoteHandle, Handle localHandle);
+
+ /**
+ * 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
+ */
+ void read(ServerSession session, String remoteHandle, DirectoryHandle localHandle, Map<String, Path> entries);
+
+ /**
+ * 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
+ */
+ void read(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, byte[] data, int dataOffset, int dataLen, int readLen);
+
+ /**
+ * Result of writing to 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 to which to write
+ * @param data Buffer holding the written data
+ * @param dataOffset Offset of write data in buffer
+ * @param dataLen Requested write length
+ */
+ void write(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, byte[] data, int dataOffset, int dataLen);
+
+ /**
+ * 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
+ * @see #blocked(ServerSession, String, FileHandle, long, long, int, Throwable)
+ */
+ void blocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask);
+
+ /**
+ * 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
+ */
+ void blocked(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask, Throwable thrown);
+
+ /**
+ * 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
+ */
+ void unblocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length);
+
+ /**
+ * 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 result If successful (i.e., <tt>thrown</tt> is {@code null}, then whether
+ * section was un-blocked
+ * @param thrown If not-{@code null} then the reason for the failure to execute
+ */
+ void unblocked(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, Boolean result, Throwable thrown);
+
+ /**
+ * 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}
+ */
+ void close(ServerSession session, String remoteHandle, Handle localHandle);
+
+ /**
+ * 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
+ * @see #created(ServerSession, Path, Map, Throwable)
+ */
+ void creating(ServerSession session, Path path, Map<String, ?> attrs);
+
+ /**
+ * 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
+ */
+ void created(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown);
+
+ /**
+ * 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
+ * @see #moved(ServerSession, Path, Path, Collection, Throwable)
+ */
+ void moving(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts);
+
+ /**
+ * 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
+ */
+ void moved(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts, Throwable thrown);
+
+ /**
+ * 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
+ * @see #removed(ServerSession, Path, Throwable)
+ */
+ void removing(ServerSession session, Path path);
+
+ /**
+ * 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
+ */
+ void removed(ServerSession session, Path path, Throwable thrown);
+
+ /**
+ * 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
+ * @see #linked(ServerSession, Path, Path, boolean, Throwable)
+ */
+ void linking(ServerSession session, Path source, Path target, boolean symLink);
+
+ /**
+ * 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
+ */
+ void linked(ServerSession session, Path source, Path target, boolean symLink, Throwable thrown);
+
+ /**
+ * 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...
+ * @see #modifiedAttributes(ServerSession, Path, Map, Throwable)
+ */
+ void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs);
+
+ /**
+ * 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
+ */
+ void modifiedAttributes(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown);
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java
new file mode 100644
index 0000000..3f91033
--- /dev/null
+++ b/sshd-core/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/0c443af5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index eb06156..2b2e8dd 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -61,6 +61,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -78,6 +79,7 @@ import org.apache.sshd.common.subsystem.sftp.SftpConstants;
import org.apache.sshd.common.subsystem.sftp.extensions.SpaceAvailableExtensionInfo;
import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
+import org.apache.sshd.common.util.EventListenerUtils;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.Int2IntFunction;
import org.apache.sshd.common.util.OsUtils;
@@ -96,13 +98,16 @@ import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.SessionAware;
import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionHolder;
/**
* SFTP subsystem
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class SftpSubsystem extends AbstractLoggingBean implements Command, Runnable, SessionAware, FileSystemAware {
+public class SftpSubsystem
+ extends AbstractLoggingBean
+ implements Command, Runnable, SessionAware, FileSystemAware, ServerSessionHolder, SftpEventListenerManager {
/**
* Properties key for the maximum of available open handles per session.
@@ -138,7 +143,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
public static final String SFTP_VERSION = "sftp-version";
public static final int LOWER_SFTP_IMPL = SftpConstants.SFTP_V3; // Working implementation from v3
- public static final int HIGHER_SFTP_IMPL = SftpConstants.SFTP_V6; // .. up to
+ public static final int HIGHER_SFTP_IMPL = SftpConstants.SFTP_V6; // .. up to and including
public static final String ALL_SFTP_IMPL;
/**
@@ -245,7 +250,6 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
protected Random randomizer;
protected int fileHandleSize = DEFAULT_FILE_HANDLE_SIZE;
protected int maxFileHandleRounds = DEFAULT_FILE_HANDLE_ROUNDS;
- protected ServerSession session;
protected boolean closed;
protected ExecutorService executors;
protected boolean shutdownExecutor;
@@ -257,9 +261,12 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
protected int version;
protected final Map<String, byte[]> extensions = new HashMap<>();
protected final Map<String, Handle> handles = new HashMap<>();
-
protected final UnsupportedAttributePolicy unsupportedAttributePolicy;
+ private ServerSession serverSession;
+ private final Collection<SftpEventListener> sftpEventListeners = new CopyOnWriteArraySet<>();
+ private final SftpEventListener sftpEventListenerProxy;
+
/**
* @param executorService The {@link ExecutorService} to be used by
* the {@link SftpSubsystem} command when starting execution. If
@@ -280,10 +287,8 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
shutdownExecutor = shutdownOnExit;
}
- if (policy == null) {
- throw new IllegalArgumentException("No policy provided");
- }
- unsupportedAttributePolicy = policy;
+ unsupportedAttributePolicy = ValidateUtils.checkNotNull(policy, "No policy provided");
+ sftpEventListenerProxy = EventListenerUtils.proxyWrapper(SftpEventListener.class, getClass().getClassLoader(), sftpEventListeners);
}
public int getVersion() {
@@ -295,8 +300,23 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
@Override
+ public SftpEventListener getSftpEventListenerProxy() {
+ return sftpEventListenerProxy;
+ }
+
+ @Override
+ public boolean addSftpEventListener(SftpEventListener listener) {
+ return sftpEventListeners.add(ValidateUtils.checkNotNull(listener, "No listener"));
+ }
+
+ @Override
+ public boolean removeSftpEventListener(SftpEventListener listener) {
+ return sftpEventListeners.remove(listener);
+ }
+
+ @Override
public void setSession(ServerSession session) {
- this.session = session;
+ this.serverSession = session;
FactoryManager manager = session.getFactoryManager();
Factory<? extends Random> factory = manager.getRandomFactory();
@@ -316,6 +336,11 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
@Override
+ public ServerSession getServerSession() {
+ return serverSession;
+ }
+
+ @Override
public void setFileSystem(FileSystem fileSystem) {
if (fileSystem != this.fileSystem) {
this.fileSystem = fileSystem;
@@ -948,6 +973,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
protected void doVersionSelect(Buffer buffer, int id) throws IOException {
String proposed = buffer.getString();
+ ServerSession session = getServerSession();
/*
* The 'version-select' MUST be the first request from the client to the
* server; if it is not, the server MUST fail the request and close the
@@ -1030,6 +1056,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
int hig = HIGHER_SFTP_IMPL;
String available = ALL_SFTP_IMPL;
// check if user wants to use a specific version
+ ServerSession session = getServerSession();
Integer sftpVersion = PropertyResolverUtils.getInteger(session, SFTP_VERSION);
if (sftpVersion != null) {
int forcedValue = sftpVersion;
@@ -1043,7 +1070,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
if (log.isTraceEnabled()) {
log.trace("checkVersionCompatibility(id={}) - proposed={}, available={}",
- id, proposed, available);
+ id, proposed, available);
}
if ((proposed < low) || (proposed > hig)) {
@@ -1078,7 +1105,16 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
FileHandle fileHandle = validateHandle(handle, p, FileHandle.class);
- fileHandle.lock(offset, length, mask);
+ SftpEventListener listener = getSftpEventListenerProxy();
+ ServerSession session = getServerSession();
+ listener.blocking(session, handle, fileHandle, offset, length, mask);
+ try {
+ fileHandle.lock(offset, length, mask);
+ listener.blocked(session, handle, fileHandle, offset, length, mask, null);
+ } catch (IOException | RuntimeException e) {
+ listener.blocked(session, handle, fileHandle, offset, length, mask, e);
+ throw e;
+ }
}
protected void doUnblock(Buffer buffer, int id) throws IOException {
@@ -1104,7 +1140,17 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
FileHandle fileHandle = validateHandle(handle, p, FileHandle.class);
- return fileHandle.unlock(offset, length);
+ SftpEventListener listener = getSftpEventListenerProxy();
+ ServerSession session = getServerSession();
+ listener.unblocking(session, handle, fileHandle, offset, length);
+ try {
+ boolean result = fileHandle.unlock(offset, length);
+ listener.unblocked(session, handle, fileHandle, offset, length, Boolean.valueOf(result), null);
+ return result;
+ } catch (IOException | RuntimeException e) {
+ listener.unblocked(session, handle, fileHandle, offset, length, null, e);
+ throw e;
+ }
}
protected void doLink(Buffer buffer, int id) throws IOException {
@@ -1159,10 +1205,19 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
id, linkPath, link, targetPath, target, symLink);
}
- if (symLink) {
- Files.createSymbolicLink(link, target);
- } else {
- Files.createLink(link, target);
+ SftpEventListener listener = getSftpEventListenerProxy();
+ ServerSession session = getServerSession();
+ listener.linking(session, link, target, symLink);
+ try {
+ if (symLink) {
+ Files.createSymbolicLink(link, target);
+ } else {
+ Files.createLink(link, target);
+ }
+ listener.linked(session, link, target, symLink, null);
+ } catch (IOException | RuntimeException e) {
+ listener.linked(session, link, target, symLink, e);
+ throw e;
}
}
@@ -1231,7 +1286,17 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
protected void doRename(int id, String oldPath, String newPath, Collection<CopyOption> opts) throws IOException {
Path o = resolveFile(oldPath);
Path n = resolveFile(newPath);
- Files.move(o, n, GenericUtils.isEmpty(opts) ? IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()]));
+ SftpEventListener listener = getSftpEventListenerProxy();
+ ServerSession session = getServerSession();
+
+ listener.moving(session, o, n, opts);
+ try {
+ Files.move(o, n, GenericUtils.isEmpty(opts) ? IoUtils.EMPTY_COPY_OPTIONS : opts.toArray(new CopyOption[opts.size()]));
+ listener.moved(session, o, n, opts, null);
+ } catch (IOException | RuntimeException e) {
+ listener.moved(session, o, n, opts, e);
+ throw e;
+ }
}
// see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-7
@@ -1511,12 +1576,32 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
Path p = resolveFile(path);
log.debug("Received SSH_FXP_RMDIR (path={})[{}]", path, p);
if (Files.isDirectory(p, options)) {
- Files.delete(p);
+ doRemove(id, p);
} else {
throw new NotDirectoryException(p.toString());
}
}
+ /**
+ * Called when need to delete a file / directory - also informs the {@link SftpEventListener}
+ *
+ * @param id Deletion request ID
+ * @param p {@link Path} to delete
+ * @throws IOException If failed to delete
+ */
+ protected void doRemove(int id, Path p) throws IOException {
+ SftpEventListener listener = getSftpEventListenerProxy();
+ ServerSession session = getServerSession();
+ listener.removing(session, p);
+ try {
+ Files.delete(p);
+ listener.removed(session, p, null);
+ } catch (IOException | RuntimeException e) {
+ listener.removed(session, p, e);
+ throw e;
+ }
+ }
+
protected void doMakeDirectory(Buffer buffer, int id) throws IOException {
String path = buffer.getString();
Map<String, Object> attrs = readAttrs(buffer);
@@ -1548,8 +1633,17 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
throw new FileNotFoundException(p.toString() + " already exists as a file");
}
} else {
- Files.createDirectory(p);
- setAttributes(p, attrs);
+ SftpEventListener listener = getSftpEventListenerProxy();
+ ServerSession session = getServerSession();
+ listener.creating(session, p, attrs);
+ try {
+ Files.createDirectory(p);
+ doSetAttributes(p, attrs);
+ listener.created(session, p, attrs, null);
+ } catch (IOException | RuntimeException e) {
+ listener.created(session, p, attrs, e);
+ throw e;
+ }
}
}
@@ -1580,7 +1674,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
} else if (Files.isDirectory(p, options)) {
throw new FileNotFoundException(p.toString() + " is as a folder");
} else {
- Files.delete(p);
+ doRemove(id, p);
}
}
@@ -1622,7 +1716,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
int lenPos = reply.wpos();
reply.putInt(0);
- int count = doReadDir(id, dh, reply, PropertyResolverUtils.getIntProperty(session, MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH));
+ int count = doReadDir(id, handle, dh, reply, PropertyResolverUtils.getIntProperty(getServerSession(), MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH));
BufferUtils.updateLengthPlaceholder(reply, lenPos, count);
if (log.isDebugEnabled()) {
log.debug("doReadDir({})[{}] - sent {} entries", handle, h, count);
@@ -1677,7 +1771,10 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
throw new AccessDeniedException("Not readable: " + p);
} else {
String handle = generateFileHandle(p);
- handles.put(handle, new DirectoryHandle(p));
+ DirectoryHandle dirHandle = new DirectoryHandle(p);
+ SftpEventListener listener = getSftpEventListenerProxy();
+ listener.open(getServerSession(), handle, dirHandle);
+ handles.put(handle, dirHandle);
return handle;
}
}
@@ -1701,7 +1798,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
log.debug("Received SSH_FXP_FSETSTAT (handle={}[{}], attrs={})", handle, h, attrs);
}
- setAttributes(validateHandle(handle, h, Handle.class).getFile(), attrs);
+ doSetAttributes(validateHandle(handle, h, Handle.class).getFile(), attrs);
}
protected void doSetStat(Buffer buffer, int id) throws IOException {
@@ -1720,7 +1817,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
protected void doSetStat(int id, String path, Map<String, ?> attrs) throws IOException {
log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs);
Path p = resolveFile(path);
- setAttributes(p, attrs);
+ doSetAttributes(p, attrs);
}
protected void doFStat(Buffer buffer, int id) throws IOException {
@@ -1812,13 +1909,16 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
} else {
fh.write(data, doff, length, offset);
}
+
+ SftpEventListener listener = getSftpEventListenerProxy();
+ listener.write(getServerSession(), handle, fh, offset, data, doff, length);
}
protected void doRead(Buffer buffer, int id) throws IOException {
String handle = buffer.getString();
long offset = buffer.getLong();
int requestedLength = buffer.getInt();
- int maxAllowed = PropertyResolverUtils.getIntProperty(session, MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH);
+ int maxAllowed = PropertyResolverUtils.getIntProperty(getServerSession(), MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH);
int readLen = Math.min(requestedLength, maxAllowed);
if (log.isTraceEnabled()) {
@@ -1858,10 +1958,13 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
log.debug("Received SSH_FXP_READ (handle={}[{}], offset={}, length={})",
handle, h, offset, length);
}
- ValidateUtils.checkTrue(length > 0, "Invalid read length: %d", length);
- FileHandle fh = validateHandle(handle, h, FileHandle.class);
- return fh.read(data, doff, length, offset);
+ ValidateUtils.checkTrue(length > 0L, "Invalid read length: %d", length);
+ FileHandle fh = validateHandle(handle, h, FileHandle.class);
+ int readLen = fh.read(data, doff, length, offset);
+ SftpEventListener listener = getSftpEventListenerProxy();
+ listener.read(getServerSession(), handle, fh, offset, data, doff, length, readLen);
+ return readLen;
}
protected void doClose(Buffer buffer, int id) throws IOException {
@@ -1880,6 +1983,9 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
Handle h = handles.remove(handle);
log.debug("Received SSH_FXP_CLOSE (handle={}[{}])", handle, h);
validateHandle(handle, h, Handle.class).close();
+
+ SftpEventListener listener = getSftpEventListenerProxy();
+ listener.close(getServerSession(), handle, h);
}
protected void doOpen(Buffer buffer, int id) throws IOException {
@@ -1963,14 +2069,17 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
path, Integer.toHexString(access), Integer.toHexString(pflags), attrs);
}
int curHandleCount = handles.size();
- int maxHandleCount = PropertyResolverUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, DEFAULT_MAX_OPEN_HANDLES);
+ int maxHandleCount = PropertyResolverUtils.getIntProperty(getServerSession(), MAX_OPEN_HANDLES_PER_SESSION, DEFAULT_MAX_OPEN_HANDLES);
if (curHandleCount > maxHandleCount) {
throw new IllegalStateException("Too many open handles: current=" + curHandleCount + ", max.=" + maxHandleCount);
}
Path file = resolveFile(path);
String handle = generateFileHandle(file);
- handles.put(handle, new FileHandle(this, file, pflags, access, attrs));
+ FileHandle fileHandle = new FileHandle(this, file, pflags, access, attrs);
+ SftpEventListener listener = getSftpEventListenerProxy();
+ listener.open(getServerSession(), handle, fileHandle);
+ handles.put(handle, fileHandle);
return handle;
}
@@ -2005,6 +2114,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
if (GenericUtils.isEmpty(all)) { // i.e. validation failed
return;
}
+
version = id;
while (buffer.available() > 0) {
String name = buffer.getString();
@@ -2018,6 +2128,9 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
buffer.putInt(version);
appendExtensions(buffer, all);
+ SftpEventListener listener = getSftpEventListenerProxy();
+ listener.initialized(getServerSession(), version);
+
send(buffer);
}
@@ -2054,7 +2167,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
protected List<OpenSSHExtension> resolveOpenSSHExtensions() {
- String value = PropertyResolverUtils.getString(session, OPENSSH_EXTENSIONS_PROP);
+ String value = PropertyResolverUtils.getString(getServerSession(), OPENSSH_EXTENSIONS_PROP);
if (value == null) { // No override
return DEFAULT_OPEN_SSH_EXTENSIONS;
}
@@ -2083,7 +2196,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
protected Collection<String> getSupportedClientExtensions() {
- String value = PropertyResolverUtils.getString(session, CLIENT_EXTENSIONS_PROP);
+ String value = PropertyResolverUtils.getString(getServerSession(), CLIENT_EXTENSIONS_PROP);
if (value == null) {
return DEFAULT_SUPPORTED_CLIENT_EXTENSIONS;
}
@@ -2288,36 +2401,42 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
/**
* @param id Request id
+ * @param handle The (opaque) handle assigned to this directory
* @param dir The {@link DirectoryHandle}
* @param buffer The {@link Buffer} to write the results
* @param maxSize Max. buffer size
* @return Number of written entries
* @throws IOException If failed to generate an entry
*/
- protected int doReadDir(int id, DirectoryHandle dir, Buffer buffer, int maxSize) throws IOException {
+ protected int doReadDir(int id, String handle, DirectoryHandle dir, Buffer buffer, int maxSize) throws IOException {
int nb = 0;
LinkOption[] options = IoUtils.getLinkOptions(false);
+ Map<String, Path> entries = new TreeMap<>();
while ((dir.isSendDot() || dir.isSendDotDot() || dir.hasNext()) && (buffer.wpos() < maxSize)) {
if (dir.isSendDot()) {
- writeDirEntry(id, dir, buffer, nb, dir.getFile(), ".", options);
+ writeDirEntry(id, dir, entries, buffer, nb, dir.getFile(), ".", options);
dir.markDotSent(); // do not send it again
} else if (dir.isSendDotDot()) {
- writeDirEntry(id, dir, buffer, nb, dir.getFile().getParent(), "..", options);
+ writeDirEntry(id, dir, entries, buffer, nb, dir.getFile().getParent(), "..", options);
dir.markDotDotSent(); // do not send it again
} else {
Path f = dir.next();
- writeDirEntry(id, dir, buffer, nb, f, getShortName(f), options);
+ writeDirEntry(id, dir, entries, buffer, nb, f, getShortName(f), options);
}
nb++;
}
+ SftpEventListener listener = getSftpEventListenerProxy();
+ listener.read(getServerSession(), handle, dir, entries);
return nb;
}
/**
* @param id Request id
* @param dir The {@link DirectoryHandle}
+ * @param entries An in / out {@link Map} for updating the written entry -
+ * key = short name, value = entry {@link Path}
* @param buffer The {@link Buffer} to write the results
* @param index Zero-based index of the entry to be written
* @param f The entry {@link Path}
@@ -2325,8 +2444,10 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
* @param options The {@link LinkOption}s to use for querying the entry-s attributes
* @throws IOException If failed to generate the entry data
*/
- protected void writeDirEntry(int id, DirectoryHandle dir, Buffer buffer, int index, Path f, String shortName, LinkOption... options) throws IOException {
+ protected void writeDirEntry(int id, DirectoryHandle dir, Map<String, Path> entries, Buffer buffer, int index, Path f, String shortName, LinkOption... options)
+ throws IOException {
Map<String, ?> attrs = resolveFileAttributes(f, SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
+ entries.put(shortName, f);
buffer.putString(shortName);
if (version == SftpConstants.SFTP_V3) {
@@ -2634,7 +2755,20 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
return Collections.emptyMap();
}
- protected void setAttributes(Path file, Map<String, ?> attributes) throws IOException {
+ protected void doSetAttributes(Path file, Map<String, ?> attributes) throws IOException {
+ SftpEventListener listener = getSftpEventListenerProxy();
+ ServerSession session = getServerSession();
+ listener.modifyingAttributes(session, file, attributes);
+ try {
+ setFileAttributes(file, attributes);
+ listener.modifiedAttributes(session, file, attributes, null);
+ } catch (IOException | RuntimeException e) {
+ listener.modifiedAttributes(session, file, attributes, e);
+ throw e;
+ }
+ }
+
+ protected void setFileAttributes(Path file, Map<String, ?> attributes) throws IOException {
Set<String> unsupported = new HashSet<>();
for (String attribute : attributes.keySet()) {
String view = null;
@@ -2765,6 +2899,8 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
/**
+ * Makes sure that the local handle is not null and of the specified type
+ *
* @param <H> The generic handle type
* @param handle The original handle id
* @param h The resolved {@link Handle} instance
@@ -2824,6 +2960,13 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
closed = true;
+ try {
+ SftpEventListener listener = getSftpEventListenerProxy();
+ listener.destroying(getServerSession());
+ } catch (Exception e) {
+ log.warn("Failed (" + e.getClass().getSimpleName() + ") to announce destruction event: " + e.getMessage(), e);
+ }
+
// if thread has not completed, cancel it
if ((pendingFuture != null) && (!pendingFuture.isDone())) {
boolean result = pendingFuture.cancel(true);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
index ce235bf..bdc5d2e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
@@ -19,10 +19,13 @@
package org.apache.sshd.server.subsystem.sftp;
+import java.util.Collection;
import java.util.concurrent.ExecutorService;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ObjectBuilder;
+import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.subsystem.SubsystemFactory;
@@ -30,36 +33,49 @@ import org.apache.sshd.server.subsystem.SubsystemFactory;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class SftpSubsystemFactory implements SubsystemFactory, Cloneable, ExecutorServiceConfigurer {
+public class SftpSubsystemFactory extends AbstractSftpEventListenerManager implements SubsystemFactory, ExecutorServiceConfigurer, SftpEventListenerManager {
public static final String NAME = SftpConstants.SFTP_SUBSYSTEM_NAME;
public static final UnsupportedAttributePolicy DEFAULT_POLICY = UnsupportedAttributePolicy.Warn;
- public static class Builder implements ObjectBuilder<SftpSubsystemFactory> {
- private final SftpSubsystemFactory factory = new SftpSubsystemFactory();
+ public static class Builder extends AbstractSftpEventListenerManager implements ObjectBuilder<SftpSubsystemFactory> {
+ private ExecutorService executors;
+ private boolean shutdownExecutor;
+ private UnsupportedAttributePolicy policy = DEFAULT_POLICY;
public Builder() {
super();
}
public Builder withExecutorService(ExecutorService service) {
- factory.setExecutorService(service);
+ executors = service;
return this;
}
public Builder withShutdownOnExit(boolean shutdown) {
- factory.setShutdownOnExit(shutdown);
+ shutdownExecutor = shutdown;
return this;
}
public Builder withUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
- factory.setUnsupportedAttributePolicy(p);
+ policy = ValidateUtils.checkNotNull(p, "No policy");
return this;
}
@Override
public SftpSubsystemFactory build() {
- // return a clone so that each invocation returns a different instance - avoid shared instances
- return factory.clone();
+ SftpSubsystemFactory factory = new SftpSubsystemFactory();
+ factory.setExecutorService(executors);
+ factory.setShutdownOnExit(shutdownExecutor);
+ factory.setUnsupportedAttributePolicy(policy);
+
+ Collection<? extends SftpEventListener> listeners = getRegisteredListeners();
+ if (GenericUtils.size(listeners) > 0) {
+ for (SftpEventListener l : listeners) {
+ factory.addSftpEventListener(l);
+ }
+ }
+
+ return factory;
}
}
@@ -111,29 +127,22 @@ public class SftpSubsystemFactory implements SubsystemFactory, Cloneable, Execut
/**
* @param p The {@link UnsupportedAttributePolicy} to use if failed to access
- * some local file attributes
+ * some local file attributes - never {@code null}
*/
public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
- if (p == null) {
- throw new IllegalArgumentException("No policy provided");
- }
-
- policy = p;
+ policy = ValidateUtils.checkNotNull(p, "No policy");
}
@Override
public Command create() {
- return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy());
- }
-
- @Override
- public SftpSubsystemFactory clone() {
- try {
- return getClass().cast(super.clone()); // shallow clone is good enough
- } catch (CloneNotSupportedException e) {
- throw new UnsupportedOperationException("Unexpected clone exception", e); // unexpected since we implement cloneable
+ SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy());
+ Collection<? extends SftpEventListener> listeners = getRegisteredListeners();
+ if (GenericUtils.size(listeners) > 0) {
+ for (SftpEventListener l : listeners) {
+ subsystem.addSftpEventListener(l);
+ }
}
- }
-
+ return subsystem;
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
index 2833fb9..c743cad 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
@@ -22,7 +22,6 @@ package org.apache.sshd.client.subsystem.sftp;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
-import java.util.Arrays;
import java.util.Collections;
import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
@@ -59,7 +58,7 @@ public abstract class AbstractSftpClientTestSupport extends BaseTestSupport {
protected void setupServer() throws Exception {
sshd = setupTestServer();
- sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
+ sshd.setSubsystemFactories(Collections.<NamedFactory<Command>>singletonList(new SftpSubsystemFactory()));
sshd.setCommandFactory(new ScpCommandFactory());
sshd.setFileSystemFactory(fileSystemFactory);
sshd.start();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0c443af5/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index 96dc836..9a46f8e 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -18,11 +18,6 @@
*/
package org.apache.sshd.client.subsystem.sftp;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_FILE_ALREADY_EXISTS;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_NO_SUCH_FILE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRUSR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWUSR;
-
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -31,6 +26,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.nio.file.CopyOption;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.LinkOption;
@@ -46,6 +42,8 @@ import java.util.Set;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
@@ -55,6 +53,7 @@ import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensi
import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.file.FileSystemFactory;
import org.apache.sshd.common.random.Random;
@@ -66,9 +65,17 @@ import org.apache.sshd.common.subsystem.sftp.extensions.SupportedParser.Supporte
import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.session.ServerSession;
+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.SftpSubsystem;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.apache.sshd.util.test.JSchLogger;
import org.apache.sshd.util.test.SimpleUserInfo;
import org.apache.sshd.util.test.Utils;
@@ -79,6 +86,8 @@ import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runners.MethodSorters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
@@ -390,7 +399,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
// NOTE: on Windows files are always readable
int perms = sftp.stat(file).perms;
- int permsMask = S_IWUSR | (isWindows ? 0 : S_IRUSR);
+ int permsMask = SftpConstants.S_IWUSR | (isWindows ? 0 : SftpConstants.S_IRUSR);
assertEquals("Mismatched permissions for " + file + ": 0x" + Integer.toHexString(perms), 0, (perms & permsMask));
javaFile.setWritable(true, false);
@@ -443,6 +452,177 @@ public class SftpTest extends AbstractSftpClientTestSupport {
@Test
public void testClient() throws Exception {
+ List<NamedFactory<Command>> factories = sshd.getSubsystemFactories();
+ assertEquals("Mismatched subsystem factories count", 1, GenericUtils.size(factories));
+
+ NamedFactory<Command> f = factories.get(0);
+ assertObjectInstanceOf("Not an SFTP subsystem factory", SftpSubsystemFactory.class, f);
+
+ SftpSubsystemFactory factory = (SftpSubsystemFactory) f;
+ final AtomicInteger versionHolder = new AtomicInteger(-1);
+ final AtomicInteger openCount = new AtomicInteger(0);
+ final AtomicInteger closeCount = new AtomicInteger(0);
+ final AtomicLong readSize = new AtomicLong(0L);
+ final AtomicLong writeSize = new AtomicLong(0L);
+ final AtomicInteger entriesCount = new AtomicInteger(0);
+ final AtomicInteger creatingCount = new AtomicInteger(0);
+ final AtomicInteger createdCount = new AtomicInteger(0);
+ final AtomicInteger removingCount = new AtomicInteger(0);
+ final AtomicInteger removedCount = new AtomicInteger(0);
+ final AtomicInteger modifyingCount = new AtomicInteger(0);
+ final AtomicInteger modifiedCount = new AtomicInteger(0);
+ factory.addSftpEventListener(new SftpEventListener() {
+ private final Logger log = LoggerFactory.getLogger(SftpEventListener.class);
+
+ @Override
+ public void initialized(ServerSession session, int version) {
+ log.info("initialized(" + session + ") version: " + version);
+ assertTrue("Initialized version below minimum", version >= SftpSubsystem.LOWER_SFTP_IMPL);
+ assertTrue("Initialized version above maximum", version <= SftpSubsystem.HIGHER_SFTP_IMPL);
+ assertTrue("Initializion re-called", versionHolder.getAndSet(version) < 0);
+ }
+
+ @Override
+ public void destroying(ServerSession session) {
+ log.info("destroying(" + session + ")");
+ assertTrue("Initialization method not called", versionHolder.get() > 0);
+ }
+
+ @Override
+ public void write(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, byte[] data, int dataOffset, int dataLen) {
+ writeSize.addAndGet(dataLen);
+ if (log.isDebugEnabled()) {
+ log.debug("write(" + session + ")[" + localHandle.getFile() + "] offset=" + offset + ", requested=" + dataLen);
+ }
+ }
+
+ @Override
+ public void removing(ServerSession session, Path path) {
+ removingCount.incrementAndGet();
+ log.info("removing(" + session + ") " + path);
+ }
+
+ @Override
+ public void removed(ServerSession session, Path path, Throwable thrown) {
+ removedCount.incrementAndGet();
+ log.info("removed(" + session + ") " + path
+ + ((thrown == null) ? "" : (": " + thrown.getClass().getSimpleName() + ": " + thrown.getMessage())));
+ }
+
+ @Override
+ public void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs) {
+ modifyingCount.incrementAndGet();
+ log.info("modifyingAttributes(" + session + ") " + path);
+ }
+
+ @Override
+ public void modifiedAttributes(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) {
+ modifiedCount.incrementAndGet();
+ log.info("modifiedAttributes(" + session + ") " + path
+ + ((thrown == null) ? "" : (": " + thrown.getClass().getSimpleName() + ": " + thrown.getMessage())));
+ }
+
+ @Override
+ public void read(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, byte[] data, int dataOffset, int dataLen, int readLen) {
+ readSize.addAndGet(readLen);
+ if (log.isDebugEnabled()) {
+ log.debug("read(" + session + ")[" + localHandle.getFile() + "] offset=" + offset + ", requested=" + dataLen + ", read=" + readLen);
+ }
+ }
+
+ @Override
+ public void read(ServerSession session, String remoteHandle, DirectoryHandle localHandle, Map<String, Path> entries) {
+ int numEntries = GenericUtils.size(entries);
+ entriesCount.addAndGet(numEntries);
+
+ if (log.isDebugEnabled()) {
+ log.debug("read(" + session + ")[" + localHandle.getFile() + "] " + numEntries + " entries");
+ }
+
+ if ((numEntries > 0) && log.isTraceEnabled()) {
+ for (Map.Entry<String, Path> ee : entries.entrySet()) {
+ log.trace("read(" + session + ")[" + localHandle.getFile() + "] " + ee.getKey() + " - " + ee.getValue());
+ }
+ }
+ }
+
+ @Override
+ public void open(ServerSession session, String remoteHandle, Handle localHandle) {
+ Path path = localHandle.getFile();
+ log.info("open(" + session + ")[" + remoteHandle + "] " + (Files.isDirectory(path) ? "directory" : "file") + " " + path);
+ openCount.incrementAndGet();
+ }
+
+ @Override
+ public void moving(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts) {
+ log.info("moving(" + session + ")[" + opts + "]" + srcPath + " => " + dstPath);
+ }
+
+ @Override
+ public void moved(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts, Throwable thrown) {
+ log.info("moved(" + session + ")[" + opts + "]" + srcPath + " => " + dstPath
+ + ((thrown == null) ? "" : (": " + thrown.getClass().getSimpleName() + ": " + thrown.getMessage())));
+ }
+
+ @Override
+ public void linking(ServerSession session, Path src, Path target, boolean symLink) {
+ log.info("linking(" + session + ")[" + symLink + "]" + src + " => " + target);
+ }
+
+ @Override
+ public void linked(ServerSession session, Path src, Path target, boolean symLink, Throwable thrown) {
+ log.info("linked(" + session + ")[" + symLink + "]" + src + " => " + target
+ + ((thrown == null) ? "" : (": " + thrown.getClass().getSimpleName() + ": " + thrown.getMessage())));
+ }
+
+ @Override
+ public void creating(ServerSession session, Path path, Map<String, ?> attrs) {
+ creatingCount.incrementAndGet();
+ log.info("creating(" + session + ") " + (Files.isDirectory(path) ? "directory" : "file") + " " + path);
+ }
+
+ @Override
+ public void created(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) {
+ createdCount.incrementAndGet();
+ log.info("created(" + session + ") " + (Files.isDirectory(path) ? "directory" : "file") + " " + path
+ + ((thrown == null) ? "" : (": " + thrown.getClass().getSimpleName() + ": " + thrown.getMessage())));
+ }
+
+ @Override
+ public void blocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask) {
+ log.info("blocking(" + session + ")[" + localHandle.getFile() + "]"
+ + " offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask));
+ }
+
+ @Override
+ public void blocked(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, long length, int mask, Throwable thrown) {
+ log.info("blocked(" + session + ")[" + localHandle.getFile() + "]"
+ + " offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask)
+ + ((thrown == null) ? "" : (": " + thrown.getClass().getSimpleName() + ": " + thrown.getMessage())));
+ }
+
+ @Override
+ public void unblocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length) {
+ log.info("unblocking(" + session + ")[" + localHandle.getFile() + "] offset=" + offset + ", length=" + length);
+ }
+
+ @Override
+ public void unblocked(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, long length, Boolean result, Throwable thrown) {
+ log.info("unblocked(" + session + ")[" + localHandle.getFile() + "]"
+ + " offset=" + offset + ", length=" + length + ", result=" + result
+ + ((thrown == null) ? "" : (": " + thrown.getClass().getSimpleName() + ": " + thrown.getMessage())));
+ }
+
+ @Override
+ public void close(ServerSession session, String remoteHandle, Handle localHandle) {
+ Path path = localHandle.getFile();
+ log.info("close(" + session + ")[" + remoteHandle + "] " + (Files.isDirectory(path) ? "directory" : "file") + " " + path);
+ closeCount.incrementAndGet();
+ }
+ });
+
try (SshClient client = setupTestClient()) {
client.start();
@@ -451,8 +631,20 @@ public class SftpTest extends AbstractSftpClientTestSupport {
session.auth().verify(5L, TimeUnit.SECONDS);
try (SftpClient sftp = session.createSftpClient()) {
+ assertEquals("Mismatched negotiated version", sftp.getVersion(), versionHolder.get());
testClient(client, sftp);
}
+
+ assertEquals("Mismatched open/close count", openCount.get(), closeCount.get());
+ assertTrue("No entries read", entriesCount.get() > 0);
+ assertTrue("No data read", readSize.get() > 0L);
+ assertTrue("No data written", writeSize.get() > 0L);
+ assertEquals("Mismatched removal counts", removingCount.get(), removedCount.get());
+ assertTrue("No removals signalled", removedCount.get() > 0);
+ assertEquals("Mismatched creation counts", creatingCount.get(), createdCount.get());
+ assertTrue("No creations signalled", creatingCount.get() > 0);
+ assertEquals("Mismatched modification counts", modifyingCount.get(), modifiedCount.get());
+ assertTrue("No modifications signalled", modifiedCount.get() > 0);
} finally {
client.stop();
}
@@ -681,7 +873,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
sftp.rename(file2Path, file3Path);
fail("Unxpected rename success of " + file2Path + " => " + file3Path);
} catch (org.apache.sshd.client.subsystem.sftp.SftpException e) {
- assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path, SSH_FX_NO_SUCH_FILE, e.getStatus());
+ assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path, SftpConstants.SSH_FX_NO_SUCH_FILE, e.getStatus());
}
try (OutputStream os = sftp.write(file2Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
@@ -692,7 +884,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
sftp.rename(file1Path, file2Path);
fail("Unxpected rename success of " + file1Path + " => " + file2Path);
} catch (org.apache.sshd.client.subsystem.sftp.SftpException e) {
- assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path, SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
+ assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path, SftpConstants.SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
}
sftp.rename(file1Path, file2Path, SftpClient.CopyMode.Overwrite);
@@ -918,6 +1110,126 @@ public class SftpTest extends AbstractSftpClientTestSupport {
public void testCreateSymbolicLink() throws Exception {
// Do not execute on windows as the file system does not support symlinks
Assume.assumeTrue("Skip non-Unix O/S", OsUtils.isUNIX());
+ List<NamedFactory<Command>> factories = sshd.getSubsystemFactories();
+ assertEquals("Mismatched subsystem factories count", 1, GenericUtils.size(factories));
+
+ NamedFactory<Command> f = factories.get(0);
+ assertObjectInstanceOf("Not an SFTP subsystem factory", SftpSubsystemFactory.class, f);
+
+ SftpSubsystemFactory factory = (SftpSubsystemFactory) f;
+ final AtomicReference<LinkData> linkDataHolder = new AtomicReference<>();
+ factory.addSftpEventListener(new SftpEventListener() {
+ @Override
+ public void write(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, byte[] data, int dataOffset, int dataLen) {
+ // ignored
+ }
+
+ @Override
+ public void removing(ServerSession session, Path path) {
+ // ignored
+ }
+
+ @Override
+ public void removed(ServerSession session, Path path, Throwable thrown) {
+ // ignored
+ }
+
+ @Override
+ public void read(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, byte[] data, int dataOffset, int dataLen, int readLen) {
+ // ignored
+ }
+
+ @Override
+ public void read(ServerSession session, String remoteHandle, DirectoryHandle localHandle, Map<String, Path> entries) {
+ // ignored
+ }
+
+ @Override
+ public void open(ServerSession session, String remoteHandle, Handle localHandle) {
+ // ignored
+ }
+
+ @Override
+ public void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs) {
+ // ignored
+ }
+
+ @Override
+ public void modifiedAttributes(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) {
+ // ignored
+ }
+
+ @Override
+ public void blocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask) {
+ // ignored
+ }
+
+ @Override
+ public void blocked(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask, Throwable thrown) {
+ // ignored
+ }
+
+ @Override
+ public void unblocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length) {
+ // ignored
+ }
+
+ @Override
+ public void unblocked(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, long length, Boolean result, Throwable thrown) {
+ // ignored
+ }
+
+ @Override
+ public void moving(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts) {
+ // ignored
+ }
+
+ @Override
+ public void moved(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts, Throwable thrown) {
+ // ignored
+ }
+
+ @Override
+ public void linking(ServerSession session, Path src, Path target, boolean symLink) {
+ assertNull("Multiple linking calls", linkDataHolder.getAndSet(new LinkData(src, target, symLink)));
+ }
+
+ @Override
+ public void linked(ServerSession session, Path src, Path target, boolean symLink, Throwable thrown) {
+ LinkData data = linkDataHolder.get();
+ assertNotNull("No previous linking call", data);
+ assertSame("Mismatched source", data.getSource(), src);
+ assertSame("Mismatched target", data.getTarget(), target);
+ assertEquals("Mismatched link type", data.isSymLink(), symLink);
+ assertNull("Unexpected failure", thrown);
+ }
+
+ @Override
+ public void initialized(ServerSession session, int version) {
+ // ignored
+ }
+
+ @Override
+ public void destroying(ServerSession session) {
+ // ignored
+ }
+
+ @Override
+ public void creating(ServerSession session, Path path, Map<String, ?> attrs) {
+ // ignored
+ }
+
+ @Override
+ public void created(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) {
+ // ignored
+ }
+
+ @Override
+ public void close(ServerSession session, String remoteHandle, Handle localHandle) {
+ // ignored
+ }
+ });
Path targetPath = detectTargetFolder();
Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
@@ -956,6 +1268,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
Files.delete(linkPath);
}
assertFalse("Target link exists before linking: " + linkPath, Files.exists(linkPath, options));
+
c.symlink(remSrcPath, remLinkPath);
assertTrue("Symlink not created: " + linkPath, Files.exists(linkPath, options));
@@ -967,6 +1280,8 @@ public class SftpTest extends AbstractSftpClientTestSupport {
} finally {
c.disconnect();
}
+
+ assertNotNull("No symlink signalled", linkDataHolder.getAndSet(null));
}
protected String readFile(String path) throws Exception {
@@ -1005,4 +1320,33 @@ public class SftpTest extends AbstractSftpClientTestSupport {
}
return sb.toString();
}
+
+ static class LinkData {
+ private final Path source;
+ private final Path target;
+ private final boolean symLink;
+
+ LinkData(Path src, Path target, boolean symLink) {
+ this.source = ValidateUtils.checkNotNull(src, "No source");
+ this.target = ValidateUtils.checkNotNull(target, "No target");
+ this.symLink = symLink;
+ }
+
+ public Path getSource() {
+ return source;
+ }
+
+ public Path getTarget() {
+ return target;
+ }
+
+ public boolean isSymLink() {
+ return symLink;
+ }
+
+ @Override
+ public String toString() {
+ return (isSymLink() ? "Symbolic" : "Hard") + " " + getSource() + " => " + getTarget();
+ }
+ }
}