Repository: mina-sshd Updated Branches: refs/heads/master 8c9fe09eb -> 1448b79ba
[SSHD-702] Add support for an SftpFileChannelOpener Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/1448b79b Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/1448b79b Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/1448b79b Branch: refs/heads/master Commit: 1448b79ba5848c8cb12bf87c8a1d6369b514fafa Parents: 8c9fe09 Author: Lyor Goldstein <[email protected]> Authored: Sun Oct 2 12:39:45 2016 +0300 Committer: Lyor Goldstein <[email protected]> Committed: Sun Oct 2 12:39:45 2016 +0300 ---------------------------------------------------------------------- README.md | 15 +++ .../server/subsystem/sftp/DirectoryHandle.java | 14 +- .../sshd/server/subsystem/sftp/FileHandle.java | 40 +++--- .../sshd/server/subsystem/sftp/Handle.java | 14 +- .../subsystem/sftp/SftpFileSystemAccessor.java | 133 +++++++++++++++++++ .../sftp/SftpFileSystemAccessorManager.java | 28 ++++ .../server/subsystem/sftp/SftpSubsystem.java | 33 +++-- .../subsystem/sftp/SftpSubsystemFactory.java | 37 ++++-- .../sshd/client/subsystem/sftp/SftpTest.java | 94 +++++++++++++ .../client/subsystem/sftp/SftpVersionsTest.java | 4 +- .../SpaceAvailableExtensionImplTest.java | 2 +- .../openssh/helpers/OpenSSHExtensionsTest.java | 2 +- 12 files changed, 369 insertions(+), 47 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 7c1cdf4..290fc68 100644 --- a/README.md +++ b/README.md @@ -406,6 +406,7 @@ the version, and all we can do at the server is require a **specific** version v configuration key. For more advanced restrictions on needs to sub-class `SftpSubSystem` and provide a non-default `SftpSubsystemFactory` that uses the sub-classed code. + ### Using `SftpFileSystemProvider` to create an `SftpFileSystem` @@ -495,6 +496,20 @@ configuration keys and values. ``` +#### Tracking accessed location via `SftpFileSystemAccessor` + +One can override the default `SftpFileSystemAccessor` and thus be able to track all opened files and folders +throughout the SFTP server subsystem code. The accessor is registered/overwritten in via the `SftpSubSystemFactory`: + +```java + + SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder() + .withFileSystemAccessor(new MySftpFileSystemAccessor()) + .build(); + server.setSubsystemFactories(Collections.singletonList(factory)); + +``` + ### Supported SFTP extensions http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java index 0319f6a..82e691e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java @@ -20,10 +20,11 @@ package org.apache.sshd.server.subsystem.sftp; import java.io.IOException; import java.nio.file.DirectoryStream; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; +import org.apache.sshd.server.session.ServerSession; + /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ @@ -36,11 +37,14 @@ public class DirectoryHandle extends Handle implements Iterator<Path> { private DirectoryStream<Path> ds; private Iterator<Path> fileList; - public DirectoryHandle(Path file) throws IOException { - super(file); - ds = Files.newDirectoryStream(file); + public DirectoryHandle(SftpSubsystem subsystem, Path dir, String handle) throws IOException { + super(dir, handle); + + SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor(); + ServerSession session = subsystem.getServerSession(); + ds = accessor.openDirectory(session, subsystem, dir, handle); - Path parent = file.getParent(); + Path parent = dir.getParent(); if (parent == null) { sendDotDot = false; // if no parent then no need to send ".." } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java index 075665a..fe3aa1e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java @@ -20,8 +20,8 @@ package org.apache.sshd.server.subsystem.sftp; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; import java.nio.channels.FileLock; +import java.nio.channels.SeekableByteChannel; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileAttribute; @@ -32,12 +32,14 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.apache.sshd.common.subsystem.sftp.SftpConstants; import org.apache.sshd.common.subsystem.sftp.SftpException; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.server.session.ServerSession; /** @@ -46,11 +48,13 @@ import org.apache.sshd.common.util.io.IoUtils; public class FileHandle extends Handle { private final int access; - private final FileChannel fileChannel; + private final SeekableByteChannel fileChannel; private final List<FileLock> locks = new ArrayList<>(); + private final SftpSubsystem subsystem; - public FileHandle(SftpSubsystem sftpSubsystem, Path file, int flags, int access, Map<String, Object> attrs) throws IOException { - super(file); + public FileHandle(SftpSubsystem subsystem, Path file, String handle, int flags, int access, Map<String, Object> attrs) throws IOException { + super(file, handle); + this.subsystem = Objects.requireNonNull(subsystem, "No subsystem instance provided"); this.access = access; Set<StandardOpenOption> options = EnumSet.noneOf(StandardOpenOption.class); @@ -99,17 +103,19 @@ public class FileHandle extends Handle { FileAttribute<?>[] fileAttrs = GenericUtils.isEmpty(attributes) ? IoUtils.EMPTY_FILE_ATTRIBUTES : attributes.toArray(new FileAttribute<?>[attributes.size()]); - FileChannel channel; + SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor(); + ServerSession session = subsystem.getServerSession(); + SeekableByteChannel channel; try { - channel = FileChannel.open(file, options, fileAttrs); + channel = accessor.openFile(session, subsystem, file, handle, options, fileAttrs); } catch (UnsupportedOperationException e) { - channel = FileChannel.open(file, options); - sftpSubsystem.doSetAttributes(file, attrs); + channel = accessor.openFile(session, subsystem, file, handle, options, IoUtils.EMPTY_FILE_ATTRIBUTES); + subsystem.doSetAttributes(file, attrs); } this.fileChannel = channel; } - public final FileChannel getFileChannel() { + public final SeekableByteChannel getFileChannel() { return fileChannel; } @@ -126,7 +132,7 @@ public class FileHandle extends Handle { } public int read(byte[] data, int doff, int length, long offset) throws IOException { - FileChannel channel = getFileChannel(); + SeekableByteChannel channel = getFileChannel(); channel.position(offset); long size = channel.size(); @@ -149,7 +155,7 @@ public class FileHandle extends Handle { } public void append(byte[] data, int doff, int length) throws IOException { - FileChannel channel = getFileChannel(); + SeekableByteChannel channel = getFileChannel(); write(data, doff, length, channel.size()); } @@ -158,7 +164,7 @@ public class FileHandle extends Handle { } public void write(byte[] data, int doff, int length, long offset) throws IOException { - FileChannel channel = getFileChannel(); + SeekableByteChannel channel = getFileChannel(); channel.position(offset); channel.write(ByteBuffer.wrap(data, doff, length)); } @@ -167,16 +173,18 @@ public class FileHandle extends Handle { public void close() throws IOException { super.close(); - FileChannel channel = getFileChannel(); + SeekableByteChannel channel = getFileChannel(); if (channel.isOpen()) { channel.close(); } } public void lock(long offset, long length, int mask) throws IOException { - FileChannel channel = getFileChannel(); + SeekableByteChannel channel = getFileChannel(); long size = (length == 0L) ? channel.size() - offset : length; - FileLock lock = channel.tryLock(offset, size, false); + SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor(); + ServerSession session = subsystem.getServerSession(); + FileLock lock = accessor.tryLock(session, subsystem, getFile(), getFileHandle(), channel, offset, size, false); if (lock == null) { throw new SftpException(SftpConstants.SSH_FX_BYTE_RANGE_LOCK_REFUSED, "Overlapping lock held by another program on range [" + offset + "-" + (offset + length)); @@ -188,7 +196,7 @@ public class FileHandle extends Handle { } public void unlock(long offset, long length) throws IOException { - FileChannel channel = getFileChannel(); + SeekableByteChannel channel = getFileChannel(); long size = (length == 0L) ? channel.size() - offset : length; FileLock lock = null; for (Iterator<FileLock> iterator = locks.iterator(); iterator.hasNext();) { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java index 4b9547d..ad166a9 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java @@ -23,21 +23,29 @@ import java.nio.file.Path; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.sshd.common.util.ValidateUtils; + /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ public abstract class Handle implements java.nio.channels.Channel { private final AtomicBoolean closed = new AtomicBoolean(false); - private Path file; + private final Path file; + private final String handle; - protected Handle(Path file) { - this.file = file; + protected Handle(Path file, String handle) { + this.file = Objects.requireNonNull(file, "No local file path"); + this.handle = ValidateUtils.checkNotNullAndNotEmpty(handle, "No assigned handle for %s", file); } public Path getFile() { return file; } + public String getFileHandle() { + return handle; + } + @Override public boolean isOpen() { return !closed.get(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java new file mode 100644 index 0000000..8d4499e --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.server.subsystem.sftp; + +import java.io.IOException; +import java.io.StreamCorruptedException; +import java.nio.channels.Channel; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.util.Set; + +import org.apache.sshd.server.session.ServerSession; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SftpFileSystemAccessor { + SftpFileSystemAccessor DEFAULT = new SftpFileSystemAccessor() { + @Override + public String toString() { + return SftpFileSystemAccessor.class.getSimpleName() + "[DEFAULT]"; + } + }; + + /** + * Called whenever a new file is opened + * + * @param session The {@link ServerSession} through which the request was received + * @param subsystem The SFTP subsystem instance that manages the session + * @param file The requested <U>local</U> file {@link Path} + * @param handle The assigned file handle through which the remote peer references this file. + * May be {@code null}/empty if the request is due to some internal functionality + * instead of due to peer requesting a handle to a file. + * @param options The requested {@link OpenOption}s + * @param attrs The requested {@link FileAttribute}s + * @return The opened {@link SeekableByteChannel} + * @throws IOException If failed to open + */ + default SeekableByteChannel openFile( + ServerSession session, SftpEventListenerManager subsystem, + Path file, String handle, Set<? extends OpenOption> options, FileAttribute<?>... attrs) + throws IOException { + return FileChannel.open(file, options, attrs); + } + + /** + * Called when locking a section of a file is requested + * + * @param session The {@link ServerSession} through which the request was received + * @param subsystem The SFTP subsystem instance that manages the session + * @param file The requested <U>local</U> file {@link Path} + * @param handle The assigned file handle through which the remote peer references this file + * @param channel The original {@link Channel} that was returned by {@link #openFile(ServerSession, SftpEventListenerManager, Path, String, Set, FileAttribute...)} + * @param position The position at which the locked region is to start - must be non-negative + * @param size The size of the locked region; must be non-negative, and the sum + * <tt>position</tt> + <tt>size</tt> must be non-negative + * @param shared {@code true} to request a shared lock, {@code false} to request an exclusive lock + * @return A lock object representing the newly-acquired lock, or {@code null} + * if the lock could not be acquired because another program holds an overlapping lock + * @throws IOException If failed to honor the request + * @see FileChannel#tryLock(long, long, boolean) + */ + default FileLock tryLock(ServerSession session, SftpEventListenerManager subsystem, + Path file, String handle, Channel channel, long position, long size, boolean shared) + throws IOException { + if (!(channel instanceof FileChannel)) { + throw new StreamCorruptedException("Non file channel to lock: " + channel); + } + + return ((FileChannel) channel).lock(position, size, shared); + } + + /** + * Called when file meta-data re-synchronization is required + * + * @param session The {@link ServerSession} through which the request was received + * @param subsystem The SFTP subsystem instance that manages the session + * @param file The requested <U>local</U> file {@link Path} + * @param handle The assigned file handle through which the remote peer references this file + * @param channel The original {@link Channel} that was returned by {@link #openFile(ServerSession, SftpEventListenerManager, Path, String, Set, FileAttribute...)} + * @throws IOException If failed to execute the request + * @see FileChannel#force(boolean) + * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 10</A> + */ + default void syncFileData(ServerSession session, SftpEventListenerManager subsystem, + Path file, String handle, Channel channel) + throws IOException { + if (!(channel instanceof FileChannel)) { + throw new StreamCorruptedException("Non file channel to sync: " + channel); + } + + ((FileChannel) channel).force(true); + } + + /** + * Called when a new directory stream is requested + * + * @param session The {@link ServerSession} through which the request was received + * @param subsystem The SFTP subsystem instance that manages the session + * @param dir The requested <U>local</U> directory + * @param handle The assigned directory handle through which the remote peer references this directory + * @return The opened {@link DirectoryStream} + * @throws IOException If failed to open + */ + default DirectoryStream<Path> openDirectory( + ServerSession session, SftpEventListenerManager subsystem, Path dir, String handle) + throws IOException { + return Files.newDirectoryStream(dir); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java new file mode 100644 index 0000000..9619794 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.server.subsystem.sftp; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SftpFileSystemAccessorManager { + SftpFileSystemAccessor getFileSystemAccessor(); + void setFileSystemAccessor(SftpFileSystemAccessor accessor); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java index 9377172..dc1de62 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java @@ -26,7 +26,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.StreamCorruptedException; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.AccessDeniedException; import java.nio.file.CopyOption; @@ -57,6 +57,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -280,6 +281,7 @@ public class SftpSubsystem private final Collection<SftpEventListener> sftpEventListeners = EventListenerUtils.synchronizedListenersSet(); private final SftpEventListener sftpEventListenerProxy; + private final SftpFileSystemAccessor fileSystemAccessor; /** * @param executorService The {@link ExecutorService} to be used by @@ -290,9 +292,10 @@ public class SftpSubsystem * service, which will be shutdown regardless * @param policy The {@link UnsupportedAttributePolicy} to use if failed to access * some local file attributes + * @param accessor The {@link SftpFileSystemAccessor} to use for opening files and directories * @see ThreadUtils#newSingleThreadExecutor(String) */ - public SftpSubsystem(ExecutorService executorService, boolean shutdownOnExit, UnsupportedAttributePolicy policy) { + public SftpSubsystem(ExecutorService executorService, boolean shutdownOnExit, UnsupportedAttributePolicy policy, SftpFileSystemAccessor accessor) { if (executorService == null) { executors = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName()); shutdownExecutor = true; // we always close the ad-hoc executor service @@ -301,7 +304,8 @@ public class SftpSubsystem shutdownExecutor = shutdownOnExit; } - unsupportedAttributePolicy = ValidateUtils.checkNotNull(policy, "No policy provided"); + unsupportedAttributePolicy = Objects.requireNonNull(policy, "No policy provided"); + fileSystemAccessor = Objects.requireNonNull(accessor, "No accessor"); sftpEventListenerProxy = EventListenerUtils.proxyWrapper(SftpEventListener.class, getClass().getClassLoader(), sftpEventListeners); } @@ -313,6 +317,10 @@ public class SftpSubsystem return unsupportedAttributePolicy; } + public final SftpFileSystemAccessor getFileSystemAccessor() { + return fileSystemAccessor; + } + @Override public SftpEventListener getSftpEventListenerProxy() { return sftpEventListenerProxy; @@ -689,8 +697,9 @@ public class SftpSubsystem } FileHandle fileHandle = validateHandle(handle, h, FileHandle.class); - FileChannel channel = fileHandle.getFileChannel(); - channel.force(false); + SftpFileSystemAccessor accessor = getFileSystemAccessor(); + ServerSession session = getServerSession(); + accessor.syncFileData(session, this, fileHandle.getFile(), fileHandle.getFileHandle(), fileHandle.getFileChannel()); } protected void doCheckFileHash(Buffer buffer, int id, String targetType) throws IOException { @@ -794,7 +803,8 @@ public class SftpSubsystem ? new byte[Math.min((int) effectiveLength, IoUtils.DEFAULT_COPY_SIZE)] : new byte[Math.min((int) effectiveLength, blockSize)]; ByteBuffer wb = ByteBuffer.wrap(digestBuf); - try (FileChannel channel = FileChannel.open(file, IoUtils.EMPTY_OPEN_OPTIONS)) { + SftpFileSystemAccessor accessor = getFileSystemAccessor(); + try (SeekableByteChannel channel = accessor.openFile(getServerSession(), this, file, "", Collections.emptySet())) { channel.position(startOffset); Digest digest = factory.create(); @@ -948,8 +958,8 @@ public class SftpSubsystem ByteBuffer wb = ByteBuffer.wrap(digestBuf); boolean hashMatches = false; byte[] hashValue = null; - - try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) { + SftpFileSystemAccessor accessor = getFileSystemAccessor(); + try (SeekableByteChannel channel = accessor.openFile(getServerSession(), this, path, null, EnumSet.of(StandardOpenOption.READ))) { channel.position(startOffset); /* @@ -1878,7 +1888,7 @@ public class SftpSubsystem throw new AccessDeniedException("Not readable: " + p); } else { String handle = generateFileHandle(p); - DirectoryHandle dirHandle = new DirectoryHandle(p); + DirectoryHandle dirHandle = new DirectoryHandle(this, p, handle); SftpEventListener listener = getSftpEventListenerProxy(); listener.open(getServerSession(), handle, dirHandle); handles.put(handle, dirHandle); @@ -2205,7 +2215,7 @@ public class SftpSubsystem Path file = resolveFile(path); String handle = generateFileHandle(file); - FileHandle fileHandle = new FileHandle(this, file, pflags, access, attrs); + FileHandle fileHandle = new FileHandle(this, file, handle, pflags, access, attrs); SftpEventListener listener = getSftpEventListenerProxy(); listener.open(getServerSession(), handle, fileHandle); handles.put(handle, fileHandle); @@ -2985,7 +2995,8 @@ public class SftpSubsystem switch (attribute) { case "size": { long newSize = ((Number) value).longValue(); - try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE)) { + SftpFileSystemAccessor accessor = getFileSystemAccessor(); + try (SeekableByteChannel channel = accessor.openFile(getServerSession(), this, file, null, EnumSet.of(StandardOpenOption.WRITE))) { channel.truncate(newSize); } continue; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java index 3fe2721..f5bd228 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java @@ -19,12 +19,12 @@ package org.apache.sshd.server.subsystem.sftp; +import java.util.Objects; import java.util.concurrent.ExecutorService; import org.apache.sshd.common.subsystem.sftp.SftpConstants; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ObjectBuilder; -import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer; import org.apache.sshd.server.Command; import org.apache.sshd.server.subsystem.SubsystemFactory; @@ -32,7 +32,9 @@ import org.apache.sshd.server.subsystem.SubsystemFactory; /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public class SftpSubsystemFactory extends AbstractSftpEventListenerManager implements SubsystemFactory, ExecutorServiceConfigurer, SftpEventListenerManager { +public class SftpSubsystemFactory + extends AbstractSftpEventListenerManager + implements SubsystemFactory, ExecutorServiceConfigurer, SftpEventListenerManager, SftpFileSystemAccessorManager { public static final String NAME = SftpConstants.SFTP_SUBSYSTEM_NAME; public static final UnsupportedAttributePolicy DEFAULT_POLICY = UnsupportedAttributePolicy.Warn; @@ -40,6 +42,7 @@ public class SftpSubsystemFactory extends AbstractSftpEventListenerManager imple private ExecutorService executors; private boolean shutdownExecutor; private UnsupportedAttributePolicy policy = DEFAULT_POLICY; + private SftpFileSystemAccessor fileSystemAccessor = SftpFileSystemAccessor.DEFAULT; public Builder() { super(); @@ -56,7 +59,12 @@ public class SftpSubsystemFactory extends AbstractSftpEventListenerManager imple } public Builder withUnsupportedAttributePolicy(UnsupportedAttributePolicy p) { - policy = ValidateUtils.checkNotNull(p, "No policy"); + policy = Objects.requireNonNull(p, "No policy"); + return this; + } + + public Builder withFileSystemAccessor(SftpFileSystemAccessor accessor) { + fileSystemAccessor = Objects.requireNonNull(accessor, "No accessor"); return this; } @@ -66,6 +74,7 @@ public class SftpSubsystemFactory extends AbstractSftpEventListenerManager imple factory.setExecutorService(executors); factory.setShutdownOnExit(shutdownExecutor); factory.setUnsupportedAttributePolicy(policy); + factory.setFileSystemAccessor(fileSystemAccessor); GenericUtils.forEach(getRegisteredListeners(), factory::addSftpEventListener); return factory; } @@ -74,6 +83,7 @@ public class SftpSubsystemFactory extends AbstractSftpEventListenerManager imple private ExecutorService executors; private boolean shutdownExecutor; private UnsupportedAttributePolicy policy = DEFAULT_POLICY; + private SftpFileSystemAccessor fileSystemAccessor = SftpFileSystemAccessor.DEFAULT; public SftpSubsystemFactory() { super(); @@ -91,7 +101,7 @@ public class SftpSubsystemFactory extends AbstractSftpEventListenerManager imple /** * @param service The {@link ExecutorService} to be used by the {@link SftpSubsystem} - * command when starting execution. If {@code null} then a single-threaded ad-hoc service is used. + * command when starting execution. If {@code null} then a single-threaded ad-hoc service is used. */ @Override public void setExecutorService(ExecutorService service) { @@ -105,7 +115,7 @@ public class SftpSubsystemFactory extends AbstractSftpEventListenerManager imple /** * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} - * will be called when subsystem terminates - unless it is the ad-hoc service, which + * will be called when subsystem terminates - unless it is the ad-hoc service, which * will be shutdown regardless */ @Override @@ -119,15 +129,26 @@ public class SftpSubsystemFactory extends AbstractSftpEventListenerManager imple /** * @param p The {@link UnsupportedAttributePolicy} to use if failed to access - * some local file attributes - never {@code null} + * some local file attributes - never {@code null} */ public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy p) { - policy = ValidateUtils.checkNotNull(p, "No policy"); + policy = Objects.requireNonNull(p, "No policy"); + } + + @Override + public SftpFileSystemAccessor getFileSystemAccessor() { + return fileSystemAccessor; + } + + @Override + public void setFileSystemAccessor(SftpFileSystemAccessor accessor) { + fileSystemAccessor = Objects.requireNonNull(accessor, "No accessor"); } @Override public Command create() { - SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy()); + SftpSubsystem subsystem = + new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy(), getFileSystemAccessor()); GenericUtils.forEach(getRegisteredListeners(), subsystem::addSftpEventListener); return subsystem; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java index d2633d3..007206a 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java @@ -29,14 +29,18 @@ import java.net.SocketTimeoutException; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.LinkOption; +import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.FileAttribute; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -45,6 +49,7 @@ import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.Vector; @@ -58,7 +63,9 @@ import com.jcraft.jsch.JSch; import org.apache.sshd.client.SshClient; import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes; import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle; +import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry; import org.apache.sshd.client.subsystem.sftp.SftpClient.OpenMode; import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions; import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension; @@ -92,6 +99,8 @@ import org.apache.sshd.server.subsystem.sftp.DirectoryHandle; import org.apache.sshd.server.subsystem.sftp.FileHandle; import org.apache.sshd.server.subsystem.sftp.Handle; import org.apache.sshd.server.subsystem.sftp.SftpEventListener; +import org.apache.sshd.server.subsystem.sftp.SftpEventListenerManager; +import org.apache.sshd.server.subsystem.sftp.SftpFileSystemAccessor; import org.apache.sshd.server.subsystem.sftp.SftpSubsystem; import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory; import org.apache.sshd.util.test.SimpleUserInfo; @@ -516,6 +525,91 @@ public class SftpTest extends AbstractSftpClientTestSupport { } @Test + public void testSftpFileSystemAccessor() throws Exception { + List<NamedFactory<Command>> factories = sshd.getSubsystemFactories(); + assertEquals("Mismatched subsystem factories count", 1, GenericUtils.size(factories)); + + NamedFactory<Command> f = factories.get(0); + assertObjectInstanceOf("Not an SFTP subsystem factory", SftpSubsystemFactory.class, f); + + SftpSubsystemFactory factory = (SftpSubsystemFactory) f; + SftpFileSystemAccessor accessor = factory.getFileSystemAccessor(); + try { + AtomicReference<Path> fileHolder = new AtomicReference<>(); + AtomicReference<Path> dirHolder = new AtomicReference<>(); + factory.setFileSystemAccessor(new SftpFileSystemAccessor() { + @Override + public SeekableByteChannel openFile(ServerSession session, SftpEventListenerManager subsystem, Path file, + String handle, Set<? extends OpenOption> options, FileAttribute<?>... attrs) + throws IOException { + fileHolder.set(file); + return SftpFileSystemAccessor.super.openFile(session, subsystem, file, handle, options, attrs); + } + + @Override + public DirectoryStream<Path> openDirectory( + ServerSession session, SftpEventListenerManager subsystem, Path dir, String handle) throws IOException { + dirHolder.set(dir); + return SftpFileSystemAccessor.super.openDirectory(session, subsystem, dir, handle); + } + + @Override + public String toString() { + return SftpFileSystemAccessor.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } + }); + + Path targetPath = detectTargetFolder(); + Path parentPath = targetPath.getParent(); + Path localFile = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); + Files.createDirectories(localFile.getParent()); + byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "[" + localFile + "]").getBytes(StandardCharsets.UTF_8); + Files.write(localFile, expected, StandardOpenOption.CREATE); + try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) { + session.addPasswordIdentity(getCurrentTestName()); + session.auth().verify(5L, TimeUnit.SECONDS); + + try (SftpClient sftp = session.createSftpClient()) { + byte[] actual = new byte[expected.length]; + try (InputStream stream = sftp.read(Utils.resolveRelativeRemotePath(parentPath, localFile), OpenMode.Read)) { + IoUtils.readFully(stream, actual); + } + + Path remoteFile = fileHolder.getAndSet(null); + assertNotNull("No remote file holder value", remoteFile); + assertEquals("Mismatched opened local files", localFile.toFile(), remoteFile.toFile()); + assertArrayEquals("Mismatched retrieved file contents", expected, actual); + + Path localParent = localFile.getParent(); + String localName = Objects.toString(localFile.getFileName(), null); + try (CloseableHandle handle = sftp.openDir(Utils.resolveRelativeRemotePath(parentPath, localParent))) { + List<DirEntry> entries = sftp.readDir(handle); + Path remoteParent = dirHolder.getAndSet(null); + assertNotNull("No remote folder holder value", remoteParent); + assertEquals("Mismatched opened folder", localParent.toFile(), remoteParent.toFile()); + assertFalse("No dir entries", GenericUtils.isEmpty(entries)); + + for (DirEntry de : entries) { + Attributes attrs = de.getAttributes(); + if (!attrs.isRegularFile()) { + continue; + } + + if (localName.equals(de.getFilename())) { + return; + } + } + + fail("Cannot find listing of " + localName); + } + } + } + } finally { + factory.setFileSystemAccessor(accessor); // restore original + } + } + + @Test @SuppressWarnings({"checkstyle:anoninnerlength", "checkstyle:methodlength"}) public void testClient() throws Exception { List<NamedFactory<Command>> factories = sshd.getSubsystemFactories(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java index 58d2eed..ce911c1 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java @@ -196,7 +196,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport { SftpSubsystemFactory factory = new SftpSubsystemFactory() { @Override public Command create() { - SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy()) { + SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy(), getFileSystemAccessor()) { @Override protected Map<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options) throws IOException { Map<String, Object> attrs = super.resolveFileAttributes(file, flags, options); @@ -314,7 +314,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport { SftpSubsystemFactory factory = new SftpSubsystemFactory() { @Override public Command create() { - SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy()) { + SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy(), getFileSystemAccessor()) { @Override protected Map<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options) throws IOException { Map<String, Object> attrs = super.resolveFileAttributes(file, flags, options); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java index 9fb6775..3470adb 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java @@ -71,7 +71,7 @@ public class SpaceAvailableExtensionImplTest extends AbstractSftpClientTestSuppo sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() { @Override public Command create() { - return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy()) { + return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy(), getFileSystemAccessor()) { @Override protected SpaceAvailableExtensionInfo doSpaceAvailable(int id, String path) throws IOException { if (!queryPath.equals(path)) { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1448b79b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java index 8557375..8799b14 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java @@ -124,7 +124,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport { sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() { @Override public Command create() { - return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy()) { + return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy(), getFileSystemAccessor()) { @Override protected List<OpenSSHExtension> resolveOpenSSHExtensions(ServerSession session) { List<OpenSSHExtension> original = super.resolveOpenSSHExtensions(session);
