You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2018/04/16 11:47:54 UTC
[07/30] mina-sshd git commit: [SSHD-815] Extract SFTP in its own
module
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultGroupPrincipal.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultGroupPrincipal.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultGroupPrincipal.java
new file mode 100644
index 0000000..acf3118
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultGroupPrincipal.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.nio.file.attribute.GroupPrincipal;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultGroupPrincipal extends PrincipalBase implements GroupPrincipal {
+
+ public DefaultGroupPrincipal(String name) {
+ super(name);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultUserPrincipal.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultUserPrincipal.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultUserPrincipal.java
new file mode 100644
index 0000000..d71d772
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DefaultUserPrincipal.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.nio.file.attribute.UserPrincipal;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultUserPrincipal extends PrincipalBase implements UserPrincipal {
+
+ public DefaultUserPrincipal(String name) {
+ super(name);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
new file mode 100644
index 0000000..0ae60cf
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DirectoryHandle extends Handle implements Iterator<Path> {
+
+ private boolean done;
+ private boolean sendDotDot = true;
+ private boolean sendDot = true;
+ // the directory should be read once at "open directory"
+ private DirectoryStream<Path> ds;
+ private Iterator<Path> fileList;
+
+ public DirectoryHandle(SftpSubsystem subsystem, Path dir, String handle) throws IOException {
+ super(dir, handle);
+ signalHandleOpening(subsystem);
+
+ SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
+ ServerSession session = subsystem.getServerSession();
+ ds = accessor.openDirectory(session, subsystem, dir, handle);
+
+ Path parent = dir.getParent();
+ if (parent == null) {
+ sendDotDot = false; // if no parent then no need to send ".."
+ }
+ fileList = ds.iterator();
+
+ try {
+ signalHandleOpen(subsystem);
+ } catch (IOException e) {
+ close();
+ throw e;
+ }
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+
+ public void markDone() {
+ this.done = true;
+ // allow the garbage collector to do the job
+ this.fileList = null;
+ }
+
+ public boolean isSendDot() {
+ return sendDot;
+ }
+
+ public void markDotSent() {
+ sendDot = false;
+ }
+
+ public boolean isSendDotDot() {
+ return sendDotDot;
+ }
+
+ public void markDotDotSent() {
+ sendDotDot = false;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return fileList.hasNext();
+ }
+
+ @Override
+ public Path next() {
+ return fileList.next();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not allowed to remove " + toString());
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ markDone(); // just making sure
+ ds.close();
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
new file mode 100644
index 0000000..b499524
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileLock;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileAttribute;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class FileHandle extends Handle {
+ private final int access;
+ private final SeekableByteChannel fileChannel;
+ private final List<FileLock> locks = new ArrayList<>();
+ private final SftpSubsystem subsystem;
+ private final Set<StandardOpenOption> openOptions;
+ private final Collection<FileAttribute<?>> fileAttributes;
+
+ public FileHandle(SftpSubsystem subsystem, Path file, String handle, int flags, int access, Map<String, Object> attrs) throws IOException {
+ super(file, handle);
+ this.subsystem = Objects.requireNonNull(subsystem, "No subsystem instance provided");
+ this.access = access;
+ this.openOptions = Collections.unmodifiableSet(getOpenOptions(flags, access));
+ this.fileAttributes = Collections.unmodifiableCollection(toFileAttributes(attrs));
+ signalHandleOpening(subsystem);
+
+ FileAttribute<?>[] fileAttrs = GenericUtils.isEmpty(fileAttributes)
+ ? IoUtils.EMPTY_FILE_ATTRIBUTES
+ : fileAttributes.toArray(new FileAttribute<?>[fileAttributes.size()]);
+ SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
+ ServerSession session = subsystem.getServerSession();
+ SeekableByteChannel channel;
+ try {
+ channel = accessor.openFile(session, subsystem, file, handle, openOptions, fileAttrs);
+ } catch (UnsupportedOperationException e) {
+ channel = accessor.openFile(session, subsystem, file, handle, openOptions, IoUtils.EMPTY_FILE_ATTRIBUTES);
+ subsystem.doSetAttributes(file, attrs);
+ }
+ this.fileChannel = channel;
+
+ try {
+ signalHandleOpen(subsystem);
+ } catch (IOException e) {
+ close();
+ throw e;
+ }
+ }
+
+ public final Set<StandardOpenOption> getOpenOptions() {
+ return openOptions;
+ }
+
+ public final Collection<FileAttribute<?>> getFileAttributes() {
+ return fileAttributes;
+ }
+
+ public final SeekableByteChannel getFileChannel() {
+ return fileChannel;
+ }
+
+ public int getAccessMask() {
+ return access;
+ }
+
+ public boolean isOpenAppend() {
+ return SftpConstants.ACE4_APPEND_DATA == (getAccessMask() & SftpConstants.ACE4_APPEND_DATA);
+ }
+
+ public int read(byte[] data, long offset) throws IOException {
+ return read(data, 0, data.length, offset);
+ }
+
+ public int read(byte[] data, int doff, int length, long offset) throws IOException {
+ SeekableByteChannel channel = getFileChannel();
+ channel = channel.position(offset);
+ return channel.read(ByteBuffer.wrap(data, doff, length));
+ }
+
+ public void append(byte[] data) throws IOException {
+ append(data, 0, data.length);
+ }
+
+ public void append(byte[] data, int doff, int length) throws IOException {
+ SeekableByteChannel channel = getFileChannel();
+ write(data, doff, length, channel.size());
+ }
+
+ public void write(byte[] data, long offset) throws IOException {
+ write(data, 0, data.length, offset);
+ }
+
+ public void write(byte[] data, int doff, int length, long offset) throws IOException {
+ SeekableByteChannel channel = getFileChannel();
+ channel = channel.position(offset);
+ channel.write(ByteBuffer.wrap(data, doff, length));
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+
+ SeekableByteChannel channel = getFileChannel();
+ if (channel.isOpen()) {
+ channel.close();
+ }
+ }
+
+ public void lock(long offset, long length, int mask) throws IOException {
+ SeekableByteChannel channel = getFileChannel();
+ long size = (length == 0L) ? channel.size() - offset : length;
+ SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
+ ServerSession session = subsystem.getServerSession();
+ FileLock lock = accessor.tryLock(session, subsystem, getFile(), getFileHandle(), channel, offset, size, false);
+ if (lock == null) {
+ throw new SftpException(SftpConstants.SSH_FX_BYTE_RANGE_LOCK_REFUSED,
+ "Overlapping lock held by another program on range [" + offset + "-" + (offset + length));
+ }
+
+ synchronized (locks) {
+ locks.add(lock);
+ }
+ }
+
+ public void unlock(long offset, long length) throws IOException {
+ SeekableByteChannel channel = getFileChannel();
+ long size = (length == 0L) ? channel.size() - offset : length;
+ FileLock lock = null;
+ for (Iterator<FileLock> iterator = locks.iterator(); iterator.hasNext();) {
+ FileLock l = iterator.next();
+ if ((l.position() == offset) && (l.size() == size)) {
+ iterator.remove();
+ lock = l;
+ break;
+ }
+ }
+ if (lock == null) {
+ throw new SftpException(SftpConstants.SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK,
+ "No matching lock found on range [" + offset + "-" + (offset + length));
+ }
+
+ lock.release();
+ }
+
+ public static Collection<FileAttribute<?>> toFileAttributes(Map<String, Object> attrs) {
+ if (GenericUtils.isEmpty(attrs)) {
+ return Collections.emptyList();
+ }
+
+ Collection<FileAttribute<?>> attributes = null;
+ // Cannot use forEach because the referenced attributes variable is not effectively final
+ for (Map.Entry<String, Object> attr : attrs.entrySet()) {
+ FileAttribute<?> fileAttr = toFileAttribute(attr.getKey(), attr.getValue());
+ if (fileAttr == null) {
+ continue;
+ }
+ if (attributes == null) {
+ attributes = new LinkedList<>();
+ }
+ attributes.add(fileAttr);
+ }
+
+ return (attributes == null) ? Collections.emptyList() : attributes;
+ }
+
+ public static FileAttribute<?> toFileAttribute(String key, Object val) {
+ // Some ignored attributes sent by the SFTP client
+ if ("isOther".equals(key)) {
+ if ((Boolean) val) {
+ throw new IllegalArgumentException("Not allowed to use " + key + "=" + val);
+ }
+ return null;
+ } else if ("isRegular".equals(key)) {
+ if (!(Boolean) val) {
+ throw new IllegalArgumentException("Not allowed to use " + key + "=" + val);
+ }
+ return null;
+ }
+
+ return new FileAttribute<Object>() {
+ private final String s = key + "=" + val;
+
+ @Override
+ public String name() {
+ return key;
+ }
+
+ @Override
+ public Object value() {
+ return val;
+ }
+
+ @Override
+ public String toString() {
+ return s;
+ }
+ };
+ }
+
+ public static Set<StandardOpenOption> getOpenOptions(int flags, int access) {
+ Set<StandardOpenOption> options = EnumSet.noneOf(StandardOpenOption.class);
+ if (((access & SftpConstants.ACE4_READ_DATA) != 0) || ((access & SftpConstants.ACE4_READ_ATTRIBUTES) != 0)) {
+ options.add(StandardOpenOption.READ);
+ }
+ if (((access & SftpConstants.ACE4_WRITE_DATA) != 0) || ((access & SftpConstants.ACE4_WRITE_ATTRIBUTES) != 0)) {
+ options.add(StandardOpenOption.WRITE);
+ }
+
+ int accessDisposition = flags & SftpConstants.SSH_FXF_ACCESS_DISPOSITION;
+ switch (accessDisposition) {
+ case SftpConstants.SSH_FXF_CREATE_NEW:
+ options.add(StandardOpenOption.CREATE_NEW);
+ break;
+ case SftpConstants.SSH_FXF_CREATE_TRUNCATE:
+ options.add(StandardOpenOption.CREATE);
+ options.add(StandardOpenOption.TRUNCATE_EXISTING);
+ break;
+ case SftpConstants.SSH_FXF_OPEN_EXISTING:
+ break;
+ case SftpConstants.SSH_FXF_OPEN_OR_CREATE:
+ options.add(StandardOpenOption.CREATE);
+ break;
+ case SftpConstants.SSH_FXF_TRUNCATE_EXISTING:
+ options.add(StandardOpenOption.TRUNCATE_EXISTING);
+ break;
+ default: // ignored
+ }
+ if ((flags & SftpConstants.SSH_FXF_APPEND_DATA) != 0) {
+ options.add(StandardOpenOption.APPEND);
+ }
+
+ return options;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
new file mode 100644
index 0000000..a860eec
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class Handle implements java.nio.channels.Channel {
+ private final AtomicBoolean closed = new AtomicBoolean(false);
+ private final Path file;
+ private final String handle;
+
+ protected Handle(Path file, String handle) {
+ this.file = Objects.requireNonNull(file, "No local file path");
+ this.handle = ValidateUtils.checkNotNullAndNotEmpty(handle, "No assigned handle for %s", file);
+ }
+
+ protected void signalHandleOpening(SftpSubsystem subsystem) throws IOException {
+ SftpEventListener listener = subsystem.getSftpEventListenerProxy();
+ ServerSession session = subsystem.getServerSession();
+ listener.opening(session, handle, this);
+ }
+
+ protected void signalHandleOpen(SftpSubsystem subsystem) throws IOException {
+ SftpEventListener listener = subsystem.getSftpEventListenerProxy();
+ ServerSession session = subsystem.getServerSession();
+ listener.open(session, handle, this);
+ }
+
+ public Path getFile() {
+ return file;
+ }
+
+ public String getFileHandle() {
+ return handle;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return !closed.get();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (!closed.getAndSet(true)) {
+ //noinspection UnnecessaryReturnStatement
+ return; // debug breakpoint
+ }
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toString(getFile());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/InvalidHandleException.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/InvalidHandleException.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/InvalidHandleException.java
new file mode 100644
index 0000000..af7b147
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/InvalidHandleException.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class InvalidHandleException extends IOException {
+ private static final long serialVersionUID = -1686077114375131889L;
+
+ public InvalidHandleException(String handle, Handle h, Class<? extends Handle> expected) {
+ super(handle + "[" + h + "] is not a " + expected.getSimpleName());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/PrincipalBase.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/PrincipalBase.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/PrincipalBase.java
new file mode 100644
index 0000000..310c3b4
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/PrincipalBase.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.security.Principal;
+import java.util.Objects;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PrincipalBase implements Principal {
+
+ private final String name;
+
+ public PrincipalBase(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name is null");
+ }
+ this.name = name;
+ }
+
+ @Override
+ public final String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if ((o == null) || (getClass() != o.getClass())) {
+ return false;
+ }
+
+ Principal that = (Principal) o;
+ return Objects.equals(getName(), that.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getName());
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpErrorStatusDataHandler.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpErrorStatusDataHandler.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpErrorStatusDataHandler.java
new file mode 100644
index 0000000..1498ba2
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpErrorStatusDataHandler.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import org.apache.sshd.common.subsystem.sftp.SftpHelper;
+
+/**
+ * Invoked in order to format failed commands messages
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpErrorStatusDataHandler {
+ SftpErrorStatusDataHandler DEFAULT = new SftpErrorStatusDataHandler() {
+ @Override
+ public String toString() {
+ return SftpErrorStatusDataHandler.class.getSimpleName() + "[DEFAULT]";
+ }
+ };
+
+ /**
+ * @param sftpSubsystem The SFTP subsystem instance
+ * @param id The command identifier
+ * @param e Thrown exception
+ * @param cmd The command that was attempted
+ * @param args The relevant command arguments - <B>Note:</B> provided only for
+ * <U>logging</U> purposes and subject to type and/or order change at any version
+ * @return The relevant sub-status to send as failure indication for the failed command
+ * @see SftpHelper#resolveSubstatus(Throwable)
+ */
+ default int resolveSubStatus(SftpSubsystemEnvironment sftpSubsystem, int id, Throwable e, int cmd, Object... args) {
+ return SftpHelper.resolveSubstatus(e);
+ }
+
+ /**
+ * @param sftpSubsystem The SFTP subsystem instance
+ * @param id The command identifier
+ * @param e Thrown exception
+ * @param subStatus The sub-status code obtained from invocation of
+ * {@link #resolveSubStatus(SftpSubsystemEnvironment, int, Throwable, int, Object...) resolveSubStatus}
+ * @param cmd The command that was attempted
+ * @param args The relevant command arguments - <B>Note:</B> provided only for
+ * <U>logging</U> purposes and subject to type and/or order change at any version
+ * @return The human readable text message that explains the failure reason
+ * @see SftpHelper#resolveStatusMessage(int)
+ */
+ default String resolveErrorMessage(
+ SftpSubsystemEnvironment sftpSubsystem, int id, Throwable e, int subStatus, int cmd, Object... args) {
+ return SftpHelper.resolveStatusMessage(subStatus);
+ }
+
+ /**
+ * @param sftpSubsystem The SFTP subsystem instance
+ * @param id The command identifier
+ * @param e Thrown exception
+ * @param subStatus The sub-status code obtained from invocation of
+ * {@link #resolveSubStatus(SftpSubsystemEnvironment, int, Throwable, int, Object...) resolveSubStatus}
+ * @param cmd The command that was attempted
+ * @param args The relevant command arguments - <B>Note:</B> provided only for
+ * <U>logging</U> purposes and subject to type and/or order change at any version
+ * @return The error message language tag - recommend returning empty string
+ */
+ default String resolveErrorLanguage(
+ SftpSubsystemEnvironment sftpSubsystem, int id, Throwable e, int subStatus, int cmd, Object... args) {
+ return "";
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
new file mode 100644
index 0000000..c518af3
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
@@ -0,0 +1,396 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.io.IOException;
+import java.nio.file.CopyOption;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.sshd.common.util.SshdEventListener;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * Can be used register for SFTP events. <B>Note:</B> it does not expose
+ * the entire set of available SFTP commands and responses (e.g., no reports
+ * for initialization, extensions, parameters re-negotiation, etc...);
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpEventListener extends SshdEventListener {
+ /**
+ * Called when the SFTP protocol has been initialized
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param version The negotiated SFTP version
+ */
+ default void initialized(ServerSession session, int version) {
+ // ignored
+ }
+
+ /**
+ * Called when subsystem is destroyed since it was closed
+ *
+ * @param session The associated {@link ServerSession}
+ */
+ default void destroying(ServerSession session) {
+ // ignored
+ }
+
+ /**
+ * Specified file / directory is being opened
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file / directory
+ * @param localHandle The associated file / directory {@link Handle}
+ * @throws IOException If failed to handle the call
+ */
+ default void opening(ServerSession session, String remoteHandle, Handle localHandle)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Specified file / directory has been opened
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file / directory
+ * @param localHandle The associated file / directory {@link Handle}
+ * @throws IOException If failed to handle the call
+ */
+ default void open(ServerSession session, String remoteHandle, Handle localHandle)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Result of reading entries from a directory - <B>Note:</B> it may be a
+ * <U>partial</U> result if the directory contains more entries than can
+ * be accommodated in the response
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the directory
+ * @param localHandle The associated {@link DirectoryHandle}
+ * @param entries A {@link Map} of the listed entries - key = short name,
+ * value = {@link Path} of the sub-entry
+ * @throws IOException If failed to handle the call
+ */
+ default void read(ServerSession session, String remoteHandle, DirectoryHandle localHandle, Map<String, Path> entries)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Preparing to read from a file
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file
+ * @param localHandle The associated {@link FileHandle}
+ * @param offset Offset in file from which to read
+ * @param data Buffer holding the read data
+ * @param dataOffset Offset of read data in buffer
+ * @param dataLen Requested read length
+ * @throws IOException If failed to handle the call
+ */
+ default void reading(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, byte[] data, int dataOffset, int dataLen) throws IOException {
+ // ignored
+ }
+
+ /**
+ * Result of reading from a file
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file
+ * @param localHandle The associated {@link FileHandle}
+ * @param offset Offset in file from which to read
+ * @param data Buffer holding the read data
+ * @param dataOffset Offset of read data in buffer
+ * @param dataLen Requested read length
+ * @param readLen Actual read length - negative if thrown exception provided
+ * @param thrown Non-{@code null} if read failed due to this exception
+ * @throws IOException If failed to handle the call
+ */
+ default void read(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, byte[] data, int dataOffset, int dataLen, int readLen, Throwable thrown)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Preparing to write to file
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file
+ * @param localHandle The associated {@link FileHandle}
+ * @param offset Offset in file to which to write
+ * @param data Buffer holding the written data
+ * @param dataOffset Offset of write data in buffer
+ * @param dataLen Requested write length
+ * @throws IOException If failed to handle the call
+ */
+ default void writing(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, byte[] data, int dataOffset, int dataLen)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Finished to writing to file
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file
+ * @param localHandle The associated {@link FileHandle}
+ * @param offset Offset in file to which to write
+ * @param data Buffer holding the written data
+ * @param dataOffset Offset of write data in buffer
+ * @param dataLen Requested write length
+ * @param thrown The reason for failing to write - {@code null} if successful
+ * @throws IOException If failed to handle the call
+ */
+ default void written(ServerSession session, String remoteHandle, FileHandle localHandle,
+ long offset, byte[] data, int dataOffset, int dataLen, Throwable thrown)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>prior</U> to blocking a file section
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file
+ * @param localHandle The associated {@link FileHandle}
+ * @param offset Offset in file for locking
+ * @param length Section size for locking
+ * @param mask Lock mask flags - see {@code SSH_FXP_BLOCK} message
+ * @throws IOException If failed to handle the call
+ * @see #blocked(ServerSession, String, FileHandle, long, long, int, Throwable)
+ */
+ default void blocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>after</U> blocking a file section
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file
+ * @param localHandle The associated {@link FileHandle}
+ * @param offset Offset in file for locking
+ * @param length Section size for locking
+ * @param mask Lock mask flags - see {@code SSH_FXP_BLOCK} message
+ * @param thrown If not-{@code null} then the reason for the failure to execute
+ * @throws IOException If failed to handle the call
+ */
+ default void blocked(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, int mask, Throwable thrown)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>prior</U> to un-blocking a file section
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file
+ * @param localHandle The associated {@link FileHandle}
+ * @param offset Offset in file for un-locking
+ * @param length Section size for un-locking
+ * @throws IOException If failed to handle the call
+ */
+ default void unblocking(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>prior</U> to un-blocking a file section
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file
+ * @param localHandle The associated {@link FileHandle}
+ * @param offset Offset in file for un-locking
+ * @param length Section size for un-locking
+ * @param thrown If not-{@code null} then the reason for the failure to execute
+ * @throws IOException If failed to handle the call
+ */
+ default void unblocked(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, long length, Throwable thrown)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Specified file / directory has been closed
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param remoteHandle The (opaque) assigned handle for the file / directory
+ * @param localHandle The associated file / directory {@link Handle}
+ */
+ default void close(ServerSession session, String remoteHandle, Handle localHandle) {
+ // ignored
+ }
+
+ /**
+ * Called <U>prior</U> to creating a directory
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param path Directory {@link Path} to be created
+ * @param attrs Requested associated attributes to set
+ * @throws IOException If failed to handle the call
+ * @see #created(ServerSession, Path, Map, Throwable)
+ */
+ default void creating(ServerSession session, Path path, Map<String, ?> attrs)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>after</U> creating a directory
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param path Directory {@link Path} to be created
+ * @param attrs Requested associated attributes to set
+ * @param thrown If not-{@code null} then the reason for the failure to execute
+ * @throws IOException If failed to handle the call
+ */
+ default void created(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>prior</U> to renaming a file / directory
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param srcPath The source {@link Path}
+ * @param dstPath The target {@link Path}
+ * @param opts The resolved renaming options
+ * @throws IOException If failed to handle the call
+ * @see #moved(ServerSession, Path, Path, Collection, Throwable)
+ */
+ default void moving(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>after</U> renaming a file / directory
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param srcPath The source {@link Path}
+ * @param dstPath The target {@link Path}
+ * @param opts The resolved renaming options
+ * @param thrown If not-{@code null} then the reason for the failure to execute
+ * @throws IOException If failed to handle the call
+ */
+ default void moved(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts, Throwable thrown)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>prior</U> to removing a file / directory
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param path The {@link Path} about to be removed
+ * @throws IOException If failed to handle the call
+ * @see #removed(ServerSession, Path, Throwable)
+ */
+ default void removing(ServerSession session, Path path) throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>after</U> a file / directory has been removed
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param path The {@link Path} to be removed
+ * @param thrown If not-{@code null} then the reason for the failure to execute
+ * @throws IOException If failed to handle the call
+ */
+ default void removed(ServerSession session, Path path, Throwable thrown) throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>prior</U> to creating a link
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param source The source {@link Path}
+ * @param target The target {@link Path}
+ * @param symLink {@code true} = symbolic link
+ * @throws IOException If failed to handle the call
+ * @see #linked(ServerSession, Path, Path, boolean, Throwable)
+ */
+ default void linking(ServerSession session, Path source, Path target, boolean symLink)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>after</U> creating a link
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param source The source {@link Path}
+ * @param target The target {@link Path}
+ * @param symLink {@code true} = symbolic link
+ * @param thrown If not-{@code null} then the reason for the failure to execute
+ * @throws IOException If failed to handle the call
+ */
+ default void linked(ServerSession session, Path source, Path target, boolean symLink, Throwable thrown)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>prior</U> to modifying the attributes of a file / directory
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param path The file / directory {@link Path} to be modified
+ * @param attrs The attributes {@link Map} - names and values depend on the
+ * O/S, view, type, etc...
+ * @throws IOException If failed to handle the call
+ * @see #modifiedAttributes(ServerSession, Path, Map, Throwable)
+ */
+ default void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs)
+ throws IOException {
+ // ignored
+ }
+
+ /**
+ * Called <U>after</U> modifying the attributes of a file / directory
+ *
+ * @param session The {@link ServerSession} through which the request was handled
+ * @param path The file / directory {@link Path} to be modified
+ * @param attrs The attributes {@link Map} - names and values depend on the
+ * O/S, view, type, etc...
+ * @param thrown If not-{@code null} then the reason for the failure to execute
+ * @throws IOException If failed to handle the call
+ */
+ default void modifiedAttributes(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown)
+ throws IOException {
+ // ignored
+ }
+
+ static <L extends SftpEventListener> L validateListener(L listener) {
+ return SshdEventListener.validateListener(listener, SftpEventListener.class.getSimpleName());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java
new file mode 100644
index 0000000..3f91033
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListenerManager.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpEventListenerManager {
+ /**
+ * @return An instance representing <U>all</U> the currently
+ * registered listeners. Any method invocation is <U>replicated</U>
+ * to the actually registered listeners
+ */
+ SftpEventListener getSftpEventListenerProxy();
+
+ /**
+ * Register a listener instance
+ *
+ * @param listener The {@link SftpEventListener} instance to add - never {@code null}
+ * @return {@code true} if listener is a previously un-registered one
+ */
+ boolean addSftpEventListener(SftpEventListener listener);
+
+ /**
+ * Remove a listener instance
+ *
+ * @param listener The {@link SftpEventListener} instance to remove - never {@code null}
+ * @return {@code true} if listener is a (removed) registered one
+ */
+ boolean removeSftpEventListener(SftpEventListener listener);
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
new file mode 100644
index 0000000..86aa670
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessor.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.nio.channels.Channel;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileAttribute;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.FileInfoExtractor;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpFileSystemAccessor {
+ List<String> DEFAULT_UNIX_VIEW = Collections.singletonList("unix:*");
+
+ /**
+ * A {@link Map} of {@link FileInfoExtractor}s to be used to complete
+ * attributes that are deemed important enough to warrant an extra
+ * effort if not accessible via the file system attributes views
+ */
+ Map<String, FileInfoExtractor<?>> FILEATTRS_RESOLVERS =
+ GenericUtils.<String, FileInfoExtractor<?>>mapBuilder(String.CASE_INSENSITIVE_ORDER)
+ .put("isRegularFile", FileInfoExtractor.ISREG)
+ .put("isDirectory", FileInfoExtractor.ISDIR)
+ .put("isSymbolicLink", FileInfoExtractor.ISSYMLINK)
+ .put("permissions", FileInfoExtractor.PERMISSIONS)
+ .put("size", FileInfoExtractor.SIZE)
+ .put("lastModifiedTime", FileInfoExtractor.LASTMODIFIED)
+ .immutable();
+
+ SftpFileSystemAccessor DEFAULT = new SftpFileSystemAccessor() {
+ @Override
+ public String toString() {
+ return SftpFileSystemAccessor.class.getSimpleName() + "[DEFAULT]";
+ }
+ };
+
+ /**
+ * Called whenever a new file is opened
+ *
+ * @param session The {@link ServerSession} through which the request was received
+ * @param subsystem The SFTP subsystem instance that manages the session
+ * @param file The requested <U>local</U> file {@link Path}
+ * @param handle The assigned file handle through which the remote peer references this file.
+ * May be {@code null}/empty if the request is due to some internal functionality
+ * instead of due to peer requesting a handle to a file.
+ * @param options The requested {@link OpenOption}s
+ * @param attrs The requested {@link FileAttribute}s
+ * @return The opened {@link SeekableByteChannel}
+ * @throws IOException If failed to open
+ */
+ default SeekableByteChannel openFile(
+ ServerSession session, SftpEventListenerManager subsystem,
+ Path file, String handle, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
+ throws IOException {
+ return FileChannel.open(file, options, attrs);
+ }
+
+ /**
+ * Called when locking a section of a file is requested
+ *
+ * @param session The {@link ServerSession} through which the request was received
+ * @param subsystem The SFTP subsystem instance that manages the session
+ * @param file The requested <U>local</U> file {@link Path}
+ * @param handle The assigned file handle through which the remote peer references this file
+ * @param channel The original {@link Channel} that was returned by {@link #openFile(ServerSession, SftpEventListenerManager, Path, String, Set, FileAttribute...)}
+ * @param position The position at which the locked region is to start - must be non-negative
+ * @param size The size of the locked region; must be non-negative, and the sum
+ * <tt>position</tt> + <tt>size</tt> must be non-negative
+ * @param shared {@code true} to request a shared lock, {@code false} to request an exclusive lock
+ * @return A lock object representing the newly-acquired lock, or {@code null}
+ * if the lock could not be acquired because another program holds an overlapping lock
+ * @throws IOException If failed to honor the request
+ * @see FileChannel#tryLock(long, long, boolean)
+ */
+ default FileLock tryLock(ServerSession session, SftpEventListenerManager subsystem,
+ Path file, String handle, Channel channel, long position, long size, boolean shared)
+ throws IOException {
+ if (!(channel instanceof FileChannel)) {
+ throw new StreamCorruptedException("Non file channel to lock: " + channel);
+ }
+
+ return ((FileChannel) channel).lock(position, size, shared);
+ }
+
+ /**
+ * Called when file meta-data re-synchronization is required
+ *
+ * @param session The {@link ServerSession} through which the request was received
+ * @param subsystem The SFTP subsystem instance that manages the session
+ * @param file The requested <U>local</U> file {@link Path}
+ * @param handle The assigned file handle through which the remote peer references this file
+ * @param channel The original {@link Channel} that was returned by {@link #openFile(ServerSession, SftpEventListenerManager, Path, String, Set, FileAttribute...)}
+ * @throws IOException If failed to execute the request
+ * @see FileChannel#force(boolean)
+ * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH - section 10</A>
+ */
+ default void syncFileData(ServerSession session, SftpEventListenerManager subsystem,
+ Path file, String handle, Channel channel)
+ throws IOException {
+ if (!(channel instanceof FileChannel)) {
+ throw new StreamCorruptedException("Non file channel to sync: " + channel);
+ }
+
+ ((FileChannel) channel).force(true);
+ }
+
+ /**
+ * Called when a new directory stream is requested
+ *
+ * @param session The {@link ServerSession} through which the request was received
+ * @param subsystem The SFTP subsystem instance that manages the session
+ * @param dir The requested <U>local</U> directory
+ * @param handle The assigned directory handle through which the remote peer references this directory
+ * @return The opened {@link DirectoryStream}
+ * @throws IOException If failed to open
+ */
+ default DirectoryStream<Path> openDirectory(
+ ServerSession session, SftpEventListenerManager subsystem, Path dir, String handle)
+ throws IOException {
+ return Files.newDirectoryStream(dir);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
new file mode 100644
index 0000000..616f9ce
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpFileSystemAccessorManager.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpFileSystemAccessorManager {
+ SftpFileSystemAccessor getFileSystemAccessor();
+
+ void setFileSystemAccessor(SftpFileSystemAccessor accessor);
+}