You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/07/01 12:57:42 UTC
[01/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Repository: mina-sshd
Updated Branches:
refs/heads/master 82791d41b -> a6e2bf9e4
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/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
new file mode 100644
index 0000000..053aad9
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -0,0 +1,652 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_FILE_ALREADY_EXISTS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_NO_SUCH_FILE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRUSR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWUSR;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Random;
+import java.util.Vector;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.root.RootedFileSystemProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.JSchLogger;
+import org.apache.sshd.util.SimpleUserInfo;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.SftpException;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SftpTest extends BaseTestSupport {
+
+ private SshServer sshd;
+ private int port;
+ private com.jcraft.jsch.Session session;
+ private final FileSystemFactory fileSystemFactory;
+
+ public SftpTest() throws IOException {
+ Path targetPath = detectTargetFolder().toPath();
+ Path parentPath = targetPath.getParent();
+ final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
+ fileSystemFactory = new FileSystemFactory() {
+ @Override
+ public FileSystem createFileSystem(Session session) throws IOException {
+ return fileSystem;
+ }
+ };
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ sshd = SshServer.setUpDefaultServer();
+ sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+ sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
+ sshd.setCommandFactory(new ScpCommandFactory());
+ sshd.setShellFactory(new EchoShellFactory());
+ sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+ sshd.setFileSystemFactory(fileSystemFactory);
+ sshd.start();
+ port = sshd.getPort();
+
+ JSchLogger.init();
+ JSch sch = new JSch();
+ session = sch.getSession("sshd", "localhost", port);
+ session.setUserInfo(new SimpleUserInfo("sshd"));
+ session.connect();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (session != null) {
+ session.disconnect();
+ }
+
+ if (sshd != null) {
+ sshd.stop(true);
+ }
+ }
+
+ @Test
+ @Ignore
+ public void testExternal() throws Exception {
+ System.out.println("SFTP subsystem available on port " + port);
+ Thread.sleep(5 * 60000);
+ }
+
+ @Test
+ public void testOpen() throws Exception {
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ Path targetPath = detectTargetFolder().toPath();
+ Path parentPath = targetPath.getParent();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Path clientFolder = lclSftp.resolve("client");
+ Path testFile = clientFolder.resolve(getCurrentTestName() + ".txt");
+ String file = Utils.resolveRelativeRemotePath(parentPath, testFile);
+
+ File javaFile = testFile.toFile();
+ assertHierarchyTargetFolderExists(javaFile.getParentFile());
+ javaFile.createNewFile();
+ javaFile.setWritable(false, false);
+ javaFile.setReadable(false, false);
+
+ try (SftpClient sftp = session.createSftpClient()) {
+ boolean isWindows = OsUtils.isWin32();
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
+ // NOTE: on Windows files are always readable
+ // see https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java
+ assertTrue("Empty read should have failed on " + file, isWindows);
+ } catch (IOException e) {
+ if (isWindows) {
+ throw e;
+ }
+ }
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
+ fail("Empty write should have failed on " + file);
+ } catch (IOException e) {
+ // ok
+ }
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate))) {
+ // NOTE: on Windows files are always readable
+ assertTrue("Empty truncate should have failed on " + file, isWindows);
+ } catch (IOException e) {
+ // ok
+ }
+
+ // NOTE: on Windows files are always readable
+ int perms=sftp.stat(file).perms;
+ int permsMask=S_IWUSR | (isWindows ? 0 : S_IRUSR);
+ assertEquals("Mismatched permissions for " + file + ": 0x" + Integer.toHexString(perms), 0, (perms & permsMask));
+
+ javaFile.setWritable(true, false);
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write))) {
+ // OK should succeed
+ assertTrue("Handle not marked as open for file=" + file, h.isOpen());
+ }
+
+ byte[] d = "0123456789\n".getBytes();
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
+ sftp.write(h, 0, d, 0, d.length);
+ sftp.write(h, d.length, d, 0, d.length);
+ }
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
+ sftp.write(h, d.length * 2, d, 0, d.length);
+ }
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
+ sftp.write(h, 3, "-".getBytes(), 0, 1);
+ }
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
+ // NOTE: on Windows files are always readable
+ assertTrue("Data read should have failed on " + file, isWindows);
+ } catch (IOException e) {
+ if (isWindows) {
+ throw e;
+ }
+ }
+
+ javaFile.setReadable(true, false);
+
+ byte[] buf = new byte[3];
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
+ int l = sftp.read(h, 2l, buf, 0, 3);
+ assertEquals("Mismatched read data", "2-4", new String(buf, 0, l));
+ }
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test
+ public void testClient() throws Exception {
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
+ String file = dir + "/" + getCurrentTestName() + ".txt";
+
+ try (SftpClient sftp = session.createSftpClient()) {
+ sftp.mkdir(dir);
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
+ byte[] d = "0123456789\n".getBytes();
+ sftp.write(h, 0, d, 0, d.length);
+ sftp.write(h, d.length, d, 0, d.length);
+
+ SftpClient.Attributes attrs = sftp.stat(h);
+ assertNotNull("No handle attributes", attrs);
+ }
+
+ try(SftpClient.CloseableHandle h = sftp.openDir(dir)) {
+ SftpClient.DirEntry[] dirEntries = sftp.readDir(h);
+ assertNotNull("No dir entries", dirEntries);
+ assertEquals("Mismatced number of dir entries", 1, dirEntries.length);
+ assertNull("Unexpected entry read", sftp.readDir(h));
+ }
+
+ sftp.remove(file);
+
+ byte[] workBuf = new byte[IoUtils.DEFAULT_COPY_SIZE * Short.SIZE];
+ new Random(System.currentTimeMillis()).nextBytes(workBuf);
+ try (OutputStream os = sftp.write(file)) {
+ os.write(workBuf);
+ }
+
+ try (InputStream is = sftp.read(file, IoUtils.DEFAULT_COPY_SIZE)) {
+ int readLen = is.read(workBuf);
+ assertEquals("Mismatched read data length", workBuf.length, readLen);
+
+ int i = is.read();
+ assertEquals("Unexpected read past EOF", -1, i);
+ }
+
+ SftpClient.Attributes attributes = sftp.stat(file);
+ assertTrue("Test file not detected as regular", attributes.isRegularFile());
+
+ attributes = sftp.stat(dir);
+ assertTrue("Test directory not reported as such", attributes.isDirectory());
+
+ int nb = 0;
+ for (SftpClient.DirEntry entry : sftp.readDir(dir)) {
+ assertNotNull("Unexpected null entry", entry);
+ nb++;
+ }
+ assertEquals("Mismatched read dir entries", 1, nb);
+
+ sftp.remove(file);
+
+ sftp.rmdir(dir);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ /**
+ * this test is meant to test out write's logic, to ensure that internal chunking (based on Buffer.MAX_LEN) is
+ * functioning properly. To do this, we write a variety of file sizes, both smaller and larger than Buffer.MAX_LEN.
+ * in addition, this test ensures that improper arguments passed in get caught with an IllegalArgumentException
+ * @throws Exception upon any uncaught exception or failure
+ */
+ @Test
+ public void testWriteChunking() throws Exception {
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
+
+ try(SftpClient sftp = session.createSftpClient()) {
+ sftp.mkdir(dir);
+
+ uploadAndVerifyFile(sftp, clientFolder, dir, 0, "emptyFile.txt");
+ uploadAndVerifyFile(sftp, clientFolder, dir, 1000, "smallFile.txt");
+ uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN - 1, "bufferMaxLenMinusOneFile.txt");
+ uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN, "bufferMaxLenFile.txt");
+ // were chunking not implemented, these would fail. these sizes should invoke our internal chunking mechanism
+ uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN + 1, "bufferMaxLenPlusOneFile.txt");
+ uploadAndVerifyFile(sftp, clientFolder, dir, (int)(1.5 * ByteArrayBuffer.MAX_LEN), "1point5BufferMaxLenFile.txt");
+ uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) - 1, "2TimesBufferMaxLenMinusOneFile.txt");
+ uploadAndVerifyFile(sftp, clientFolder, dir, 2 * ByteArrayBuffer.MAX_LEN, "2TimesBufferMaxLenFile.txt");
+ uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) + 1, "2TimesBufferMaxLenPlusOneFile.txt");
+ uploadAndVerifyFile(sftp, clientFolder, dir, 200000, "largerFile.txt");
+
+ // test erroneous calls that check for negative values
+ Path invalidPath = clientFolder.resolve(getCurrentTestName() + "-invalid");
+ testInvalidParams(sftp, invalidPath, Utils.resolveRelativeRemotePath(parentPath, invalidPath));
+
+ // cleanup
+ sftp.rmdir(dir);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ private void testInvalidParams(SftpClient sftp, Path file, String filePath) throws Exception {
+ // generate random file and upload it
+ String randomData = randomString(5);
+ byte[] randomBytes = randomData.getBytes();
+ try(SftpClient.CloseableHandle handle = sftp.open(filePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
+ try {
+ sftp.write(handle, -1, randomBytes, 0, 0);
+ fail("should not have been able to write file with invalid file offset for " + filePath);
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ sftp.write(handle, 0, randomBytes, -1, 0);
+ fail("should not have been able to write file with invalid source offset for " + filePath);
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ sftp.write(handle, 0, randomBytes, 0, -1);
+ fail("should not have been able to write file with invalid length for " + filePath);
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ sftp.write(handle, 0, randomBytes, 0, randomBytes.length + 1);
+ fail("should not have been able to write file with length bigger than array itself (no offset) for " + filePath);
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ sftp.write(handle, 0, randomBytes, randomBytes.length, 1);
+ fail("should not have been able to write file with length bigger than array itself (with offset) for " + filePath);
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ sftp.remove(filePath);
+ assertFalse("File should not be there: " + file.toString(), Files.exists(file));
+ }
+
+ private void uploadAndVerifyFile(SftpClient sftp, Path clientFolder, String remoteDir, int size, String filename) throws Exception {
+ // generate random file and upload it
+ String remotePath = remoteDir + "/" + filename;
+ String randomData = randomString(size);
+ try(SftpClient.CloseableHandle handle = sftp.open(remotePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
+ sftp.write(handle, 0, randomData.getBytes(), 0, randomData.length());
+ }
+
+ // verify results
+ Path resultPath = clientFolder.resolve(filename);
+ assertTrue("File should exist on disk: " + resultPath, Files.exists(resultPath));
+ assertTrue("Mismatched file contents: " + resultPath, randomData.equals(readFile(remotePath)));
+
+ // cleanup
+ sftp.remove(remotePath);
+ assertFalse("File should have been removed: " + resultPath, Files.exists(resultPath));
+ }
+
+ @Test
+ public void testSftp() throws Exception {
+ String d = getCurrentTestName() + "\n";
+
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Path target = lclSftp.resolve(getCurrentTestName() + ".txt");
+ String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), target);
+
+ final int NUM_ITERATIONS=10;
+ StringBuilder sb = new StringBuilder(d.length() * NUM_ITERATIONS * NUM_ITERATIONS);
+ for (int j = 1; j <= NUM_ITERATIONS; j++) {
+ if (sb.length() > 0) {
+ sb.setLength(0);
+ }
+
+ for (int i = 0; i < j; i++) {
+ sb.append(d);
+ }
+
+ sendFile(remotePath, sb.toString());
+ assertFileLength(target, sb.length(), 5000);
+ Files.delete(target);
+ }
+ }
+
+ @Test
+ public void testReadWriteWithOffset() throws Exception {
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Path localPath = lclSftp.resolve(getCurrentTestName() + ".txt");
+ String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), localPath);
+ String data = getCurrentTestName();
+ String extraData = "@" + getClass().getSimpleName();
+ int appendOffset = -5;
+
+ ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+ c.connect();
+ try {
+ c.put(new ByteArrayInputStream(data.getBytes()), remotePath);
+
+ assertTrue("Remote file not created after initial write: " + localPath, Files.exists(localPath));
+ assertEquals("Mismatched data read from " + remotePath, data, readFile(remotePath));
+
+ try(OutputStream os = c.put(remotePath, null, ChannelSftp.APPEND, appendOffset)) {
+ os.write(extraData.getBytes());
+ }
+ } finally {
+ c.disconnect();
+ }
+
+ assertTrue("Remote file not created after data update: " + localPath, Files.exists(localPath));
+
+ String expected = data.substring(0, data.length() + appendOffset) + extraData;
+ String actual = readFile(remotePath);
+ assertEquals("Mismatched final file data in " + remotePath, expected, actual);
+ }
+
+ @Test
+ public void testReadDir() throws Exception {
+ ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+ c.connect();
+ try {
+ URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
+ URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
+ String path = new File(base.relativize(url).getPath()).getParent() + "/";
+ path = path.replace('\\', '/');
+ Vector<?> res = c.ls(path);
+ for (Object f : res) {
+ System.out.println(f.toString());
+ }
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ @Test
+ public void testRealPath() throws Exception {
+ ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+ c.connect();
+
+ try {
+ URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
+ URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
+ String path = new File(base.relativize(url).getPath()).getParent() + "/";
+ path = path.replace('\\', '/');
+ String real = c.realpath(path);
+ System.out.println(real);
+ try {
+ real = c.realpath(path + "/foobar");
+ System.out.println(real);
+ fail("Expected SftpException");
+ } catch (SftpException e) {
+ // ok
+ }
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ @Test
+ public void testRename() throws Exception {
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
+
+ try(SftpClient sftp = session.createSftpClient()) {
+ Path file1 = clientFolder.resolve(getCurrentTestName() + "-1.txt");
+ String file1Path = Utils.resolveRelativeRemotePath(parentPath, file1);
+ try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
+ os.write((getCurrentTestName() + "\n").getBytes());
+ }
+
+ Path file2 = clientFolder.resolve(getCurrentTestName() + "-2.txt");
+ String file2Path = Utils.resolveRelativeRemotePath(parentPath, file2);
+ Path file3 = clientFolder.resolve(getCurrentTestName() + "-3.txt");
+ String file3Path = Utils.resolveRelativeRemotePath(parentPath, file3);
+ try {
+ sftp.rename(file2Path, file3Path);
+ fail("Unxpected rename success of " + file2Path + " => " + file3Path);
+ } catch (org.apache.sshd.client.SftpException e) {
+ assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path, SSH_FX_NO_SUCH_FILE, e.getStatus());
+ }
+
+ try (OutputStream os = sftp.write(file2Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
+ os.write("H".getBytes());
+ }
+
+ try {
+ sftp.rename(file1Path, file2Path);
+ fail("Unxpected rename success of " + file1Path + " => " + file2Path);
+ } catch (org.apache.sshd.client.SftpException e) {
+ assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path, SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
+ }
+
+ sftp.rename(file1Path, file2Path, SftpClient.CopyMode.Overwrite);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test
+ public void testCreateSymbolicLink() throws Exception {
+ // Do not execute on windows as the file system does not support symlinks
+ Assume.assumeTrue("Skip non-Unix O/S", OsUtils.isUNIX());
+
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Path parentPath = targetPath.getParent();
+ Path sourcePath = lclSftp.resolve(getCurrentTestName() + ".txt");
+ String remSrcPath = Utils.resolveRelativeRemotePath(parentPath, sourcePath);
+ Path linkPath = lclSftp.resolve("link-" + sourcePath.getFileName());
+ String remLinkPath = Utils.resolveRelativeRemotePath(parentPath, linkPath);
+
+ String data = getCurrentTestName();
+ ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+ c.connect();
+ try {
+ c.put(new ByteArrayInputStream(data.getBytes()), remSrcPath);
+
+ assertTrue("Source file not created: " + sourcePath, Files.exists(sourcePath));
+ assertEquals("Mismatched stored data in " + remSrcPath, data, readFile(remSrcPath));
+
+ c.symlink(remSrcPath, remLinkPath);
+
+ assertTrue("Symlink not created: " + linkPath, Files.exists(linkPath));
+ assertEquals("Mismatche link data in " + remLinkPath, data, readFile(remLinkPath));
+
+ String str1 = c.readlink(remLinkPath);
+ String str2 = c.realpath(remSrcPath);
+ assertEquals("Mismatched link vs. real path", str1, str2);
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ protected String readFile(String path) throws Exception {
+ ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+ c.connect();
+
+ try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ InputStream is = c.get(path)) {
+
+ byte[] buffer = new byte[256];
+ int count;
+ while (-1 != (count = is.read(buffer))) {
+ bos.write(buffer, 0, count);
+ }
+
+ return bos.toString();
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ protected void sendFile(String path, String data) throws Exception {
+ ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+ c.connect();
+ try {
+ c.put(new ByteArrayInputStream(data.getBytes()), path);
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ private String randomString(int size) {
+ StringBuilder sb = new StringBuilder(size);
+ for (int i = 0; i < size; i++) {
+ sb.append((char) ((i % 10) + '0'));
+ }
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
index f9d6e17..43af998 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java
@@ -54,7 +54,7 @@ import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.session.SessionListener;
import org.apache.sshd.deprecated.ClientUserAuthServiceOld;
import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.apache.sshd.util.BaseTestSupport;
import org.apache.sshd.util.BogusPasswordAuthenticator;
import org.apache.sshd.util.EchoShellFactory;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/server/sftp/SftpSubsystemFactoryTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/sftp/SftpSubsystemFactoryTest.java b/sshd-core/src/test/java/org/apache/sshd/server/sftp/SftpSubsystemFactoryTest.java
deleted file mode 100644
index 8807fae..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/server/sftp/SftpSubsystemFactoryTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.sftp;
-
-import java.util.concurrent.ExecutorService;
-
-import org.apache.sshd.util.BaseTestSupport;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.mockito.Mockito;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SftpSubsystemFactoryTest extends BaseTestSupport {
- public SftpSubsystemFactoryTest() {
- super();
- }
-
- /**
- * Make sure that the builder returns a factory with the default values
- * if no {@code withXXX} method is invoked
- */
- @Test
- public void testBuilderDefaultFactoryValues() {
- SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder().build();
- assertNull("Mismatched executor", factory.getExecutorService());
- assertFalse("Mismatched shutdown state", factory.isShutdownOnExit());
- assertSame("Mismatched unsupported attribute policy", SftpSubsystemFactory.DEFAULT_POLICY, factory.getUnsupportedAttributePolicy());
- }
-
- /**
- * Make sure that the builder initializes correctly the built factory
- */
- @Test
- public void testBuilderCorrectlyInitializesFactory() {
- SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
- ExecutorService service = dummyExecutor();
- SftpSubsystemFactory factory = builder.withExecutorService(service)
- .withShutdownOnExit(true)
- .build();
- assertSame("Mismatched executor", service, factory.getExecutorService());
- assertTrue("Mismatched shutdown state", factory.isShutdownOnExit());
-
- for (UnsupportedAttributePolicy policy : UnsupportedAttributePolicy.VALUES) {
- SftpSubsystemFactory actual = builder.withUnsupportedAttributePolicy(policy).build();
- assertSame("Mismatched unsupported attribute policy", policy, actual.getUnsupportedAttributePolicy());
- }
- }
-
- /**
- * <UL>
- * <LI>
- * Make sure the builder returns new instances on every call to
- * {@link SftpSubsystemFactory.Builder#build()} method
- * </LI>
- * <p/>
- * <LI>
- * Make sure values are preserved between successive invocations
- * of the {@link SftpSubsystemFactory.Builder#build()} method
- * </LI>
- * </UL
- */
- @Test
- public void testBuilderUniqueInstance() {
- SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
- SftpSubsystemFactory f1 = builder.withExecutorService(dummyExecutor()).build();
- SftpSubsystemFactory f2 = builder.build();
- assertNotSame("No new instance built", f1, f2);
- assertSame("Mismatched executors", f1.getExecutorService(), f2.getExecutorService());
-
- SftpSubsystemFactory f3 = builder.withExecutorService(dummyExecutor()).build();
- assertNotSame("Executor service not changed", f1.getExecutorService(), f3.getExecutorService());
- }
-
- private static ExecutorService dummyExecutor() {
- return Mockito.mock(ExecutorService.class);
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java b/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
new file mode 100644
index 0000000..7a251ea
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.UnsupportedAttributePolicy;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SftpSubsystemFactoryTest extends BaseTestSupport {
+ public SftpSubsystemFactoryTest() {
+ super();
+ }
+
+ /**
+ * Make sure that the builder returns a factory with the default values
+ * if no {@code withXXX} method is invoked
+ */
+ @Test
+ public void testBuilderDefaultFactoryValues() {
+ SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder().build();
+ assertNull("Mismatched executor", factory.getExecutorService());
+ assertFalse("Mismatched shutdown state", factory.isShutdownOnExit());
+ assertSame("Mismatched unsupported attribute policy", SftpSubsystemFactory.DEFAULT_POLICY, factory.getUnsupportedAttributePolicy());
+ }
+
+ /**
+ * Make sure that the builder initializes correctly the built factory
+ */
+ @Test
+ public void testBuilderCorrectlyInitializesFactory() {
+ SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
+ ExecutorService service = dummyExecutor();
+ SftpSubsystemFactory factory = builder.withExecutorService(service)
+ .withShutdownOnExit(true)
+ .build();
+ assertSame("Mismatched executor", service, factory.getExecutorService());
+ assertTrue("Mismatched shutdown state", factory.isShutdownOnExit());
+
+ for (UnsupportedAttributePolicy policy : UnsupportedAttributePolicy.VALUES) {
+ SftpSubsystemFactory actual = builder.withUnsupportedAttributePolicy(policy).build();
+ assertSame("Mismatched unsupported attribute policy", policy, actual.getUnsupportedAttributePolicy());
+ }
+ }
+
+ /**
+ * <UL>
+ * <LI>
+ * Make sure the builder returns new instances on every call to
+ * {@link SftpSubsystemFactory.Builder#build()} method
+ * </LI>
+ * <p/>
+ * <LI>
+ * Make sure values are preserved between successive invocations
+ * of the {@link SftpSubsystemFactory.Builder#build()} method
+ * </LI>
+ * </UL
+ */
+ @Test
+ public void testBuilderUniqueInstance() {
+ SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder();
+ SftpSubsystemFactory f1 = builder.withExecutorService(dummyExecutor()).build();
+ SftpSubsystemFactory f2 = builder.build();
+ assertNotSame("No new instance built", f1, f2);
+ assertSame("Mismatched executors", f1.getExecutorService(), f2.getExecutorService());
+
+ SftpSubsystemFactory f3 = builder.withExecutorService(dummyExecutor()).build();
+ assertNotSame("Executor service not changed", f1.getExecutorService(), f3.getExecutorService());
+ }
+
+ private static ExecutorService dummyExecutor() {
+ return Mockito.mock(ExecutorService.class);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
index 210e35c..7ba2362 100644
--- a/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
+++ b/sshd-git/src/test/java/org/apache/sshd/git/pack/GitPackCommandTest.java
@@ -28,7 +28,7 @@ import org.apache.sshd.git.util.EchoShellFactory;
import org.apache.sshd.git.util.Utils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.SshSessionFactory;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
----------------------------------------------------------------------
diff --git a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
index ba186fa..b1d45e9 100644
--- a/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
+++ b/sshd-git/src/test/java/org/apache/sshd/git/pgm/GitPgmCommandTest.java
@@ -32,7 +32,7 @@ import org.apache.sshd.git.util.EchoShellFactory;
import org.apache.sshd.git.util.Utils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
import org.eclipse.jgit.api.Git;
import org.junit.FixMethodOrder;
import org.junit.Test;
[05/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
deleted file mode 100644
index d696a4f..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
+++ /dev/null
@@ -1,2280 +0,0 @@
-/*
- * 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.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.*;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclEntryFlag;
-import java.nio.file.attribute.AclEntryPermission;
-import java.nio.file.attribute.AclEntryType;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.common.FactoryManagerUtils;
-import org.apache.sshd.common.file.FileSystemAware;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.SelectorUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-import org.apache.sshd.common.util.threads.ThreadUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.Environment;
-import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
-import org.apache.sshd.server.session.ServerSession;
-
-/**
- * SFTP subsystem
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpSubsystem extends AbstractLoggingBean implements Command, Runnable, SessionAware, FileSystemAware {
-
- /**
- * Properties key for the maximum of available open handles per session.
- */
- public static final String MAX_OPEN_HANDLES_PER_SESSION = "max-open-handles-per-session";
-
- /**
- * Force the use of a given sftp version
- */
- public static final String SFTP_VERSION = "sftp-version";
-
- public static final int LOWER_SFTP_IMPL = SFTP_V3; // Working implementation from v3
- public static final int HIGHER_SFTP_IMPL = SFTP_V6; // .. up to
- public static final String ALL_SFTP_IMPL;
- public static final int MAX_PACKET_LENGTH = 1024 * 16;
-
- static {
- StringBuilder sb = new StringBuilder(2 * (1 + (HIGHER_SFTP_IMPL - LOWER_SFTP_IMPL)));
- for (int v = LOWER_SFTP_IMPL; v <= HIGHER_SFTP_IMPL; v++) {
- if (sb.length() > 0) {
- sb.append(',');
- }
- sb.append(v);
- }
- ALL_SFTP_IMPL = sb.toString();
- }
-
- private ExitCallback callback;
- private InputStream in;
- private OutputStream out;
- private OutputStream err;
- private Environment env;
- private ServerSession session;
- private boolean closed = false;
- private ExecutorService executors;
- private boolean shutdownExecutor;
- private Future<?> pendingFuture;
-
- private FileSystem fileSystem = FileSystems.getDefault();
- private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
-
- private int version;
- private final Map<String, byte[]> extensions = new HashMap<>();
- private final Map<String, Handle> handles = new HashMap<>();
-
- private final UnsupportedAttributePolicy unsupportedAttributePolicy;
-
- protected static abstract class Handle implements java.io.Closeable {
- private Path file;
-
- public Handle(Path file) {
- this.file = file;
- }
-
- public Path getFile() {
- return file;
- }
-
- @Override
- public void close() throws IOException {
- // ignored
- }
-
- @Override
- public String toString() {
- return Objects.toString(getFile());
- }
- }
-
- protected static class DirectoryHandle extends Handle implements Iterator<Path> {
- private boolean done;
- // the directory should be read once at "open directory"
- private DirectoryStream<Path> ds;
- private Iterator<Path> fileList;
-
- public DirectoryHandle(Path file) throws IOException {
- super(file);
- ds = Files.newDirectoryStream(file);
- fileList = ds.iterator();
- }
-
- public boolean isDone() {
- return done;
- }
-
- public void setDone(boolean done) {
- this.done = done;
- }
-
- @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());
- }
-
- public void clearFileList() {
- // allow the garbage collector to do the job
- fileList = null;
- }
-
- @Override
- public void close() throws IOException {
- ds.close();
- }
- }
-
- protected class FileHandle extends Handle {
- private final FileChannel channel;
- private long pos;
- private final List<FileLock> locks = new ArrayList<>();
-
- public FileHandle(Path file, int flags, int access, Map<String, Object> attrs) throws IOException {
- super(file);
- Set<OpenOption> options = new HashSet<>();
- if ((access & ACE4_READ_DATA) != 0 || (access & ACE4_READ_ATTRIBUTES) != 0) {
- options.add(StandardOpenOption.READ);
- }
- if ((access & ACE4_WRITE_DATA) != 0 || (access & ACE4_WRITE_ATTRIBUTES) != 0) {
- options.add(StandardOpenOption.WRITE);
- }
- switch (flags & SSH_FXF_ACCESS_DISPOSITION) {
- case SSH_FXF_CREATE_NEW:
- options.add(StandardOpenOption.CREATE_NEW);
- break;
- case SSH_FXF_CREATE_TRUNCATE:
- options.add(StandardOpenOption.CREATE);
- options.add(StandardOpenOption.TRUNCATE_EXISTING);
- break;
- case SSH_FXF_OPEN_EXISTING:
- break;
- case SSH_FXF_OPEN_OR_CREATE:
- options.add(StandardOpenOption.CREATE);
- break;
- case SSH_FXF_TRUNCATE_EXISTING:
- options.add(StandardOpenOption.TRUNCATE_EXISTING);
- break;
- default: // ignored
- }
- if ((flags & SSH_FXF_APPEND_DATA) != 0) {
- options.add(StandardOpenOption.APPEND);
- }
- FileAttribute<?>[] attributes = new FileAttribute<?>[attrs.size()];
- int index = 0;
- for (Map.Entry<String, Object> attr : attrs.entrySet()) {
- final String key = attr.getKey();
- final Object val = attr.getValue();
- attributes[index++] = new FileAttribute<Object>() {
- @Override
- public String name() {
- return key;
- }
-
- @Override
- public Object value() {
- return val;
- }
- };
- }
- FileChannel channel;
- try {
- channel = FileChannel.open(file, options, attributes);
- } catch (UnsupportedOperationException e) {
- channel = FileChannel.open(file, options);
- setAttributes(file, attrs);
- }
- this.channel = channel;
- this.pos = 0;
- }
-
- 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 {
- if (pos != offset) {
- channel.position(offset);
- pos = offset;
- }
- int read = channel.read(ByteBuffer.wrap(data, doff, length));
- pos += read;
- return read;
- }
-
- 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 {
- if (pos != offset) {
- channel.position(offset);
- pos = offset;
- }
- channel.write(ByteBuffer.wrap(data, doff, length));
- pos += length;
- }
-
- @Override
- public void close() throws IOException {
- channel.close();
- }
-
- public void lock(long offset, long length, int mask) throws IOException {
- long size = length == 0 ? channel.size() - offset : length;
- FileLock lock = channel.tryLock(offset, size, false);
- synchronized (locks) {
- locks.add(lock);
- }
- }
-
- public boolean unlock(long offset, long length) throws IOException {
- long size = length == 0 ? 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) {
- lock.release();
- return true;
- }
- return false;
- }
- }
-
- /**
- * @param executorService 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.
- * @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 shutdown regardless
- * @param policy The {@link UnsupportedAttributePolicy} to use if failed to access
- * some local file attributes
- * @see ThreadUtils#newSingleThreadExecutor(String)
- */
- public SftpSubsystem(ExecutorService executorService, boolean shutdownOnExit, UnsupportedAttributePolicy policy) {
- if ((executors = executorService) == null) {
- executors = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
- shutdownExecutor = true; // we always close the ad-hoc executor service
- } else {
- shutdownExecutor = shutdownOnExit;
- }
-
- if ((unsupportedAttributePolicy=policy) == null) {
- throw new IllegalArgumentException("No policy provided");
- }
- }
-
- public final UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
- return unsupportedAttributePolicy;
- }
-
- @Override
- public void setSession(ServerSession session) {
- this.session = session;
- }
-
- @Override
- public void setFileSystem(FileSystem fileSystem) {
- if (fileSystem != this.fileSystem) {
- this.fileSystem = fileSystem;
- this.defaultDir = fileSystem.getRootDirectories().iterator().next();
- }
- }
-
- @Override
- public void setExitCallback(ExitCallback callback) {
- this.callback = callback;
- }
-
- @Override
- public void setInputStream(InputStream in) {
- this.in = in;
- }
-
- @Override
- public void setOutputStream(OutputStream out) {
- this.out = out;
- }
-
- @Override
- public void setErrorStream(OutputStream err) {
- this.err = err;
- }
-
- @Override
- public void start(Environment env) throws IOException {
- this.env = env;
- try {
- pendingFuture = executors.submit(this);
- } catch (RuntimeException e) { // e.g., RejectedExecutionException
- log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.toString(), e);
- throw new IOException(e);
- }
- }
-
- @Override
- public void run() {
- DataInputStream dis = null;
- try {
- dis = new DataInputStream(in);
- while (true) {
- int length = dis.readInt();
- if (length < 5) {
- throw new IllegalArgumentException("Bad length to read: " + length);
- }
- Buffer buffer = new ByteArrayBuffer(length + 4);
- buffer.putInt(length);
- int nb = length;
- while (nb > 0) {
- int l = dis.read(buffer.array(), buffer.wpos(), nb);
- if (l < 0) {
- throw new IllegalArgumentException("Premature EOF while read length=" + length + " while remain=" + nb);
- }
- buffer.wpos(buffer.wpos() + l);
- nb -= l;
- }
- process(buffer);
- }
- } catch (Throwable t) {
- if (!closed && !(t instanceof EOFException)) { // Ignore
- log.error("Exception caught in SFTP subsystem", t);
- }
- } finally {
- if (dis != null) {
- try {
- dis.close();
- } catch (IOException ioe) {
- log.error("Could not close DataInputStream", ioe);
- }
- }
-
- if (handles != null) {
- for (Map.Entry<String, Handle> entry : handles.entrySet()) {
- Handle handle = entry.getValue();
- try {
- handle.close();
- } catch (IOException ioe) {
- log.error("Could not close open handle: " + entry.getKey(), ioe);
- }
- }
- }
- callback.onExit(0);
- }
- }
-
- protected void process(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getByte();
- int id = buffer.getInt();
- if (log.isDebugEnabled()) {
- log.debug("process(length={}, type={}, id={})",
- new Object[] { Integer.valueOf(length), Integer.valueOf(type), Integer.valueOf(id) });
- }
-
- switch (type) {
- case SSH_FXP_INIT:
- doInit(buffer, id);
- break;
- case SSH_FXP_OPEN:
- doOpen(buffer, id);
- break;
- case SSH_FXP_CLOSE:
- doClose(buffer, id);
- break;
- case SSH_FXP_READ:
- doRead(buffer, id);
- break;
- case SSH_FXP_WRITE:
- doWrite(buffer, id);
- break;
- case SSH_FXP_LSTAT:
- doLStat(buffer, id);
- break;
- case SSH_FXP_FSTAT:
- doFStat(buffer, id);
- break;
- case SSH_FXP_SETSTAT:
- doSetStat(buffer, id);
- break;
- case SSH_FXP_FSETSTAT:
- doFSetStat(buffer, id);
- break;
- case SSH_FXP_OPENDIR:
- doOpenDir(buffer, id);
- break;
- case SSH_FXP_READDIR:
- doReadDir(buffer, id);
- break;
- case SSH_FXP_REMOVE:
- doRemove(buffer, id);
- break;
- case SSH_FXP_MKDIR:
- doMakeDirectory(buffer, id);
- break;
- case SSH_FXP_RMDIR:
- doRemoveDirectory(buffer, id);
- break;
- case SSH_FXP_REALPATH:
- doRealPath(buffer, id);
- break;
- case SSH_FXP_STAT:
- doStat(buffer, id);
- break;
- case SSH_FXP_RENAME:
- doRename(buffer, id);
- break;
- case SSH_FXP_READLINK:
- doReadLink(buffer, id);
- break;
- case SSH_FXP_SYMLINK:
- doSymLink(buffer, id);
- break;
- case SSH_FXP_LINK:
- doLink(buffer, id);
- break;
- case SSH_FXP_BLOCK:
- doBlock(buffer, id);
- break;
- case SSH_FXP_UNBLOCK:
- doUnblock(buffer, id);
- break;
- case SSH_FXP_EXTENDED:
- doExtended(buffer, id);
- break;
- default:
- log.warn("Unknown command type received: {}", Integer.valueOf(type));
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
- }
- }
-
- protected void doExtended(Buffer buffer, int id) throws IOException {
- String extension = buffer.getString();
- switch (extension) {
- case "text-seek":
- doTextSeek(buffer, id);
- break;
- case "version-select":
- doVersionSelect(buffer, id);
- break;
- default:
- log.info("Received unsupported SSH_FXP_EXTENDED({})", extension);
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(" + extension + ") is unsupported or not implemented");
- break;
- }
- }
-
- protected void doTextSeek(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long line = buffer.getLong();
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_EXTENDED(text-seek) (handle={}, line={})", handle, Long.valueOf(line));
- }
-
- // TODO : implement text-seek
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(text-seek) is unsupported or not implemented");
- }
-
- protected void doVersionSelect(Buffer buffer, int id) throws IOException {
- String ver = buffer.getString();
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_EXTENDED(version-select) (version={})", Integer.valueOf(version));
- }
-
- if (GenericUtils.length(ver) == 1) {
- char digit = ver.charAt(0);
- if ((digit >= '0') && (digit <= '9')) {
- int value = digit - '0';
- String all = checkVersionCompatibility(id, value, SSH_FX_FAILURE);
- if (GenericUtils.isEmpty(all)) { // validation failed
- return;
- }
-
- version = value;
- sendStatus(id, SSH_FX_OK, "");
- return;
- }
- }
-
- sendStatus(id, SSH_FX_FAILURE, "Unsupported version " + ver);
- }
-
- /**
- * Checks if a proposed version is within supported range. <B>Note:</B>
- * if the user forced a specific value via the {@link #SFTP_VERSION}
- * property, then it is used to validate the proposed value
- * @param id The SSH message ID to be used to send the failure message
- * if required
- * @param proposed The proposed version value
- * @param failureOpcode The failure opcode to send if validation fails
- * @return A {@link String} of comma separated values representing all
- * the supported version - {@code null} if validation failed and an
- * appropriate status message was sent
- * @throws IOException If failed to send the failure status message
- */
- protected String checkVersionCompatibility(int id, int proposed, int failureOpcode) throws IOException {
- int low = LOWER_SFTP_IMPL;
- int hig = HIGHER_SFTP_IMPL;
- String available = ALL_SFTP_IMPL;
- // check if user wants to use a specific version
- Integer sftpVersion = FactoryManagerUtils.getInteger(session, SFTP_VERSION);
- if (sftpVersion != null) {
- int forcedValue = sftpVersion.intValue();
- if ((forcedValue < LOWER_SFTP_IMPL) || (forcedValue > HIGHER_SFTP_IMPL)) {
- throw new IllegalStateException("Forced SFTP version (" + sftpVersion + ") not within supported values: " + available);
- }
- low = hig = sftpVersion.intValue();
- available = sftpVersion.toString();
- }
-
- if (log.isTraceEnabled()) {
- log.trace("checkVersionCompatibility(id={}) - proposed={}, available={}",
- new Object[] { Integer.valueOf(id), Integer.valueOf(proposed), available });
- }
-
- if ((proposed < low) || (proposed > hig)) {
- sendStatus(id, failureOpcode, "Proposed version (" + proposed + ") not in supported range: " + available);
- return null;
- }
-
- return available;
- }
-
- protected void doBlock(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long offset = buffer.getLong();
- long length = buffer.getLong();
- int mask = buffer.getInt();
-
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_BLOCK (handle={}, offset={}, length={}, mask=0x{})",
- new Object[] { handle, Long.valueOf(offset), Long.valueOf(length), Integer.toHexString(mask) });
- }
-
- try {
- Handle p = handles.get(handle);
- if (!(p instanceof FileHandle)) {
- sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
- return;
- }
- FileHandle fileHandle = (FileHandle) p;
- fileHandle.lock(offset, length, mask);
- sendStatus(id, SSH_FX_OK, "");
- } catch (IOException | OverlappingFileLockException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doUnblock(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long offset = buffer.getLong();
- long length = buffer.getLong();
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_UNBLOCK (handle={}, offset={}, length={})",
- new Object[] { handle, Long.valueOf(offset), Long.valueOf(length) });
- }
-
- try {
- Handle p = handles.get(handle);
- if (!(p instanceof FileHandle)) {
- sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
- return;
- }
- FileHandle fileHandle = (FileHandle) p;
- boolean found = fileHandle.unlock(offset, length);
- sendStatus(id, found ? SSH_FX_OK : SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK, "");
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doLink(Buffer buffer, int id) throws IOException {
- String targetpath = buffer.getString();
- String linkpath = buffer.getString();
- boolean symLink = buffer.getBoolean();
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_LINK (linkpath={}, targetpath={}, symlink={})",
- new Object[] { linkpath, targetpath, Boolean.valueOf(symLink) });
- }
-
- try {
- Path link = resolveFile(linkpath);
- Path target = fileSystem.getPath(targetpath);
- if (symLink) {
- Files.createSymbolicLink(link, target);
- } else {
- Files.createLink(link, target);
- }
- sendStatus(id, SSH_FX_OK, "");
- } catch (UnsupportedOperationException e) {
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_SYMLINK is unsupported or not implemented");
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doSymLink(Buffer buffer, int id) throws IOException {
- String targetpath = buffer.getString();
- String linkpath = buffer.getString();
- log.debug("Received SSH_FXP_SYMLINK (linkpath={}, targetpath={})", linkpath, targetpath);
- try {
- Path link = resolveFile(linkpath);
- Path target = fileSystem.getPath(targetpath);
- Files.createSymbolicLink(link, target);
- sendStatus(id, SSH_FX_OK, "");
- } catch (UnsupportedOperationException e) {
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_SYMLINK is unsupported or not implemented");
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doReadLink(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_READLINK (path={})", path);
- try {
- Path f = resolveFile(path);
- String l = Files.readSymbolicLink(f).toString();
- sendLink(id, l);
- } catch (UnsupportedOperationException e) {
- sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_READLINK is unsupported or not implemented");
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doRename(Buffer buffer, int id) throws IOException {
- String oldPath = buffer.getString();
- String newPath = buffer.getString();
- int flags = 0;
- if (version >= SFTP_V5) {
- flags = buffer.getInt();
- }
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={}, flags=0x{})",
- new Object[] { oldPath, newPath, Integer.toHexString(flags) });
- }
- try {
- List<CopyOption> opts = new ArrayList<>();
- if ((flags & SSH_FXP_RENAME_ATOMIC) != 0) {
- opts.add(StandardCopyOption.ATOMIC_MOVE);
- }
- if ((flags & SSH_FXP_RENAME_OVERWRITE) != 0) {
- opts.add(StandardCopyOption.REPLACE_EXISTING);
- }
- Path o = resolveFile(oldPath);
- Path n = resolveFile(newPath);
- Files.move(o, n, opts.toArray(new CopyOption[opts.size()]));
- sendStatus(id, SSH_FX_OK, "");
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doStat(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- int flags = SSH_FILEXFER_ATTR_ALL;
- if (version >= SFTP_V4) {
- flags = buffer.getInt();
- }
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_STAT (path={}, flags={})", path, "0x" + Integer.toHexString(flags));
- }
- try {
- Path p = resolveFile(path);
- sendAttrs(id, p, flags, true);
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doRealPath(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_REALPATH (path={})", path);
- path = GenericUtils.trimToEmpty(path);
- if (GenericUtils.isEmpty(path)) {
- path = ".";
- }
-
- try {
- if (version < SFTP_V6) {
- Path f = resolveFile(path);
- Path abs = f.toAbsolutePath();
- Path p = abs.normalize();
- Boolean status = IoUtils.checkFileExists(p, IoUtils.EMPTY_LINK_OPTIONS);
- if (status == null) {
- p = handleUnknownRealPathStatus(path, abs, p);
- } else if (!status.booleanValue()) {
- throw new FileNotFoundException(p.toString());
- }
- sendPath(id, p, Collections.<String, Object>emptyMap());
- } else {
- // Read control byte
- int control = 0;
- if (buffer.available() > 0) {
- control = buffer.getByte();
- }
- List<String> paths = new ArrayList<>();
- while (buffer.available() > 0) {
- paths.add(buffer.getString());
- }
- // Resolve path
- Path p = resolveFile(path);
- for (String p2 : paths) {
- p = p.resolve(p2);
- }
- p = p.toAbsolutePath().normalize();
-
- Map<String, Object> attrs = Collections.emptyMap();
- if (control == SSH_FXP_REALPATH_STAT_IF) {
- try {
- attrs = getAttributes(p, false);
- } catch (IOException e) {
- // ignore
- }
- } else if (control == SSH_FXP_REALPATH_STAT_ALWAYS) {
- attrs = getAttributes(p, false);
- }
- sendPath(id, p, attrs);
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected Path handleUnknownRealPathStatus(String path, Path absolute, Path normalized) throws IOException {
- switch(unsupportedAttributePolicy) {
- case Ignore:
- break;
- case Warn:
- log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized);
- break;
- case ThrowException:
- throw new AccessDeniedException("Cannot determine existence status of real path: " + normalized);
-
- default:
- log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized
- + " - unknown policy: " + unsupportedAttributePolicy);
- }
-
- return absolute;
- }
-
- protected void doRemoveDirectory(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_RMDIR (path={})", path);
- // attrs
- try {
- Path p = resolveFile(path);
- if (Files.isDirectory(p, IoUtils.getLinkOptions(false))) {
- Files.delete(p);
- sendStatus(id, SSH_FX_OK, "");
- } else {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doMakeDirectory(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- Map<String, Object> attrs = readAttrs(buffer);
-
- log.debug("Received SSH_FXP_MKDIR (path={})", path);
- // attrs
- try {
- Path p = resolveFile(path);
- LinkOption[] options = IoUtils.getLinkOptions(false);
- Boolean status = IoUtils.checkFileExists(p, options);
- if (status == null) {
- throw new AccessDeniedException("Cannot make-directory existence for " + p);
- }
- if (status.booleanValue()) {
- if (Files.isDirectory(p, options)) {
- sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.toString());
- } else {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
- }
- } else {
- Files.createDirectory(p);
- setAttributes(p, attrs);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doRemove(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_REMOVE (path={})", path);
- try {
- Path p = resolveFile(path);
- LinkOption[] options = IoUtils.getLinkOptions(false);
- Boolean status = IoUtils.checkFileExists(p, options);
- if (status == null) {
- throw new AccessDeniedException("Cannot determine existence of remove candidate: " + p);
- }
- if (!status.booleanValue()) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
- } else if (Files.isDirectory(p, options)) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
- } else {
- Files.delete(p);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doReadDir(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- log.debug("Received SSH_FXP_READDIR (handle={})", handle);
- Handle p = handles.get(handle);
- try {
- if (!(p instanceof DirectoryHandle)) {
- sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
- return;
- }
-
- if (((DirectoryHandle) p).isDone()) {
- sendStatus(id, SSH_FX_EOF, "", "");
- return;
- }
-
- Path file = p.getFile();
- LinkOption[] options = IoUtils.getLinkOptions(false);
- Boolean status = IoUtils.checkFileExists(file, options);
- if (status == null) {
- throw new AccessDeniedException("Cannot determine existence of read-dir for " + file);
- }
-
- if (!status.booleanValue()) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, file.toString());
- } else if (!Files.isDirectory(file, options)) {
- sendStatus(id, SSH_FX_NOT_A_DIRECTORY, file.toString());
- } else if (!Files.isReadable(file)) {
- sendStatus(id, SSH_FX_PERMISSION_DENIED, file.toString());
- } else {
- DirectoryHandle dh = (DirectoryHandle) p;
- if (dh.hasNext()) {
- // There is at least one file in the directory.
- // Send only a few files at a time to not create packets of a too
- // large size or have a timeout to occur.
- sendName(id, dh);
- if (!dh.hasNext()) {
- // if no more files to send
- dh.setDone(true);
- dh.clearFileList();
- }
- } else {
- // empty directory
- dh.setDone(true);
- dh.clearFileList();
- sendStatus(id, SSH_FX_EOF, "", "");
- }
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doOpenDir(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- log.debug("Received SSH_FXP_OPENDIR (path={})", path);
- try {
- Path p = resolveFile(path);
- LinkOption[] options = IoUtils.getLinkOptions(false);
- Boolean status = IoUtils.checkFileExists(p, options);
- if (status == null) {
- throw new AccessDeniedException("Cannot determine open-dir existence of " + p);
- }
-
- if (!status.booleanValue()) {
- sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
- } else if (!Files.isDirectory(p, options)) {
- sendStatus(id, SSH_FX_NOT_A_DIRECTORY, path);
- } else if (!Files.isReadable(p)) {
- sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
- } else {
- String handle = UUID.randomUUID().toString();
- handles.put(handle, new DirectoryHandle(p));
- sendHandle(id, handle);
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doFSetStat(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- Map<String, Object> attrs = readAttrs(buffer);
- log.debug("Received SSH_FXP_FSETSTAT (handle={}, attrs={})", handle, attrs);
- try {
- Handle p = handles.get(handle);
- if (p == null) {
- sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
- } else {
- setAttributes(p.getFile(), attrs);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (IOException | UnsupportedOperationException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doSetStat(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- Map<String, Object> attrs = readAttrs(buffer);
- log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs);
- try {
- Path p = resolveFile(path);
- setAttributes(p, attrs);
- sendStatus(id, SSH_FX_OK, "");
- } catch (IOException | UnsupportedOperationException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doFStat(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- int flags = SSH_FILEXFER_ATTR_ALL;
- if (version >= SFTP_V4) {
- flags = buffer.getInt();
- }
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_FSTAT (handle={}, flags={})", handle, "0x" + Integer.toHexString(flags));
- }
- try {
- Handle p = handles.get(handle);
- if (p == null) {
- sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
- } else {
- sendAttrs(id, p.getFile(), flags, true);
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doLStat(Buffer buffer, int id) throws IOException {
- String path = buffer.getString();
- int flags = SSH_FILEXFER_ATTR_ALL;
- if (version >= SFTP_V4) {
- flags = buffer.getInt();
- }
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_LSTAT (path={}, flags={})", path, "0x" + Integer.toHexString(flags));
- }
- try {
- Path p = resolveFile(path);
- sendAttrs(id, p, flags, false);
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doWrite(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long offset = buffer.getLong();
- int length = buffer.getInt();
- if (length < 0) {
- throw new IllegalStateException();
- }
- if (buffer.available() < length) {
- throw new BufferUnderflowException();
- }
- byte[] data = buffer.array();
- int doff = buffer.rpos();
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])",
- new Object[] { handle, Long.valueOf(offset), Integer.valueOf(length) });
- }
- try {
- Handle p = handles.get(handle);
- if (!(p instanceof FileHandle)) {
- sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
- } else {
- FileHandle fh = (FileHandle) p;
- fh.write(data, doff, length, offset);
- sendStatus(id, SSH_FX_OK, "");
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doRead(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- long offset = buffer.getLong();
- int len = buffer.getInt();
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_READ (handle={}, offset={}, length={})",
- new Object[]{handle, Long.valueOf(offset), Integer.valueOf(len) });
- }
- try {
- Handle p = handles.get(handle);
- if (!(p instanceof FileHandle)) {
- sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
- } else {
- FileHandle fh = (FileHandle) p;
- Buffer buf = new ByteArrayBuffer(len + 9);
- buf.putByte((byte) SSH_FXP_DATA);
- buf.putInt(id);
- int pos = buf.wpos();
- buf.putInt(0);
- len = fh.read(buf.array(), buf.wpos(), len, offset);
- if (len >= 0) {
- buf.wpos(pos);
- buf.putInt(len);
- buf.wpos(pos + 4 + len);
- send(buf);
- } else {
- sendStatus(id, SSH_FX_EOF, "");
- }
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doClose(Buffer buffer, int id) throws IOException {
- String handle = buffer.getString();
- log.debug("Received SSH_FXP_CLOSE (handle={})", handle);
- try {
- Handle h = handles.get(handle);
- if (h == null) {
- sendStatus(id, SSH_FX_INVALID_HANDLE, handle, "");
- } else {
- handles.remove(handle);
- h.close();
- sendStatus(id, SSH_FX_OK, "", "");
- }
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doOpen(Buffer buffer, int id) throws IOException {
- int maxHandleCount = FactoryManagerUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, Integer.MAX_VALUE);
- if (handles.size() > maxHandleCount) {
- sendStatus(id, SSH_FX_FAILURE, "Too many open handles");
- return;
- }
-
- String path = buffer.getString();
- int access = 0;
- if (version >= SFTP_V5) {
- access = buffer.getInt();
- }
- int pflags = buffer.getInt();
- if (version < SFTP_V5) {
- int flags = pflags;
- pflags = 0;
- switch (flags & (SSH_FXF_READ | SSH_FXF_WRITE)) {
- case SSH_FXF_READ:
- access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
- break;
- case SSH_FXF_WRITE:
- access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
- break;
- default:
- access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
- access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
- break;
- }
- if ((flags & SSH_FXF_APPEND) != 0) {
- access |= ACE4_APPEND_DATA;
- pflags |= SSH_FXF_APPEND_DATA | SSH_FXF_APPEND_DATA_ATOMIC;
- }
- if ((flags & SSH_FXF_CREAT) != 0) {
- if ((flags & SSH_FXF_EXCL) != 0) {
- pflags |= SSH_FXF_CREATE_NEW;
- } else if ((flags & SSH_FXF_TRUNC) != 0) {
- pflags |= SSH_FXF_CREATE_TRUNCATE;
- } else {
- pflags |= SSH_FXF_OPEN_OR_CREATE;
- }
- } else {
- if ((flags & SSH_FXF_TRUNC) != 0) {
- pflags |= SSH_FXF_TRUNCATE_EXISTING;
- } else {
- pflags |= SSH_FXF_OPEN_EXISTING;
- }
- }
- }
- Map<String, Object> attrs = readAttrs(buffer);
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_OPEN (path={}, access=0x{}, pflags=0x{}, attrs={})",
- new Object[]{path, Integer.toHexString(access), Integer.toHexString(pflags), attrs});
- }
- try {
- Path file = resolveFile(path);
- String handle = UUID.randomUUID().toString();
- handles.put(handle, new FileHandle(file, pflags, access, attrs));
- sendHandle(id, handle);
- } catch (IOException e) {
- sendStatus(id, e);
- }
- }
-
- protected void doInit(Buffer buffer, int id) throws IOException {
- if (log.isDebugEnabled()) {
- log.debug("Received SSH_FXP_INIT (version={})", Integer.valueOf(id));
- }
-
- String all = checkVersionCompatibility(id, id, SSH_FX_OP_UNSUPPORTED);
- if (GenericUtils.isEmpty(all)) { // i.e. validation failed
- return;
- }
- version = id;
- while (buffer.available() > 0) {
- String name = buffer.getString();
- byte[] data = buffer.getBytes();
- extensions.put(name, data);
- }
-
- buffer.clear();
- buffer.putByte((byte) SSH_FXP_VERSION);
- buffer.putInt(version);
-
- // newline
- buffer.putString("newline");
- buffer.putString(System.getProperty("line.separator"));
-
- // versions
- buffer.putString("versions");
- buffer.putString(all);
-
- // supported
- buffer.putString("supported");
- buffer.putInt(5 * 4); // length of 5 integers
- // supported-attribute-mask
- buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
- | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
- | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
- | SSH_FILEXFER_ATTR_BITS);
- // TODO: supported-attribute-bits
- buffer.putInt(0);
- // supported-open-flags
- buffer.putInt(SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
- | SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_EXCL);
- // TODO: supported-access-mask
- buffer.putInt(0);
- // max-read-size
- buffer.putInt(0);
-
- // supported2
- buffer.putString("supported2");
- buffer.putInt(8 * 4); // length of 7 integers + 2 shorts
- // supported-attribute-mask
- buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
- | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
- | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
- | SSH_FILEXFER_ATTR_BITS);
- // TODO: supported-attribute-bits
- buffer.putInt(0);
- // supported-open-flags
- buffer.putInt(SSH_FXF_ACCESS_DISPOSITION | SSH_FXF_APPEND_DATA);
- // TODO: supported-access-mask
- buffer.putInt(0);
- // max-read-size
- buffer.putInt(0);
- // supported-open-block-vector
- buffer.putShort(0);
- // supported-block-vector
- buffer.putShort(0);
- // attrib-extension-count
- buffer.putInt(0);
- // extension-count
- buffer.putInt(0);
-
- /*
- buffer.putString("acl-supported");
- buffer.putInt(4);
- // capabilities
- buffer.putInt(0);
- */
-
- send(buffer);
- }
-
- protected void sendHandle(int id, String handle) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putByte((byte) SSH_FXP_HANDLE);
- buffer.putInt(id);
- buffer.putString(handle);
- send(buffer);
- }
-
- protected void sendAttrs(int id, Path file, int flags, boolean followLinks) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putByte((byte) SSH_FXP_ATTRS);
- buffer.putInt(id);
- writeAttrs(buffer, file, flags, followLinks);
- send(buffer);
- }
-
- protected void sendPath(int id, Path f, Map<String, Object> attrs) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putByte((byte) SSH_FXP_NAME);
- buffer.putInt(id);
- buffer.putInt(1);
-
- String originalPath = f.toString();
- //in case we are running on Windows
- String unixPath = originalPath.replace(File.separatorChar, '/');
- //normalize the given path, use *nix style separator
- String normalizedPath = SelectorUtils.normalizePath(unixPath, "/");
- if (normalizedPath.length() == 0) {
- normalizedPath = "/";
- }
- buffer.putString(normalizedPath, StandardCharsets.UTF_8);
-
- if (version == SFTP_V3) {
- f = resolveFile(normalizedPath);
- buffer.putString(getLongName(f, attrs), StandardCharsets.UTF_8); // Format specified in the specs
- buffer.putInt(0);
- } else if (version >= SFTP_V4) {
- writeAttrs(buffer, attrs);
- } else {
- throw new IllegalStateException("sendPath(" + f + ") unsupported version: " + version);
- }
- send(buffer);
- }
-
- protected void sendLink(int id, String link) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putByte((byte) SSH_FXP_NAME);
- buffer.putInt(id);
- buffer.putInt(1);
- //normalize the given path, use *nix style separator
- buffer.putString(link);
- buffer.putString(link);
- buffer.putInt(0);
- send(buffer);
- }
-
- protected void sendName(int id, Iterator<Path> files) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putByte((byte) SSH_FXP_NAME);
- buffer.putInt(id);
- int wpos = buffer.wpos();
- buffer.putInt(0);
- int nb = 0;
- while (files.hasNext() && (buffer.wpos() < MAX_PACKET_LENGTH)) {
- Path f = files.next();
- String shortName = getShortName(f);
- buffer.putString(shortName, StandardCharsets.UTF_8);
- if (version == SFTP_V3) {
- String longName = getLongName(f);
- buffer.putString(longName, StandardCharsets.UTF_8); // Format specified in the specs
- if (log.isTraceEnabled()) {
- log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName + " [" + longName + "]");
- }
- } else {
- if (log.isTraceEnabled()) {
- log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName);
- }
- }
- writeAttrs(buffer, f, SSH_FILEXFER_ATTR_ALL, false);
- nb++;
- }
-
- int oldpos = buffer.wpos();
- buffer.wpos(wpos);
- buffer.putInt(nb);
- buffer.wpos(oldpos);
- send(buffer);
- }
-
- private String getLongName(Path f) throws IOException {
- return getLongName(f, true);
- }
-
- private String getLongName(Path f, boolean sendAttrs) throws IOException {
- Map<String, Object> attributes;
- if (sendAttrs) {
- attributes = getAttributes(f, false);
- } else {
- attributes = Collections.emptyMap();
- }
- return getLongName(f, attributes);
- }
-
- private String getLongName(Path f, Map<String, Object> attributes) throws IOException {
- String username;
- if (attributes.containsKey("owner")) {
- username = attributes.get("owner").toString();
- } else {
- username = "owner";
- }
- if (username.length() > 8) {
- username = username.substring(0, 8);
- } else {
- for (int i = username.length(); i < 8; i++) {
- username = username + " ";
- }
- }
- String group;
- if (attributes.containsKey("group")) {
- group = attributes.get("group").toString();
- } else {
- group = "group";
- }
- if (group.length() > 8) {
- group = group.substring(0, 8);
- } else {
- for (int i = group.length(); i < 8; i++) {
- group = group + " ";
- }
- }
-
- Number length = (Number) attributes.get("size");
- if (length == null) {
- length = Long.valueOf(0L);
- }
- String lengthString = String.format("%1$8s", length);
-
- Boolean isDirectory = (Boolean) attributes.get("isDirectory");
- Boolean isLink = (Boolean) attributes.get("isSymbolicLink");
- @SuppressWarnings("unchecked")
- Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
- if (perms == null) {
- perms = EnumSet.noneOf(PosixFilePermission.class);
- }
-
- StringBuilder sb = new StringBuilder();
- sb.append((isDirectory != null && isDirectory.booleanValue()) ? "d" : (isLink != null && isLink.booleanValue()) ? "l" : "-");
- sb.append(PosixFilePermissions.toString(perms));
- sb.append(" ");
- sb.append(attributes.containsKey("nlink") ? attributes.get("nlink") : "1");
- sb.append(" ");
- sb.append(username);
- sb.append(" ");
- sb.append(group);
- sb.append(" ");
- sb.append(lengthString);
- sb.append(" ");
- sb.append(getUnixDate((FileTime) attributes.get("lastModifiedTime")));
- sb.append(" ");
- sb.append(getShortName(f));
-
- return sb.toString();
- }
-
- protected String getShortName(Path f) {
- if (OsUtils.isUNIX()) {
- Path name=f.getFileName();
- if (name == null) {
- Path p=resolveFile(".");
- name = p.getFileName();
- }
-
- return name.toString();
- } else { // need special handling for Windows root drives
- Path abs=f.toAbsolutePath().normalize();
- int count=abs.getNameCount();
- /*
- * According to the javadoc:
- *
- * The number of elements in the path, or 0 if this path only
- * represents a root component
- */
- if (count > 0) {
- Path name=abs.getFileName();
- return name.toString();
- } else {
- return abs.toString().replace(File.separatorChar, '/');
- }
- }
- }
-
- protected int attributesToPermissions(boolean isReg, boolean isDir, boolean isLnk, Collection<PosixFilePermission> perms) {
- int pf = 0;
- if (perms != null) {
- for (PosixFilePermission p : perms) {
- switch (p) {
- case OWNER_READ:
- pf |= S_IRUSR;
- break;
- case OWNER_WRITE:
- pf |= S_IWUSR;
- break;
- case OWNER_EXECUTE:
- pf |= S_IXUSR;
- break;
- case GROUP_READ:
- pf |= S_IRGRP;
- break;
- case GROUP_WRITE:
- pf |= S_IWGRP;
- break;
- case GROUP_EXECUTE:
- pf |= S_IXGRP;
- break;
- case OTHERS_READ:
- pf |= S_IROTH;
- break;
- case OTHERS_WRITE:
- pf |= S_IWOTH;
- break;
- case OTHERS_EXECUTE:
- pf |= S_IXOTH;
- break;
- default: // ignored
- }
- }
- }
- pf |= isReg ? S_IFREG : 0;
- pf |= isDir ? S_IFDIR : 0;
- pf |= isLnk ? S_IFLNK : 0;
- return pf;
- }
-
- protected void writeAttrs(Buffer buffer, Path file, int flags, boolean followLinks) throws IOException {
- LinkOption[] options = IoUtils.getLinkOptions(followLinks);
- Boolean status = IoUtils.checkFileExists(file, options);
- Map<String, Object> attributes;
- if (status == null) {
- attributes = handleUnknownStatusFileAttributes(file, flags, followLinks);
- } else if (!status.booleanValue()) {
- throw new FileNotFoundException(file.toString());
- } else {
- attributes = getAttributes(file, flags, followLinks);
- }
-
- writeAttrs(buffer, attributes);
- }
-
- protected void writeAttrs(Buffer buffer, Map<String, Object> attributes) throws IOException {
- boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
- boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
- boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
- @SuppressWarnings("unchecked")
- Collection<PosixFilePermission> perms = (Collection<PosixFilePermission>) attributes.get("permissions");
- Number size = (Number) attributes.get("size");
- FileTime lastModifiedTime = (FileTime) attributes.get("lastModifiedTime");
- FileTime lastAccessTime = (FileTime) attributes.get("lastAccessTime");
-
- if (version == SFTP_V3) {
- int flags =
- ((isReg || isLnk) && (size != null) ? SSH_FILEXFER_ATTR_SIZE : 0) |
- (attributes.containsKey("uid") && attributes.containsKey("gid") ? SSH_FILEXFER_ATTR_UIDGID : 0) |
- ((perms != null) ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
- (((lastModifiedTime != null) && (lastAccessTime != null)) ? SSH_FILEXFER_ATTR_ACMODTIME : 0);
- buffer.putInt(flags);
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong(size.longValue());
- }
- if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
- buffer.putInt(((Number) attributes.get("uid")).intValue());
- buffer.putInt(((Number) attributes.get("gid")).intValue());
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(attributesToPermissions(isReg, isDir, isLnk, perms));
- }
- if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- buffer.putInt(lastAccessTime.to(TimeUnit.SECONDS));
- buffer.putInt(lastModifiedTime.to(TimeUnit.SECONDS));
- }
- } else if (version >= SFTP_V4) {
- FileTime creationTime = (FileTime) attributes.get("creationTime");
- int flags = (((isReg || isLnk) && (size != null)) ? SSH_FILEXFER_ATTR_SIZE : 0) |
- ((attributes.containsKey("owner") && attributes.containsKey("group")) ? SSH_FILEXFER_ATTR_OWNERGROUP : 0) |
- ((perms != null) ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
- ((lastModifiedTime != null) ? SSH_FILEXFER_ATTR_MODIFYTIME : 0) |
- ((creationTime != null) ? SSH_FILEXFER_ATTR_CREATETIME : 0) |
- ((lastAccessTime != null) ? SSH_FILEXFER_ATTR_ACCESSTIME : 0);
- buffer.putInt(flags);
- buffer.putByte((byte) (isReg ? SSH_FILEXFER_TYPE_REGULAR :
- isDir ? SSH_FILEXFER_TYPE_DIRECTORY :
- isLnk ? SSH_FILEXFER_TYPE_SYMLINK :
- SSH_FILEXFER_TYPE_UNKNOWN));
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong(size.longValue());
- }
- if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
- buffer.putString(attributes.get("owner").toString(), StandardCharsets.UTF_8);
- buffer.putString(attributes.get("group").toString(), StandardCharsets.UTF_8);
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(attributesToPermissions(isReg, isDir, isLnk, perms));
- }
-
- if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
- putFileTime(buffer, flags, lastAccessTime);
- }
-
- if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
- putFileTime(buffer, flags, lastAccessTime);
- }
- if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
- putFileTime(buffer, flags, lastModifiedTime);
- }
- // TODO: acls
- // TODO: bits
- // TODO: extended
- }
- }
-
- protected void putFileTime(Buffer buffer, int flags, FileTime time) {
- buffer.putLong(time.to(TimeUnit.SECONDS));
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- long nanos = time.to(TimeUnit.NANOSECONDS);
- nanos = nanos % TimeUnit.SECONDS.toNanos(1);
- buffer.putInt((int) nanos);
- }
- }
-
- protected boolean getBool(Boolean bool) {
- return (bool != null) && bool.booleanValue();
- }
-
- protected Map<String, Object> getAttributes(Path file, boolean followLinks) throws IOException {
- return getAttributes(file, SSH_FILEXFER_ATTR_ALL, followLinks);
- }
-
- public static final List<String> DEFAULT_UNIX_VIEW=Collections.singletonList("unix:*");
-
- protected Map<String, Object> handleUnknownStatusFileAttributes(Path file, int flags, boolean followLinks) throws IOException {
- switch(unsupportedAttributePolicy) {
- case Ignore:
- break;
- case ThrowException:
- throw new AccessDeniedException("Cannot determine existence for attributes of " + file);
- case Warn:
- log.warn("handleUnknownStatusFileAttributes(" + file + ") cannot determine existence");
- break;
- default:
- log.warn("handleUnknownStatusFileAttributes(" + file + ") unknown policy: " + unsupportedAttributePolicy);
- }
-
- return getAttributes(file, flags, followLinks);
- }
-
- protected Map<String, Object> getAttributes(Path file, int flags, boolean followLinks) throws IOException {
- FileSystem fs=file.getFileSystem();
- Collection<String> supportedViews=fs.supportedFileAttributeViews();
- LinkOption[] opts=IoUtils.getLinkOptions(followLinks);
- Map<String,Object> attrs=new HashMap<>();
- Collection<String> views;
-
- if (GenericUtils.isEmpty(supportedViews)) {
- views = Collections.<String>emptyList();
- } else if (supportedViews.contains("unix")) {
- views = DEFAULT_UNIX_VIEW;
- } else {
- views = new ArrayList<String>(supportedViews.size());
- for (String v : supportedViews) {
- views.add(v + ":*");
- }
- }
-
- for (String v : views) {
- Map<String, Object> ta=readFileAttributes(file, v, opts);
- attrs.putAll(ta);
- }
-
- // if did not get permissions from the supported views return a best approximation
- if (!attrs.containsKey("permissions")) {
- Set<PosixFilePermission> perms=IoUtils.getPermissionsFromFile(file.toFile());
- attrs.put("permissions", perms);
- }
-
- return attrs;
- }
-
- protected Map<String, Object> readFileAttributes(Path file, String view, LinkOption ... opts) throws IOException {
- try {
- return Files.readAttributes(file, view, opts);
- } catch(IOException e) {
- return handleReadFileAttributesException(file, view, opts, e);
- }
- }
-
- protected Map<String, Object> handleReadFileAttributesException(Path file, String view, LinkOption[] opts, IOException e) throws IOException {
- switch(unsupportedAttributePolicy) {
- case Ignore:
- break;
- case Warn:
- log.warn("handleReadFileAttributesException(" + file + ")[" + view + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
- break;
- case ThrowException:
- throw e;
- default:
- log.warn("handleReadFileAttributesException(" + file + ")[" + view + "]"
- + " Unknown policy (" + unsupportedAttributePolicy + ")"
- + " for " + e.getClass().getSimpleName() + ": " + e.getMessage());
- }
-
- return Collections.emptyMap();
- }
-
- protected void setAttributes(Path file, Map<String, Object> attributes) throws IOException {
- Set<String> unsupported = new HashSet<>();
- for (String attribute : attributes.keySet()) {
- String view = null;
- Object value = attributes.get(attribute);
- switch (attribute) {
- case "size": {
- long newSize = ((Number) value).longValue();
- try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE)) {
- channel.truncate(newSize);
- }
- continue;
- }
- case "uid":
- view = "unix";
- break;
- case "gid":
- view = "unix";
- break;
- case "owner":
- view = "posix";
- value = toUser(file, (UserPrincipal) value);
- break;
- case "group":
- view = "posix";
- value = toGroup(file, (GroupPrincipal) value);
- break;
- case "permissions":
- if (OsUtils.isWin32()) {
- @SuppressWarnings("unchecked")
- Collection<PosixFilePermission> perms = (Collection<PosixFilePermission>) value;
- IoUtils.setPermissionsToFile(file.toFile(), perms);
- continue;
- }
- view = "posix";
- break;
-
- case "creationTime":
- view = "basic";
- break;
- case "lastModifiedTime":
- view = "basic";
- break;
- case "lastAccessTime":
- view = "basic";
- break;
- default: // ignored
- }
- if (view != null && value != null) {
- try {
- Files.setAttribute(file, view + ":" + attribute, value, IoUtils.getLinkOptions(false));
- } catch (UnsupportedOperationException e) {
- unsupported.add(attribute);
- }
- }
- }
- handleUnsupportedAttributes(unsupported);
- }
-
- protected void handleUnsupportedAttributes(Collection<String> attributes) {
- if (!attributes.isEmpty()) {
- StringBuilder sb = new StringBuilder();
- for (String attr : attributes) {
- if (sb.length() > 0) {
- sb.append(", ");
- }
- sb.append(attr);
- }
- switch (unsupportedAttributePolicy) {
- case Ignore:
- break;
- case Warn:
- log.warn("Unsupported attributes: " + sb.toString());
- break;
- case ThrowException:
- throw new UnsupportedOperationException("Unsupported attributes: " + sb.toString());
- default:
- log.warn("Unknown policy for attributes=" + sb.toString() + ": " + unsupportedAttributePolicy);
- }
- }
- }
-
- private GroupPrincipal toGroup(Path file, GroupPrincipal name) throws IOException {
- String groupName = name.toString();
- FileSystem fileSystem = file.getFileSystem();
- UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
- try {
- return lookupService.lookupPrincipalByGroupName(groupName);
- } catch (IOException e) {
- handleUserPrincipalLookupServiceException(GroupPrincipal.class, groupName, e);
- return null;
- }
- }
-
- private UserPrincipal toUser(Path file, UserPrincipal name) throws IOException {
- String username = name.toString();
- FileSystem fileSystem = file.getFileSystem();
- UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
- try {
- return lookupService.lookupPrincipalByName(username);
- } catch (IOException e) {
- handleUserPrincipalLookupServiceException(UserPrincipal.class, username, e);
- return null;
- }
- }
-
- protected void handleUserPrincipalLookupServiceException(Class<? extends Principal> principalType, String name, IOException e) throws IOException {
- /* According to Javadoc:
- *
- * "Where an implementation does not support any notion of group
- * or user then this method always throws UserPrincipalNotFoundException."
- */
- switch (unsupportedAttributePolicy) {
- case Ignore:
- break;
- case Warn:
- log.warn("handleUserPrincipalLookupServiceException(" + principalType.getSimpleName() + "[" + name + "])"
- + " failed (" + e.getClass().getSimpleName() + "): " + e.getMessage());
- break;
- case ThrowException:
- throw e;
- default:
- log.warn("Unknown policy for principal=" + principalType.getSimpleName() + "[" + name + "]: " + unsupportedAttributePolicy);
- }
- }
-
- private Set<PosixFilePermission> permissionsToAttributes(int perms) {
- Set<PosixFilePermission> p = new HashSet<>();
- if ((perms & S_IRUSR) != 0) {
- p.add(PosixFilePermission.OWNER_READ);
- }
- if ((perms & S_IWUSR) != 0) {
- p.add(PosixFilePermission.OWNER_WRITE);
- }
- if ((perms & S_IXUSR) != 0) {
- p.add(PosixFilePermission.OWNER_EXECUTE);
- }
- if ((perms & S_IRGRP) != 0) {
- p.add(PosixFilePermission.GROUP_READ);
- }
- if ((perms & S_IWGRP) != 0) {
- p.add(PosixFilePermission.GROUP_WRITE);
- }
- if ((perms & S_IXGRP) != 0) {
- p.add(PosixFilePermission.GROUP_EXECUTE);
- }
- if ((perms & S_IROTH) != 0) {
- p.add(PosixFilePermission.OTHERS_READ);
- }
- if ((perms & S_IWOTH) != 0) {
- p.add(PosixFilePermission.OTHERS_WRITE);
- }
- if ((perms & S_IXOTH) != 0) {
- p.add(PosixFilePermission.OTHERS_EXECUTE);
- }
- return p;
- }
-
- protected Map<String, Object> readAttrs(Buffer buffer) throws IOException {
- Map<String, Object> attrs = new HashMap<>();
- int flags = buffer.getInt();
- if (version >= SFTP_V4) {
- byte type = buffer.getByte();
- switch (type) {
- case SSH_FILEXFER_TYPE_REGULAR:
- attrs.put("isRegular", Boolean.TRUE);
- break;
- case SSH_FILEXFER_TYPE_DIRECTORY:
- attrs.put("isDirectory", Boolean.TRUE);
- break;
- case SSH_FILEXFER_TYPE_SYMLINK:
- attrs.put("isSymbolicLink", Boolean.TRUE);
- break;
- case SSH_FILEXFER_TYPE_UNKNOWN:
- attrs.put("isOther", Boolean.TRUE);
- break;
- default: // ignored
- }
- }
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- attrs.put("size", Long.valueOf(buffer.getLong()));
- }
- if ((flags & SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0) {
- attrs.put("allocationSize", Long.valueOf(buffer.getLong()));
- }
- if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
- attrs.put("uid", Integer.valueOf(buffer.getInt()));
- attrs.put("gid", Integer.valueOf(buffer.getInt()));
- }
- if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
- attrs.put("owner", new DefaultGroupPrincipal(buffer.getString()));
- attrs.put("group", new DefaultGroupPrincipal(buffer.getString()));
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- attrs.put("permissions", permissionsToAttributes(buffer.getInt()));
- }
- if (version == SFTP_V3) {
- if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- attrs.put("lastAccessTime", readTime(buffer, flags));
- attrs.put("lastModifiedTime", readTime(buffer, flags));
- }
- } else if (version >= SFTP_V4) {
- if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
- attrs.put("lastAccessTime", readTime(buffer, flags));
- }
- if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
- attrs.put("creationTime", readTime(buffer, flags));
- }
- if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
- attrs.put("lastModifiedTime", readTime(buffer, flags));
- }
- if ((flags & SSH_FILEXFER_ATTR_CTIME) != 0) {
- attrs.put("ctime", readTime(buffer, flags));
- }
- }
- if ((flags & SSH_FILEXFER_ATTR_ACL) != 0) {
- int count = buffer.getInt();
- List<AclEntry> acls = new ArrayList<>();
- for (int i = 0; i < count; i++) {
- int aclType = buffer.getInt();
- int aclFlag = buffer.getInt();
- int aclMask = buffer.getInt();
- String aclWho = buffer.getString();
- acls.add(buildAclEntry(aclType, aclFlag, aclMask, aclWho));
- }
- attrs.put("acl", acls);
- }
- if ((flags & SSH_FILEXFER_ATTR_BITS) != 0) {
- int bits = buffer.getInt();
- int valid = 0xffffffff;
- if (version >= SFTP_V6) {
- valid = buffer.getInt();
- }
- // TODO: handle attrib bits
- }
- if ((flags & SSH_FILEXFER_ATTR_TEXT_HINT) != 0) {
- boolean text = buffer.getBoolean();
- // TODO: handle text
- }
- if ((flags & SSH_FILEXFER_ATTR_MIME_TYPE) != 0) {
- String mimeType = buffer.getString();
- // TODO: handle mime-type
- }
- if ((flags & SSH_FILEXFER_ATTR_LINK_COUNT) != 0) {
- int nlink = buffer.getInt();
- // TODO: handle link-count
- }
- if ((flags & SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) {
- String untranslated = buffer.getString();
- // TODO: handle untranslated-name
- }
- if ((flags & SSH_FILEXFER_ATTR_EXTENDED) != 0) {
- int count = buffer.getInt();
- Map<String, String> extended = new HashMap<>();
- for (int i = 0; i < count; i++) {
- String key = buffer.getString();
- String val = buffer.getString();
- extended.put(key, val);
- }
- attrs.put("extended", extended);
- }
-
- return attrs;
- }
-
- private FileTime readTime(Buffer buffer, int flags) {
- long secs = buffer.getLong();
- long millis = secs * 1000;
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- millis += buffer.getInt() / 1000000l;
- }
- return FileTime.from(millis, TimeUnit.MILLISECONDS);
- }
-
- private AclEntry buildAclEntry(int aclType, int aclFlag, int aclMask, final String aclWho) {
- AclEntryType type;
- switch (aclType) {
- case ACE4_ACCESS_ALLOWED_ACE_TYPE:
- type = AclEntryType.ALLOW;
- break;
- case ACE4_ACCESS_DENIED_ACE_TYPE:
- type = AclEntryType.DENY;
- break;
- case ACE4_SYSTEM_AUDIT_ACE_TYPE:
- type = AclEntryType.AUDIT;
- break;
- case ACE4_SYSTEM_ALARM_ACE_TYPE:
- type = AclEntryType.AUDIT;
- break;
- default:
- throw new IllegalStateException("Unknown acl type: " + aclType);
- }
- Set<AclEntryFlag> flags = new HashSet<>();
- if ((aclFlag & ACE4_FILE_INHERIT_ACE) != 0) {
- flags.add(AclEntryFlag.FILE_INHERIT);
- }
- if ((aclFlag & ACE4_DIRECTORY_INHERIT_ACE) != 0) {
- flags.add(AclEntryFlag.DIRECTORY_INHERIT);
- }
- if ((aclFlag & ACE4_NO_PROPAGATE_INHERIT_ACE) != 0) {
- flags.add(AclEntryFlag.NO_PROPAGATE_INHERIT);
- }
- if ((aclFlag & ACE4_INHERIT_ONLY_ACE) != 0) {
- flags.add(AclEntryFlag.INHERIT_ONLY);
- }
- Set<AclEntryPermission> mask = new HashSet<>();
- if ((aclMask & ACE4_READ_DATA) != 0) {
- mask.add(AclEntryPermission.READ_DATA);
- }
- if ((aclMask & ACE4_LIST_DIRECTORY) != 0) {
- mask.add(AclEntryPermission.LIST_DIRECTORY);
- }
- if ((aclMask & ACE4_WRITE_DATA) != 0) {
- mask.add(AclEntryPermission.WRITE_DATA);
- }
- if ((aclMask & ACE4_ADD_FILE) != 0) {
- mask.add(AclEntryPermission.ADD_FILE);
- }
- if ((aclMask & ACE4_APPEND_DATA) != 0) {
- mask.add(AclEntryPermission.APPEND_DATA);
- }
- if ((aclMask & ACE4_ADD_SUBDIRECTORY) != 0) {
- mask.add(AclEntryPermission.ADD_SUBDIRECTORY);
- }
- if ((aclMask & ACE4_READ_NAMED_ATTRS) != 0) {
- mask.add(AclEntryPermission.READ_NAMED_ATTRS);
- }
- if ((aclMask & ACE4_WRITE_NAMED_ATTRS) != 0) {
- mask.add(AclEntryPermission.WRITE_NAMED_ATTRS);
- }
- if ((aclMask & ACE4_EXECUTE) != 0) {
- mask.add(AclEntryPermission.EXECUTE);
- }
- if ((aclMask & ACE4_DELETE_CHILD) != 0) {
- mask.add(AclEntryPermission.DELETE_CHILD);
- }
- if ((aclMask & ACE4_READ_ATTRIBUTES) != 0) {
- mask.add(AclEntryPermission.READ_ATTRIBUTES);
- }
- if ((aclMask & ACE4_WRITE_ATTRIBUTES) != 0) {
- mask.add(AclEntryPermission.WRITE_ATTRIBUTES);
- }
- if ((aclMask & ACE4_DELETE) != 0) {
- mask.add(AclEntryPermission.DELETE);
- }
- if ((aclMask & ACE4_READ_ACL) != 0) {
- mask.add(AclEntryPermission.READ_ACL);
- }
- if ((aclMask & ACE4_WRITE_ACL) != 0) {
- mask.add(AclEntryPermission.WRITE_ACL);
- }
- if ((aclMask & ACE4_WRITE_OWNER) != 0) {
- mask.add(AclEntryPermission.WRITE_OWNER);
- }
- if ((aclMask & ACE4_SYNCHRONIZE) != 0) {
- mask.add(AclEntryPermission.SYNCHRONIZE);
- }
- UserPrincipal who = new DefaultGroupPrincipal(aclWho);
- return AclEntry.newBuilder()
- .setType(type)
- .setFlags(flags)
- .setPermissions(mask)
- .setPrincipal(who)
- .build();
- }
-
- protected void sendStatus(int id, Exception e) throws IOException {
- int substatus;
- if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
- substatus = SSH_FX_NO_SUCH_FILE;
- } else if (e instanceof FileAlreadyExistsException) {
- substatus = SSH_FX_FILE_ALREADY_EXISTS;
- } else if (e instanceof DirectoryNotEmptyException) {
- substatus = SSH_FX_DIR_NOT_EMPTY;
- } else if (e instanceof AccessDeniedException) {
- substatus = SSH_FX_PERMISSION_DENIED;
- } else if (e instanceof OverlappingFileLockException) {
- substatus = SSH_FX_LOCK_CONFLICT;
- } else {
- substatus = SSH_FX_FAILURE;
- }
- sendStatus(id, substatus, e.toString());
- }
-
- protected void sendStatus(int id, int substatus, String msg) throws IOException {
- sendStatus(id, substatus, msg != null ? msg : "", "");
- }
-
- protected void sendStatus(int id, int substatus, String msg, String lang) throws IOException {
- if (log.isDebugEnabled()) {
- log.debug("Send SSH_FXP_STATUS (substatus={}, lang={}, msg={})",
- new Object[] { Integer.valueOf(substatus), lang, msg });
- }
-
- Buffer buffer = new ByteArrayBuffer();
- buffer.putByte((byte) SSH_FXP_STATUS);
- buffer.putInt(id);
- buffer.putInt(substatus);
- buffer.putString(msg);
- buffer.putString(lang);
- send(buffer);
- }
-
- protected void send(Buffer buffer) throws IOException {
- DataOutputStream dos = new DataOutputStream(out);
- dos.writeInt(buffer.available());
- dos.write(buffer.array(), buffer.rpos(), buffer.available());
- dos.flush();
- }
-
- @Override
- public void destroy() {
- if (!closed) {
- if (log.isDebugEnabled()) {
- log.debug("destroy() - mark as closed");
- }
-
- closed = true;
-
- // if thread has not completed, cancel it
- if ((pendingFuture != null) && (!pendingFuture.isDone())) {
- boolean result = pendingFuture.cancel(true);
- // TODO consider waiting some reasonable (?) amount of time for cancellation
- if (log.isDebugEnabled()) {
- log.debug("destroy() - cancel pending future=" + result);
- }
- }
-
- pendingFuture = null;
-
- if ((executors != null) && (!executors.isShutdown()) && shutdownExecutor) {
- Collection<Runnable> runners = executors.shutdownNow();
- if (log.isDebugEnabled()) {
- log.debug("destroy() - shutdown executor service - runners count=" + ((runners == null) ? 0 : runners.size()));
- }
- }
-
- executors = null;
-
- try {
- fileSystem.close();
- } catch (UnsupportedOperationException e) {
- // Ignore
- } catch (IOException e) {
- log.debug("Error closing FileSystem", e);
- }
- }
- }
-
- private Path resolveFile(String path) {
- //in case we are running on Windows
- String localPath = SelectorUtils.translateToLocalPath(path);
- return defaultDir.resolve(localPath);
- }
-
- private final static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May",
- "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-
- /**
- * Get unix style date string.
- */
- private static String getUnixDate(FileTime time) {
- return getUnixDate(time != null ? time.toMillis() : -1);
- }
-
- private static String getUnixDate(long millis) {
- if (millis < 0) {
- return "------------";
- }
-
- StringBuilder sb = new StringBuilder(16);
- Calendar cal = new GregorianCalendar();
- cal.setTimeInMillis(millis);
-
- // month
- sb.append(MONTHS[cal.get(Calendar.MONTH)]);
- sb.append(' ');
-
- // day
- int day = cal.get(Calendar.DATE);
- if (day < 10) {
- sb.append(' ');
- }
- sb.append(day);
- sb.append(' ');
-
- long sixMonth = 15811200000L; // 183L * 24L * 60L * 60L * 1000L;
- long nowTime = System.currentTimeMillis();
- if (Math.abs(nowTime - millis) > sixMonth) {
-
- // year
- int year = cal.get(Calendar.YEAR);
- sb.append(' ');
- sb.append(year);
- } else {
-
- // hour
- int hh = cal.get(Calendar.HOUR_OF_DAY);
- if (hh < 10) {
- sb.append('0');
- }
- sb.append(hh);
- sb.append(':');
-
- // minute
- int mm = cal.get(Calendar.MINUTE);
- if (mm < 10) {
- sb.append('0');
- }
- sb.append(mm);
- }
- return sb.toString();
- }
-
- protected static 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;
- if (Objects.equals(getName(),that.getName())) {
- return true;
- } else {
- return false; // debug breakpoint
- }
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(getName());
- }
-
- @Override
- public String toString() {
- return getName();
- }
- }
-
- protected static class DefaultUserPrincipal extends PrincipalBase implements UserPrincipal {
- public DefaultUserPrincipal(String name) {
- super(name);
- }
- }
-
- protected static class DefaultGroupPrincipal extends PrincipalBase implements GroupPrincipal {
- public DefaultGroupPrincipal(String name) {
- super(name);
- }
- }
-}
[03/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/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
new file mode 100644
index 0000000..c6baa5c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -0,0 +1,2280 @@
+/*
+ * 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 static org.apache.sshd.common.subsystem.sftp.SftpConstants.*;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclEntryFlag;
+import java.nio.file.attribute.AclEntryPermission;
+import java.nio.file.attribute.AclEntryType;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.file.FileSystemAware;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.SelectorUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.common.util.threads.ThreadUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * SFTP subsystem
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpSubsystem extends AbstractLoggingBean implements Command, Runnable, SessionAware, FileSystemAware {
+
+ /**
+ * Properties key for the maximum of available open handles per session.
+ */
+ public static final String MAX_OPEN_HANDLES_PER_SESSION = "max-open-handles-per-session";
+
+ /**
+ * Force the use of a given sftp version
+ */
+ public static final String SFTP_VERSION = "sftp-version";
+
+ public static final int LOWER_SFTP_IMPL = SFTP_V3; // Working implementation from v3
+ public static final int HIGHER_SFTP_IMPL = SFTP_V6; // .. up to
+ public static final String ALL_SFTP_IMPL;
+ public static final int MAX_PACKET_LENGTH = 1024 * 16;
+
+ static {
+ StringBuilder sb = new StringBuilder(2 * (1 + (HIGHER_SFTP_IMPL - LOWER_SFTP_IMPL)));
+ for (int v = LOWER_SFTP_IMPL; v <= HIGHER_SFTP_IMPL; v++) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append(v);
+ }
+ ALL_SFTP_IMPL = sb.toString();
+ }
+
+ private ExitCallback callback;
+ private InputStream in;
+ private OutputStream out;
+ private OutputStream err;
+ private Environment env;
+ private ServerSession session;
+ private boolean closed = false;
+ private ExecutorService executors;
+ private boolean shutdownExecutor;
+ private Future<?> pendingFuture;
+
+ private FileSystem fileSystem = FileSystems.getDefault();
+ private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
+
+ private int version;
+ private final Map<String, byte[]> extensions = new HashMap<>();
+ private final Map<String, Handle> handles = new HashMap<>();
+
+ private final UnsupportedAttributePolicy unsupportedAttributePolicy;
+
+ protected static abstract class Handle implements java.io.Closeable {
+ private Path file;
+
+ public Handle(Path file) {
+ this.file = file;
+ }
+
+ public Path getFile() {
+ return file;
+ }
+
+ @Override
+ public void close() throws IOException {
+ // ignored
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toString(getFile());
+ }
+ }
+
+ protected static class DirectoryHandle extends Handle implements Iterator<Path> {
+ private boolean done;
+ // the directory should be read once at "open directory"
+ private DirectoryStream<Path> ds;
+ private Iterator<Path> fileList;
+
+ public DirectoryHandle(Path file) throws IOException {
+ super(file);
+ ds = Files.newDirectoryStream(file);
+ fileList = ds.iterator();
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+
+ public void setDone(boolean done) {
+ this.done = done;
+ }
+
+ @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());
+ }
+
+ public void clearFileList() {
+ // allow the garbage collector to do the job
+ fileList = null;
+ }
+
+ @Override
+ public void close() throws IOException {
+ ds.close();
+ }
+ }
+
+ protected class FileHandle extends Handle {
+ private final FileChannel channel;
+ private long pos;
+ private final List<FileLock> locks = new ArrayList<>();
+
+ public FileHandle(Path file, int flags, int access, Map<String, Object> attrs) throws IOException {
+ super(file);
+ Set<OpenOption> options = new HashSet<>();
+ if ((access & ACE4_READ_DATA) != 0 || (access & ACE4_READ_ATTRIBUTES) != 0) {
+ options.add(StandardOpenOption.READ);
+ }
+ if ((access & ACE4_WRITE_DATA) != 0 || (access & ACE4_WRITE_ATTRIBUTES) != 0) {
+ options.add(StandardOpenOption.WRITE);
+ }
+ switch (flags & SSH_FXF_ACCESS_DISPOSITION) {
+ case SSH_FXF_CREATE_NEW:
+ options.add(StandardOpenOption.CREATE_NEW);
+ break;
+ case SSH_FXF_CREATE_TRUNCATE:
+ options.add(StandardOpenOption.CREATE);
+ options.add(StandardOpenOption.TRUNCATE_EXISTING);
+ break;
+ case SSH_FXF_OPEN_EXISTING:
+ break;
+ case SSH_FXF_OPEN_OR_CREATE:
+ options.add(StandardOpenOption.CREATE);
+ break;
+ case SSH_FXF_TRUNCATE_EXISTING:
+ options.add(StandardOpenOption.TRUNCATE_EXISTING);
+ break;
+ default: // ignored
+ }
+ if ((flags & SSH_FXF_APPEND_DATA) != 0) {
+ options.add(StandardOpenOption.APPEND);
+ }
+ FileAttribute<?>[] attributes = new FileAttribute<?>[attrs.size()];
+ int index = 0;
+ for (Map.Entry<String, Object> attr : attrs.entrySet()) {
+ final String key = attr.getKey();
+ final Object val = attr.getValue();
+ attributes[index++] = new FileAttribute<Object>() {
+ @Override
+ public String name() {
+ return key;
+ }
+
+ @Override
+ public Object value() {
+ return val;
+ }
+ };
+ }
+ FileChannel channel;
+ try {
+ channel = FileChannel.open(file, options, attributes);
+ } catch (UnsupportedOperationException e) {
+ channel = FileChannel.open(file, options);
+ setAttributes(file, attrs);
+ }
+ this.channel = channel;
+ this.pos = 0;
+ }
+
+ 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 {
+ if (pos != offset) {
+ channel.position(offset);
+ pos = offset;
+ }
+ int read = channel.read(ByteBuffer.wrap(data, doff, length));
+ pos += read;
+ return read;
+ }
+
+ 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 {
+ if (pos != offset) {
+ channel.position(offset);
+ pos = offset;
+ }
+ channel.write(ByteBuffer.wrap(data, doff, length));
+ pos += length;
+ }
+
+ @Override
+ public void close() throws IOException {
+ channel.close();
+ }
+
+ public void lock(long offset, long length, int mask) throws IOException {
+ long size = length == 0 ? channel.size() - offset : length;
+ FileLock lock = channel.tryLock(offset, size, false);
+ synchronized (locks) {
+ locks.add(lock);
+ }
+ }
+
+ public boolean unlock(long offset, long length) throws IOException {
+ long size = length == 0 ? 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) {
+ lock.release();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * @param executorService 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.
+ * @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 shutdown regardless
+ * @param policy The {@link UnsupportedAttributePolicy} to use if failed to access
+ * some local file attributes
+ * @see ThreadUtils#newSingleThreadExecutor(String)
+ */
+ public SftpSubsystem(ExecutorService executorService, boolean shutdownOnExit, UnsupportedAttributePolicy policy) {
+ if ((executors = executorService) == null) {
+ executors = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
+ shutdownExecutor = true; // we always close the ad-hoc executor service
+ } else {
+ shutdownExecutor = shutdownOnExit;
+ }
+
+ if ((unsupportedAttributePolicy=policy) == null) {
+ throw new IllegalArgumentException("No policy provided");
+ }
+ }
+
+ public final UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
+ return unsupportedAttributePolicy;
+ }
+
+ @Override
+ public void setSession(ServerSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public void setFileSystem(FileSystem fileSystem) {
+ if (fileSystem != this.fileSystem) {
+ this.fileSystem = fileSystem;
+ this.defaultDir = fileSystem.getRootDirectories().iterator().next();
+ }
+ }
+
+ @Override
+ public void setExitCallback(ExitCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void setInputStream(InputStream in) {
+ this.in = in;
+ }
+
+ @Override
+ public void setOutputStream(OutputStream out) {
+ this.out = out;
+ }
+
+ @Override
+ public void setErrorStream(OutputStream err) {
+ this.err = err;
+ }
+
+ @Override
+ public void start(Environment env) throws IOException {
+ this.env = env;
+ try {
+ pendingFuture = executors.submit(this);
+ } catch (RuntimeException e) { // e.g., RejectedExecutionException
+ log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.toString(), e);
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public void run() {
+ DataInputStream dis = null;
+ try {
+ dis = new DataInputStream(in);
+ while (true) {
+ int length = dis.readInt();
+ if (length < 5) {
+ throw new IllegalArgumentException("Bad length to read: " + length);
+ }
+ Buffer buffer = new ByteArrayBuffer(length + 4);
+ buffer.putInt(length);
+ int nb = length;
+ while (nb > 0) {
+ int l = dis.read(buffer.array(), buffer.wpos(), nb);
+ if (l < 0) {
+ throw new IllegalArgumentException("Premature EOF while read length=" + length + " while remain=" + nb);
+ }
+ buffer.wpos(buffer.wpos() + l);
+ nb -= l;
+ }
+ process(buffer);
+ }
+ } catch (Throwable t) {
+ if (!closed && !(t instanceof EOFException)) { // Ignore
+ log.error("Exception caught in SFTP subsystem", t);
+ }
+ } finally {
+ if (dis != null) {
+ try {
+ dis.close();
+ } catch (IOException ioe) {
+ log.error("Could not close DataInputStream", ioe);
+ }
+ }
+
+ if (handles != null) {
+ for (Map.Entry<String, Handle> entry : handles.entrySet()) {
+ Handle handle = entry.getValue();
+ try {
+ handle.close();
+ } catch (IOException ioe) {
+ log.error("Could not close open handle: " + entry.getKey(), ioe);
+ }
+ }
+ }
+ callback.onExit(0);
+ }
+ }
+
+ protected void process(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+ if (log.isDebugEnabled()) {
+ log.debug("process(length={}, type={}, id={})",
+ new Object[] { Integer.valueOf(length), Integer.valueOf(type), Integer.valueOf(id) });
+ }
+
+ switch (type) {
+ case SSH_FXP_INIT:
+ doInit(buffer, id);
+ break;
+ case SSH_FXP_OPEN:
+ doOpen(buffer, id);
+ break;
+ case SSH_FXP_CLOSE:
+ doClose(buffer, id);
+ break;
+ case SSH_FXP_READ:
+ doRead(buffer, id);
+ break;
+ case SSH_FXP_WRITE:
+ doWrite(buffer, id);
+ break;
+ case SSH_FXP_LSTAT:
+ doLStat(buffer, id);
+ break;
+ case SSH_FXP_FSTAT:
+ doFStat(buffer, id);
+ break;
+ case SSH_FXP_SETSTAT:
+ doSetStat(buffer, id);
+ break;
+ case SSH_FXP_FSETSTAT:
+ doFSetStat(buffer, id);
+ break;
+ case SSH_FXP_OPENDIR:
+ doOpenDir(buffer, id);
+ break;
+ case SSH_FXP_READDIR:
+ doReadDir(buffer, id);
+ break;
+ case SSH_FXP_REMOVE:
+ doRemove(buffer, id);
+ break;
+ case SSH_FXP_MKDIR:
+ doMakeDirectory(buffer, id);
+ break;
+ case SSH_FXP_RMDIR:
+ doRemoveDirectory(buffer, id);
+ break;
+ case SSH_FXP_REALPATH:
+ doRealPath(buffer, id);
+ break;
+ case SSH_FXP_STAT:
+ doStat(buffer, id);
+ break;
+ case SSH_FXP_RENAME:
+ doRename(buffer, id);
+ break;
+ case SSH_FXP_READLINK:
+ doReadLink(buffer, id);
+ break;
+ case SSH_FXP_SYMLINK:
+ doSymLink(buffer, id);
+ break;
+ case SSH_FXP_LINK:
+ doLink(buffer, id);
+ break;
+ case SSH_FXP_BLOCK:
+ doBlock(buffer, id);
+ break;
+ case SSH_FXP_UNBLOCK:
+ doUnblock(buffer, id);
+ break;
+ case SSH_FXP_EXTENDED:
+ doExtended(buffer, id);
+ break;
+ default:
+ log.warn("Unknown command type received: {}", Integer.valueOf(type));
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
+ }
+ }
+
+ protected void doExtended(Buffer buffer, int id) throws IOException {
+ String extension = buffer.getString();
+ switch (extension) {
+ case "text-seek":
+ doTextSeek(buffer, id);
+ break;
+ case "version-select":
+ doVersionSelect(buffer, id);
+ break;
+ default:
+ log.info("Received unsupported SSH_FXP_EXTENDED({})", extension);
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(" + extension + ") is unsupported or not implemented");
+ break;
+ }
+ }
+
+ protected void doTextSeek(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long line = buffer.getLong();
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_EXTENDED(text-seek) (handle={}, line={})", handle, Long.valueOf(line));
+ }
+
+ // TODO : implement text-seek
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(text-seek) is unsupported or not implemented");
+ }
+
+ protected void doVersionSelect(Buffer buffer, int id) throws IOException {
+ String ver = buffer.getString();
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_EXTENDED(version-select) (version={})", Integer.valueOf(version));
+ }
+
+ if (GenericUtils.length(ver) == 1) {
+ char digit = ver.charAt(0);
+ if ((digit >= '0') && (digit <= '9')) {
+ int value = digit - '0';
+ String all = checkVersionCompatibility(id, value, SSH_FX_FAILURE);
+ if (GenericUtils.isEmpty(all)) { // validation failed
+ return;
+ }
+
+ version = value;
+ sendStatus(id, SSH_FX_OK, "");
+ return;
+ }
+ }
+
+ sendStatus(id, SSH_FX_FAILURE, "Unsupported version " + ver);
+ }
+
+ /**
+ * Checks if a proposed version is within supported range. <B>Note:</B>
+ * if the user forced a specific value via the {@link #SFTP_VERSION}
+ * property, then it is used to validate the proposed value
+ * @param id The SSH message ID to be used to send the failure message
+ * if required
+ * @param proposed The proposed version value
+ * @param failureOpcode The failure opcode to send if validation fails
+ * @return A {@link String} of comma separated values representing all
+ * the supported version - {@code null} if validation failed and an
+ * appropriate status message was sent
+ * @throws IOException If failed to send the failure status message
+ */
+ protected String checkVersionCompatibility(int id, int proposed, int failureOpcode) throws IOException {
+ int low = LOWER_SFTP_IMPL;
+ int hig = HIGHER_SFTP_IMPL;
+ String available = ALL_SFTP_IMPL;
+ // check if user wants to use a specific version
+ Integer sftpVersion = FactoryManagerUtils.getInteger(session, SFTP_VERSION);
+ if (sftpVersion != null) {
+ int forcedValue = sftpVersion.intValue();
+ if ((forcedValue < LOWER_SFTP_IMPL) || (forcedValue > HIGHER_SFTP_IMPL)) {
+ throw new IllegalStateException("Forced SFTP version (" + sftpVersion + ") not within supported values: " + available);
+ }
+ low = hig = sftpVersion.intValue();
+ available = sftpVersion.toString();
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("checkVersionCompatibility(id={}) - proposed={}, available={}",
+ new Object[] { Integer.valueOf(id), Integer.valueOf(proposed), available });
+ }
+
+ if ((proposed < low) || (proposed > hig)) {
+ sendStatus(id, failureOpcode, "Proposed version (" + proposed + ") not in supported range: " + available);
+ return null;
+ }
+
+ return available;
+ }
+
+ protected void doBlock(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long offset = buffer.getLong();
+ long length = buffer.getLong();
+ int mask = buffer.getInt();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_BLOCK (handle={}, offset={}, length={}, mask=0x{})",
+ new Object[] { handle, Long.valueOf(offset), Long.valueOf(length), Integer.toHexString(mask) });
+ }
+
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ return;
+ }
+ FileHandle fileHandle = (FileHandle) p;
+ fileHandle.lock(offset, length, mask);
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (IOException | OverlappingFileLockException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doUnblock(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long offset = buffer.getLong();
+ long length = buffer.getLong();
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_UNBLOCK (handle={}, offset={}, length={})",
+ new Object[] { handle, Long.valueOf(offset), Long.valueOf(length) });
+ }
+
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ return;
+ }
+ FileHandle fileHandle = (FileHandle) p;
+ boolean found = fileHandle.unlock(offset, length);
+ sendStatus(id, found ? SSH_FX_OK : SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK, "");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doLink(Buffer buffer, int id) throws IOException {
+ String targetpath = buffer.getString();
+ String linkpath = buffer.getString();
+ boolean symLink = buffer.getBoolean();
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_LINK (linkpath={}, targetpath={}, symlink={})",
+ new Object[] { linkpath, targetpath, Boolean.valueOf(symLink) });
+ }
+
+ try {
+ Path link = resolveFile(linkpath);
+ Path target = fileSystem.getPath(targetpath);
+ if (symLink) {
+ Files.createSymbolicLink(link, target);
+ } else {
+ Files.createLink(link, target);
+ }
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (UnsupportedOperationException e) {
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_SYMLINK is unsupported or not implemented");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doSymLink(Buffer buffer, int id) throws IOException {
+ String targetpath = buffer.getString();
+ String linkpath = buffer.getString();
+ log.debug("Received SSH_FXP_SYMLINK (linkpath={}, targetpath={})", linkpath, targetpath);
+ try {
+ Path link = resolveFile(linkpath);
+ Path target = fileSystem.getPath(targetpath);
+ Files.createSymbolicLink(link, target);
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (UnsupportedOperationException e) {
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_SYMLINK is unsupported or not implemented");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doReadLink(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_READLINK (path={})", path);
+ try {
+ Path f = resolveFile(path);
+ String l = Files.readSymbolicLink(f).toString();
+ sendLink(id, l);
+ } catch (UnsupportedOperationException e) {
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_READLINK is unsupported or not implemented");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doRename(Buffer buffer, int id) throws IOException {
+ String oldPath = buffer.getString();
+ String newPath = buffer.getString();
+ int flags = 0;
+ if (version >= SFTP_V5) {
+ flags = buffer.getInt();
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_RENAME (oldPath={}, newPath={}, flags=0x{})",
+ new Object[] { oldPath, newPath, Integer.toHexString(flags) });
+ }
+ try {
+ List<CopyOption> opts = new ArrayList<>();
+ if ((flags & SSH_FXP_RENAME_ATOMIC) != 0) {
+ opts.add(StandardCopyOption.ATOMIC_MOVE);
+ }
+ if ((flags & SSH_FXP_RENAME_OVERWRITE) != 0) {
+ opts.add(StandardCopyOption.REPLACE_EXISTING);
+ }
+ Path o = resolveFile(oldPath);
+ Path n = resolveFile(newPath);
+ Files.move(o, n, opts.toArray(new CopyOption[opts.size()]));
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doStat(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ int flags = SSH_FILEXFER_ATTR_ALL;
+ if (version >= SFTP_V4) {
+ flags = buffer.getInt();
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_STAT (path={}, flags={})", path, "0x" + Integer.toHexString(flags));
+ }
+ try {
+ Path p = resolveFile(path);
+ sendAttrs(id, p, flags, true);
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doRealPath(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_REALPATH (path={})", path);
+ path = GenericUtils.trimToEmpty(path);
+ if (GenericUtils.isEmpty(path)) {
+ path = ".";
+ }
+
+ try {
+ if (version < SFTP_V6) {
+ Path f = resolveFile(path);
+ Path abs = f.toAbsolutePath();
+ Path p = abs.normalize();
+ Boolean status = IoUtils.checkFileExists(p, IoUtils.EMPTY_LINK_OPTIONS);
+ if (status == null) {
+ p = handleUnknownRealPathStatus(path, abs, p);
+ } else if (!status.booleanValue()) {
+ throw new FileNotFoundException(p.toString());
+ }
+ sendPath(id, p, Collections.<String, Object>emptyMap());
+ } else {
+ // Read control byte
+ int control = 0;
+ if (buffer.available() > 0) {
+ control = buffer.getByte();
+ }
+ List<String> paths = new ArrayList<>();
+ while (buffer.available() > 0) {
+ paths.add(buffer.getString());
+ }
+ // Resolve path
+ Path p = resolveFile(path);
+ for (String p2 : paths) {
+ p = p.resolve(p2);
+ }
+ p = p.toAbsolutePath().normalize();
+
+ Map<String, Object> attrs = Collections.emptyMap();
+ if (control == SSH_FXP_REALPATH_STAT_IF) {
+ try {
+ attrs = getAttributes(p, false);
+ } catch (IOException e) {
+ // ignore
+ }
+ } else if (control == SSH_FXP_REALPATH_STAT_ALWAYS) {
+ attrs = getAttributes(p, false);
+ }
+ sendPath(id, p, attrs);
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected Path handleUnknownRealPathStatus(String path, Path absolute, Path normalized) throws IOException {
+ switch(unsupportedAttributePolicy) {
+ case Ignore:
+ break;
+ case Warn:
+ log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized);
+ break;
+ case ThrowException:
+ throw new AccessDeniedException("Cannot determine existence status of real path: " + normalized);
+
+ default:
+ log.warn("handleUnknownRealPathStatus(" + path + ") abs=" + absolute + ", normal=" + normalized
+ + " - unknown policy: " + unsupportedAttributePolicy);
+ }
+
+ return absolute;
+ }
+
+ protected void doRemoveDirectory(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_RMDIR (path={})", path);
+ // attrs
+ try {
+ Path p = resolveFile(path);
+ if (Files.isDirectory(p, IoUtils.getLinkOptions(false))) {
+ Files.delete(p);
+ sendStatus(id, SSH_FX_OK, "");
+ } else {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doMakeDirectory(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ Map<String, Object> attrs = readAttrs(buffer);
+
+ log.debug("Received SSH_FXP_MKDIR (path={})", path);
+ // attrs
+ try {
+ Path p = resolveFile(path);
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ Boolean status = IoUtils.checkFileExists(p, options);
+ if (status == null) {
+ throw new AccessDeniedException("Cannot make-directory existence for " + p);
+ }
+ if (status.booleanValue()) {
+ if (Files.isDirectory(p, options)) {
+ sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.toString());
+ } else {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+ }
+ } else {
+ Files.createDirectory(p);
+ setAttributes(p, attrs);
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doRemove(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_REMOVE (path={})", path);
+ try {
+ Path p = resolveFile(path);
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ Boolean status = IoUtils.checkFileExists(p, options);
+ if (status == null) {
+ throw new AccessDeniedException("Cannot determine existence of remove candidate: " + p);
+ }
+ if (!status.booleanValue()) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+ } else if (Files.isDirectory(p, options)) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.toString());
+ } else {
+ Files.delete(p);
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doReadDir(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ log.debug("Received SSH_FXP_READDIR (handle={})", handle);
+ Handle p = handles.get(handle);
+ try {
+ if (!(p instanceof DirectoryHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ return;
+ }
+
+ if (((DirectoryHandle) p).isDone()) {
+ sendStatus(id, SSH_FX_EOF, "", "");
+ return;
+ }
+
+ Path file = p.getFile();
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ Boolean status = IoUtils.checkFileExists(file, options);
+ if (status == null) {
+ throw new AccessDeniedException("Cannot determine existence of read-dir for " + file);
+ }
+
+ if (!status.booleanValue()) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, file.toString());
+ } else if (!Files.isDirectory(file, options)) {
+ sendStatus(id, SSH_FX_NOT_A_DIRECTORY, file.toString());
+ } else if (!Files.isReadable(file)) {
+ sendStatus(id, SSH_FX_PERMISSION_DENIED, file.toString());
+ } else {
+ DirectoryHandle dh = (DirectoryHandle) p;
+ if (dh.hasNext()) {
+ // There is at least one file in the directory.
+ // Send only a few files at a time to not create packets of a too
+ // large size or have a timeout to occur.
+ sendName(id, dh);
+ if (!dh.hasNext()) {
+ // if no more files to send
+ dh.setDone(true);
+ dh.clearFileList();
+ }
+ } else {
+ // empty directory
+ dh.setDone(true);
+ dh.clearFileList();
+ sendStatus(id, SSH_FX_EOF, "", "");
+ }
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doOpenDir(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ log.debug("Received SSH_FXP_OPENDIR (path={})", path);
+ try {
+ Path p = resolveFile(path);
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ Boolean status = IoUtils.checkFileExists(p, options);
+ if (status == null) {
+ throw new AccessDeniedException("Cannot determine open-dir existence of " + p);
+ }
+
+ if (!status.booleanValue()) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
+ } else if (!Files.isDirectory(p, options)) {
+ sendStatus(id, SSH_FX_NOT_A_DIRECTORY, path);
+ } else if (!Files.isReadable(p)) {
+ sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
+ } else {
+ String handle = UUID.randomUUID().toString();
+ handles.put(handle, new DirectoryHandle(p));
+ sendHandle(id, handle);
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doFSetStat(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ Map<String, Object> attrs = readAttrs(buffer);
+ log.debug("Received SSH_FXP_FSETSTAT (handle={}, attrs={})", handle, attrs);
+ try {
+ Handle p = handles.get(handle);
+ if (p == null) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ setAttributes(p.getFile(), attrs);
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException | UnsupportedOperationException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doSetStat(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ Map<String, Object> attrs = readAttrs(buffer);
+ log.debug("Received SSH_FXP_SETSTAT (path={}, attrs={})", path, attrs);
+ try {
+ Path p = resolveFile(path);
+ setAttributes(p, attrs);
+ sendStatus(id, SSH_FX_OK, "");
+ } catch (IOException | UnsupportedOperationException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doFStat(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ int flags = SSH_FILEXFER_ATTR_ALL;
+ if (version >= SFTP_V4) {
+ flags = buffer.getInt();
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_FSTAT (handle={}, flags={})", handle, "0x" + Integer.toHexString(flags));
+ }
+ try {
+ Handle p = handles.get(handle);
+ if (p == null) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ sendAttrs(id, p.getFile(), flags, true);
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doLStat(Buffer buffer, int id) throws IOException {
+ String path = buffer.getString();
+ int flags = SSH_FILEXFER_ATTR_ALL;
+ if (version >= SFTP_V4) {
+ flags = buffer.getInt();
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_LSTAT (path={}, flags={})", path, "0x" + Integer.toHexString(flags));
+ }
+ try {
+ Path p = resolveFile(path);
+ sendAttrs(id, p, flags, false);
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doWrite(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long offset = buffer.getLong();
+ int length = buffer.getInt();
+ if (length < 0) {
+ throw new IllegalStateException();
+ }
+ if (buffer.available() < length) {
+ throw new BufferUnderflowException();
+ }
+ byte[] data = buffer.array();
+ int doff = buffer.rpos();
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_WRITE (handle={}, offset={}, data=byte[{}])",
+ new Object[] { handle, Long.valueOf(offset), Integer.valueOf(length) });
+ }
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ FileHandle fh = (FileHandle) p;
+ fh.write(data, doff, length, offset);
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doRead(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ long offset = buffer.getLong();
+ int len = buffer.getInt();
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_READ (handle={}, offset={}, length={})",
+ new Object[]{handle, Long.valueOf(offset), Integer.valueOf(len) });
+ }
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ FileHandle fh = (FileHandle) p;
+ Buffer buf = new ByteArrayBuffer(len + 9);
+ buf.putByte((byte) SSH_FXP_DATA);
+ buf.putInt(id);
+ int pos = buf.wpos();
+ buf.putInt(0);
+ len = fh.read(buf.array(), buf.wpos(), len, offset);
+ if (len >= 0) {
+ buf.wpos(pos);
+ buf.putInt(len);
+ buf.wpos(pos + 4 + len);
+ send(buf);
+ } else {
+ sendStatus(id, SSH_FX_EOF, "");
+ }
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doClose(Buffer buffer, int id) throws IOException {
+ String handle = buffer.getString();
+ log.debug("Received SSH_FXP_CLOSE (handle={})", handle);
+ try {
+ Handle h = handles.get(handle);
+ if (h == null) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle, "");
+ } else {
+ handles.remove(handle);
+ h.close();
+ sendStatus(id, SSH_FX_OK, "", "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doOpen(Buffer buffer, int id) throws IOException {
+ int maxHandleCount = FactoryManagerUtils.getIntProperty(session, MAX_OPEN_HANDLES_PER_SESSION, Integer.MAX_VALUE);
+ if (handles.size() > maxHandleCount) {
+ sendStatus(id, SSH_FX_FAILURE, "Too many open handles");
+ return;
+ }
+
+ String path = buffer.getString();
+ int access = 0;
+ if (version >= SFTP_V5) {
+ access = buffer.getInt();
+ }
+ int pflags = buffer.getInt();
+ if (version < SFTP_V5) {
+ int flags = pflags;
+ pflags = 0;
+ switch (flags & (SSH_FXF_READ | SSH_FXF_WRITE)) {
+ case SSH_FXF_READ:
+ access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+ break;
+ case SSH_FXF_WRITE:
+ access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+ break;
+ default:
+ access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+ access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+ break;
+ }
+ if ((flags & SSH_FXF_APPEND) != 0) {
+ access |= ACE4_APPEND_DATA;
+ pflags |= SSH_FXF_APPEND_DATA | SSH_FXF_APPEND_DATA_ATOMIC;
+ }
+ if ((flags & SSH_FXF_CREAT) != 0) {
+ if ((flags & SSH_FXF_EXCL) != 0) {
+ pflags |= SSH_FXF_CREATE_NEW;
+ } else if ((flags & SSH_FXF_TRUNC) != 0) {
+ pflags |= SSH_FXF_CREATE_TRUNCATE;
+ } else {
+ pflags |= SSH_FXF_OPEN_OR_CREATE;
+ }
+ } else {
+ if ((flags & SSH_FXF_TRUNC) != 0) {
+ pflags |= SSH_FXF_TRUNCATE_EXISTING;
+ } else {
+ pflags |= SSH_FXF_OPEN_EXISTING;
+ }
+ }
+ }
+ Map<String, Object> attrs = readAttrs(buffer);
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_OPEN (path={}, access=0x{}, pflags=0x{}, attrs={})",
+ new Object[]{path, Integer.toHexString(access), Integer.toHexString(pflags), attrs});
+ }
+ try {
+ Path file = resolveFile(path);
+ String handle = UUID.randomUUID().toString();
+ handles.put(handle, new FileHandle(file, pflags, access, attrs));
+ sendHandle(id, handle);
+ } catch (IOException e) {
+ sendStatus(id, e);
+ }
+ }
+
+ protected void doInit(Buffer buffer, int id) throws IOException {
+ if (log.isDebugEnabled()) {
+ log.debug("Received SSH_FXP_INIT (version={})", Integer.valueOf(id));
+ }
+
+ String all = checkVersionCompatibility(id, id, SSH_FX_OP_UNSUPPORTED);
+ if (GenericUtils.isEmpty(all)) { // i.e. validation failed
+ return;
+ }
+ version = id;
+ while (buffer.available() > 0) {
+ String name = buffer.getString();
+ byte[] data = buffer.getBytes();
+ extensions.put(name, data);
+ }
+
+ buffer.clear();
+ buffer.putByte((byte) SSH_FXP_VERSION);
+ buffer.putInt(version);
+
+ // newline
+ buffer.putString("newline");
+ buffer.putString(System.getProperty("line.separator"));
+
+ // versions
+ buffer.putString("versions");
+ buffer.putString(all);
+
+ // supported
+ buffer.putString("supported");
+ buffer.putInt(5 * 4); // length of 5 integers
+ // supported-attribute-mask
+ buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
+ | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
+ | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
+ | SSH_FILEXFER_ATTR_BITS);
+ // TODO: supported-attribute-bits
+ buffer.putInt(0);
+ // supported-open-flags
+ buffer.putInt(SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
+ | SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_EXCL);
+ // TODO: supported-access-mask
+ buffer.putInt(0);
+ // max-read-size
+ buffer.putInt(0);
+
+ // supported2
+ buffer.putString("supported2");
+ buffer.putInt(8 * 4); // length of 7 integers + 2 shorts
+ // supported-attribute-mask
+ buffer.putInt(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS
+ | SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_CREATETIME
+ | SSH_FILEXFER_ATTR_MODIFYTIME | SSH_FILEXFER_ATTR_OWNERGROUP
+ | SSH_FILEXFER_ATTR_BITS);
+ // TODO: supported-attribute-bits
+ buffer.putInt(0);
+ // supported-open-flags
+ buffer.putInt(SSH_FXF_ACCESS_DISPOSITION | SSH_FXF_APPEND_DATA);
+ // TODO: supported-access-mask
+ buffer.putInt(0);
+ // max-read-size
+ buffer.putInt(0);
+ // supported-open-block-vector
+ buffer.putShort(0);
+ // supported-block-vector
+ buffer.putShort(0);
+ // attrib-extension-count
+ buffer.putInt(0);
+ // extension-count
+ buffer.putInt(0);
+
+ /*
+ buffer.putString("acl-supported");
+ buffer.putInt(4);
+ // capabilities
+ buffer.putInt(0);
+ */
+
+ send(buffer);
+ }
+
+ protected void sendHandle(int id, String handle) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putByte((byte) SSH_FXP_HANDLE);
+ buffer.putInt(id);
+ buffer.putString(handle);
+ send(buffer);
+ }
+
+ protected void sendAttrs(int id, Path file, int flags, boolean followLinks) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putByte((byte) SSH_FXP_ATTRS);
+ buffer.putInt(id);
+ writeAttrs(buffer, file, flags, followLinks);
+ send(buffer);
+ }
+
+ protected void sendPath(int id, Path f, Map<String, Object> attrs) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putByte((byte) SSH_FXP_NAME);
+ buffer.putInt(id);
+ buffer.putInt(1);
+
+ String originalPath = f.toString();
+ //in case we are running on Windows
+ String unixPath = originalPath.replace(File.separatorChar, '/');
+ //normalize the given path, use *nix style separator
+ String normalizedPath = SelectorUtils.normalizePath(unixPath, "/");
+ if (normalizedPath.length() == 0) {
+ normalizedPath = "/";
+ }
+ buffer.putString(normalizedPath, StandardCharsets.UTF_8);
+
+ if (version == SFTP_V3) {
+ f = resolveFile(normalizedPath);
+ buffer.putString(getLongName(f, attrs), StandardCharsets.UTF_8); // Format specified in the specs
+ buffer.putInt(0);
+ } else if (version >= SFTP_V4) {
+ writeAttrs(buffer, attrs);
+ } else {
+ throw new IllegalStateException("sendPath(" + f + ") unsupported version: " + version);
+ }
+ send(buffer);
+ }
+
+ protected void sendLink(int id, String link) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putByte((byte) SSH_FXP_NAME);
+ buffer.putInt(id);
+ buffer.putInt(1);
+ //normalize the given path, use *nix style separator
+ buffer.putString(link);
+ buffer.putString(link);
+ buffer.putInt(0);
+ send(buffer);
+ }
+
+ protected void sendName(int id, Iterator<Path> files) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putByte((byte) SSH_FXP_NAME);
+ buffer.putInt(id);
+ int wpos = buffer.wpos();
+ buffer.putInt(0);
+ int nb = 0;
+ while (files.hasNext() && (buffer.wpos() < MAX_PACKET_LENGTH)) {
+ Path f = files.next();
+ String shortName = getShortName(f);
+ buffer.putString(shortName, StandardCharsets.UTF_8);
+ if (version == SFTP_V3) {
+ String longName = getLongName(f);
+ buffer.putString(longName, StandardCharsets.UTF_8); // Format specified in the specs
+ if (log.isTraceEnabled()) {
+ log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName + " [" + longName + "]");
+ }
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName);
+ }
+ }
+ writeAttrs(buffer, f, SSH_FILEXFER_ATTR_ALL, false);
+ nb++;
+ }
+
+ int oldpos = buffer.wpos();
+ buffer.wpos(wpos);
+ buffer.putInt(nb);
+ buffer.wpos(oldpos);
+ send(buffer);
+ }
+
+ private String getLongName(Path f) throws IOException {
+ return getLongName(f, true);
+ }
+
+ private String getLongName(Path f, boolean sendAttrs) throws IOException {
+ Map<String, Object> attributes;
+ if (sendAttrs) {
+ attributes = getAttributes(f, false);
+ } else {
+ attributes = Collections.emptyMap();
+ }
+ return getLongName(f, attributes);
+ }
+
+ private String getLongName(Path f, Map<String, Object> attributes) throws IOException {
+ String username;
+ if (attributes.containsKey("owner")) {
+ username = attributes.get("owner").toString();
+ } else {
+ username = "owner";
+ }
+ if (username.length() > 8) {
+ username = username.substring(0, 8);
+ } else {
+ for (int i = username.length(); i < 8; i++) {
+ username = username + " ";
+ }
+ }
+ String group;
+ if (attributes.containsKey("group")) {
+ group = attributes.get("group").toString();
+ } else {
+ group = "group";
+ }
+ if (group.length() > 8) {
+ group = group.substring(0, 8);
+ } else {
+ for (int i = group.length(); i < 8; i++) {
+ group = group + " ";
+ }
+ }
+
+ Number length = (Number) attributes.get("size");
+ if (length == null) {
+ length = Long.valueOf(0L);
+ }
+ String lengthString = String.format("%1$8s", length);
+
+ Boolean isDirectory = (Boolean) attributes.get("isDirectory");
+ Boolean isLink = (Boolean) attributes.get("isSymbolicLink");
+ @SuppressWarnings("unchecked")
+ Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
+ if (perms == null) {
+ perms = EnumSet.noneOf(PosixFilePermission.class);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append((isDirectory != null && isDirectory.booleanValue()) ? "d" : (isLink != null && isLink.booleanValue()) ? "l" : "-");
+ sb.append(PosixFilePermissions.toString(perms));
+ sb.append(" ");
+ sb.append(attributes.containsKey("nlink") ? attributes.get("nlink") : "1");
+ sb.append(" ");
+ sb.append(username);
+ sb.append(" ");
+ sb.append(group);
+ sb.append(" ");
+ sb.append(lengthString);
+ sb.append(" ");
+ sb.append(getUnixDate((FileTime) attributes.get("lastModifiedTime")));
+ sb.append(" ");
+ sb.append(getShortName(f));
+
+ return sb.toString();
+ }
+
+ protected String getShortName(Path f) {
+ if (OsUtils.isUNIX()) {
+ Path name=f.getFileName();
+ if (name == null) {
+ Path p=resolveFile(".");
+ name = p.getFileName();
+ }
+
+ return name.toString();
+ } else { // need special handling for Windows root drives
+ Path abs=f.toAbsolutePath().normalize();
+ int count=abs.getNameCount();
+ /*
+ * According to the javadoc:
+ *
+ * The number of elements in the path, or 0 if this path only
+ * represents a root component
+ */
+ if (count > 0) {
+ Path name=abs.getFileName();
+ return name.toString();
+ } else {
+ return abs.toString().replace(File.separatorChar, '/');
+ }
+ }
+ }
+
+ protected int attributesToPermissions(boolean isReg, boolean isDir, boolean isLnk, Collection<PosixFilePermission> perms) {
+ int pf = 0;
+ if (perms != null) {
+ for (PosixFilePermission p : perms) {
+ switch (p) {
+ case OWNER_READ:
+ pf |= S_IRUSR;
+ break;
+ case OWNER_WRITE:
+ pf |= S_IWUSR;
+ break;
+ case OWNER_EXECUTE:
+ pf |= S_IXUSR;
+ break;
+ case GROUP_READ:
+ pf |= S_IRGRP;
+ break;
+ case GROUP_WRITE:
+ pf |= S_IWGRP;
+ break;
+ case GROUP_EXECUTE:
+ pf |= S_IXGRP;
+ break;
+ case OTHERS_READ:
+ pf |= S_IROTH;
+ break;
+ case OTHERS_WRITE:
+ pf |= S_IWOTH;
+ break;
+ case OTHERS_EXECUTE:
+ pf |= S_IXOTH;
+ break;
+ default: // ignored
+ }
+ }
+ }
+ pf |= isReg ? S_IFREG : 0;
+ pf |= isDir ? S_IFDIR : 0;
+ pf |= isLnk ? S_IFLNK : 0;
+ return pf;
+ }
+
+ protected void writeAttrs(Buffer buffer, Path file, int flags, boolean followLinks) throws IOException {
+ LinkOption[] options = IoUtils.getLinkOptions(followLinks);
+ Boolean status = IoUtils.checkFileExists(file, options);
+ Map<String, Object> attributes;
+ if (status == null) {
+ attributes = handleUnknownStatusFileAttributes(file, flags, followLinks);
+ } else if (!status.booleanValue()) {
+ throw new FileNotFoundException(file.toString());
+ } else {
+ attributes = getAttributes(file, flags, followLinks);
+ }
+
+ writeAttrs(buffer, attributes);
+ }
+
+ protected void writeAttrs(Buffer buffer, Map<String, Object> attributes) throws IOException {
+ boolean isReg = getBool((Boolean) attributes.get("isRegularFile"));
+ boolean isDir = getBool((Boolean) attributes.get("isDirectory"));
+ boolean isLnk = getBool((Boolean) attributes.get("isSymbolicLink"));
+ @SuppressWarnings("unchecked")
+ Collection<PosixFilePermission> perms = (Collection<PosixFilePermission>) attributes.get("permissions");
+ Number size = (Number) attributes.get("size");
+ FileTime lastModifiedTime = (FileTime) attributes.get("lastModifiedTime");
+ FileTime lastAccessTime = (FileTime) attributes.get("lastAccessTime");
+
+ if (version == SFTP_V3) {
+ int flags =
+ ((isReg || isLnk) && (size != null) ? SSH_FILEXFER_ATTR_SIZE : 0) |
+ (attributes.containsKey("uid") && attributes.containsKey("gid") ? SSH_FILEXFER_ATTR_UIDGID : 0) |
+ ((perms != null) ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
+ (((lastModifiedTime != null) && (lastAccessTime != null)) ? SSH_FILEXFER_ATTR_ACMODTIME : 0);
+ buffer.putInt(flags);
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong(size.longValue());
+ }
+ if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ buffer.putInt(((Number) attributes.get("uid")).intValue());
+ buffer.putInt(((Number) attributes.get("gid")).intValue());
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributesToPermissions(isReg, isDir, isLnk, perms));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ buffer.putInt(lastAccessTime.to(TimeUnit.SECONDS));
+ buffer.putInt(lastModifiedTime.to(TimeUnit.SECONDS));
+ }
+ } else if (version >= SFTP_V4) {
+ FileTime creationTime = (FileTime) attributes.get("creationTime");
+ int flags = (((isReg || isLnk) && (size != null)) ? SSH_FILEXFER_ATTR_SIZE : 0) |
+ ((attributes.containsKey("owner") && attributes.containsKey("group")) ? SSH_FILEXFER_ATTR_OWNERGROUP : 0) |
+ ((perms != null) ? SSH_FILEXFER_ATTR_PERMISSIONS : 0) |
+ ((lastModifiedTime != null) ? SSH_FILEXFER_ATTR_MODIFYTIME : 0) |
+ ((creationTime != null) ? SSH_FILEXFER_ATTR_CREATETIME : 0) |
+ ((lastAccessTime != null) ? SSH_FILEXFER_ATTR_ACCESSTIME : 0);
+ buffer.putInt(flags);
+ buffer.putByte((byte) (isReg ? SSH_FILEXFER_TYPE_REGULAR :
+ isDir ? SSH_FILEXFER_TYPE_DIRECTORY :
+ isLnk ? SSH_FILEXFER_TYPE_SYMLINK :
+ SSH_FILEXFER_TYPE_UNKNOWN));
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong(size.longValue());
+ }
+ if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ buffer.putString(attributes.get("owner").toString(), StandardCharsets.UTF_8);
+ buffer.putString(attributes.get("group").toString(), StandardCharsets.UTF_8);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributesToPermissions(isReg, isDir, isLnk, perms));
+ }
+
+ if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ putFileTime(buffer, flags, lastAccessTime);
+ }
+
+ if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ putFileTime(buffer, flags, lastAccessTime);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ putFileTime(buffer, flags, lastModifiedTime);
+ }
+ // TODO: acls
+ // TODO: bits
+ // TODO: extended
+ }
+ }
+
+ protected void putFileTime(Buffer buffer, int flags, FileTime time) {
+ buffer.putLong(time.to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = time.to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ }
+
+ protected boolean getBool(Boolean bool) {
+ return (bool != null) && bool.booleanValue();
+ }
+
+ protected Map<String, Object> getAttributes(Path file, boolean followLinks) throws IOException {
+ return getAttributes(file, SSH_FILEXFER_ATTR_ALL, followLinks);
+ }
+
+ public static final List<String> DEFAULT_UNIX_VIEW=Collections.singletonList("unix:*");
+
+ protected Map<String, Object> handleUnknownStatusFileAttributes(Path file, int flags, boolean followLinks) throws IOException {
+ switch(unsupportedAttributePolicy) {
+ case Ignore:
+ break;
+ case ThrowException:
+ throw new AccessDeniedException("Cannot determine existence for attributes of " + file);
+ case Warn:
+ log.warn("handleUnknownStatusFileAttributes(" + file + ") cannot determine existence");
+ break;
+ default:
+ log.warn("handleUnknownStatusFileAttributes(" + file + ") unknown policy: " + unsupportedAttributePolicy);
+ }
+
+ return getAttributes(file, flags, followLinks);
+ }
+
+ protected Map<String, Object> getAttributes(Path file, int flags, boolean followLinks) throws IOException {
+ FileSystem fs=file.getFileSystem();
+ Collection<String> supportedViews=fs.supportedFileAttributeViews();
+ LinkOption[] opts=IoUtils.getLinkOptions(followLinks);
+ Map<String,Object> attrs=new HashMap<>();
+ Collection<String> views;
+
+ if (GenericUtils.isEmpty(supportedViews)) {
+ views = Collections.<String>emptyList();
+ } else if (supportedViews.contains("unix")) {
+ views = DEFAULT_UNIX_VIEW;
+ } else {
+ views = new ArrayList<String>(supportedViews.size());
+ for (String v : supportedViews) {
+ views.add(v + ":*");
+ }
+ }
+
+ for (String v : views) {
+ Map<String, Object> ta=readFileAttributes(file, v, opts);
+ attrs.putAll(ta);
+ }
+
+ // if did not get permissions from the supported views return a best approximation
+ if (!attrs.containsKey("permissions")) {
+ Set<PosixFilePermission> perms=IoUtils.getPermissionsFromFile(file.toFile());
+ attrs.put("permissions", perms);
+ }
+
+ return attrs;
+ }
+
+ protected Map<String, Object> readFileAttributes(Path file, String view, LinkOption ... opts) throws IOException {
+ try {
+ return Files.readAttributes(file, view, opts);
+ } catch(IOException e) {
+ return handleReadFileAttributesException(file, view, opts, e);
+ }
+ }
+
+ protected Map<String, Object> handleReadFileAttributesException(Path file, String view, LinkOption[] opts, IOException e) throws IOException {
+ switch(unsupportedAttributePolicy) {
+ case Ignore:
+ break;
+ case Warn:
+ log.warn("handleReadFileAttributesException(" + file + ")[" + view + "] " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ break;
+ case ThrowException:
+ throw e;
+ default:
+ log.warn("handleReadFileAttributesException(" + file + ")[" + view + "]"
+ + " Unknown policy (" + unsupportedAttributePolicy + ")"
+ + " for " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+
+ return Collections.emptyMap();
+ }
+
+ protected void setAttributes(Path file, Map<String, Object> attributes) throws IOException {
+ Set<String> unsupported = new HashSet<>();
+ for (String attribute : attributes.keySet()) {
+ String view = null;
+ Object value = attributes.get(attribute);
+ switch (attribute) {
+ case "size": {
+ long newSize = ((Number) value).longValue();
+ try (FileChannel channel = FileChannel.open(file, StandardOpenOption.WRITE)) {
+ channel.truncate(newSize);
+ }
+ continue;
+ }
+ case "uid":
+ view = "unix";
+ break;
+ case "gid":
+ view = "unix";
+ break;
+ case "owner":
+ view = "posix";
+ value = toUser(file, (UserPrincipal) value);
+ break;
+ case "group":
+ view = "posix";
+ value = toGroup(file, (GroupPrincipal) value);
+ break;
+ case "permissions":
+ if (OsUtils.isWin32()) {
+ @SuppressWarnings("unchecked")
+ Collection<PosixFilePermission> perms = (Collection<PosixFilePermission>) value;
+ IoUtils.setPermissionsToFile(file.toFile(), perms);
+ continue;
+ }
+ view = "posix";
+ break;
+
+ case "creationTime":
+ view = "basic";
+ break;
+ case "lastModifiedTime":
+ view = "basic";
+ break;
+ case "lastAccessTime":
+ view = "basic";
+ break;
+ default: // ignored
+ }
+ if (view != null && value != null) {
+ try {
+ Files.setAttribute(file, view + ":" + attribute, value, IoUtils.getLinkOptions(false));
+ } catch (UnsupportedOperationException e) {
+ unsupported.add(attribute);
+ }
+ }
+ }
+ handleUnsupportedAttributes(unsupported);
+ }
+
+ protected void handleUnsupportedAttributes(Collection<String> attributes) {
+ if (!attributes.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ for (String attr : attributes) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(attr);
+ }
+ switch (unsupportedAttributePolicy) {
+ case Ignore:
+ break;
+ case Warn:
+ log.warn("Unsupported attributes: " + sb.toString());
+ break;
+ case ThrowException:
+ throw new UnsupportedOperationException("Unsupported attributes: " + sb.toString());
+ default:
+ log.warn("Unknown policy for attributes=" + sb.toString() + ": " + unsupportedAttributePolicy);
+ }
+ }
+ }
+
+ private GroupPrincipal toGroup(Path file, GroupPrincipal name) throws IOException {
+ String groupName = name.toString();
+ FileSystem fileSystem = file.getFileSystem();
+ UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
+ try {
+ return lookupService.lookupPrincipalByGroupName(groupName);
+ } catch (IOException e) {
+ handleUserPrincipalLookupServiceException(GroupPrincipal.class, groupName, e);
+ return null;
+ }
+ }
+
+ private UserPrincipal toUser(Path file, UserPrincipal name) throws IOException {
+ String username = name.toString();
+ FileSystem fileSystem = file.getFileSystem();
+ UserPrincipalLookupService lookupService = fileSystem.getUserPrincipalLookupService();
+ try {
+ return lookupService.lookupPrincipalByName(username);
+ } catch (IOException e) {
+ handleUserPrincipalLookupServiceException(UserPrincipal.class, username, e);
+ return null;
+ }
+ }
+
+ protected void handleUserPrincipalLookupServiceException(Class<? extends Principal> principalType, String name, IOException e) throws IOException {
+ /* According to Javadoc:
+ *
+ * "Where an implementation does not support any notion of group
+ * or user then this method always throws UserPrincipalNotFoundException."
+ */
+ switch (unsupportedAttributePolicy) {
+ case Ignore:
+ break;
+ case Warn:
+ log.warn("handleUserPrincipalLookupServiceException(" + principalType.getSimpleName() + "[" + name + "])"
+ + " failed (" + e.getClass().getSimpleName() + "): " + e.getMessage());
+ break;
+ case ThrowException:
+ throw e;
+ default:
+ log.warn("Unknown policy for principal=" + principalType.getSimpleName() + "[" + name + "]: " + unsupportedAttributePolicy);
+ }
+ }
+
+ private Set<PosixFilePermission> permissionsToAttributes(int perms) {
+ Set<PosixFilePermission> p = new HashSet<>();
+ if ((perms & S_IRUSR) != 0) {
+ p.add(PosixFilePermission.OWNER_READ);
+ }
+ if ((perms & S_IWUSR) != 0) {
+ p.add(PosixFilePermission.OWNER_WRITE);
+ }
+ if ((perms & S_IXUSR) != 0) {
+ p.add(PosixFilePermission.OWNER_EXECUTE);
+ }
+ if ((perms & S_IRGRP) != 0) {
+ p.add(PosixFilePermission.GROUP_READ);
+ }
+ if ((perms & S_IWGRP) != 0) {
+ p.add(PosixFilePermission.GROUP_WRITE);
+ }
+ if ((perms & S_IXGRP) != 0) {
+ p.add(PosixFilePermission.GROUP_EXECUTE);
+ }
+ if ((perms & S_IROTH) != 0) {
+ p.add(PosixFilePermission.OTHERS_READ);
+ }
+ if ((perms & S_IWOTH) != 0) {
+ p.add(PosixFilePermission.OTHERS_WRITE);
+ }
+ if ((perms & S_IXOTH) != 0) {
+ p.add(PosixFilePermission.OTHERS_EXECUTE);
+ }
+ return p;
+ }
+
+ protected Map<String, Object> readAttrs(Buffer buffer) throws IOException {
+ Map<String, Object> attrs = new HashMap<>();
+ int flags = buffer.getInt();
+ if (version >= SFTP_V4) {
+ byte type = buffer.getByte();
+ switch (type) {
+ case SSH_FILEXFER_TYPE_REGULAR:
+ attrs.put("isRegular", Boolean.TRUE);
+ break;
+ case SSH_FILEXFER_TYPE_DIRECTORY:
+ attrs.put("isDirectory", Boolean.TRUE);
+ break;
+ case SSH_FILEXFER_TYPE_SYMLINK:
+ attrs.put("isSymbolicLink", Boolean.TRUE);
+ break;
+ case SSH_FILEXFER_TYPE_UNKNOWN:
+ attrs.put("isOther", Boolean.TRUE);
+ break;
+ default: // ignored
+ }
+ }
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ attrs.put("size", Long.valueOf(buffer.getLong()));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0) {
+ attrs.put("allocationSize", Long.valueOf(buffer.getLong()));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ attrs.put("uid", Integer.valueOf(buffer.getInt()));
+ attrs.put("gid", Integer.valueOf(buffer.getInt()));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ attrs.put("owner", new DefaultGroupPrincipal(buffer.getString()));
+ attrs.put("group", new DefaultGroupPrincipal(buffer.getString()));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ attrs.put("permissions", permissionsToAttributes(buffer.getInt()));
+ }
+ if (version == SFTP_V3) {
+ if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ attrs.put("lastAccessTime", readTime(buffer, flags));
+ attrs.put("lastModifiedTime", readTime(buffer, flags));
+ }
+ } else if (version >= SFTP_V4) {
+ if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ attrs.put("lastAccessTime", readTime(buffer, flags));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ attrs.put("creationTime", readTime(buffer, flags));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ attrs.put("lastModifiedTime", readTime(buffer, flags));
+ }
+ if ((flags & SSH_FILEXFER_ATTR_CTIME) != 0) {
+ attrs.put("ctime", readTime(buffer, flags));
+ }
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACL) != 0) {
+ int count = buffer.getInt();
+ List<AclEntry> acls = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ int aclType = buffer.getInt();
+ int aclFlag = buffer.getInt();
+ int aclMask = buffer.getInt();
+ String aclWho = buffer.getString();
+ acls.add(buildAclEntry(aclType, aclFlag, aclMask, aclWho));
+ }
+ attrs.put("acl", acls);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_BITS) != 0) {
+ int bits = buffer.getInt();
+ int valid = 0xffffffff;
+ if (version >= SFTP_V6) {
+ valid = buffer.getInt();
+ }
+ // TODO: handle attrib bits
+ }
+ if ((flags & SSH_FILEXFER_ATTR_TEXT_HINT) != 0) {
+ boolean text = buffer.getBoolean();
+ // TODO: handle text
+ }
+ if ((flags & SSH_FILEXFER_ATTR_MIME_TYPE) != 0) {
+ String mimeType = buffer.getString();
+ // TODO: handle mime-type
+ }
+ if ((flags & SSH_FILEXFER_ATTR_LINK_COUNT) != 0) {
+ int nlink = buffer.getInt();
+ // TODO: handle link-count
+ }
+ if ((flags & SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) {
+ String untranslated = buffer.getString();
+ // TODO: handle untranslated-name
+ }
+ if ((flags & SSH_FILEXFER_ATTR_EXTENDED) != 0) {
+ int count = buffer.getInt();
+ Map<String, String> extended = new HashMap<>();
+ for (int i = 0; i < count; i++) {
+ String key = buffer.getString();
+ String val = buffer.getString();
+ extended.put(key, val);
+ }
+ attrs.put("extended", extended);
+ }
+
+ return attrs;
+ }
+
+ private FileTime readTime(Buffer buffer, int flags) {
+ long secs = buffer.getLong();
+ long millis = secs * 1000;
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ millis += buffer.getInt() / 1000000l;
+ }
+ return FileTime.from(millis, TimeUnit.MILLISECONDS);
+ }
+
+ private AclEntry buildAclEntry(int aclType, int aclFlag, int aclMask, final String aclWho) {
+ AclEntryType type;
+ switch (aclType) {
+ case ACE4_ACCESS_ALLOWED_ACE_TYPE:
+ type = AclEntryType.ALLOW;
+ break;
+ case ACE4_ACCESS_DENIED_ACE_TYPE:
+ type = AclEntryType.DENY;
+ break;
+ case ACE4_SYSTEM_AUDIT_ACE_TYPE:
+ type = AclEntryType.AUDIT;
+ break;
+ case ACE4_SYSTEM_ALARM_ACE_TYPE:
+ type = AclEntryType.AUDIT;
+ break;
+ default:
+ throw new IllegalStateException("Unknown acl type: " + aclType);
+ }
+ Set<AclEntryFlag> flags = new HashSet<>();
+ if ((aclFlag & ACE4_FILE_INHERIT_ACE) != 0) {
+ flags.add(AclEntryFlag.FILE_INHERIT);
+ }
+ if ((aclFlag & ACE4_DIRECTORY_INHERIT_ACE) != 0) {
+ flags.add(AclEntryFlag.DIRECTORY_INHERIT);
+ }
+ if ((aclFlag & ACE4_NO_PROPAGATE_INHERIT_ACE) != 0) {
+ flags.add(AclEntryFlag.NO_PROPAGATE_INHERIT);
+ }
+ if ((aclFlag & ACE4_INHERIT_ONLY_ACE) != 0) {
+ flags.add(AclEntryFlag.INHERIT_ONLY);
+ }
+ Set<AclEntryPermission> mask = new HashSet<>();
+ if ((aclMask & ACE4_READ_DATA) != 0) {
+ mask.add(AclEntryPermission.READ_DATA);
+ }
+ if ((aclMask & ACE4_LIST_DIRECTORY) != 0) {
+ mask.add(AclEntryPermission.LIST_DIRECTORY);
+ }
+ if ((aclMask & ACE4_WRITE_DATA) != 0) {
+ mask.add(AclEntryPermission.WRITE_DATA);
+ }
+ if ((aclMask & ACE4_ADD_FILE) != 0) {
+ mask.add(AclEntryPermission.ADD_FILE);
+ }
+ if ((aclMask & ACE4_APPEND_DATA) != 0) {
+ mask.add(AclEntryPermission.APPEND_DATA);
+ }
+ if ((aclMask & ACE4_ADD_SUBDIRECTORY) != 0) {
+ mask.add(AclEntryPermission.ADD_SUBDIRECTORY);
+ }
+ if ((aclMask & ACE4_READ_NAMED_ATTRS) != 0) {
+ mask.add(AclEntryPermission.READ_NAMED_ATTRS);
+ }
+ if ((aclMask & ACE4_WRITE_NAMED_ATTRS) != 0) {
+ mask.add(AclEntryPermission.WRITE_NAMED_ATTRS);
+ }
+ if ((aclMask & ACE4_EXECUTE) != 0) {
+ mask.add(AclEntryPermission.EXECUTE);
+ }
+ if ((aclMask & ACE4_DELETE_CHILD) != 0) {
+ mask.add(AclEntryPermission.DELETE_CHILD);
+ }
+ if ((aclMask & ACE4_READ_ATTRIBUTES) != 0) {
+ mask.add(AclEntryPermission.READ_ATTRIBUTES);
+ }
+ if ((aclMask & ACE4_WRITE_ATTRIBUTES) != 0) {
+ mask.add(AclEntryPermission.WRITE_ATTRIBUTES);
+ }
+ if ((aclMask & ACE4_DELETE) != 0) {
+ mask.add(AclEntryPermission.DELETE);
+ }
+ if ((aclMask & ACE4_READ_ACL) != 0) {
+ mask.add(AclEntryPermission.READ_ACL);
+ }
+ if ((aclMask & ACE4_WRITE_ACL) != 0) {
+ mask.add(AclEntryPermission.WRITE_ACL);
+ }
+ if ((aclMask & ACE4_WRITE_OWNER) != 0) {
+ mask.add(AclEntryPermission.WRITE_OWNER);
+ }
+ if ((aclMask & ACE4_SYNCHRONIZE) != 0) {
+ mask.add(AclEntryPermission.SYNCHRONIZE);
+ }
+ UserPrincipal who = new DefaultGroupPrincipal(aclWho);
+ return AclEntry.newBuilder()
+ .setType(type)
+ .setFlags(flags)
+ .setPermissions(mask)
+ .setPrincipal(who)
+ .build();
+ }
+
+ protected void sendStatus(int id, Exception e) throws IOException {
+ int substatus;
+ if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
+ substatus = SSH_FX_NO_SUCH_FILE;
+ } else if (e instanceof FileAlreadyExistsException) {
+ substatus = SSH_FX_FILE_ALREADY_EXISTS;
+ } else if (e instanceof DirectoryNotEmptyException) {
+ substatus = SSH_FX_DIR_NOT_EMPTY;
+ } else if (e instanceof AccessDeniedException) {
+ substatus = SSH_FX_PERMISSION_DENIED;
+ } else if (e instanceof OverlappingFileLockException) {
+ substatus = SSH_FX_LOCK_CONFLICT;
+ } else {
+ substatus = SSH_FX_FAILURE;
+ }
+ sendStatus(id, substatus, e.toString());
+ }
+
+ protected void sendStatus(int id, int substatus, String msg) throws IOException {
+ sendStatus(id, substatus, msg != null ? msg : "", "");
+ }
+
+ protected void sendStatus(int id, int substatus, String msg, String lang) throws IOException {
+ if (log.isDebugEnabled()) {
+ log.debug("Send SSH_FXP_STATUS (substatus={}, lang={}, msg={})",
+ new Object[] { Integer.valueOf(substatus), lang, msg });
+ }
+
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putByte((byte) SSH_FXP_STATUS);
+ buffer.putInt(id);
+ buffer.putInt(substatus);
+ buffer.putString(msg);
+ buffer.putString(lang);
+ send(buffer);
+ }
+
+ protected void send(Buffer buffer) throws IOException {
+ DataOutputStream dos = new DataOutputStream(out);
+ dos.writeInt(buffer.available());
+ dos.write(buffer.array(), buffer.rpos(), buffer.available());
+ dos.flush();
+ }
+
+ @Override
+ public void destroy() {
+ if (!closed) {
+ if (log.isDebugEnabled()) {
+ log.debug("destroy() - mark as closed");
+ }
+
+ closed = true;
+
+ // if thread has not completed, cancel it
+ if ((pendingFuture != null) && (!pendingFuture.isDone())) {
+ boolean result = pendingFuture.cancel(true);
+ // TODO consider waiting some reasonable (?) amount of time for cancellation
+ if (log.isDebugEnabled()) {
+ log.debug("destroy() - cancel pending future=" + result);
+ }
+ }
+
+ pendingFuture = null;
+
+ if ((executors != null) && (!executors.isShutdown()) && shutdownExecutor) {
+ Collection<Runnable> runners = executors.shutdownNow();
+ if (log.isDebugEnabled()) {
+ log.debug("destroy() - shutdown executor service - runners count=" + ((runners == null) ? 0 : runners.size()));
+ }
+ }
+
+ executors = null;
+
+ try {
+ fileSystem.close();
+ } catch (UnsupportedOperationException e) {
+ // Ignore
+ } catch (IOException e) {
+ log.debug("Error closing FileSystem", e);
+ }
+ }
+ }
+
+ private Path resolveFile(String path) {
+ //in case we are running on Windows
+ String localPath = SelectorUtils.translateToLocalPath(path);
+ return defaultDir.resolve(localPath);
+ }
+
+ private final static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May",
+ "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+ /**
+ * Get unix style date string.
+ */
+ private static String getUnixDate(FileTime time) {
+ return getUnixDate(time != null ? time.toMillis() : -1);
+ }
+
+ private static String getUnixDate(long millis) {
+ if (millis < 0) {
+ return "------------";
+ }
+
+ StringBuilder sb = new StringBuilder(16);
+ Calendar cal = new GregorianCalendar();
+ cal.setTimeInMillis(millis);
+
+ // month
+ sb.append(MONTHS[cal.get(Calendar.MONTH)]);
+ sb.append(' ');
+
+ // day
+ int day = cal.get(Calendar.DATE);
+ if (day < 10) {
+ sb.append(' ');
+ }
+ sb.append(day);
+ sb.append(' ');
+
+ long sixMonth = 15811200000L; // 183L * 24L * 60L * 60L * 1000L;
+ long nowTime = System.currentTimeMillis();
+ if (Math.abs(nowTime - millis) > sixMonth) {
+
+ // year
+ int year = cal.get(Calendar.YEAR);
+ sb.append(' ');
+ sb.append(year);
+ } else {
+
+ // hour
+ int hh = cal.get(Calendar.HOUR_OF_DAY);
+ if (hh < 10) {
+ sb.append('0');
+ }
+ sb.append(hh);
+ sb.append(':');
+
+ // minute
+ int mm = cal.get(Calendar.MINUTE);
+ if (mm < 10) {
+ sb.append('0');
+ }
+ sb.append(mm);
+ }
+ return sb.toString();
+ }
+
+ protected static 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;
+ if (Objects.equals(getName(),that.getName())) {
+ return true;
+ } else {
+ return false; // debug breakpoint
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getName());
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ }
+
+ protected static class DefaultUserPrincipal extends PrincipalBase implements UserPrincipal {
+ public DefaultUserPrincipal(String name) {
+ super(name);
+ }
+ }
+
+ protected static class DefaultGroupPrincipal extends PrincipalBase implements GroupPrincipal {
+ public DefaultGroupPrincipal(String name) {
+ super(name);
+ }
+ }
+}
[02/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/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
new file mode 100644
index 0000000..98c0205
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.ObjectBuilder;
+import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.subsystem.SubsystemFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpSubsystemFactory implements SubsystemFactory, Cloneable, ExecutorServiceConfigurer {
+ public static final String NAME = SftpConstants.SFTP_SUBSYSTEM_NAME;
+ public static final UnsupportedAttributePolicy DEFAULT_POLICY = UnsupportedAttributePolicy.Warn;
+
+ public static class Builder implements ObjectBuilder<SftpSubsystemFactory> {
+ private final SftpSubsystemFactory factory = new SftpSubsystemFactory();
+
+ public Builder() {
+ super();
+ }
+
+ public Builder withExecutorService(ExecutorService service) {
+ factory.setExecutorService(service);
+ return this;
+ }
+
+ public Builder withShutdownOnExit(boolean shutdown) {
+ factory.setShutdownOnExit(shutdown);
+ return this;
+ }
+
+ public Builder withUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
+ factory.setUnsupportedAttributePolicy(p);
+ return this;
+ }
+
+ @Override
+ public SftpSubsystemFactory build() {
+ // return a clone so that each invocation returns a different instance - avoid shared instances
+ return factory.clone();
+ }
+ }
+
+ private ExecutorService executors;
+ private boolean shutdownExecutor;
+ private UnsupportedAttributePolicy policy = DEFAULT_POLICY;
+
+ public SftpSubsystemFactory() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public ExecutorService getExecutorService() {
+ return executors;
+ }
+
+ /**
+ * @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.
+ */
+ @Override
+ public void setExecutorService(ExecutorService service) {
+ executors = service;
+ }
+
+ @Override
+ public boolean isShutdownOnExit() {
+ return shutdownExecutor;
+ }
+
+ /**
+ * @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 shutdown regardless
+ */
+ @Override
+ public void setShutdownOnExit(boolean shutdownOnExit) {
+ shutdownExecutor = shutdownOnExit;
+ }
+
+ public UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
+ return policy;
+ }
+
+ /**
+ * @param p The {@link UnsupportedAttributePolicy} to use if failed to access
+ * some local file attributes
+ */
+ public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
+ if (p == null) {
+ throw new IllegalArgumentException("No policy provided");
+ }
+
+ policy = p;
+ }
+
+ @Override
+ public Command create() {
+ return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy());
+ }
+
+ @Override
+ public SftpSubsystemFactory clone() {
+ try {
+ return getClass().cast(super.clone()); // shallow clone is good enough
+ } catch (CloneNotSupportedException e) {
+ throw new UnsupportedOperationException("Unexpected clone exception", e); // unexpected since we implement cloneable
+ }
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java
new file mode 100644
index 0000000..87a0c01
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.subsystem.sftp;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum UnsupportedAttributePolicy {
+ Ignore,
+ Warn,
+ ThrowException;
+
+ public static final Set<UnsupportedAttributePolicy> VALUES =
+ Collections.unmodifiableSet(EnumSet.allOf(UnsupportedAttributePolicy.class));
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index 96f69b8..4ec4891 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -70,7 +70,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.common.session.ConnectionService;
import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.Transformer;
import org.apache.sshd.common.util.buffer.Buffer;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java
deleted file mode 100644
index 22f1a70..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.client.sftp;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sshd.client.sftp.SftpClient.CloseableHandle;
-import org.apache.sshd.client.sftp.SftpClient.Handle;
-import org.apache.sshd.util.BaseTestSupport;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class DefaultCloseableHandleTest extends BaseTestSupport {
- public DefaultCloseableHandleTest() {
- super();
- }
-
- @Test
- public void testChannelBehavior() throws IOException {
- final String id = getCurrentTestName();
- SftpClient client = Mockito.mock(SftpClient.class);
- Mockito.doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- Object[] args = invocation.getArguments();
- Handle handle = (Handle) args[0];
- assertEquals("Mismatched closing handle", id, handle.id);
- return null;
- }
- }).when(client).close(Matchers.any(Handle.class));
-
- CloseableHandle handle = new DefaultCloseableHandle(client, id);
- try {
- assertTrue("Handle not initially open", handle.isOpen());
- } finally {
- handle.close();
- }
- assertFalse("Handle not marked as closed", handle.isOpen());
- // make sure close was called
- Mockito.verify(client).close(handle);
- }
-
- @Test
- public void testCloseIdempotent() throws IOException {
- SftpClient client = Mockito.mock(SftpClient.class);
- final AtomicBoolean closeCalled = new AtomicBoolean(false);
- Mockito.doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- Object[] args = invocation.getArguments();
- assertFalse("Close already called on handle=" + args[0], closeCalled.getAndSet(true));
- return null;
- }
- }).when(client).close(Matchers.any(Handle.class));
-
- CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName());
- for (int index=0; index < Byte.SIZE; index++) {
- handle.close();
- }
-
- assertTrue("Close method not called", closeCalled.get());
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
deleted file mode 100644
index cbe27a8..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * 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.client.sftp;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.attribute.UserPrincipalNotFoundException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.root.RootedFileSystemProvider;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
-import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
-import org.apache.sshd.util.Utils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SftpFileSystemTest extends BaseTestSupport {
-
- private SshServer sshd;
- private int port;
- private final FileSystemFactory fileSystemFactory;
-
- public SftpFileSystemTest() throws IOException {
- Path targetPath = detectTargetFolder().toPath();
- Path parentPath = targetPath.getParent();
- final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
- fileSystemFactory = new FileSystemFactory() {
- @Override
- public FileSystem createFileSystem(Session session) throws IOException {
- return fileSystem;
- }
- };
- }
-
- @Before
- public void setUp() throws Exception {
- sshd = SshServer.setUpDefaultServer();
- sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
- sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
- sshd.setCommandFactory(new ScpCommandFactory());
- sshd.setShellFactory(new EchoShellFactory());
- sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
- sshd.setFileSystemFactory(fileSystemFactory);
- sshd.start();
- port = sshd.getPort();
- }
-
- @After
- public void tearDown() throws Exception {
- if (sshd != null) {
- sshd.stop(true);
- }
- }
-
- @Test
- public void testFileSystem() throws IOException {
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
-
- try(FileSystem fs = FileSystems.newFileSystem(
- URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"),
- new TreeMap<String,Object>() {
- private static final long serialVersionUID = 1L; // we're not serializing it
-
- {
- put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
- put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
- }
- })) {
-
- Iterable<Path> rootDirs = fs.getRootDirectories();
- for (Path root : rootDirs) {
- String rootName = root.toString();
- try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
- for (Path child : ds) {
- System.out.append('\t').append('[').append(rootName).append("] ").println(child);
- }
- } catch(IOException | RuntimeException e) {
- // TODO on Windows one might get share problems for *.sys files
- // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
- // for now, Windows is less of a target so we are lenient with it
- if (OsUtils.isWin32()) {
- System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
- } else {
- throw e;
- }
- }
- }
-
- Path current = fs.getPath(".").toRealPath().normalize();
- System.out.append("CWD: ").println(current);
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = lclSftp.resolve("client");
- String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt"));
- Path file1 = fs.getPath(remFile1Path);
- Files.createDirectories(file1.getParent());
-
- String expected="Hello world: " + getCurrentTestName();
- {
- Files.write(file1, expected.getBytes());
- String buf = new String(Files.readAllBytes(file1));
- assertEquals("Mismatched read test data", expected, buf);
- }
-
- String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt"));
- Path file2 = fs.getPath(remFile2Path);
- String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt"));
- Path file3 = fs.getPath(remFile3Path);
- try {
- Files.move(file2, file3);
- fail("Unexpected success in moving " + file2 + " => " + file3);
- } catch (NoSuchFileException e) {
- // expected
- }
-
- Files.write(file2, "h".getBytes());
- try {
- Files.move(file1, file2);
- fail("Unexpected success in moving " + file1 + " => " + file2);
- } catch (FileAlreadyExistsException e) {
- // expected
- }
- Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);
- Files.move(file2, file1);
-
- Map<String, Object> attrs = Files.readAttributes(file1, "*");
- System.out.append(file1.toString()).append(" attributes: ").println(attrs);
-
- // TODO: symbolic links only work for absolute files
- // Path link = fs.getPath("target/sftp/client/test2.txt");
- // Files.createSymbolicLink(link, link.relativize(file));
- // assertTrue(Files.isSymbolicLink(link));
- // assertEquals("test.txt", Files.readSymbolicLink(link).toString());
-
- // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
- if (OsUtils.isUNIX()) {
- Path link = fs.getPath(remFile2Path);
- Path linkParent = link.getParent();
- Path relPath = linkParent.relativize(file1);
- Files.createSymbolicLink(link, relPath);
- assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
-
- Path symLink = Files.readSymbolicLink(link);
- assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
- Files.delete(link);
- }
-
- attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
- System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs);
-
- assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1)));
-
- try (FileChannel channel = FileChannel.open(file1)) {
- try (FileLock lock = channel.lock()) {
- System.out.println("Locked " + lock.toString());
-
- try (FileChannel channel2 = FileChannel.open(file1)) {
- try (FileLock lock2 = channel2.lock()) {
- System.out.println("Locked " + lock2.toString());
- fail("Unexpected success in re-locking " + file1);
- } catch (OverlappingFileLockException e) {
- // expected
- }
- }
- }
- }
-
- Files.delete(file1);
- }
- }
-
- @Test
- public void testAttributes() throws IOException {
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
-
- try(FileSystem fs = FileSystems.newFileSystem(
- URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"),
- new TreeMap<String,Object>() {
- private static final long serialVersionUID = 1L; // we're not serializing it
-
- {
- put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_READ_BUFFER_SIZE));
- put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_WRITE_BUFFER_SIZE));
- }
- })) {
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = lclSftp.resolve("client");
- String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + ".txt"));
- Path file = fs.getPath(remFilePath);
- Files.createDirectories(file.getParent());
- Files.write(file, (getCurrentTestName() + "\n").getBytes());
-
- Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
- assertNotNull("NO attributes read for " + file, attrs);
-
- Files.setAttribute(file, "basic:size", Long.valueOf(2L));
- Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
- Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L));
-
- FileSystem fileSystem = file.getFileSystem();
- try {
- UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
- GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
- Files.setAttribute(file, "posix:group", group);
- } catch (UserPrincipalNotFoundException e) {
- // Also, according to the Javadoc:
- // "Where an implementation does not support any notion of
- // group then this method always throws UserPrincipalNotFoundException."
- // Therefore we are lenient with this exception for Windows
- if (OsUtils.isWin32()) {
- System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
- } else {
- throw e;
- }
- }
- }
- }
-
- @Test
- public void testRootFileSystem() throws IOException {
- Path targetPath = detectTargetFolder().toPath();
- Path rootNative = targetPath.resolve("root").toAbsolutePath();
- Utils.deleteRecursive(rootNative);
- Files.createDirectories(rootNative);
-
- try(FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
- Path dir = Files.createDirectories(fs.getPath("test/foo"));
- System.out.println("Created " + dir);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java
deleted file mode 100644
index ad6f603..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java
+++ /dev/null
@@ -1,651 +0,0 @@
-/*
- * 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.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_FILE_ALREADY_EXISTS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_NO_SUCH_FILE;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IRUSR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWUSR;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Random;
-import java.util.Vector;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.root.RootedFileSystemProvider;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
-import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
-import org.apache.sshd.util.JSchLogger;
-import org.apache.sshd.util.SimpleUserInfo;
-import org.apache.sshd.util.Utils;
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.FixMethodOrder;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-
-import com.jcraft.jsch.ChannelSftp;
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.SftpException;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SftpTest extends BaseTestSupport {
-
- private SshServer sshd;
- private int port;
- private com.jcraft.jsch.Session session;
- private final FileSystemFactory fileSystemFactory;
-
- public SftpTest() throws IOException {
- Path targetPath = detectTargetFolder().toPath();
- Path parentPath = targetPath.getParent();
- final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
- fileSystemFactory = new FileSystemFactory() {
- @Override
- public FileSystem createFileSystem(Session session) throws IOException {
- return fileSystem;
- }
- };
- }
-
- @Before
- public void setUp() throws Exception {
- sshd = SshServer.setUpDefaultServer();
- sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
- sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
- sshd.setCommandFactory(new ScpCommandFactory());
- sshd.setShellFactory(new EchoShellFactory());
- sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
- sshd.setFileSystemFactory(fileSystemFactory);
- sshd.start();
- port = sshd.getPort();
-
- JSchLogger.init();
- JSch sch = new JSch();
- session = sch.getSession("sshd", "localhost", port);
- session.setUserInfo(new SimpleUserInfo("sshd"));
- session.connect();
- }
-
- @After
- public void tearDown() throws Exception {
- if (session != null) {
- session.disconnect();
- }
-
- if (sshd != null) {
- sshd.stop(true);
- }
- }
-
- @Test
- @Ignore
- public void testExternal() throws Exception {
- System.out.println("SFTP subsystem available on port " + port);
- Thread.sleep(5 * 60000);
- }
-
- @Test
- public void testOpen() throws Exception {
- try(SshClient client = SshClient.setUpDefaultClient()) {
- client.start();
-
- try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
- session.addPasswordIdentity(getCurrentTestName());
- session.auth().verify(5L, TimeUnit.SECONDS);
-
- Path targetPath = detectTargetFolder().toPath();
- Path parentPath = targetPath.getParent();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Path clientFolder = lclSftp.resolve("client");
- Path testFile = clientFolder.resolve(getCurrentTestName() + ".txt");
- String file = Utils.resolveRelativeRemotePath(parentPath, testFile);
-
- File javaFile = testFile.toFile();
- assertHierarchyTargetFolderExists(javaFile.getParentFile());
- javaFile.createNewFile();
- javaFile.setWritable(false, false);
- javaFile.setReadable(false, false);
-
- try (SftpClient sftp = session.createSftpClient()) {
- boolean isWindows = OsUtils.isWin32();
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
- // NOTE: on Windows files are always readable
- // see https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java
- assertTrue("Empty read should have failed on " + file, isWindows);
- } catch (IOException e) {
- if (isWindows) {
- throw e;
- }
- }
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
- fail("Empty write should have failed on " + file);
- } catch (IOException e) {
- // ok
- }
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate))) {
- // NOTE: on Windows files are always readable
- assertTrue("Empty truncate should have failed on " + file, isWindows);
- } catch (IOException e) {
- // ok
- }
-
- // NOTE: on Windows files are always readable
- int perms=sftp.stat(file).perms;
- int permsMask=S_IWUSR | (isWindows ? 0 : S_IRUSR);
- assertEquals("Mismatched permissions for " + file + ": 0x" + Integer.toHexString(perms), 0, (perms & permsMask));
-
- javaFile.setWritable(true, false);
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write))) {
- // OK should succeed
- assertTrue("Handle not marked as open for file=" + file, h.isOpen());
- }
-
- byte[] d = "0123456789\n".getBytes();
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
- sftp.write(h, 0, d, 0, d.length);
- sftp.write(h, d.length, d, 0, d.length);
- }
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
- sftp.write(h, d.length * 2, d, 0, d.length);
- }
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) {
- sftp.write(h, 3, "-".getBytes(), 0, 1);
- }
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
- // NOTE: on Windows files are always readable
- assertTrue("Data read should have failed on " + file, isWindows);
- } catch (IOException e) {
- if (isWindows) {
- throw e;
- }
- }
-
- javaFile.setReadable(true, false);
-
- byte[] buf = new byte[3];
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) {
- int l = sftp.read(h, 2l, buf, 0, 3);
- assertEquals("Mismatched read data", "2-4", new String(buf, 0, l));
- }
- }
- } finally {
- client.stop();
- }
- }
- }
-
- @Test
- public void testClient() throws Exception {
- try(SshClient client = SshClient.setUpDefaultClient()) {
- client.start();
-
- try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
- session.addPasswordIdentity(getCurrentTestName());
- session.auth().verify(5L, TimeUnit.SECONDS);
-
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
- Files.createDirectories(lclSftp);
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = lclSftp.resolve("client");
- String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
- String file = dir + "/" + getCurrentTestName() + ".txt";
-
- try (SftpClient sftp = session.createSftpClient()) {
- sftp.mkdir(dir);
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
- byte[] d = "0123456789\n".getBytes();
- sftp.write(h, 0, d, 0, d.length);
- sftp.write(h, d.length, d, 0, d.length);
-
- SftpClient.Attributes attrs = sftp.stat(h);
- assertNotNull("No handle attributes", attrs);
- }
-
- try(SftpClient.CloseableHandle h = sftp.openDir(dir)) {
- SftpClient.DirEntry[] dirEntries = sftp.readDir(h);
- assertNotNull("No dir entries", dirEntries);
- assertEquals("Mismatced number of dir entries", 1, dirEntries.length);
- assertNull("Unexpected entry read", sftp.readDir(h));
- }
-
- sftp.remove(file);
-
- byte[] workBuf = new byte[IoUtils.DEFAULT_COPY_SIZE * Short.SIZE];
- new Random(System.currentTimeMillis()).nextBytes(workBuf);
- try (OutputStream os = sftp.write(file)) {
- os.write(workBuf);
- }
-
- try (InputStream is = sftp.read(file, IoUtils.DEFAULT_COPY_SIZE)) {
- int readLen = is.read(workBuf);
- assertEquals("Mismatched read data length", workBuf.length, readLen);
-
- int i = is.read();
- assertEquals("Unexpected read past EOF", -1, i);
- }
-
- SftpClient.Attributes attributes = sftp.stat(file);
- assertTrue("Test file not detected as regular", attributes.isRegularFile());
-
- attributes = sftp.stat(dir);
- assertTrue("Test directory not reported as such", attributes.isDirectory());
-
- int nb = 0;
- for (SftpClient.DirEntry entry : sftp.readDir(dir)) {
- assertNotNull("Unexpected null entry", entry);
- nb++;
- }
- assertEquals("Mismatched read dir entries", 1, nb);
-
- sftp.remove(file);
-
- sftp.rmdir(dir);
- }
- } finally {
- client.stop();
- }
- }
- }
-
- /**
- * this test is meant to test out write's logic, to ensure that internal chunking (based on Buffer.MAX_LEN) is
- * functioning properly. To do this, we write a variety of file sizes, both smaller and larger than Buffer.MAX_LEN.
- * in addition, this test ensures that improper arguments passed in get caught with an IllegalArgumentException
- * @throws Exception upon any uncaught exception or failure
- */
- @Test
- public void testWriteChunking() throws Exception {
- try(SshClient client = SshClient.setUpDefaultClient()) {
- client.start();
-
- try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
- session.addPasswordIdentity(getCurrentTestName());
- session.auth().verify(5L, TimeUnit.SECONDS);
-
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
- Files.createDirectories(lclSftp);
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = lclSftp.resolve("client");
- String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
-
- try(SftpClient sftp = session.createSftpClient()) {
- sftp.mkdir(dir);
-
- uploadAndVerifyFile(sftp, clientFolder, dir, 0, "emptyFile.txt");
- uploadAndVerifyFile(sftp, clientFolder, dir, 1000, "smallFile.txt");
- uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN - 1, "bufferMaxLenMinusOneFile.txt");
- uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN, "bufferMaxLenFile.txt");
- // were chunking not implemented, these would fail. these sizes should invoke our internal chunking mechanism
- uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN + 1, "bufferMaxLenPlusOneFile.txt");
- uploadAndVerifyFile(sftp, clientFolder, dir, (int)(1.5 * ByteArrayBuffer.MAX_LEN), "1point5BufferMaxLenFile.txt");
- uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) - 1, "2TimesBufferMaxLenMinusOneFile.txt");
- uploadAndVerifyFile(sftp, clientFolder, dir, 2 * ByteArrayBuffer.MAX_LEN, "2TimesBufferMaxLenFile.txt");
- uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) + 1, "2TimesBufferMaxLenPlusOneFile.txt");
- uploadAndVerifyFile(sftp, clientFolder, dir, 200000, "largerFile.txt");
-
- // test erroneous calls that check for negative values
- Path invalidPath = clientFolder.resolve(getCurrentTestName() + "-invalid");
- testInvalidParams(sftp, invalidPath, Utils.resolveRelativeRemotePath(parentPath, invalidPath));
-
- // cleanup
- sftp.rmdir(dir);
- }
- } finally {
- client.stop();
- }
- }
- }
-
- private void testInvalidParams(SftpClient sftp, Path file, String filePath) throws Exception {
- // generate random file and upload it
- String randomData = randomString(5);
- byte[] randomBytes = randomData.getBytes();
- try(SftpClient.CloseableHandle handle = sftp.open(filePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
- try {
- sftp.write(handle, -1, randomBytes, 0, 0);
- fail("should not have been able to write file with invalid file offset for " + filePath);
- } catch (IllegalArgumentException e) {
- // expected
- }
- try {
- sftp.write(handle, 0, randomBytes, -1, 0);
- fail("should not have been able to write file with invalid source offset for " + filePath);
- } catch (IllegalArgumentException e) {
- // expected
- }
- try {
- sftp.write(handle, 0, randomBytes, 0, -1);
- fail("should not have been able to write file with invalid length for " + filePath);
- } catch (IllegalArgumentException e) {
- // expected
- }
- try {
- sftp.write(handle, 0, randomBytes, 0, randomBytes.length + 1);
- fail("should not have been able to write file with length bigger than array itself (no offset) for " + filePath);
- } catch (IllegalArgumentException e) {
- // expected
- }
- try {
- sftp.write(handle, 0, randomBytes, randomBytes.length, 1);
- fail("should not have been able to write file with length bigger than array itself (with offset) for " + filePath);
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- sftp.remove(filePath);
- assertFalse("File should not be there: " + file.toString(), Files.exists(file));
- }
-
- private void uploadAndVerifyFile(SftpClient sftp, Path clientFolder, String remoteDir, int size, String filename) throws Exception {
- // generate random file and upload it
- String remotePath = remoteDir + "/" + filename;
- String randomData = randomString(size);
- try(SftpClient.CloseableHandle handle = sftp.open(remotePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
- sftp.write(handle, 0, randomData.getBytes(), 0, randomData.length());
- }
-
- // verify results
- Path resultPath = clientFolder.resolve(filename);
- assertTrue("File should exist on disk: " + resultPath, Files.exists(resultPath));
- assertTrue("Mismatched file contents: " + resultPath, randomData.equals(readFile(remotePath)));
-
- // cleanup
- sftp.remove(remotePath);
- assertFalse("File should have been removed: " + resultPath, Files.exists(resultPath));
- }
-
- @Test
- public void testSftp() throws Exception {
- String d = getCurrentTestName() + "\n";
-
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
- Files.createDirectories(lclSftp);
-
- Path target = lclSftp.resolve(getCurrentTestName() + ".txt");
- String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), target);
-
- final int NUM_ITERATIONS=10;
- StringBuilder sb = new StringBuilder(d.length() * NUM_ITERATIONS * NUM_ITERATIONS);
- for (int j = 1; j <= NUM_ITERATIONS; j++) {
- if (sb.length() > 0) {
- sb.setLength(0);
- }
-
- for (int i = 0; i < j; i++) {
- sb.append(d);
- }
-
- sendFile(remotePath, sb.toString());
- assertFileLength(target, sb.length(), 5000);
- Files.delete(target);
- }
- }
-
- @Test
- public void testReadWriteWithOffset() throws Exception {
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
- Files.createDirectories(lclSftp);
-
- Path localPath = lclSftp.resolve(getCurrentTestName() + ".txt");
- String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), localPath);
- String data = getCurrentTestName();
- String extraData = "@" + getClass().getSimpleName();
- int appendOffset = -5;
-
- ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
- c.connect();
- try {
- c.put(new ByteArrayInputStream(data.getBytes()), remotePath);
-
- assertTrue("Remote file not created after initial write: " + localPath, Files.exists(localPath));
- assertEquals("Mismatched data read from " + remotePath, data, readFile(remotePath));
-
- try(OutputStream os = c.put(remotePath, null, ChannelSftp.APPEND, appendOffset)) {
- os.write(extraData.getBytes());
- }
- } finally {
- c.disconnect();
- }
-
- assertTrue("Remote file not created after data update: " + localPath, Files.exists(localPath));
-
- String expected = data.substring(0, data.length() + appendOffset) + extraData;
- String actual = readFile(remotePath);
- assertEquals("Mismatched final file data in " + remotePath, expected, actual);
- }
-
- @Test
- public void testReadDir() throws Exception {
- ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
- c.connect();
- try {
- URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
- URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
- String path = new File(base.relativize(url).getPath()).getParent() + "/";
- path = path.replace('\\', '/');
- Vector<?> res = c.ls(path);
- for (Object f : res) {
- System.out.println(f.toString());
- }
- } finally {
- c.disconnect();
- }
- }
-
- @Test
- public void testRealPath() throws Exception {
- ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
- c.connect();
-
- try {
- URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
- URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
- String path = new File(base.relativize(url).getPath()).getParent() + "/";
- path = path.replace('\\', '/');
- String real = c.realpath(path);
- System.out.println(real);
- try {
- real = c.realpath(path + "/foobar");
- System.out.println(real);
- fail("Expected SftpException");
- } catch (SftpException e) {
- // ok
- }
- } finally {
- c.disconnect();
- }
- }
-
- @Test
- public void testRename() throws Exception {
- try(SshClient client = SshClient.setUpDefaultClient()) {
- client.start();
-
- try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
- session.addPasswordIdentity(getCurrentTestName());
- session.auth().verify(5L, TimeUnit.SECONDS);
-
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
- Files.createDirectories(lclSftp);
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
-
- try(SftpClient sftp = session.createSftpClient()) {
- Path file1 = clientFolder.resolve(getCurrentTestName() + "-1.txt");
- String file1Path = Utils.resolveRelativeRemotePath(parentPath, file1);
- try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
- os.write((getCurrentTestName() + "\n").getBytes());
- }
-
- Path file2 = clientFolder.resolve(getCurrentTestName() + "-2.txt");
- String file2Path = Utils.resolveRelativeRemotePath(parentPath, file2);
- Path file3 = clientFolder.resolve(getCurrentTestName() + "-3.txt");
- String file3Path = Utils.resolveRelativeRemotePath(parentPath, file3);
- try {
- sftp.rename(file2Path, file3Path);
- fail("Unxpected rename success of " + file2Path + " => " + file3Path);
- } catch (org.apache.sshd.client.SftpException e) {
- assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path, SSH_FX_NO_SUCH_FILE, e.getStatus());
- }
-
- try (OutputStream os = sftp.write(file2Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
- os.write("H".getBytes());
- }
-
- try {
- sftp.rename(file1Path, file2Path);
- fail("Unxpected rename success of " + file1Path + " => " + file2Path);
- } catch (org.apache.sshd.client.SftpException e) {
- assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path, SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
- }
-
- sftp.rename(file1Path, file2Path, SftpClient.CopyMode.Overwrite);
- }
- } finally {
- client.stop();
- }
- }
- }
-
- @Test
- public void testCreateSymbolicLink() throws Exception {
- // Do not execute on windows as the file system does not support symlinks
- Assume.assumeTrue("Skip non-Unix O/S", OsUtils.isUNIX());
-
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
- Files.createDirectories(lclSftp);
-
- Path parentPath = targetPath.getParent();
- Path sourcePath = lclSftp.resolve(getCurrentTestName() + ".txt");
- String remSrcPath = Utils.resolveRelativeRemotePath(parentPath, sourcePath);
- Path linkPath = lclSftp.resolve("link-" + sourcePath.getFileName());
- String remLinkPath = Utils.resolveRelativeRemotePath(parentPath, linkPath);
-
- String data = getCurrentTestName();
- ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
- c.connect();
- try {
- c.put(new ByteArrayInputStream(data.getBytes()), remSrcPath);
-
- assertTrue("Source file not created: " + sourcePath, Files.exists(sourcePath));
- assertEquals("Mismatched stored data in " + remSrcPath, data, readFile(remSrcPath));
-
- c.symlink(remSrcPath, remLinkPath);
-
- assertTrue("Symlink not created: " + linkPath, Files.exists(linkPath));
- assertEquals("Mismatche link data in " + remLinkPath, data, readFile(remLinkPath));
-
- String str1 = c.readlink(remLinkPath);
- String str2 = c.realpath(remSrcPath);
- assertEquals("Mismatched link vs. real path", str1, str2);
- } finally {
- c.disconnect();
- }
- }
-
- protected String readFile(String path) throws Exception {
- ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
- c.connect();
-
- try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
- InputStream is = c.get(path)) {
-
- byte[] buffer = new byte[256];
- int count;
- while (-1 != (count = is.read(buffer))) {
- bos.write(buffer, 0, count);
- }
-
- return bos.toString();
- } finally {
- c.disconnect();
- }
- }
-
- protected void sendFile(String path, String data) throws Exception {
- ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
- c.connect();
- try {
- c.put(new ByteArrayInputStream(data.getBytes()), path);
- } finally {
- c.disconnect();
- }
- }
-
- private String randomString(int size) {
- StringBuilder sb = new StringBuilder(size);
- for (int i = 0; i < size; i++) {
- sb.append((char) ((i % 10) + '0'));
- }
- return sb.toString();
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
new file mode 100644
index 0000000..8dbab78
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.subsystem.sftp.DefaultCloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class DefaultCloseableHandleTest extends BaseTestSupport {
+ public DefaultCloseableHandleTest() {
+ super();
+ }
+
+ @Test
+ public void testChannelBehavior() throws IOException {
+ final String id = getCurrentTestName();
+ SftpClient client = Mockito.mock(SftpClient.class);
+ Mockito.doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ Handle handle = (Handle) args[0];
+ assertEquals("Mismatched closing handle", id, handle.id);
+ return null;
+ }
+ }).when(client).close(Matchers.any(Handle.class));
+
+ CloseableHandle handle = new DefaultCloseableHandle(client, id);
+ try {
+ assertTrue("Handle not initially open", handle.isOpen());
+ } finally {
+ handle.close();
+ }
+ assertFalse("Handle not marked as closed", handle.isOpen());
+ // make sure close was called
+ Mockito.verify(client).close(handle);
+ }
+
+ @Test
+ public void testCloseIdempotent() throws IOException {
+ SftpClient client = Mockito.mock(SftpClient.class);
+ final AtomicBoolean closeCalled = new AtomicBoolean(false);
+ Mockito.doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ assertFalse("Close already called on handle=" + args[0], closeCalled.getAndSet(true));
+ return null;
+ }
+ }).when(client).close(Matchers.any(Handle.class));
+
+ CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName());
+ for (int index=0; index < Byte.SIZE; index++) {
+ handle.close();
+ }
+
+ assertTrue("Close method not called", closeCalled.get());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
new file mode 100644
index 0000000..fe3a123
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
@@ -0,0 +1,288 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.UserPrincipalNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.root.RootedFileSystemProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SftpFileSystemTest extends BaseTestSupport {
+
+ private SshServer sshd;
+ private int port;
+ private final FileSystemFactory fileSystemFactory;
+
+ public SftpFileSystemTest() throws IOException {
+ Path targetPath = detectTargetFolder().toPath();
+ Path parentPath = targetPath.getParent();
+ final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
+ fileSystemFactory = new FileSystemFactory() {
+ @Override
+ public FileSystem createFileSystem(Session session) throws IOException {
+ return fileSystem;
+ }
+ };
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ sshd = SshServer.setUpDefaultServer();
+ sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+ sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
+ sshd.setCommandFactory(new ScpCommandFactory());
+ sshd.setShellFactory(new EchoShellFactory());
+ sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+ sshd.setFileSystemFactory(fileSystemFactory);
+ sshd.start();
+ port = sshd.getPort();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (sshd != null) {
+ sshd.stop(true);
+ }
+ }
+
+ @Test
+ public void testFileSystem() throws IOException {
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+
+ try(FileSystem fs = FileSystems.newFileSystem(
+ URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"),
+ new TreeMap<String,Object>() {
+ private static final long serialVersionUID = 1L; // we're not serializing it
+
+ {
+ put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
+ put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
+ }
+ })) {
+
+ Iterable<Path> rootDirs = fs.getRootDirectories();
+ for (Path root : rootDirs) {
+ String rootName = root.toString();
+ try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+ for (Path child : ds) {
+ System.out.append('\t').append('[').append(rootName).append("] ").println(child);
+ }
+ } catch(IOException | RuntimeException e) {
+ // TODO on Windows one might get share problems for *.sys files
+ // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
+ // for now, Windows is less of a target so we are lenient with it
+ if (OsUtils.isWin32()) {
+ System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ Path current = fs.getPath(".").toRealPath().normalize();
+ System.out.append("CWD: ").println(current);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt"));
+ Path file1 = fs.getPath(remFile1Path);
+ Files.createDirectories(file1.getParent());
+
+ String expected="Hello world: " + getCurrentTestName();
+ {
+ Files.write(file1, expected.getBytes());
+ String buf = new String(Files.readAllBytes(file1));
+ assertEquals("Mismatched read test data", expected, buf);
+ }
+
+ String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt"));
+ Path file2 = fs.getPath(remFile2Path);
+ String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt"));
+ Path file3 = fs.getPath(remFile3Path);
+ try {
+ Files.move(file2, file3);
+ fail("Unexpected success in moving " + file2 + " => " + file3);
+ } catch (NoSuchFileException e) {
+ // expected
+ }
+
+ Files.write(file2, "h".getBytes());
+ try {
+ Files.move(file1, file2);
+ fail("Unexpected success in moving " + file1 + " => " + file2);
+ } catch (FileAlreadyExistsException e) {
+ // expected
+ }
+ Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);
+ Files.move(file2, file1);
+
+ Map<String, Object> attrs = Files.readAttributes(file1, "*");
+ System.out.append(file1.toString()).append(" attributes: ").println(attrs);
+
+ // TODO: symbolic links only work for absolute files
+ // Path link = fs.getPath("target/sftp/client/test2.txt");
+ // Files.createSymbolicLink(link, link.relativize(file));
+ // assertTrue(Files.isSymbolicLink(link));
+ // assertEquals("test.txt", Files.readSymbolicLink(link).toString());
+
+ // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
+ if (OsUtils.isUNIX()) {
+ Path link = fs.getPath(remFile2Path);
+ Path linkParent = link.getParent();
+ Path relPath = linkParent.relativize(file1);
+ Files.createSymbolicLink(link, relPath);
+ assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
+
+ Path symLink = Files.readSymbolicLink(link);
+ assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
+ Files.delete(link);
+ }
+
+ attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
+ System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs);
+
+ assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1)));
+
+ try (FileChannel channel = FileChannel.open(file1)) {
+ try (FileLock lock = channel.lock()) {
+ System.out.println("Locked " + lock.toString());
+
+ try (FileChannel channel2 = FileChannel.open(file1)) {
+ try (FileLock lock2 = channel2.lock()) {
+ System.out.println("Locked " + lock2.toString());
+ fail("Unexpected success in re-locking " + file1);
+ } catch (OverlappingFileLockException e) {
+ // expected
+ }
+ }
+ }
+ }
+
+ Files.delete(file1);
+ }
+ }
+
+ @Test
+ public void testAttributes() throws IOException {
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+
+ try(FileSystem fs = FileSystems.newFileSystem(
+ URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"),
+ new TreeMap<String,Object>() {
+ private static final long serialVersionUID = 1L; // we're not serializing it
+
+ {
+ put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_READ_BUFFER_SIZE));
+ put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_WRITE_BUFFER_SIZE));
+ }
+ })) {
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + ".txt"));
+ Path file = fs.getPath(remFilePath);
+ Files.createDirectories(file.getParent());
+ Files.write(file, (getCurrentTestName() + "\n").getBytes());
+
+ Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
+ assertNotNull("NO attributes read for " + file, attrs);
+
+ Files.setAttribute(file, "basic:size", Long.valueOf(2L));
+ Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
+ Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L));
+
+ FileSystem fileSystem = file.getFileSystem();
+ try {
+ UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
+ GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
+ Files.setAttribute(file, "posix:group", group);
+ } catch (UserPrincipalNotFoundException e) {
+ // Also, according to the Javadoc:
+ // "Where an implementation does not support any notion of
+ // group then this method always throws UserPrincipalNotFoundException."
+ // Therefore we are lenient with this exception for Windows
+ if (OsUtils.isWin32()) {
+ System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testRootFileSystem() throws IOException {
+ Path targetPath = detectTargetFolder().toPath();
+ Path rootNative = targetPath.resolve("root").toAbsolutePath();
+ Utils.deleteRecursive(rootNative);
+ Files.createDirectories(rootNative);
+
+ try(FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
+ Path dir = Files.createDirectories(fs.getPath("test/foo"));
+ System.out.println("Created " + dir);
+ }
+ }
+}
[04/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystemFactory.java
deleted file mode 100644
index 04d0239..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystemFactory.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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.sftp;
-
-import java.util.concurrent.ExecutorService;
-
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.ObjectBuilder;
-import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
-import org.apache.sshd.server.Command;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpSubsystemFactory implements NamedFactory<Command>, Cloneable, ExecutorServiceConfigurer {
- public static final String NAME = SftpConstants.SFTP_SUBSYSTEM_NAME;
- public static final UnsupportedAttributePolicy DEFAULT_POLICY = UnsupportedAttributePolicy.Warn;
-
- public static class Builder implements ObjectBuilder<SftpSubsystemFactory> {
- private final SftpSubsystemFactory factory = new SftpSubsystemFactory();
-
- public Builder() {
- super();
- }
-
- public Builder withExecutorService(ExecutorService service) {
- factory.setExecutorService(service);
- return this;
- }
-
- public Builder withShutdownOnExit(boolean shutdown) {
- factory.setShutdownOnExit(shutdown);
- return this;
- }
-
- public Builder withUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
- factory.setUnsupportedAttributePolicy(p);
- return this;
- }
-
- @Override
- public SftpSubsystemFactory build() {
- // return a clone so that each invocation returns a different instance - avoid shared instances
- return factory.clone();
- }
- }
-
- private ExecutorService executors;
- private boolean shutdownExecutor;
- private UnsupportedAttributePolicy policy = DEFAULT_POLICY;
-
- public SftpSubsystemFactory() {
- super();
- }
-
- @Override
- public String getName() {
- return NAME;
- }
-
- @Override
- public ExecutorService getExecutorService() {
- return executors;
- }
-
- /**
- * @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.
- */
- @Override
- public void setExecutorService(ExecutorService service) {
- executors = service;
- }
-
- @Override
- public boolean isShutdownOnExit() {
- return shutdownExecutor;
- }
-
- /**
- * @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 shutdown regardless
- */
- @Override
- public void setShutdownOnExit(boolean shutdownOnExit) {
- shutdownExecutor = shutdownOnExit;
- }
-
- public UnsupportedAttributePolicy getUnsupportedAttributePolicy() {
- return policy;
- }
-
- /**
- * @param p The {@link UnsupportedAttributePolicy} to use if failed to access
- * some local file attributes
- */
- public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy p) {
- if (p == null) {
- throw new IllegalArgumentException("No policy provided");
- }
-
- policy = p;
- }
-
- @Override
- public Command create() {
- return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy());
- }
-
- @Override
- public SftpSubsystemFactory clone() {
- try {
- return getClass().cast(super.clone()); // shallow clone is good enough
- } catch (CloneNotSupportedException e) {
- throw new UnsupportedOperationException("Unexpected clone exception", e); // unexpected since we implement cloneable
- }
- }
-
-
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/sftp/UnsupportedAttributePolicy.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/UnsupportedAttributePolicy.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/UnsupportedAttributePolicy.java
deleted file mode 100644
index f9a1472..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/UnsupportedAttributePolicy.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.sftp;
-
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Set;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public enum UnsupportedAttributePolicy {
- Ignore,
- Warn,
- ThrowException;
-
- public static final Set<UnsupportedAttributePolicy> VALUES =
- Collections.unmodifiableSet(EnumSet.allOf(UnsupportedAttributePolicy.class));
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
index e46a005..98d7e40 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
@@ -19,10 +19,6 @@
package org.apache.sshd.server.shell;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Set;
-
import org.apache.sshd.common.util.OsUtils;
/**
@@ -34,6 +30,10 @@ public class InteractiveProcessShellFactory extends ProcessShellFactory {
private static final String[] LINUX_COMMAND = { "/bin/sh", "-i", "-l" };
private static final String[] WINDOWS_COMMAND = { "cmd.exe" };
+ public static String[] resolveDefaultInteractiveCommand() {
+ return resolveInteractiveCommand(OsUtils.isWin32());
+ }
+
public static String[] resolveInteractiveCommand(boolean isWin32) {
// return clone(s) to avoid inadvertent modification
if (isWin32) {
@@ -42,23 +42,10 @@ public class InteractiveProcessShellFactory extends ProcessShellFactory {
return LINUX_COMMAND.clone();
}
}
-
- public static final Set<TtyOptions> LINUX_OPTIONS =
- Collections.unmodifiableSet(EnumSet.of(TtyOptions.ONlCr));
- public static final Set<TtyOptions> WINDOWS_OPTIONS =
- Collections.unmodifiableSet(EnumSet.of(TtyOptions.Echo, TtyOptions.ICrNl, TtyOptions.ONlCr));
-
- public static Set<TtyOptions> resolveTtyOptions(boolean isWin32) {
- if (isWin32) {
- return WINDOWS_OPTIONS;
- } else {
- return LINUX_OPTIONS;
- }
- }
public static final InteractiveProcessShellFactory INSTANCE = new InteractiveProcessShellFactory();
public InteractiveProcessShellFactory() {
- super(resolveInteractiveCommand(OsUtils.isWin32()), resolveTtyOptions(OsUtils.isWin32()));
+ super(resolveDefaultInteractiveCommand(), TtyOptions.resolveDefaultTtyOptions());
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
index 033f476..eb7334f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
@@ -31,6 +31,7 @@ import java.util.Set;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -44,12 +45,30 @@ import org.apache.sshd.server.Command;
*/
public class ProcessShellFactory extends AbstractLoggingBean implements Factory<Command> {
- public enum TtyOptions {
+ public static enum TtyOptions {
Echo,
INlCr,
ICrNl,
ONlCr,
- OCrNl
+ OCrNl;
+
+
+ public static final Set<TtyOptions> LINUX_OPTIONS =
+ Collections.unmodifiableSet(EnumSet.of(TtyOptions.ONlCr));
+ public static final Set<TtyOptions> WINDOWS_OPTIONS =
+ Collections.unmodifiableSet(EnumSet.of(TtyOptions.Echo, TtyOptions.ICrNl, TtyOptions.ONlCr));
+
+ public static Set<TtyOptions> resolveDefaultTtyOptions() {
+ return resolveTtyOptions(OsUtils.isWin32());
+ }
+
+ public static Set<TtyOptions> resolveTtyOptions(boolean isWin32) {
+ if (isWin32) {
+ return WINDOWS_OPTIONS;
+ } else {
+ return LINUX_OPTIONS;
+ }
+ }
}
private String[] command;
@@ -60,7 +79,7 @@ public class ProcessShellFactory extends AbstractLoggingBean implements Factory<
}
public ProcessShellFactory(String[] command) {
- this(command, EnumSet.noneOf(TtyOptions.class));
+ this(command, TtyOptions.resolveDefaultTtyOptions());
}
public ProcessShellFactory(String[] command, Collection<TtyOptions> ttyOptions) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/subsystem/SubsystemFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/SubsystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/SubsystemFactory.java
new file mode 100644
index 0000000..2a888e3
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/SubsystemFactory.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.util.Transformer;
+import org.apache.sshd.server.Command;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SubsystemFactory extends NamedFactory<Command> {
+ // required because of generics issues
+ Transformer<SubsystemFactory,NamedFactory<Command>> FAC2NAMED=new Transformer<SubsystemFactory,NamedFactory<Command>>() {
+ @Override
+ public NamedFactory<Command> transform(SubsystemFactory input) {
+ return input;
+ }
+ };
+
+}
[08/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
new file mode 100644
index 0000000..6110900
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
@@ -0,0 +1,1179 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_APPEND_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_READ_ATTRIBUTES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_READ_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_WRITE_ATTRIBUTES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_WRITE_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V3;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V4;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V5;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V6;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ALL;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_CREATETIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_SIZE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_UIDGID;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_REGULAR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_SYMLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_APPEND;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREATE_NEW;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREATE_TRUNCATE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_EXCL;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_OPEN_EXISTING;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_OPEN_OR_CREATE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_READ;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_TRUNC;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_WRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_ATTRS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_BLOCK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_CLOSE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_FSETSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_FSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_HANDLE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_INIT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_LINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_LSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_MKDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_NAME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_OPEN;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_OPENDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READ;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_REALPATH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_REMOVE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME_ATOMIC;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME_OVERWRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RMDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_SETSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_STAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_STATUS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_SYMLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_UNBLOCK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_VERSION;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_WRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_EOF;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_OK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFLNK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFREG;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.attribute.FileTime;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SftpException;
+import org.apache.sshd.client.channel.ChannelSubsystem;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.InputStreamWithChannel;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.NoCloseOutputStream;
+import org.apache.sshd.common.util.io.OutputStreamWithChannel;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultSftpClient extends AbstractSftpClient {
+ private final ClientSession clientSession;
+ private final ChannelSubsystem channel;
+ private final Map<Integer, Buffer> messages;
+ private final AtomicInteger cmdId = new AtomicInteger(100);
+ private final Buffer receiveBuffer = new ByteArrayBuffer();
+ private boolean closing;
+ private int version;
+ private final Map<String, byte[]> extensions = new HashMap<>();
+
+ public DefaultSftpClient(ClientSession clientSession) throws IOException {
+ this.clientSession = clientSession;
+ this.channel = clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
+ this.messages = new HashMap<>();
+ this.channel.setOut(new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ write(new byte[] { (byte) b }, 0, 1);
+ }
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ data(b, off, len);
+ }
+ });
+ this.channel.setErr(new ByteArrayOutputStream(Byte.MAX_VALUE));
+ this.channel.open().verify(FactoryManagerUtils.getLongProperty(clientSession, SFTP_CHANNEL_OPEN_TIMEOUT, DEFAULT_CHANNEL_OPEN_TIMEOUT));
+ this.channel.onClose(new Runnable() {
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public void run() {
+ synchronized (messages) {
+ closing = true;
+ messages.notifyAll();
+ }
+ }
+ });
+ init();
+ }
+
+ @Override
+ public int getVersion() {
+ return version;
+ }
+
+ @Override
+ public boolean isClosing() {
+ return closing;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return this.channel.isOpen();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ this.channel.close(false);
+ }
+ }
+
+ /**
+ * Receive binary data
+ */
+ protected int data(byte[] buf, int start, int len) throws IOException {
+ Buffer incoming = new ByteArrayBuffer(buf, start, len);
+ // If we already have partial data, we need to append it to the buffer and use it
+ if (receiveBuffer.available() > 0) {
+ receiveBuffer.putBuffer(incoming);
+ incoming = receiveBuffer;
+ }
+ // Process commands
+ int rpos = incoming.rpos();
+ for (int count=0; receive(incoming); count++) {
+ if (log.isTraceEnabled()) {
+ log.trace("Processed " + count + " data messages");
+ }
+ }
+
+ int read = incoming.rpos() - rpos;
+ // Compact and add remaining data
+ receiveBuffer.compact();
+ if (receiveBuffer != incoming && incoming.available() > 0) {
+ receiveBuffer.putBuffer(incoming);
+ }
+ return read;
+ }
+
+ /**
+ * Read SFTP packets from buffer
+ */
+ protected boolean receive(Buffer incoming) throws IOException {
+ int rpos = incoming.rpos();
+ int wpos = incoming.wpos();
+ clientSession.resetIdleTimeout();
+ if ((wpos - rpos) > 4) {
+ int length = incoming.getInt();
+ if (length < 5) {
+ throw new IOException("Illegal sftp packet length: " + length);
+ }
+ if ((wpos - rpos) >= (length + 4)) {
+ incoming.rpos(rpos);
+ incoming.wpos(rpos + 4 + length);
+ process(incoming);
+ incoming.rpos(rpos + 4 + length);
+ incoming.wpos(wpos);
+ return true;
+ }
+ }
+ incoming.rpos(rpos);
+ return false;
+ }
+
+ /**
+ * Process an SFTP packet
+ */
+ protected void process(Buffer incoming) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(incoming.available());
+ buffer.putBuffer(incoming);
+ buffer.rpos(5);
+ int id = buffer.getInt();
+ buffer.rpos(0);
+ synchronized (messages) {
+ messages.put(Integer.valueOf(id), buffer);
+ messages.notifyAll();
+ }
+ }
+
+ protected int send(int cmd, Buffer buffer) throws IOException {
+ int id = cmdId.incrementAndGet();
+
+ try(DataOutputStream dos = new DataOutputStream(new NoCloseOutputStream(channel.getInvertedIn()))) {
+ dos.writeInt(5 + buffer.available());
+ dos.writeByte(cmd);
+ dos.writeInt(id);
+ dos.write(buffer.array(), buffer.rpos(), buffer.available());
+ dos.flush();
+ }
+
+ return id;
+ }
+
+ protected Buffer receive(int id) throws IOException {
+ synchronized (messages) {
+ while (true) {
+ if (closing) {
+ throw new SshException("Channel has been closed");
+ }
+ Buffer buffer = messages.remove(Integer.valueOf(id));
+ if (buffer != null) {
+ return buffer;
+ }
+ try {
+ messages.wait();
+ } catch (InterruptedException e) {
+ throw (IOException) new InterruptedIOException("Interrupted while waiting for messages").initCause(e);
+ }
+ }
+ }
+ }
+
+ protected Buffer read() throws IOException {
+ try(DataInputStream dis=new DataInputStream(new NoCloseInputStream(channel.getInvertedOut()))) {
+ int length = dis.readInt();
+ if (length < 5) {
+ throw new IllegalArgumentException("Bad length: " + length);
+ }
+ Buffer buffer = new ByteArrayBuffer(length + 4);
+ buffer.putInt(length);
+ int nb = length;
+ while (nb > 0) {
+ int readLen = dis.read(buffer.array(), buffer.wpos(), nb);
+ if (readLen < 0) {
+ throw new IllegalArgumentException("Premature EOF while read " + length + " bytes - remaining=" + nb);
+ }
+ buffer.wpos(buffer.wpos() + readLen);
+ nb -= readLen;
+ }
+
+ return buffer;
+ }
+ }
+
+ protected void init() throws IOException {
+ // Init packet
+ try(DataOutputStream dos = new DataOutputStream(new NoCloseOutputStream(channel.getInvertedIn()))) {
+ dos.writeInt(5);
+ dos.writeByte(SSH_FXP_INIT);
+ dos.writeInt(SFTP_V6);
+ dos.flush();
+ }
+
+ Buffer buffer;
+ synchronized (messages) {
+ while (messages.isEmpty()) {
+ try {
+ messages.wait();
+ } catch (InterruptedException e) {
+ throw (IOException) new InterruptedIOException("Interruppted init()").initCause(e);
+ }
+ }
+ buffer = messages.remove(messages.keySet().iterator().next());
+
+ }
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_VERSION) {
+ if (id < SFTP_V3) {
+ throw new SshException("Unsupported sftp version " + id);
+ }
+ version = id;
+ while (buffer.available() > 0) {
+ String name = buffer.getString();
+ byte[] data = buffer.getBytes();
+ extensions.put(name, data);
+ }
+ } else if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("init(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+
+ throw new SftpException(substatus, msg);
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected void checkStatus(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkStatus(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+
+ if (substatus != SSH_FX_OK) {
+ throw new SftpException(substatus, msg);
+ }
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected String checkHandle(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkHandle(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_HANDLE) {
+ String handle = ValidateUtils.checkNotNullAndNotEmpty(buffer.getString(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
+ return handle;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected Attributes checkAttributes(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkAttributes(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_ATTRS) {
+ return readAttributes(buffer);
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected String checkOneName(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkOneName(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_NAME) {
+ int len = buffer.getInt();
+ if (len != 1) {
+ throw new SshException("SFTP error: received " + len + " names instead of 1");
+ }
+ String name = buffer.getString(), longName = null;
+ if (version == SFTP_V3) {
+ longName = buffer.getString();
+ }
+ Attributes attrs = readAttributes(buffer);
+ if (log.isTraceEnabled()) {
+ log.trace("checkOneName(id={}) ({})[{}]: {}", Integer.valueOf(id), name, longName, attrs);
+ }
+ return name;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected Attributes readAttributes(Buffer buffer) throws IOException {
+ Attributes attrs = new Attributes();
+ int flags = buffer.getInt();
+ if (version == SFTP_V3) {
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ attrs.flags.add(Attribute.Size);
+ attrs.size = buffer.getLong();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ attrs.flags.add(Attribute.UidGid);
+ attrs.uid = buffer.getInt();
+ attrs.gid = buffer.getInt();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ attrs.flags.add(Attribute.Perms);
+ attrs.perms = buffer.getInt();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ attrs.flags.add(Attribute.AcModTime);
+ attrs.atime = buffer.getInt();
+ attrs.mtime = buffer.getInt();
+ }
+ } else if (version >= SFTP_V4) {
+ attrs.type = buffer.getByte();
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ attrs.flags.add(Attribute.Size);
+ attrs.size = buffer.getLong();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ attrs.flags.add(Attribute.OwnerGroup);
+ attrs.owner = buffer.getString();
+ attrs.group = buffer.getString();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ attrs.flags.add(Attribute.Perms);
+ attrs.perms = buffer.getInt();
+ }
+
+ // update the permissions according to the type
+ switch (attrs.type) {
+ case SSH_FILEXFER_TYPE_REGULAR:
+ attrs.perms |= S_IFREG;
+ break;
+ case SSH_FILEXFER_TYPE_DIRECTORY:
+ attrs.perms |= S_IFDIR;
+ break;
+ case SSH_FILEXFER_TYPE_SYMLINK:
+ attrs.perms |= S_IFLNK;
+ break;
+ default: // do nothing
+ }
+
+ if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ attrs.flags.add(Attribute.AccessTime);
+ attrs.accessTime = readTime(buffer, flags);
+ attrs.atime = (int) attrs.accessTime.to(TimeUnit.SECONDS);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ attrs.flags.add(Attribute.CreateTime);
+ attrs.createTime = readTime(buffer, flags);
+ attrs.ctime = (int) attrs.createTime.to(TimeUnit.SECONDS);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ attrs.flags.add(Attribute.ModifyTime);
+ attrs.modifyTime = readTime(buffer, flags);
+ attrs.mtime = (int) attrs.modifyTime.to(TimeUnit.SECONDS);
+ }
+ // TODO: acl
+ } else {
+ throw new IllegalStateException("readAttributes - unsupported version: " + version);
+ }
+ return attrs;
+ }
+
+ private FileTime readTime(Buffer buffer, int flags) {
+ long secs = buffer.getLong();
+ long millis = secs * 1000;
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ millis += buffer.getInt() / 1000000l;
+ }
+ return FileTime.from(millis, TimeUnit.MILLISECONDS);
+ }
+
+ protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
+ if (version == SFTP_V3) {
+ int flags = 0;
+ for (Attribute a : attributes.flags) {
+ switch (a) {
+ case Size:
+ flags |= SSH_FILEXFER_ATTR_SIZE;
+ break;
+ case UidGid:
+ flags |= SSH_FILEXFER_ATTR_UIDGID;
+ break;
+ case Perms:
+ flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+ break;
+ case AcModTime:
+ flags |= SSH_FILEXFER_ATTR_ACMODTIME;
+ break;
+ default: // do nothing
+ }
+ }
+ buffer.putInt(flags);
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong(attributes.size);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ buffer.putInt(attributes.uid);
+ buffer.putInt(attributes.gid);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributes.perms);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ buffer.putInt(attributes.atime);
+ buffer.putInt(attributes.mtime);
+ }
+ } else if (version >= SFTP_V4) {
+ int flags = 0;
+ for (Attribute a : attributes.flags) {
+ switch (a) {
+ case Size:
+ flags |= SSH_FILEXFER_ATTR_SIZE;
+ break;
+ case OwnerGroup:
+ flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
+ break;
+ case Perms:
+ flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+ break;
+ case AccessTime:
+ flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
+ break;
+ case ModifyTime:
+ flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
+ break;
+ case CreateTime:
+ flags |= SSH_FILEXFER_ATTR_CREATETIME;
+ break;
+ default: // do nothing
+ }
+ }
+ buffer.putInt(flags);
+ buffer.putByte(attributes.type);
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong(attributes.size);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ buffer.putString(attributes.owner != null ? attributes.owner : "OWNER@", StandardCharsets.UTF_8);
+ buffer.putString(attributes.group != null ? attributes.group : "GROUP@", StandardCharsets.UTF_8);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributes.perms);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ buffer.putLong(attributes.accessTime.to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = attributes.accessTime.to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ buffer.putInt(attributes.atime);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ buffer.putLong(attributes.createTime.to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = attributes.createTime.to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ buffer.putInt(attributes.atime);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ buffer.putLong(attributes.modifyTime.to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = attributes.modifyTime.to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ buffer.putInt(attributes.atime);
+ }
+ // TODO: acl
+ } else {
+ throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
+ }
+ }
+
+ @Override
+ public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ if (version == SFTP_V3) {
+ int mode = 0;
+ for (OpenMode m : options) {
+ switch (m) {
+ case Read:
+ mode |= SSH_FXF_READ;
+ break;
+ case Write:
+ mode |= SSH_FXF_WRITE;
+ break;
+ case Append:
+ mode |= SSH_FXF_APPEND;
+ break;
+ case Create:
+ mode |= SSH_FXF_CREAT;
+ break;
+ case Truncate:
+ mode |= SSH_FXF_TRUNC;
+ break;
+ case Exclusive:
+ mode |= SSH_FXF_EXCL;
+ break;
+ default: // do nothing
+ }
+ }
+ buffer.putInt(mode);
+ } else {
+ int mode = 0;
+ int access = 0;
+ if (options.contains(OpenMode.Read)) {
+ access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+ }
+ if (options.contains(OpenMode.Write)) {
+ access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+ }
+ if (options.contains(OpenMode.Append)) {
+ access |= ACE4_APPEND_DATA;
+ }
+ if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) {
+ mode |= SSH_FXF_CREATE_NEW;
+ } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) {
+ mode |= SSH_FXF_CREATE_TRUNCATE;
+ } else if (options.contains(OpenMode.Create)) {
+ mode |= SSH_FXF_OPEN_OR_CREATE;
+ } else if (options.contains(OpenMode.Truncate)) {
+ mode |= SSH_FXF_TRUNCATE_EXISTING;
+ } else {
+ mode |= SSH_FXF_OPEN_EXISTING;
+ }
+ if (version >= SFTP_V5) {
+ buffer.putInt(access);
+ }
+ buffer.putInt(mode);
+ }
+ writeAttributes(buffer, new Attributes());
+ return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPEN, buffer))));
+ }
+
+ @Override
+ public void close(Handle handle) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(handle.id);
+ checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
+ }
+
+ @Override
+ public void remove(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ checkStatus(receive(send(SSH_FXP_REMOVE, buffer)));
+ }
+
+ @Override
+ public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(oldPath);
+ buffer.putString(newPath);
+
+ int numOptions = GenericUtils.size(options);
+ if (version >= SFTP_V5) {
+ int opts = 0;
+ if (numOptions > 0) {
+ for (CopyMode opt : options) {
+ switch (opt) {
+ case Atomic:
+ opts |= SSH_FXP_RENAME_ATOMIC;
+ break;
+ case Overwrite:
+ opts |= SSH_FXP_RENAME_OVERWRITE;
+ break;
+ default: // do nothing
+ }
+ }
+ }
+ buffer.putInt(opts);
+ } else if (numOptions > 0) {
+ throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
+ + " - copy options can not be used with this SFTP version: " + options);
+ }
+ checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
+ }
+
+ @Override
+ public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(handle.id);
+ buffer.putLong(fileOffset);
+ buffer.putInt(len);
+ return checkData(receive(send(SSH_FXP_READ, buffer)), dstoff, dst);
+ }
+
+ protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkData(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+
+ if (substatus == SSH_FX_EOF) {
+ return -1;
+ }
+
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_DATA) {
+ int len = buffer.getInt();
+ buffer.getRawBytes(dst, dstoff, len);
+ return len;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ @Override
+ public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
+ // do some bounds checking first
+ if ((fileOffset < 0) || (srcoff < 0) || (len < 0)) {
+ throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters "
+ + " are non-negative values: file-offset=" + fileOffset
+ + ", src-offset=" + srcoff + ", len=" + len);
+ }
+ if ((srcoff + len) > src.length) {
+ throw new IllegalArgumentException("write(" + handle + ")"
+ + " cannot read bytes " + srcoff + " to " + (srcoff + len)
+ + " when array is only of length " + src.length);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(handle.id.length() + len + Long.SIZE /* some extra fields */);
+ buffer.putString(handle.id);
+ buffer.putLong(fileOffset);
+ buffer.putBytes(src, srcoff, len);
+ checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
+ }
+
+ @Override
+ public void mkdir(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path, StandardCharsets.UTF_8);
+ buffer.putInt(0);
+ if (version != SFTP_V3) {
+ buffer.putByte((byte) 0);
+ }
+ checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
+ }
+
+ @Override
+ public void rmdir(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ checkStatus(receive(send(SSH_FXP_RMDIR, buffer)));
+ }
+
+ @Override
+ public CloseableHandle openDir(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPENDIR, buffer))));
+ }
+
+ @Override
+ public DirEntry[] readDir(Handle handle) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(handle.id);
+ return checkDir(receive(send(SSH_FXP_READDIR, buffer)));
+ }
+
+ protected DirEntry[] checkDir(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkDir(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+ if (substatus == SSH_FX_EOF) {
+ return null;
+ }
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_NAME) {
+ int len = buffer.getInt();
+ DirEntry[] entries = new DirEntry[len];
+ for (int i = 0; i < len; i++) {
+ String name = buffer.getString();
+ String longName = (version == SFTP_V3) ? buffer.getString() : null;
+ Attributes attrs = readAttributes(buffer);
+ if (log.isTraceEnabled()) {
+ log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
+ }
+
+ entries[i] = new DirEntry(name, longName, attrs);
+ }
+ return entries;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ @Override
+ public String canonicalPath(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+ return checkOneName(receive(send(SSH_FXP_REALPATH, buffer)));
+ }
+
+ @Override
+ public Attributes stat(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+ if (version >= SFTP_V4) {
+ buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+ }
+ return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
+ }
+
+ @Override
+ public Attributes lstat(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+ if (version >= SFTP_V4) {
+ buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+ }
+ return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
+ }
+
+ @Override
+ public Attributes stat(Handle handle) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(handle.id);
+ if (version >= SFTP_V4) {
+ buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+ }
+ return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
+ }
+
+ @Override
+ public void setStat(String path, Attributes attributes) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+ writeAttributes(buffer, attributes);
+ checkStatus(receive(send(SSH_FXP_SETSTAT, buffer)));
+ }
+
+ @Override
+ public void setStat(Handle handle, Attributes attributes) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(handle.id);
+ writeAttributes(buffer, attributes);
+ checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
+ }
+
+ @Override
+ public String readLink(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ return checkOneName(receive(send(SSH_FXP_READLINK, buffer)));
+ }
+
+ @Override
+ public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */);
+ if (version < SFTP_V6) {
+ if (!symbolic) {
+ throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
+ }
+ buffer.putString(targetPath);
+ buffer.putString(linkPath);
+ checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
+ } else {
+ buffer.putString(targetPath);
+ buffer.putString(linkPath);
+ buffer.putBoolean(symbolic);
+ checkStatus(receive(send(SSH_FXP_LINK, buffer)));
+ }
+ }
+
+ @Override
+ public void lock(Handle handle, long offset, long length, int mask) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(handle.id);
+ buffer.putLong(offset);
+ buffer.putLong(length);
+ buffer.putInt(mask);
+ checkStatus(receive(send(SSH_FXP_BLOCK, buffer)));
+ }
+
+ @Override
+ public void unlock(Handle handle, long offset, long length) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(handle.id);
+ buffer.putLong(offset);
+ buffer.putLong(length);
+ checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));
+ }
+
+ @Override
+ public Iterable<DirEntry> readDir(final String path) throws IOException {
+ return new Iterable<DirEntry>() {
+ @Override
+ public Iterator<DirEntry> iterator() {
+ return new Iterator<DirEntry>() {
+ private CloseableHandle handle;
+ private DirEntry[] entries;
+ private int index;
+
+ {
+ open();
+ load();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return (entries != null) && (index < entries.length);
+ }
+
+ @Override
+ public DirEntry next() {
+ DirEntry entry = entries[index++];
+ if (index >= entries.length) {
+ load();
+ }
+ return entry;
+ }
+
+ @SuppressWarnings("synthetic-access")
+ private void open() {
+ try {
+ handle = openDir(path);
+ if (log.isDebugEnabled()) {
+ log.debug("readDir(" + path + ") handle=" + handle);
+ }
+ } catch (IOException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("readDir(" + path + ") failed (" + e.getClass().getSimpleName() + ") to open dir: " + e.getMessage());
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ @SuppressWarnings("synthetic-access")
+ private void load() {
+ try {
+ entries = readDir(handle);
+ index = 0;
+ if (entries == null) {
+ handle.close();
+ }
+ } catch (IOException e) {
+ entries = null;
+ try {
+ handle.close();
+ } catch (IOException t) {
+ if (log.isTraceEnabled()) {
+ log.trace(t.getClass().getSimpleName() + " while close handle=" + handle
+ + " due to " + e.getClass().getSimpleName() + " [" + e.getMessage() + "]"
+ + ": " + t.getMessage());
+ }
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("readDir(" + path + ") Iterator#remove() N/A");
+ }
+ };
+ }
+ };
+ }
+
+ @Override
+ public InputStream read(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
+ if (bufferSize < MIN_READ_BUFFER_SIZE) {
+ throw new IllegalArgumentException("Insufficient read buffer size: " + bufferSize + ", min.=" + MIN_READ_BUFFER_SIZE);
+ }
+
+ return new InputStreamWithChannel() {
+ private byte[] bb = new byte[1];
+ private byte[] buffer = new byte[bufferSize];
+ private int index;
+ private int available;
+ private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
+ private long offset;
+
+ @Override
+ public boolean isOpen() {
+ return (handle != null) && handle.isOpen();
+ }
+
+ @Override
+ public int read() throws IOException {
+ int read = read(bb, 0, 1);
+ if (read > 0) {
+ return bb[0];
+ }
+
+ return read;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("read(" + path + ") stream closed");
+ }
+
+ int idx = off;
+ while (len > 0) {
+ if (index >= available) {
+ available = DefaultSftpClient.this.read(handle, offset, buffer, 0, buffer.length);
+ if (available < 0) {
+ if (idx == off) {
+ return -1;
+ } else {
+ break;
+ }
+ }
+ offset += available;
+ index = 0;
+ }
+ if (index >= available) {
+ break;
+ }
+ int nb = Math.min(len, available - index);
+ System.arraycopy(buffer, index, b, idx, nb);
+ index += nb;
+ idx += nb;
+ len -= nb;
+ }
+ return idx - off;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ try {
+ handle.close();
+ } finally {
+ handle = null;
+ }
+ }
+ }
+ };
+ }
+
+ @Override
+ public OutputStream write(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
+ if (bufferSize < MIN_WRITE_BUFFER_SIZE) {
+ throw new IllegalArgumentException("Insufficient write buffer size: " + bufferSize + ", min.=" + MIN_WRITE_BUFFER_SIZE);
+ }
+
+ return new OutputStreamWithChannel() {
+ private byte[] bb = new byte[1];
+ private byte[] buffer = new byte[bufferSize];
+ private int index;
+ private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
+ private long offset;
+
+ @Override
+ public boolean isOpen() {
+ return (handle != null) && handle.isOpen();
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ bb[0] = (byte) b;
+ write(bb, 0, 1);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("write(" + path + ")[len=" + len + "] stream is closed");
+ }
+
+ do {
+ int nb = Math.min(len, buffer.length - index);
+ System.arraycopy(b, off, buffer, index, nb);
+ index += nb;
+ if (index == buffer.length) {
+ flush();
+ }
+ off += nb;
+ len -= nb;
+ } while (len > 0);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (!isOpen()) {
+ throw new IOException("flush(" + path + ") stream is closed");
+ }
+
+ DefaultSftpClient.this.write(handle, offset, buffer, 0, index);
+ offset += index;
+ index = 0;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ try {
+ try {
+ if (index > 0) {
+ flush();
+ }
+ } finally {
+ handle.close();
+ }
+ } finally {
+ handle = null;
+ }
+ }
+ }
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
new file mode 100644
index 0000000..2af762c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
@@ -0,0 +1,310 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFLNK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFMT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFREG;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.Channel;
+import java.nio.file.attribute.FileTime;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.subsystem.SubsystemClient;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ */
+public interface SftpClient extends SubsystemClient {
+
+ enum OpenMode {
+ Read,
+ Write,
+ Append,
+ Create,
+ Truncate,
+ Exclusive
+ }
+
+ enum CopyMode {
+ Atomic,
+ Overwrite
+ }
+
+ enum Attribute {
+ Size,
+ UidGid,
+ Perms,
+ AcModTime,
+ OwnerGroup,
+ AccessTime,
+ ModifyTime,
+ CreateTime,
+ }
+
+ public static class Handle {
+ public final String id;
+ public Handle(String id) {
+ this.id = ValidateUtils.checkNotNullAndNotEmpty(id, "No handle ID", GenericUtils.EMPTY_OBJECT_ARRAY);
+ }
+
+ @Override
+ public String toString() {
+ return id;
+ }
+ }
+
+ public static abstract class CloseableHandle extends Handle implements Channel, Closeable {
+ protected CloseableHandle(String id) {
+ super(id);
+ }
+ }
+
+ public static class Attributes {
+ public final Set<Attribute> flags = EnumSet.noneOf(Attribute.class);
+ public long size;
+ public byte type;
+ public int uid;
+ public int gid;
+ public int perms;
+ public int atime;
+ public int ctime;
+ public int mtime;
+ public String owner;
+ public String group;
+ public FileTime accessTime;
+ public FileTime createTime;
+ public FileTime modifyTime;
+
+ @Override
+ public String toString() {
+ return "type=" + type
+ + ";size=" + size
+ + ";uid=" + uid
+ + ";gid=" + gid
+ + ";perms=0x" + Integer.toHexString(perms)
+ + ";flags=" + flags
+ + ";owner=" + owner
+ + ";group=" + group
+ + ";aTime=(" + atime + ")[" + accessTime + "]"
+ + ";cTime=(" + ctime + ")[" + createTime + "]"
+ + ";mTime=(" + mtime + ")[" + modifyTime + "]"
+ ;
+ }
+
+ public Attributes size(long size) {
+ flags.add(Attribute.Size);
+ this.size = size;
+ return this;
+ }
+ public Attributes owner(String owner) {
+ flags.add(Attribute.OwnerGroup);
+ this.owner = owner;
+ if (GenericUtils.isEmpty(group)) {
+ group = "GROUP@";
+ }
+ return this;
+ }
+ public Attributes group(String group) {
+ flags.add(Attribute.OwnerGroup);
+ this.group = group;
+ if (GenericUtils.isEmpty(owner)) {
+ owner = "OWNER@";
+ }
+ return this;
+ }
+ public Attributes owner(int uid, int gid) {
+ flags.add(Attribute.UidGid);
+ this.uid = uid;
+ this.gid = gid;
+ return this;
+ }
+ public Attributes perms(int perms) {
+ flags.add(Attribute.Perms);
+ this.perms = perms;
+ return this;
+ }
+ public Attributes atime(int atime) {
+ flags.add(Attribute.AccessTime);
+ this.atime = atime;
+ this.accessTime = FileTime.from(atime, TimeUnit.SECONDS);
+ return this;
+ }
+ public Attributes ctime(int ctime) {
+ flags.add(Attribute.CreateTime);
+ this.ctime = ctime;
+ this.createTime = FileTime.from(atime, TimeUnit.SECONDS);
+ return this;
+ }
+ public Attributes mtime(int mtime) {
+ flags.add(Attribute.ModifyTime);
+ this.mtime = mtime;
+ this.modifyTime = FileTime.from(atime, TimeUnit.SECONDS);
+ return this;
+ }
+ public Attributes time(int atime, int mtime) {
+ flags.add(Attribute.AcModTime);
+ this.atime = atime;
+ this.mtime = mtime;
+ return this;
+ }
+ public Attributes accessTime(FileTime atime) {
+ flags.add(Attribute.AccessTime);
+ this.atime = (int) atime.to(TimeUnit.SECONDS);
+ this.accessTime = atime;
+ return this;
+ }
+ public Attributes createTime(FileTime ctime) {
+ flags.add(Attribute.CreateTime);
+ this.ctime = (int) ctime.to(TimeUnit.SECONDS);
+ this.createTime = ctime;
+ return this;
+ }
+ public Attributes modifyTime(FileTime mtime) {
+ flags.add(Attribute.ModifyTime);
+ this.mtime = (int) mtime.to(TimeUnit.SECONDS);
+ this.modifyTime = mtime;
+ return this;
+ }
+ public boolean isRegularFile() {
+ return (perms & S_IFMT) == S_IFREG;
+ }
+ public boolean isDirectory() {
+ return (perms & S_IFMT) == S_IFDIR;
+ }
+ public boolean isSymbolicLink() {
+ return (perms & S_IFMT) == S_IFLNK;
+ }
+ public boolean isOther() {
+ return !isRegularFile() && !isDirectory() && !isSymbolicLink();
+ }
+ }
+
+ public static class DirEntry {
+ public String filename;
+ public String longFilename;
+ public Attributes attributes;
+ public DirEntry(String filename, String longFilename, Attributes attributes) {
+ this.filename = filename;
+ this.longFilename = longFilename;
+ this.attributes = attributes;
+ }
+ }
+
+ int getVersion();
+
+ boolean isClosing();
+
+ //
+ // Low level API
+ //
+
+ CloseableHandle open(String path) throws IOException;
+ CloseableHandle open(String path, OpenMode ... options) throws IOException;
+ CloseableHandle open(String path, Collection<OpenMode> options) throws IOException;
+
+ void close(Handle handle) throws IOException;
+
+ void remove(String path) throws IOException;
+
+ void rename(String oldPath, String newPath) throws IOException;
+ void rename(String oldPath, String newPath, CopyMode... options) throws IOException;
+ void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException;
+
+ int read(Handle handle, long fileOffset, byte[] dst) throws IOException;
+ int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException;
+
+ void write(Handle handle, long fileOffset, byte[] src) throws IOException;
+ void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException;
+
+ void mkdir(String path) throws IOException;
+
+ void rmdir(String path) throws IOException;
+
+ CloseableHandle openDir(String path) throws IOException;
+
+ DirEntry[] readDir(Handle handle) throws IOException;
+
+ String canonicalPath(String path) throws IOException;
+
+ Attributes stat(String path) throws IOException;
+
+ Attributes lstat(String path) throws IOException;
+
+ Attributes stat(Handle handle) throws IOException;
+
+ void setStat(String path, Attributes attributes) throws IOException;
+
+ void setStat(Handle handle, Attributes attributes) throws IOException;
+
+ String readLink(String path) throws IOException;
+
+ void symLink(String linkPath, String targetPath) throws IOException;
+
+ void link(String linkPath, String targetPath, boolean symbolic) throws IOException;
+
+ void lock(Handle handle, long offset, long length, int mask) throws IOException;
+
+ void unlock(Handle handle, long offset, long length) throws IOException;
+
+ //
+ // High level API
+ //
+
+ Iterable<DirEntry> readDir(String path) throws IOException;
+
+ // default values used if none specified
+ int MIN_BUFFER_SIZE=Byte.MAX_VALUE, MIN_READ_BUFFER_SIZE=MIN_BUFFER_SIZE, MIN_WRITE_BUFFER_SIZE=MIN_BUFFER_SIZE;
+ int IO_BUFFER_SIZE=32 * 1024, DEFAULT_READ_BUFFER_SIZE=IO_BUFFER_SIZE, DEFAULT_WRITE_BUFFER_SIZE=IO_BUFFER_SIZE;
+ long DEFAULT_WAIT_TIMEOUT=TimeUnit.SECONDS.toMillis(30L);
+
+ /**
+ * Property that can be used on the {@link org.apache.sshd.common.FactoryManager}
+ * to control the internal timeout used by the client to open a channel.
+ * If not specified then {@link #DEFAULT_CHANNEL_OPEN_TIMEOUT} value
+ * is used
+ */
+ String SFTP_CHANNEL_OPEN_TIMEOUT = "sftp-channel-open-timeout";
+ long DEFAULT_CHANNEL_OPEN_TIMEOUT = DEFAULT_WAIT_TIMEOUT;
+
+ InputStream read(String path) throws IOException;
+ InputStream read(String path, int bufferSize) throws IOException;
+ InputStream read(String path, OpenMode ... mode) throws IOException;
+ InputStream read(String path, int bufferSize, OpenMode ... mode) throws IOException;
+ InputStream read(String path, Collection<OpenMode> mode) throws IOException;
+ InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
+
+ OutputStream write(String path) throws IOException;
+ OutputStream write(String path, int bufferSize) throws IOException;
+ OutputStream write(String path, OpenMode ... mode) throws IOException;
+ OutputStream write(String path, int bufferSize, OpenMode ... mode) throws IOException;
+ OutputStream write(String path, Collection<OpenMode> mode) throws IOException;
+ OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
new file mode 100644
index 0000000..a5ba947
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileChannel.java
@@ -0,0 +1,389 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_LOCK_CONFLICT;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.SftpException;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+public class SftpFileChannel extends FileChannel {
+
+ private final SftpPath p;
+ private final Collection<SftpClient.OpenMode> modes;
+ private final SftpClient sftp;
+ private final SftpClient.CloseableHandle handle;
+ private final Object lock = new Object();
+ private volatile long pos;
+ private volatile Thread blockingThread;
+
+ public SftpFileChannel(SftpPath p, Collection<SftpClient.OpenMode> modes) throws IOException {
+ this.p = ValidateUtils.checkNotNull(p, "No target path", GenericUtils.EMPTY_OBJECT_ARRAY);
+ this.modes = ValidateUtils.checkNotNull(modes, "No channel modes specified", GenericUtils.EMPTY_OBJECT_ARRAY);
+
+ SftpFileSystem fs=p.getFileSystem();
+ sftp = fs.getClient();
+ handle = sftp.open(p.toString(), modes);
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ return (int) doRead(Collections.singletonList(dst), -1);
+ }
+
+ @Override
+ public int read(ByteBuffer dst, long position) throws IOException {
+ if (position < 0) {
+ throw new IllegalArgumentException("read(" + p + ") illegal position to read from: " + position);
+ }
+ return (int) doRead(Collections.singletonList(dst), position);
+ }
+
+ @Override
+ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+ List<ByteBuffer> buffers = Arrays.asList(dsts).subList(offset, offset + length);
+ return doRead(buffers, -1);
+ }
+
+ public static final Set<SftpClient.OpenMode> READ_MODES=
+ Collections.unmodifiableSet(EnumSet.of(SftpClient.OpenMode.Read));
+
+ protected long doRead(List<ByteBuffer> buffers, long position) throws IOException {
+ ensureOpen(READ_MODES);
+ synchronized (lock) {
+ boolean completed = false;
+ boolean eof = false;
+ long curPos = position >= 0 ? position : pos;
+ try {
+ long totalRead = 0;
+ beginBlocking();
+ loop:
+ for (ByteBuffer buffer : buffers) {
+ while (buffer.remaining() > 0) {
+ ByteBuffer wrap = buffer;
+ if (!buffer.hasArray()) {
+ wrap = ByteBuffer.allocate(Math.min(8192, buffer.remaining()));
+ }
+ int read = sftp.read(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), wrap.remaining());
+ if (read > 0) {
+ if (wrap == buffer) {
+ wrap.position(wrap.position() + read);
+ } else {
+ buffer.put(wrap.array(), wrap.arrayOffset(), read);
+ }
+ curPos += read;
+ totalRead += read;
+ } else {
+ eof = read == -1;
+ break loop;
+ }
+ }
+ }
+ completed = true;
+ return totalRead > 0 ? totalRead : eof ? -1 : 0;
+ } finally {
+ if (position < 0) {
+ pos = curPos;
+ }
+ endBlocking(completed);
+ }
+ }
+ }
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ return (int) doWrite(Collections.singletonList(src), -1);
+ }
+
+ @Override
+ public int write(ByteBuffer src, long position) throws IOException {
+ if (position < 0) {
+ throw new IllegalArgumentException("write(" + p + ") illegal position to write to: " + position);
+ }
+ return (int) doWrite(Collections.singletonList(src), position);
+ }
+
+ @Override
+ public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+ List<ByteBuffer> buffers = Arrays.asList(srcs).subList(offset, offset + length);
+ return doWrite(buffers, -1);
+ }
+
+ public static final Set<SftpClient.OpenMode> WRITE_MODES=
+ Collections.unmodifiableSet(
+ EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Append, SftpClient.OpenMode.Create, SftpClient.OpenMode.Truncate));
+
+ protected long doWrite(List<ByteBuffer> buffers, long position) throws IOException {
+ ensureOpen(WRITE_MODES);
+ synchronized (lock) {
+ boolean completed = false;
+ long curPos = position >= 0 ? position : pos;
+ try {
+ long totalWritten = 0;
+ beginBlocking();
+ for (ByteBuffer buffer : buffers) {
+ while (buffer.remaining() > 0) {
+ ByteBuffer wrap = buffer;
+ if (!buffer.hasArray()) {
+ wrap = ByteBuffer.allocate(Math.min(8192, buffer.remaining()));
+ buffer.get(wrap.array(), wrap.arrayOffset(), wrap.remaining());
+ }
+ int written = wrap.remaining();
+ sftp.write(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), written);
+ if (wrap == buffer) {
+ wrap.position(wrap.position() + written);
+ }
+ curPos += written;
+ totalWritten += written;
+ }
+ }
+ completed = true;
+ return totalWritten;
+ } finally {
+ if (position < 0) {
+ pos = curPos;
+ }
+ endBlocking(completed);
+ }
+ }
+ }
+
+ @Override
+ public long position() throws IOException {
+ ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+ return pos;
+ }
+
+ @Override
+ public FileChannel position(long newPosition) throws IOException {
+ if (newPosition < 0) {
+ throw new IllegalArgumentException("position(" + p + ") illegal file channel position: " + newPosition);
+ }
+
+ ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+ synchronized (lock) {
+ pos = newPosition;
+ return this;
+ }
+ }
+
+ @Override
+ public long size() throws IOException {
+ ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+ return sftp.stat(handle).size;
+ }
+
+ @Override
+ public FileChannel truncate(long size) throws IOException {
+ ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+ sftp.setStat(handle, new SftpClient.Attributes().size(size));
+ return this;
+ }
+
+ @Override
+ public void force(boolean metaData) throws IOException {
+ ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+ }
+
+ @Override
+ public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
+ if ((position < 0) || (count < 0)) {
+ throw new IllegalArgumentException("transferTo(" + p + ") illegal position (" + position + ") or count (" + count + ")");
+ }
+ ensureOpen(READ_MODES);
+ synchronized (lock) {
+ boolean completed = false;
+ boolean eof = false;
+ long curPos = position;
+ try {
+ beginBlocking();
+
+ int bufSize = (int) Math.min(count, 32768);
+ byte[] buffer = new byte[bufSize];
+ long totalRead = 0L;
+ while (totalRead < count) {
+ int read = sftp.read(handle, curPos, buffer, 0, buffer.length);
+ if (read > 0) {
+ ByteBuffer wrap = ByteBuffer.wrap(buffer);
+ while (wrap.remaining() > 0) {
+ target.write(wrap);
+ }
+ curPos += read;
+ totalRead += read;
+ } else {
+ eof = read == -1;
+ }
+ }
+ completed = true;
+ return totalRead > 0 ? totalRead : eof ? -1 : 0;
+ } finally {
+ endBlocking(completed);
+ }
+ }
+ }
+
+ @Override
+ public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
+ if ((position < 0) || (count < 0)) {
+ throw new IllegalArgumentException("transferFrom(" + p + ") illegal position (" + position + ") or count (" + count + ")");
+ }
+ ensureOpen(WRITE_MODES);
+
+ synchronized(lock) {
+ boolean completed = false;
+ long curPos = position >= 0 ? position : pos;
+ try {
+ long totalRead = 0;
+ beginBlocking();
+
+ byte[] buffer = new byte[32768];
+ while (totalRead < count) {
+ ByteBuffer wrap = ByteBuffer.wrap(buffer, 0, (int) Math.min(buffer.length, count - totalRead));
+ int read = src.read(wrap);
+ if (read > 0) {
+ sftp.write(handle, curPos, buffer, 0, read);
+ curPos += read;
+ totalRead += read;
+ } else {
+ break;
+ }
+ }
+ completed = true;
+ return totalRead;
+ } finally {
+ endBlocking(completed);
+ }
+ }
+ }
+
+ @Override
+ public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+ throw new UnsupportedOperationException("map(" + p + ")[" + mode + "," + position + "," + size + "] N/A");
+ }
+
+ @Override
+ public FileLock lock(long position, long size, boolean shared) throws IOException {
+ return tryLock(position, size, shared);
+ }
+
+ @Override
+ public FileLock tryLock(final long position, final long size, boolean shared) throws IOException {
+ ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
+
+ try {
+ sftp.lock(handle, position, size, 0);
+ } catch (SftpException e) {
+ if (e.getStatus() == SSH_FX_LOCK_CONFLICT) {
+ throw new OverlappingFileLockException();
+ }
+ throw e;
+ }
+
+ return new FileLock(this, position, size, shared) {
+ private final AtomicBoolean valid = new AtomicBoolean(true);
+
+ @Override
+ public boolean isValid() {
+ return acquiredBy().isOpen() && valid.get();
+ }
+
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public void release() throws IOException {
+ if (valid.compareAndSet(true, false)) {
+ sftp.unlock(handle, position, size);
+ }
+ }
+ };
+ }
+
+ @Override
+ protected void implCloseChannel() throws IOException {
+ try {
+ final Thread thread = blockingThread;
+ if (thread != null) {
+ thread.interrupt();
+ }
+ } finally {
+ try {
+ handle.close();
+ } finally {
+ sftp.close();
+ }
+ }
+ }
+
+ private void beginBlocking() {
+ begin();
+ blockingThread = Thread.currentThread();
+ }
+
+ private void endBlocking(boolean completed) throws AsynchronousCloseException {
+ blockingThread = null;
+ end(completed);
+ }
+
+ /**
+ * Checks that the channel is open and that its current mode contains
+ * at least one of the required ones
+ * @param reqModes The required modes - ignored if {@code null}/empty
+ * @throws IOException If channel not open or the required modes are not
+ * satisfied
+ */
+ private void ensureOpen(Collection<SftpClient.OpenMode> reqModes) throws IOException {
+ if (!isOpen()) {
+ throw new ClosedChannelException();
+ }
+
+ if (GenericUtils.size(reqModes) > 0) {
+ for (SftpClient.OpenMode m : reqModes) {
+ if (this.modes.contains(m)) {
+ return;
+ }
+ }
+
+ throw new IOException("ensureOpen(" + p + ") current channel modes (" + this.modes + ") do contain any of the required: " + reqModes);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toString(p);
+ }
+}
[07/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
new file mode 100644
index 0000000..edbf82b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
@@ -0,0 +1,465 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.file.util.BaseFileSystem;
+import org.apache.sshd.common.file.util.ImmutableList;
+
+public class SftpFileSystem extends BaseFileSystem<SftpPath> {
+
+ private final ClientSession session;
+ private final Queue<SftpClient> pool;
+ private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
+ private SftpPath defaultDir;
+ private int readBufferSize = SftpClient.DEFAULT_READ_BUFFER_SIZE;
+ private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
+
+ public SftpFileSystem(SftpFileSystemProvider provider, ClientSession session) throws IOException {
+ super(provider);
+ this.session = session;
+ this.pool = new LinkedBlockingQueue<>(8);
+ try (SftpClient client = getClient()) {
+ defaultDir = getPath(client.canonicalPath("."));
+ }
+ }
+
+ public int getReadBufferSize() {
+ return readBufferSize;
+ }
+
+ public void setReadBufferSize(int size) {
+ if (size < SftpClient.MIN_READ_BUFFER_SIZE) {
+ throw new IllegalArgumentException("Insufficient read buffer size: " + size + ", min.=" + SftpClient.MIN_READ_BUFFER_SIZE);
+ }
+
+ readBufferSize = size;
+ }
+
+ public int getWriteBufferSize() {
+ return writeBufferSize;
+ }
+
+ public void setWriteBufferSize(int size) {
+ if (size < SftpClient.MIN_WRITE_BUFFER_SIZE) {
+ throw new IllegalArgumentException("Insufficient write buffer size: " + size + ", min.=" + SftpClient.MIN_WRITE_BUFFER_SIZE);
+ }
+
+ writeBufferSize = size;
+ }
+
+ @Override
+ protected SftpPath create(String root, ImmutableList<String> names) {
+ return new SftpPath(this, root, names);
+ }
+
+ public ClientSession getSession() {
+ return session;
+ }
+
+ @SuppressWarnings("synthetic-access")
+ public SftpClient getClient() throws IOException {
+ Wrapper wrapper = wrappers.get();
+ if (wrapper == null) {
+ while (wrapper == null) {
+ SftpClient client = pool.poll();
+ if (client == null) {
+ client = session.createSftpClient();
+ }
+ if (!client.isClosing()) {
+ wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
+ }
+ }
+ wrappers.set(wrapper);
+ } else {
+ wrapper.increment();
+ }
+ return wrapper;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ session.close(true);
+ }
+ }
+
+ @Override
+ public boolean isOpen() {
+ return !session.isClosing();
+ }
+
+ @Override
+ public Set<String> supportedFileAttributeViews() {
+ Set<String> set = new HashSet<>();
+ set.addAll(Arrays.asList("basic", "posix", "owner"));
+ return Collections.unmodifiableSet(set);
+ }
+
+ @Override
+ public UserPrincipalLookupService getUserPrincipalLookupService() {
+ return new DefaultUserPrincipalLookupService();
+ }
+
+ @Override
+ public SftpPath getDefaultDir() {
+ return defaultDir;
+ }
+
+ private class Wrapper extends AbstractSftpClient {
+
+ private final SftpClient delegate;
+ private final AtomicInteger count = new AtomicInteger(1);
+ private final int readSize, writeSize;
+
+ private Wrapper(SftpClient delegate, int readSize, int writeSize) {
+ this.delegate = delegate;
+ this.readSize = readSize;
+ this.writeSize = writeSize;
+ }
+
+ @Override
+ public int getVersion() {
+ return delegate.getVersion();
+ }
+
+ @Override
+ public boolean isClosing() {
+ return false;
+ }
+
+ @Override
+ public boolean isOpen() {
+ if (count.get() > 0) {
+ return true;
+ } else {
+ return false; // debug breakpoint
+ }
+ }
+
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public void close() throws IOException {
+ if (count.decrementAndGet() <= 0) {
+ if (!pool.offer(delegate)) {
+ delegate.close();
+ }
+ wrappers.set(null);
+ }
+ }
+
+ public void increment() {
+ count.incrementAndGet();
+ }
+
+ @Override
+ public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("open(" + path + ")[" + options + "] client is closed");
+ }
+ return delegate.open(path, options);
+ }
+
+ @Override
+ public void close(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("close(" + handle + ") client is closed");
+ }
+ delegate.close(handle);
+ }
+
+ @Override
+ public void remove(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("remove(" + path + ") client is closed");
+ }
+ delegate.remove(path);
+ }
+
+ @Override
+ public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
+ }
+ delegate.rename(oldPath, newPath, options);
+ }
+
+ @Override
+ public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
+ }
+ return delegate.read(handle, fileOffset, dst, dstOffset, len);
+ }
+
+ @Override
+ public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
+ }
+ delegate.write(handle, fileOffset, src, srcOffset, len);
+ }
+
+ @Override
+ public void mkdir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("mkdir(" + path + ") client is closed");
+ }
+ delegate.mkdir(path);
+ }
+
+ @Override
+ public void rmdir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("rmdir(" + path + ") client is closed");
+ }
+ delegate.rmdir(path);
+ }
+
+ @Override
+ public CloseableHandle openDir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("openDir(" + path + ") client is closed");
+ }
+ return delegate.openDir(path);
+ }
+
+ @Override
+ public DirEntry[] readDir(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("readDir(" + handle + ") client is closed");
+ }
+ return delegate.readDir(handle);
+ }
+
+ @Override
+ public String canonicalPath(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("canonicalPath(" + path + ") client is closed");
+ }
+ return delegate.canonicalPath(path);
+ }
+
+ @Override
+ public Attributes stat(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("stat(" + path + ") client is closed");
+ }
+ return delegate.stat(path);
+ }
+
+ @Override
+ public Attributes lstat(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("lstat(" + path + ") client is closed");
+ }
+ return delegate.lstat(path);
+ }
+
+ @Override
+ public Attributes stat(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("stat(" + handle + ") client is closed");
+ }
+ return delegate.stat(handle);
+ }
+
+ @Override
+ public void setStat(String path, Attributes attributes) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed");
+ }
+ delegate.setStat(path, attributes);
+ }
+
+ @Override
+ public void setStat(Handle handle, Attributes attributes) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
+ }
+ delegate.setStat(handle, attributes);
+ }
+
+ @Override
+ public String readLink(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("readLink(" + path + ") client is closed");
+ }
+ return delegate.readLink(path);
+ }
+
+ @Override
+ public void symLink(String linkPath, String targetPath) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("symLink(" + linkPath + " => " + targetPath + ") client is closed");
+ }
+ delegate.symLink(linkPath, targetPath);
+ }
+
+ @Override
+ public Iterable<DirEntry> readDir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("readDir(" + path + ") client is closed");
+ }
+ return delegate.readDir(path);
+ }
+
+ @Override
+ public InputStream read(String path) throws IOException {
+ return read(path, readSize);
+ }
+
+ @Override
+ public InputStream read(String path, OpenMode... mode) throws IOException {
+ return read(path, readSize, mode);
+ }
+
+ @Override
+ public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
+ return read(path, readSize, mode);
+ }
+
+ @Override
+ public InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("read(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
+ }
+ return delegate.read(path, bufferSize, mode);
+ }
+
+ @Override
+ public OutputStream write(String path) throws IOException {
+ return write(path, writeSize);
+ }
+
+ @Override
+ public OutputStream write(String path, OpenMode... mode) throws IOException {
+ return write(path, writeSize, mode);
+ }
+
+ @Override
+ public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
+ return write(path, writeSize, mode);
+ }
+
+ @Override
+ public OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
+ }
+ return delegate.write(path, bufferSize, mode);
+ }
+
+ @Override
+ public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("link(" + linkPath + " => " + targetPath + "] symbolic=" + symbolic + ": client is closed");
+ }
+ delegate.link(linkPath, targetPath, symbolic);
+ }
+
+ @Override
+ public void lock(Handle handle, long offset, long length, int mask) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
+ }
+ delegate.lock(handle, offset, length, mask);
+ }
+
+ @Override
+ public void unlock(Handle handle, long offset, long length) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
+ }
+ delegate.unlock(handle, offset, length);
+ }
+ }
+
+ protected static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
+
+ @Override
+ public UserPrincipal lookupPrincipalByName(String name) throws IOException {
+ return new DefaultUserPrincipal(name);
+ }
+
+ @Override
+ public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
+ return new DefaultGroupPrincipal(group);
+ }
+ }
+
+ protected static class DefaultUserPrincipal implements UserPrincipal {
+
+ private final String name;
+
+ public DefaultUserPrincipal(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name is null");
+ }
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DefaultUserPrincipal that = (DefaultUserPrincipal) o;
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ protected static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {
+
+ public DefaultGroupPrincipal(String name) {
+ super(name);
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
new file mode 100644
index 0000000..0e5169c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -0,0 +1,892 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V3;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRGRP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IROTH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IRUSR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWGRP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWOTH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IWUSR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXGRP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXOTH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IXUSR;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.FileSystemException;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.ClientBuilder;
+import org.apache.sshd.client.SftpException;
+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.common.FactoryManagerUtils;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SftpFileSystemProvider extends FileSystemProvider {
+ public static final String READ_BUFFER_PROP_NAME = "sftp-fs-read-buffer-size";
+ public static final int DEFAULT_READ_BUFFER_SIZE = SftpClient.DEFAULT_READ_BUFFER_SIZE;
+ public static final String WRITE_BUFFER_PROP_NAME = "sftp-fs-write-buffer-size";
+ public static final int DEFAULT_WRITE_BUFFER_SIZE = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
+ public static final String CONNECT_TIME_PROP_NAME = "sftp-fs-connect-time";
+ public static final long DEFAULT_CONNECT_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
+
+ private final SshClient client;
+ private final Map<String, SftpFileSystem> fileSystems = new HashMap<String, SftpFileSystem>();
+ protected final Logger log;
+
+ public SftpFileSystemProvider() {
+ this(null);
+ }
+
+ public SftpFileSystemProvider(SshClient client) {
+ this.log = LoggerFactory.getLogger(getClass());
+ if (client == null) {
+ // TODO: make this configurable using system properties
+ client = ClientBuilder.builder().build();
+ }
+ this.client = client;
+ this.client.start();
+ }
+
+ @Override
+ public String getScheme() {
+ return SftpConstants.SFTP_SUBSYSTEM_NAME;
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+ synchronized (fileSystems) {
+ String authority = uri.getAuthority();
+ SftpFileSystem fileSystem = fileSystems.get(authority);
+ if (fileSystem != null) {
+ throw new FileSystemAlreadyExistsException(authority);
+ }
+ String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+ String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+ String[] ui = GenericUtils.split(userInfo, ':');
+ int port = uri.getPort();
+ if (port <= 0) {
+ port = SshConfigFileReader.DEFAULT_PORT;
+ }
+
+ ClientSession session=null;
+ try {
+ session = client.connect(ui[0], host, port)
+ .verify(FactoryManagerUtils.getLongProperty(env, CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME))
+ .getSession()
+ ;
+ session.addPasswordIdentity(ui[1]);
+ session.auth().verify();
+ fileSystem = new SftpFileSystem(this, session);
+ fileSystem.setReadBufferSize(FactoryManagerUtils.getIntProperty(env, READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
+ fileSystem.setWriteBufferSize(FactoryManagerUtils.getIntProperty(env, WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
+ fileSystems.put(authority, fileSystem);
+ return fileSystem;
+ } catch(Exception e) {
+ if (session != null) {
+ try {
+ session.close();
+ } catch(IOException t) {
+ if (log.isDebugEnabled()) {
+ log.debug("Failed (" + t.getClass().getSimpleName() + ")"
+ + " to close session for new file system on " + host + ":" + port
+ + " due to " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]"
+ + ": " + t.getMessage());
+ }
+ }
+ }
+
+ if (e instanceof IOException) {
+ throw (IOException) e;
+ } else if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else {
+ throw new IOException(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ synchronized (fileSystems) {
+ String authority = uri.getAuthority();
+ SftpFileSystem fileSystem = fileSystems.get(authority);
+ if (fileSystem == null) {
+ throw new FileSystemNotFoundException(authority);
+ }
+ return fileSystem;
+ }
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ FileSystem fs = getFileSystem(uri);
+ return fs.getPath(uri.getPath());
+ }
+
+ @Override
+ public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+ return newFileChannel(path, options, attrs);
+ }
+
+ @Override
+ public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+ Collection<SftpClient.OpenMode> modes = EnumSet.noneOf(SftpClient.OpenMode.class);
+ for (OpenOption option : options) {
+ if (option == StandardOpenOption.READ) {
+ modes.add(SftpClient.OpenMode.Read);
+ } else if (option == StandardOpenOption.APPEND) {
+ modes.add(SftpClient.OpenMode.Append);
+ } else if (option == StandardOpenOption.CREATE) {
+ modes.add(SftpClient.OpenMode.Create);
+ } else if (option == StandardOpenOption.TRUNCATE_EXISTING) {
+ modes.add(SftpClient.OpenMode.Truncate);
+ } else if (option == StandardOpenOption.WRITE) {
+ modes.add(SftpClient.OpenMode.Write);
+ } else if (option == StandardOpenOption.CREATE_NEW) {
+ modes.add(SftpClient.OpenMode.Create);
+ modes.add(SftpClient.OpenMode.Exclusive);
+ } else if (option == StandardOpenOption.SPARSE) {
+ /*
+ * As per the Javadoc:
+ *
+ * The option is ignored when the file system does not
+ * support the creation of sparse files
+ */
+ continue;
+ } else {
+ throw new IllegalArgumentException("newFileChannel(" + path + ") unsupported open option: " + option);
+ }
+ }
+ if (modes.isEmpty()) {
+ modes.add(SftpClient.OpenMode.Read);
+ modes.add(SftpClient.OpenMode.Write);
+ }
+ // TODO: attrs
+ return new SftpFileChannel(toSftpPath(path), modes);
+ }
+
+ @Override
+ public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
+ final SftpPath p = toSftpPath(dir);
+ return new DirectoryStream<Path>() {
+ private final SftpFileSystem fs = p.getFileSystem();
+ private final SftpClient sftp = fs.getClient();
+ private final Iterable<SftpClient.DirEntry> iter = sftp.readDir(p.toString());
+
+ @Override
+ public Iterator<Path> iterator() {
+ return new Iterator<Path>() {
+ @SuppressWarnings("synthetic-access")
+ private final Iterator<SftpClient.DirEntry> it = iter.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public Path next() {
+ SftpClient.DirEntry entry = it.next();
+ return p.resolve(entry.filename);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
+ }
+ };
+ }
+
+ @Override
+ public void close() throws IOException {
+ sftp.close();
+ }
+ };
+ }
+
+ @Override
+ public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+ SftpPath p = toSftpPath(dir);
+ SftpFileSystem fs = p.getFileSystem();
+ try (SftpClient sftp = fs.getClient()) {
+ try {
+ sftp.mkdir(dir.toString());
+ } catch (SftpException e) {
+ int sftpStatus=e.getStatus();
+ if ((sftp.getVersion() == SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
+ try {
+ Attributes attributes = sftp.stat(dir.toString());
+ if (attributes != null) {
+ throw new FileAlreadyExistsException(p.toString());
+ }
+ } catch (SshException e2) {
+ e.addSuppressed(e2);
+ }
+ }
+ if (sftpStatus == SftpConstants.SSH_FX_FILE_ALREADY_EXISTS) {
+ throw new FileAlreadyExistsException(p.toString());
+ }
+ throw e;
+ }
+ for (FileAttribute<?> attr : attrs) {
+ setAttribute(p, attr.name(), attr.value());
+ }
+ }
+ }
+
+ @Override
+ public void delete(Path path) throws IOException {
+ SftpPath p = toSftpPath(path);
+ checkAccess(p, AccessMode.WRITE);
+
+ SftpFileSystem fs = p.getFileSystem();
+ try (SftpClient sftp = fs.getClient()) {
+ BasicFileAttributes attributes = readAttributes(path, BasicFileAttributes.class);
+ if (attributes.isDirectory()) {
+ sftp.rmdir(path.toString());
+ } else {
+ sftp.remove(path.toString());
+ }
+ }
+ }
+
+ @Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ SftpPath src = toSftpPath(source);
+ SftpPath dst = toSftpPath(target);
+ if (src.getFileSystem() != dst.getFileSystem()) {
+ throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
+ }
+ checkAccess(src);
+
+ boolean replaceExisting = false;
+ boolean copyAttributes = false;
+ boolean noFollowLinks = false;
+ for (CopyOption opt : options) {
+ replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
+ copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
+ noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
+ }
+ LinkOption[] linkOptions = IoUtils.getLinkOptions(!noFollowLinks);
+
+ // attributes of source file
+ BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
+ if (attrs.isSymbolicLink())
+ throw new IOException("Copying of symbolic links not supported");
+
+ // delete target if it exists and REPLACE_EXISTING is specified
+ Boolean status=IoUtils.checkFileExists(target, linkOptions);
+ if (status == null) {
+ throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
+ }
+
+ if (replaceExisting) {
+ deleteIfExists(target);
+ } else {
+ if (status.booleanValue()) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+ }
+
+ // create directory or copy file
+ if (attrs.isDirectory()) {
+ createDirectory(target);
+ } else {
+ try (InputStream in = newInputStream(source);
+ OutputStream os = newOutputStream(target)) {
+ IoUtils.copy(in, os);
+ }
+ }
+
+ // copy basic attributes to target
+ if (copyAttributes) {
+ BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
+ try {
+ view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
+ } catch (Throwable x) {
+ // rollback
+ try {
+ delete(target);
+ } catch (Throwable suppressed) {
+ x.addSuppressed(suppressed);
+ }
+ throw x;
+ }
+ }
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ SftpPath src = toSftpPath(source);
+ SftpFileSystem fsSrc = src.getFileSystem();
+ SftpPath dst = toSftpPath(target);
+
+ if (src.getFileSystem() != dst.getFileSystem()) {
+ throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
+ }
+ checkAccess(src);
+
+ boolean replaceExisting = false;
+ boolean copyAttributes = false;
+ boolean noFollowLinks = false;
+ for (CopyOption opt : options) {
+ replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
+ copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
+ noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
+ }
+ LinkOption[] linkOptions = IoUtils.getLinkOptions(noFollowLinks);
+
+ // attributes of source file
+ BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
+ if (attrs.isSymbolicLink()) {
+ throw new IOException("Copying of symbolic links not supported");
+ }
+
+ // delete target if it exists and REPLACE_EXISTING is specified
+ Boolean status=IoUtils.checkFileExists(target, linkOptions);
+ if (status == null) {
+ throw new AccessDeniedException("Existence cannot be determined for move target " + target);
+ }
+
+ if (replaceExisting) {
+ deleteIfExists(target);
+ } else if (status.booleanValue()) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+
+ try (SftpClient sftp = fsSrc.getClient()) {
+ sftp.rename(src.toString(), dst.toString());
+ }
+
+ // copy basic attributes to target
+ if (copyAttributes) {
+ BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
+ try {
+ view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
+ } catch (Throwable x) {
+ // rollback
+ try {
+ delete(target);
+ } catch (Throwable suppressed) {
+ x.addSuppressed(suppressed);
+ }
+ throw x;
+ }
+ }
+ }
+
+ @Override
+ public boolean isSameFile(Path path1, Path path2) throws IOException {
+ SftpPath p1 = toSftpPath(path1);
+ SftpPath p2 = toSftpPath(path2);
+ if (p1.getFileSystem() != p2.getFileSystem()) {
+ throw new ProviderMismatchException("Mismatched file system providers for " + p1 + " vs. " + p2);
+ }
+ checkAccess(p1);
+ checkAccess(p2);
+ return p1.equals(p2);
+ }
+
+ @Override
+ public boolean isHidden(Path path) throws IOException {
+ return false;
+ }
+
+ @Override
+ public FileStore getFileStore(Path path) throws IOException {
+ throw new FileSystemException(path.toString(), path.toString(), "getFileStore(" + path + ") N/A");
+ }
+
+ @Override
+ public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
+ SftpPath l = toSftpPath(link);
+ SftpFileSystem fsLink = l.getFileSystem();
+ SftpPath t = toSftpPath(target);
+ if (fsLink != t.getFileSystem()) {
+ throw new ProviderMismatchException("Mismatched file system providers for " + l + " vs. " + t);
+ }
+ try (SftpClient client = fsLink.getClient()) {
+ client.symLink(l.toString(), t.toString());
+ }
+ }
+
+ @Override
+ public Path readSymbolicLink(Path link) throws IOException {
+ SftpPath l = toSftpPath(link);
+ SftpFileSystem fsLink = l.getFileSystem();
+ try (SftpClient client = fsLink.getClient()) {
+ return fsLink.getPath(client.readLink(l.toString()));
+ }
+ }
+
+ @Override
+ public void checkAccess(Path path, AccessMode... modes) throws IOException {
+ SftpPath p = toSftpPath(path);
+ boolean w = false;
+ boolean x = false;
+ if (GenericUtils.length(modes) > 0) {
+ for (AccessMode mode : modes) {
+ switch (mode) {
+ case READ:
+ break;
+ case WRITE:
+ w = true;
+ break;
+ case EXECUTE:
+ x = true;
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported mode: " + mode);
+ }
+ }
+ }
+
+ BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
+ if ((attrs == null) && !(p.isAbsolute() && p.getNameCount() == 0)) {
+ throw new NoSuchFileException(path.toString());
+ }
+
+ SftpFileSystem fs = p.getFileSystem();
+ if (x || (w && fs.isReadOnly())) {
+ throw new AccessDeniedException(path.toString());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <V extends FileAttributeView> V getFileAttributeView(final Path path, Class<V> type, final LinkOption... options) {
+ if (type.isAssignableFrom(PosixFileAttributeView.class)) {
+ return (V) new PosixFileAttributeView() {
+ @Override
+ public String name() {
+ return "view";
+ }
+
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public PosixFileAttributes readAttributes() throws IOException {
+ SftpPath p = toSftpPath(path);
+ SftpFileSystem fs = p.getFileSystem();
+ final SftpClient.Attributes attributes;
+ try (SftpClient client =fs.getClient()) {
+ try {
+ if (followLinks(options)) {
+ attributes = client.stat(p.toString());
+ } else {
+ attributes = client.lstat(p.toString());
+ }
+ } catch (SftpException e) {
+ if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
+ throw new NoSuchFileException(p.toString());
+ }
+ throw e;
+ }
+ }
+ return new PosixFileAttributes() {
+ @Override
+ public UserPrincipal owner() {
+ return attributes.owner != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.owner) : null;
+ }
+
+ @Override
+ public GroupPrincipal group() {
+ return attributes.group != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.group) : null;
+ }
+
+ @Override
+ public Set<PosixFilePermission> permissions() {
+ return permissionsToAttributes(attributes.perms);
+ }
+
+ @Override
+ public FileTime lastModifiedTime() {
+ return FileTime.from(attributes.mtime, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ return FileTime.from(attributes.atime, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public FileTime creationTime() {
+ return FileTime.from(attributes.ctime, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ return attributes.isRegularFile();
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return attributes.isDirectory();
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ return attributes.isSymbolicLink();
+ }
+
+ @Override
+ public boolean isOther() {
+ return attributes.isOther();
+ }
+
+ @Override
+ public long size() {
+ return attributes.size;
+ }
+
+ @Override
+ public Object fileKey() {
+ // TODO
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
+ if (lastModifiedTime != null) {
+ setAttribute(path, "lastModifiedTime", lastModifiedTime, options);
+ }
+ if (lastAccessTime != null) {
+ setAttribute(path, "lastAccessTime", lastAccessTime, options);
+ }
+ if (createTime != null) {
+ setAttribute(path, "createTime", createTime, options);
+ }
+ }
+
+ @Override
+ public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
+ setAttribute(path, "permissions", perms, options);
+ }
+
+ @Override
+ public void setGroup(GroupPrincipal group) throws IOException {
+ setAttribute(path, "group", group, options);
+ }
+
+ @Override
+ public UserPrincipal getOwner() throws IOException {
+ return readAttributes().owner();
+ }
+
+ @Override
+ public void setOwner(UserPrincipal owner) throws IOException {
+ setAttribute(path, "owner", owner, options);
+ }
+ };
+ } else {
+ throw new UnsupportedOperationException("getFileAttributeView(" + path + ") view not supported: " + type.getSimpleName());
+ }
+ }
+
+ @Override
+ public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
+ if (type.isAssignableFrom(PosixFileAttributes.class)) {
+ return type.cast(getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes());
+ }
+
+ throw new UnsupportedOperationException("readAttributes(" + path + ")[" + type.getSimpleName() + "] N/A");
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+ String view;
+ String attrs;
+ int i = attributes.indexOf(':');
+ if (i == -1) {
+ view = "basic";
+ attrs = attributes;
+ } else {
+ view = attributes.substring(0, i++);
+ attrs = attributes.substring(i);
+ }
+ SftpPath p = toSftpPath(path);
+ SftpFileSystem fs = p.getFileSystem();
+ Collection<String> views = fs.supportedFileAttributeViews();
+ if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
+ throw new UnsupportedOperationException("readAttributes(" + path + ")[" + attributes + "] view " + view + " not supported: " + views);
+ }
+
+ PosixFileAttributes v = readAttributes(path, PosixFileAttributes.class, options);
+ if ("*".equals(attrs)) {
+ attrs = "lastModifiedTime,lastAccessTime,creationTime,size,isRegularFile,isDirectory,isSymbolicLink,isOther,fileKey,owner,permissions,group";
+ }
+ Map<String, Object> map = new HashMap<>();
+ for (String attr : attrs.split(",")) {
+ switch (attr) {
+ case "lastModifiedTime":
+ map.put(attr, v.lastModifiedTime());
+ break;
+ case "lastAccessTime":
+ map.put(attr, v.lastAccessTime());
+ break;
+ case "creationTime":
+ map.put(attr, v.creationTime());
+ break;
+ case "size":
+ map.put(attr, Long.valueOf(v.size()));
+ break;
+ case "isRegularFile":
+ map.put(attr, Boolean.valueOf(v.isRegularFile()));
+ break;
+ case "isDirectory":
+ map.put(attr, Boolean.valueOf(v.isDirectory()));
+ break;
+ case "isSymbolicLink":
+ map.put(attr, Boolean.valueOf(v.isSymbolicLink()));
+ break;
+ case "isOther":
+ map.put(attr, Boolean.valueOf(v.isOther()));
+ break;
+ case "fileKey":
+ map.put(attr, v.fileKey());
+ break;
+ case "owner":
+ map.put(attr, v.owner());
+ break;
+ case "permissions":
+ map.put(attr, v.permissions());
+ break;
+ case "group":
+ map.put(attr, v.group());
+ break;
+ default:
+ if (log.isTraceEnabled()) {
+ log.trace("readAttributes({})[{}] ignored {}={}", path, attributes, attr, v);
+ }
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+ String view;
+ String attr;
+ int i = attribute.indexOf(':');
+ if (i == -1) {
+ view = "basic";
+ attr = attribute;
+ } else {
+ view = attribute.substring(0, i++);
+ attr = attribute.substring(i);
+ }
+ SftpPath p = toSftpPath(path);
+ SftpFileSystem fs = p.getFileSystem();
+ Collection<String> views = fs.supportedFileAttributeViews();
+ if (GenericUtils.isEmpty(views) || (!view.contains(view))) {
+ throw new UnsupportedOperationException("setAttribute(" + path + ")[" + attribute + "=" + value + "] view " + view + " not supported: " + views);
+ }
+
+ SftpClient.Attributes attributes = new SftpClient.Attributes();
+ switch (attr) {
+ case "lastModifiedTime":
+ attributes.mtime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+ break;
+ case "lastAccessTime":
+ attributes.atime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+ break;
+ case "creationTime":
+ attributes.ctime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+ break;
+ case "size":
+ attributes.size(((Number) value).longValue());
+ break;
+ case "permissions": {
+ @SuppressWarnings("unchecked")
+ Set<PosixFilePermission> attrSet = (Set<PosixFilePermission>) value;
+ attributes.perms(attributesToPermissions(path, attrSet));
+ }
+ break;
+ case "owner":
+ attributes.owner(((UserPrincipal) value).getName());
+ break;
+ case "group":
+ attributes.group(((GroupPrincipal) value).getName());
+ break;
+ case "isRegularFile":
+ case "isDirectory":
+ case "isSymbolicLink":
+ case "isOther":
+ case "fileKey":
+ throw new UnsupportedOperationException("setAttribute(" + path + ")[" + attribute + "=" + value + "]"
+ + " unknown view=" + view + " attribute: " + attr);
+ default:
+ if (log.isTraceEnabled()) {
+ log.trace("setAttribute({})[{}] ignore {}={}", path, attribute, attr, value);
+ }
+ }
+
+ try (SftpClient client = fs.getClient()) {
+ client.setStat(p.toString(), attributes);
+ }
+ }
+
+ private SftpPath toSftpPath(Path path) {
+ ValidateUtils.checkNotNull(path, "No path provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+ if (!(path instanceof SftpPath)) {
+ throw new ProviderMismatchException("Path is not SFTP: " + path);
+ }
+ return (SftpPath) path;
+ }
+
+ static boolean followLinks(LinkOption... paramVarArgs) {
+ boolean bool = true;
+ for (LinkOption localLinkOption : paramVarArgs) {
+ if (localLinkOption == LinkOption.NOFOLLOW_LINKS) {
+ bool = false;
+ }
+ }
+ return bool;
+ }
+
+ private Set<PosixFilePermission> permissionsToAttributes(int perms) {
+ Set<PosixFilePermission> p = new HashSet<>();
+ if ((perms & S_IRUSR) != 0) {
+ p.add(PosixFilePermission.OWNER_READ);
+ }
+ if ((perms & S_IWUSR) != 0) {
+ p.add(PosixFilePermission.OWNER_WRITE);
+ }
+ if ((perms & S_IXUSR) != 0) {
+ p.add(PosixFilePermission.OWNER_EXECUTE);
+ }
+ if ((perms & S_IRGRP) != 0) {
+ p.add(PosixFilePermission.GROUP_READ);
+ }
+ if ((perms & S_IWGRP) != 0) {
+ p.add(PosixFilePermission.GROUP_WRITE);
+ }
+ if ((perms & S_IXGRP) != 0) {
+ p.add(PosixFilePermission.GROUP_EXECUTE);
+ }
+ if ((perms & S_IROTH) != 0) {
+ p.add(PosixFilePermission.OTHERS_READ);
+ }
+ if ((perms & S_IWOTH) != 0) {
+ p.add(PosixFilePermission.OTHERS_WRITE);
+ }
+ if ((perms & S_IXOTH) != 0) {
+ p.add(PosixFilePermission.OTHERS_EXECUTE);
+ }
+ return p;
+ }
+
+ protected int attributesToPermissions(Path path, Collection<PosixFilePermission> perms) {
+ if (GenericUtils.isEmpty(perms)) {
+ return 0;
+ }
+
+ int pf = 0;
+ for (PosixFilePermission p : perms) {
+ switch (p) {
+ case OWNER_READ:
+ pf |= S_IRUSR;
+ break;
+ case OWNER_WRITE:
+ pf |= S_IWUSR;
+ break;
+ case OWNER_EXECUTE:
+ pf |= S_IXUSR;
+ break;
+ case GROUP_READ:
+ pf |= S_IRGRP;
+ break;
+ case GROUP_WRITE:
+ pf |= S_IWGRP;
+ break;
+ case GROUP_EXECUTE:
+ pf |= S_IXGRP;
+ break;
+ case OTHERS_READ:
+ pf |= S_IROTH;
+ break;
+ case OTHERS_WRITE:
+ pf |= S_IWOTH;
+ break;
+ case OTHERS_EXECUTE:
+ pf |= S_IXOTH;
+ break;
+ default:
+ if (log.isTraceEnabled()) {
+ log.trace("attributesToPermissions(" + path + ") ignored " + p);
+ }
+ }
+ }
+
+ return pf;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
new file mode 100644
index 0000000..f130d1a
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
@@ -0,0 +1,46 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.spi.FileSystemProvider;
+
+import org.apache.sshd.common.file.util.BasePath;
+import org.apache.sshd.common.file.util.ImmutableList;
+
+public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
+ public SftpPath(SftpFileSystem fileSystem, String root, ImmutableList<String> names) {
+ super(fileSystem, root, names);
+ }
+
+ @Override
+ public SftpPath toRealPath(LinkOption... options) throws IOException {
+// try (SftpClient client = fileSystem.getClient()) {
+// client.realP
+// }
+ // TODO: handle links
+ SftpPath absolute = toAbsolutePath();
+ FileSystem fs = getFileSystem();
+ FileSystemProvider provider = fs.provider();
+ provider.checkAccess(absolute);
+ return absolute;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
index bad235f..ef67e34 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/root/RootedFileSystemProvider.java
@@ -33,7 +33,6 @@ import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
-import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
@@ -73,42 +72,52 @@ public class RootedFileSystemProvider extends FileSystemProvider {
@Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
Path path = uriToPath(uri);
- synchronized (fileSystems)
- {
- Path localPath2 = null;
- if (ensureDirectory(path))
- {
- localPath2 = path.toRealPath();
- if (this.fileSystems.containsKey(localPath2)) {
- throw new FileSystemAlreadyExistsException();
- }
+ Path localPath2 = ensureDirectory(path).toRealPath();
+
+ RootedFileSystem rootedFs=null;
+ synchronized (fileSystems) {
+ if (!this.fileSystems.containsKey(localPath2)) {
+ rootedFs = new RootedFileSystem(this, path, env);
+ this.fileSystems.put(localPath2, rootedFs);
}
- RootedFileSystem rootedFs = new RootedFileSystem(this, path, env);
- this.fileSystems.put(localPath2, rootedFs);
- return rootedFs;
}
+
+ // do all the throwing outside the synchronized block to minimize its lock time
+ if (rootedFs == null) {
+ throw new FileSystemAlreadyExistsException("newFileSystem(" + uri + ") already mapped " + localPath2);
+ }
+
+ return rootedFs;
}
@Override
public FileSystem getFileSystem(URI uri) {
+ Path path = uriToPath(uri);
+ Path real;
+ try {
+ real = path.toRealPath();
+ } catch (IOException e) {
+ FileSystemNotFoundException err = new FileSystemNotFoundException(uri.toString());
+ err.initCause(e);
+ throw err;
+ }
+
+ RootedFileSystem fileSystem = null;
synchronized (fileSystems) {
- RootedFileSystem fileSystem = null;
- try {
- fileSystem = fileSystems.get(uriToPath(uri).toRealPath());
- } catch (IOException ignore) {
- // ignored
- }
- if (fileSystem == null) {
- throw new FileSystemNotFoundException(uri.toString());
- }
- return fileSystem;
+ fileSystem = fileSystems.get(real);
}
+
+ // do all the throwing outside the synchronized block to minimize its lock time
+ if (fileSystem == null) {
+ throw new FileSystemNotFoundException(uri.toString());
+ }
+
+ return fileSystem;
}
@Override
public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException {
- ensureDirectory(path);
- return new RootedFileSystem(this, path, env);
+ return new RootedFileSystem(this, ensureDirectory(path), env);
}
protected Path uriToPath(URI uri) {
@@ -130,11 +139,8 @@ public class RootedFileSystemProvider extends FileSystemProvider {
}
}
- private boolean ensureDirectory(Path path) {
- if (!Files.isDirectory(path, IoUtils.getLinkOptions(false))) {
- throw new UnsupportedOperationException("Not a directory: " + path);
- }
- return true;
+ private static Path ensureDirectory(Path path) {
+ return IoUtils.ensureDirectory(path, IoUtils.getLinkOptions(false));
}
@Override
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/common/sftp/SftpConstants.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/sftp/SftpConstants.java b/sshd-core/src/main/java/org/apache/sshd/common/sftp/SftpConstants.java
deleted file mode 100644
index dd54bf3..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/sftp/SftpConstants.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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.common.sftp;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpConstants {
- public static String SFTP_SUBSYSTEM_NAME = "sftp";
-
- public static final int SSH_FXP_INIT = 1;
- public static final int SSH_FXP_VERSION = 2;
- public static final int SSH_FXP_OPEN = 3;
- public static final int SSH_FXP_CLOSE = 4;
- public static final int SSH_FXP_READ = 5;
- public static final int SSH_FXP_WRITE = 6;
- public static final int SSH_FXP_LSTAT = 7;
- public static final int SSH_FXP_FSTAT = 8;
- public static final int SSH_FXP_SETSTAT = 9;
- public static final int SSH_FXP_FSETSTAT = 10;
- public static final int SSH_FXP_OPENDIR = 11;
- public static final int SSH_FXP_READDIR = 12;
- public static final int SSH_FXP_REMOVE = 13;
- public static final int SSH_FXP_MKDIR = 14;
- public static final int SSH_FXP_RMDIR = 15;
- public static final int SSH_FXP_REALPATH = 16;
- public static final int SSH_FXP_STAT = 17;
- public static final int SSH_FXP_RENAME = 18;
- public static final int SSH_FXP_READLINK = 19;
- public static final int SSH_FXP_SYMLINK = 20; // v3 -> v5
- public static final int SSH_FXP_LINK = 21; // v6
- public static final int SSH_FXP_BLOCK = 22; // v6
- public static final int SSH_FXP_UNBLOCK = 23; // v6
- public static final int SSH_FXP_STATUS = 101;
- public static final int SSH_FXP_HANDLE = 102;
- public static final int SSH_FXP_DATA = 103;
- public static final int SSH_FXP_NAME = 104;
- public static final int SSH_FXP_ATTRS = 105;
- public static final int SSH_FXP_EXTENDED = 200;
- public static final int SSH_FXP_EXTENDED_REPLY = 201;
-
- public static final int SSH_FX_OK = 0;
- public static final int SSH_FX_EOF = 1;
- public static final int SSH_FX_NO_SUCH_FILE = 2;
- public static final int SSH_FX_PERMISSION_DENIED = 3;
- public static final int SSH_FX_FAILURE = 4;
- public static final int SSH_FX_BAD_MESSAGE = 5;
- public static final int SSH_FX_NO_CONNECTION = 6;
- public static final int SSH_FX_CONNECTION_LOST = 7;
- public static final int SSH_FX_OP_UNSUPPORTED = 8;
- public static final int SSH_FX_INVALID_HANDLE = 9;
- public static final int SSH_FX_NO_SUCH_PATH = 10;
- public static final int SSH_FX_FILE_ALREADY_EXISTS = 11;
- public static final int SSH_FX_WRITE_PROTECT = 12;
- public static final int SSH_FX_NO_MEDIA = 13;
- public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14;
- public static final int SSH_FX_QUOTA_EXCEEDED = 15;
- public static final int SSH_FX_UNKNOWN_PRINCIPLE = 16;
- public static final int SSH_FX_LOCK_CONFLICT = 17;
- public static final int SSH_FX_DIR_NOT_EMPTY = 18;
- public static final int SSH_FX_NOT_A_DIRECTORY = 19;
- public static final int SSH_FX_INVALID_FILENAME = 20;
- public static final int SSH_FX_LINK_LOOP = 21;
- public static final int SSH_FX_CANNOT_DELETE = 22;
- public static final int SSH_FX_INVALID_PARAMETER = 23;
- public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24;
- public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25;
- public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26;
- public static final int SSH_FX_DELETE_PENDING = 27;
- public static final int SSH_FX_FILE_CORRUPT = 28;
- public static final int SSH_FX_OWNER_INVALID = 29;
- public static final int SSH_FX_GROUP_INVALID = 30;
- public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31;
-
- public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001;
- public static final int SSH_FILEXFER_ATTR_UIDGID = 0x00000002;
- public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004;
- public static final int SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008; // v3 naming convention
- public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; // v4
- public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; // v4
- public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; // v4
- public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; // v4
- public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; // v4
- public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; // v5
- public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; // v5
- public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; // v6
- public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; // v6
- public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; // v6
- public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; // v6
- public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; // v6
- public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; // v6
- public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000;
-
- public static final int SSH_FILEXFER_ATTR_ALL = 0x0000FFFF; // All attributes
-
- public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001;
- public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002;
- public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004;
- public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008;
- public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010;
- public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020;
- public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040;
- public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080;
- public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100;
- public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200;
- public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400;
-
- public static final int SSH_FILEXFER_TYPE_REGULAR = 1;
- public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2;
- public static final int SSH_FILEXFER_TYPE_SYMLINK = 3;
- public static final int SSH_FILEXFER_TYPE_SPECIAL = 4;
- public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5;
- public static final int SSH_FILEXFER_TYPE_SOCKET = 6; // v5
- public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; // v5
- public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; // v5
- public static final int SSH_FILEXFER_TYPE_FIFO = 9; // v5
-
- public static final int SSH_FXF_READ = 0x00000001;
- public static final int SSH_FXF_WRITE = 0x00000002;
- public static final int SSH_FXF_APPEND = 0x00000004;
- public static final int SSH_FXF_CREAT = 0x00000008;
- public static final int SSH_FXF_TRUNC = 0x00000010;
- public static final int SSH_FXF_EXCL = 0x00000020;
- public static final int SSH_FXF_TEXT = 0x00000040;
-
- public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007;
- public static final int SSH_FXF_CREATE_NEW = 0x00000000;
- public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001;
- public static final int SSH_FXF_OPEN_EXISTING = 0x00000002;
- public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003;
- public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004;
- public static final int SSH_FXF_APPEND_DATA = 0x00000008;
- public static final int SSH_FXF_APPEND_DATA_ATOMIC = 0x00000010;
- public static final int SSH_FXF_TEXT_MODE = 0x00000020;
- public static final int SSH_FXF_READ_LOCK = 0x00000040;
- public static final int SSH_FXF_WRITE_LOCK = 0x00000080;
- public static final int SSH_FXF_DELETE_LOCK = 0x00000100;
-
- public static final int SSH_FXP_RENAME_OVERWRITE = 0x00000001;
- public static final int SSH_FXP_RENAME_ATOMIC = 0x00000002;
- public static final int SSH_FXP_RENAME_NATIVE = 0x00000004;
-
- public static final int SSH_FXP_REALPATH_NO_CHECK = 0x00000001;
- public static final int SSH_FXP_REALPATH_STAT_IF = 0x00000002;
- public static final int SSH_FXP_REALPATH_STAT_ALWAYS = 0x00000003;
-
- public static final int SSH_FXF_RENAME_OVERWRITE = 0x00000001;
- public static final int SSH_FXF_RENAME_ATOMIC = 0x00000002;
- public static final int SSH_FXF_RENAME_NATIVE = 0x00000004;
-
- public static final int ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000;
- public static final int ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001;
- public static final int ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002;
- public static final int ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003;
-
- public static final int ACE4_FILE_INHERIT_ACE = 0x00000001;
- public static final int ACE4_DIRECTORY_INHERIT_ACE = 0x00000002;
- public static final int ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004;
- public static final int ACE4_INHERIT_ONLY_ACE = 0x00000008;
- public static final int ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010;
- public static final int ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020;
- public static final int ACE4_IDENTIFIER_GROUP = 0x00000040;
-
- public static final int ACE4_READ_DATA = 0x00000001;
- public static final int ACE4_LIST_DIRECTORY = 0x00000001;
- public static final int ACE4_WRITE_DATA = 0x00000002;
- public static final int ACE4_ADD_FILE = 0x00000002;
- public static final int ACE4_APPEND_DATA = 0x00000004;
- public static final int ACE4_ADD_SUBDIRECTORY = 0x00000004;
- public static final int ACE4_READ_NAMED_ATTRS = 0x00000008;
- public static final int ACE4_WRITE_NAMED_ATTRS = 0x00000010;
- public static final int ACE4_EXECUTE = 0x00000020;
- public static final int ACE4_DELETE_CHILD = 0x00000040;
- public static final int ACE4_READ_ATTRIBUTES = 0x00000080;
- public static final int ACE4_WRITE_ATTRIBUTES = 0x00000100;
- public static final int ACE4_DELETE = 0x00010000;
- public static final int ACE4_READ_ACL = 0x00020000;
- public static final int ACE4_WRITE_ACL = 0x00040000;
- public static final int ACE4_WRITE_OWNER = 0x00080000;
- public static final int ACE4_SYNCHRONIZE = 0x00100000;
-
- public static final int S_IFMT = 0170000; // bitmask for the file type bitfields
- public static final int S_IFSOCK = 0140000; // socket
- public static final int S_IFLNK = 0120000; // symbolic link
- public static final int S_IFREG = 0100000; // regular file
- public static final int S_IFBLK = 0060000; // block device
- public static final int S_IFDIR = 0040000; // directory
- public static final int S_IFCHR = 0020000; // character device
- public static final int S_IFIFO = 0010000; // fifo
- public static final int S_ISUID = 0004000; // set UID bit
- public static final int S_ISGID = 0002000; // set GID bit
- public static final int S_ISVTX = 0001000; // sticky bit
- public static final int S_IRUSR = 0000400;
- public static final int S_IWUSR = 0000200;
- public static final int S_IXUSR = 0000100;
- public static final int S_IRGRP = 0000040;
- public static final int S_IWGRP = 0000020;
- public static final int S_IXGRP = 0000010;
- public static final int S_IROTH = 0000004;
- public static final int S_IWOTH = 0000002;
- public static final int S_IXOTH = 0000001;
-
- public static int SFTP_V3 = 3;
- public static int SFTP_V4 = 4;
- public static int SFTP_V5 = 5;
- public static int SFTP_V6 = 6;
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
new file mode 100644
index 0000000..f2f005d
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
@@ -0,0 +1,223 @@
+/*
+ * 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.common.subsystem.sftp;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpConstants {
+ public static String SFTP_SUBSYSTEM_NAME = "sftp";
+
+ public static final int SSH_FXP_INIT = 1;
+ public static final int SSH_FXP_VERSION = 2;
+ public static final int SSH_FXP_OPEN = 3;
+ public static final int SSH_FXP_CLOSE = 4;
+ public static final int SSH_FXP_READ = 5;
+ public static final int SSH_FXP_WRITE = 6;
+ public static final int SSH_FXP_LSTAT = 7;
+ public static final int SSH_FXP_FSTAT = 8;
+ public static final int SSH_FXP_SETSTAT = 9;
+ public static final int SSH_FXP_FSETSTAT = 10;
+ public static final int SSH_FXP_OPENDIR = 11;
+ public static final int SSH_FXP_READDIR = 12;
+ public static final int SSH_FXP_REMOVE = 13;
+ public static final int SSH_FXP_MKDIR = 14;
+ public static final int SSH_FXP_RMDIR = 15;
+ public static final int SSH_FXP_REALPATH = 16;
+ public static final int SSH_FXP_STAT = 17;
+ public static final int SSH_FXP_RENAME = 18;
+ public static final int SSH_FXP_READLINK = 19;
+ public static final int SSH_FXP_SYMLINK = 20; // v3 -> v5
+ public static final int SSH_FXP_LINK = 21; // v6
+ public static final int SSH_FXP_BLOCK = 22; // v6
+ public static final int SSH_FXP_UNBLOCK = 23; // v6
+ public static final int SSH_FXP_STATUS = 101;
+ public static final int SSH_FXP_HANDLE = 102;
+ public static final int SSH_FXP_DATA = 103;
+ public static final int SSH_FXP_NAME = 104;
+ public static final int SSH_FXP_ATTRS = 105;
+ public static final int SSH_FXP_EXTENDED = 200;
+ public static final int SSH_FXP_EXTENDED_REPLY = 201;
+
+ public static final int SSH_FX_OK = 0;
+ public static final int SSH_FX_EOF = 1;
+ public static final int SSH_FX_NO_SUCH_FILE = 2;
+ public static final int SSH_FX_PERMISSION_DENIED = 3;
+ public static final int SSH_FX_FAILURE = 4;
+ public static final int SSH_FX_BAD_MESSAGE = 5;
+ public static final int SSH_FX_NO_CONNECTION = 6;
+ public static final int SSH_FX_CONNECTION_LOST = 7;
+ public static final int SSH_FX_OP_UNSUPPORTED = 8;
+ public static final int SSH_FX_INVALID_HANDLE = 9;
+ public static final int SSH_FX_NO_SUCH_PATH = 10;
+ public static final int SSH_FX_FILE_ALREADY_EXISTS = 11;
+ public static final int SSH_FX_WRITE_PROTECT = 12;
+ public static final int SSH_FX_NO_MEDIA = 13;
+ public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM = 14;
+ public static final int SSH_FX_QUOTA_EXCEEDED = 15;
+ public static final int SSH_FX_UNKNOWN_PRINCIPLE = 16;
+ public static final int SSH_FX_LOCK_CONFLICT = 17;
+ public static final int SSH_FX_DIR_NOT_EMPTY = 18;
+ public static final int SSH_FX_NOT_A_DIRECTORY = 19;
+ public static final int SSH_FX_INVALID_FILENAME = 20;
+ public static final int SSH_FX_LINK_LOOP = 21;
+ public static final int SSH_FX_CANNOT_DELETE = 22;
+ public static final int SSH_FX_INVALID_PARAMETER = 23;
+ public static final int SSH_FX_FILE_IS_A_DIRECTORY = 24;
+ public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT = 25;
+ public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED = 26;
+ public static final int SSH_FX_DELETE_PENDING = 27;
+ public static final int SSH_FX_FILE_CORRUPT = 28;
+ public static final int SSH_FX_OWNER_INVALID = 29;
+ public static final int SSH_FX_GROUP_INVALID = 30;
+ public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31;
+
+ public static final int SSH_FILEXFER_ATTR_SIZE = 0x00000001;
+ public static final int SSH_FILEXFER_ATTR_UIDGID = 0x00000002;
+ public static final int SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004;
+ public static final int SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008; // v3 naming convention
+ public static final int SSH_FILEXFER_ATTR_ACCESSTIME = 0x00000008; // v4
+ public static final int SSH_FILEXFER_ATTR_CREATETIME = 0x00000010; // v4
+ public static final int SSH_FILEXFER_ATTR_MODIFYTIME = 0x00000020; // v4
+ public static final int SSH_FILEXFER_ATTR_ACL = 0x00000040; // v4
+ public static final int SSH_FILEXFER_ATTR_OWNERGROUP = 0x00000080; // v4
+ public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES = 0x00000100; // v5
+ public static final int SSH_FILEXFER_ATTR_BITS = 0x00000200; // v5
+ public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE = 0x00000400; // v6
+ public static final int SSH_FILEXFER_ATTR_TEXT_HINT = 0x00000800; // v6
+ public static final int SSH_FILEXFER_ATTR_MIME_TYPE = 0x00001000; // v6
+ public static final int SSH_FILEXFER_ATTR_LINK_COUNT = 0x00002000; // v6
+ public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000; // v6
+ public static final int SSH_FILEXFER_ATTR_CTIME = 0x00008000; // v6
+ public static final int SSH_FILEXFER_ATTR_EXTENDED = 0x80000000;
+
+ public static final int SSH_FILEXFER_ATTR_ALL = 0x0000FFFF; // All attributes
+
+ public static final int SSH_FILEXFER_ATTR_FLAGS_READONLY = 0x00000001;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_SYSTEM = 0x00000002;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_HIDDEN = 0x00000004;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE = 0x00000008;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_ARCHIVE = 0x00000010;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED = 0x00000020;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_COMPRESSED = 0x00000040;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_SPARSE = 0x00000080;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY = 0x00000100;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE = 0x00000200;
+ public static final int SSH_FILEXFER_ATTR_FLAGS_SYNC = 0x00000400;
+
+ public static final int SSH_FILEXFER_TYPE_REGULAR = 1;
+ public static final int SSH_FILEXFER_TYPE_DIRECTORY = 2;
+ public static final int SSH_FILEXFER_TYPE_SYMLINK = 3;
+ public static final int SSH_FILEXFER_TYPE_SPECIAL = 4;
+ public static final int SSH_FILEXFER_TYPE_UNKNOWN = 5;
+ public static final int SSH_FILEXFER_TYPE_SOCKET = 6; // v5
+ public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE = 7; // v5
+ public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE = 8; // v5
+ public static final int SSH_FILEXFER_TYPE_FIFO = 9; // v5
+
+ public static final int SSH_FXF_READ = 0x00000001;
+ public static final int SSH_FXF_WRITE = 0x00000002;
+ public static final int SSH_FXF_APPEND = 0x00000004;
+ public static final int SSH_FXF_CREAT = 0x00000008;
+ public static final int SSH_FXF_TRUNC = 0x00000010;
+ public static final int SSH_FXF_EXCL = 0x00000020;
+ public static final int SSH_FXF_TEXT = 0x00000040;
+
+ public static final int SSH_FXF_ACCESS_DISPOSITION = 0x00000007;
+ public static final int SSH_FXF_CREATE_NEW = 0x00000000;
+ public static final int SSH_FXF_CREATE_TRUNCATE = 0x00000001;
+ public static final int SSH_FXF_OPEN_EXISTING = 0x00000002;
+ public static final int SSH_FXF_OPEN_OR_CREATE = 0x00000003;
+ public static final int SSH_FXF_TRUNCATE_EXISTING = 0x00000004;
+ public static final int SSH_FXF_APPEND_DATA = 0x00000008;
+ public static final int SSH_FXF_APPEND_DATA_ATOMIC = 0x00000010;
+ public static final int SSH_FXF_TEXT_MODE = 0x00000020;
+ public static final int SSH_FXF_READ_LOCK = 0x00000040;
+ public static final int SSH_FXF_WRITE_LOCK = 0x00000080;
+ public static final int SSH_FXF_DELETE_LOCK = 0x00000100;
+
+ public static final int SSH_FXP_RENAME_OVERWRITE = 0x00000001;
+ public static final int SSH_FXP_RENAME_ATOMIC = 0x00000002;
+ public static final int SSH_FXP_RENAME_NATIVE = 0x00000004;
+
+ public static final int SSH_FXP_REALPATH_NO_CHECK = 0x00000001;
+ public static final int SSH_FXP_REALPATH_STAT_IF = 0x00000002;
+ public static final int SSH_FXP_REALPATH_STAT_ALWAYS = 0x00000003;
+
+ public static final int SSH_FXF_RENAME_OVERWRITE = 0x00000001;
+ public static final int SSH_FXF_RENAME_ATOMIC = 0x00000002;
+ public static final int SSH_FXF_RENAME_NATIVE = 0x00000004;
+
+ public static final int ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000;
+ public static final int ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001;
+ public static final int ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002;
+ public static final int ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003;
+
+ public static final int ACE4_FILE_INHERIT_ACE = 0x00000001;
+ public static final int ACE4_DIRECTORY_INHERIT_ACE = 0x00000002;
+ public static final int ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004;
+ public static final int ACE4_INHERIT_ONLY_ACE = 0x00000008;
+ public static final int ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010;
+ public static final int ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020;
+ public static final int ACE4_IDENTIFIER_GROUP = 0x00000040;
+
+ public static final int ACE4_READ_DATA = 0x00000001;
+ public static final int ACE4_LIST_DIRECTORY = 0x00000001;
+ public static final int ACE4_WRITE_DATA = 0x00000002;
+ public static final int ACE4_ADD_FILE = 0x00000002;
+ public static final int ACE4_APPEND_DATA = 0x00000004;
+ public static final int ACE4_ADD_SUBDIRECTORY = 0x00000004;
+ public static final int ACE4_READ_NAMED_ATTRS = 0x00000008;
+ public static final int ACE4_WRITE_NAMED_ATTRS = 0x00000010;
+ public static final int ACE4_EXECUTE = 0x00000020;
+ public static final int ACE4_DELETE_CHILD = 0x00000040;
+ public static final int ACE4_READ_ATTRIBUTES = 0x00000080;
+ public static final int ACE4_WRITE_ATTRIBUTES = 0x00000100;
+ public static final int ACE4_DELETE = 0x00010000;
+ public static final int ACE4_READ_ACL = 0x00020000;
+ public static final int ACE4_WRITE_ACL = 0x00040000;
+ public static final int ACE4_WRITE_OWNER = 0x00080000;
+ public static final int ACE4_SYNCHRONIZE = 0x00100000;
+
+ public static final int S_IFMT = 0170000; // bitmask for the file type bitfields
+ public static final int S_IFSOCK = 0140000; // socket
+ public static final int S_IFLNK = 0120000; // symbolic link
+ public static final int S_IFREG = 0100000; // regular file
+ public static final int S_IFBLK = 0060000; // block device
+ public static final int S_IFDIR = 0040000; // directory
+ public static final int S_IFCHR = 0020000; // character device
+ public static final int S_IFIFO = 0010000; // fifo
+ public static final int S_ISUID = 0004000; // set UID bit
+ public static final int S_ISGID = 0002000; // set GID bit
+ public static final int S_ISVTX = 0001000; // sticky bit
+ public static final int S_IRUSR = 0000400;
+ public static final int S_IWUSR = 0000200;
+ public static final int S_IXUSR = 0000100;
+ public static final int S_IRGRP = 0000040;
+ public static final int S_IWGRP = 0000020;
+ public static final int S_IXGRP = 0000010;
+ public static final int S_IROTH = 0000004;
+ public static final int S_IWOTH = 0000002;
+ public static final int S_IXOTH = 0000001;
+
+ public static int SFTP_V3 = 3;
+ public static int SFTP_V4 = 4;
+ public static int SFTP_V5 = 5;
+ public static int SFTP_V6 = 6;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
index 7afc506..74dd77f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
@@ -312,4 +312,17 @@ public class IoUtils {
return null;
}
+
+ /**
+ * @param path The {@link Path} to check
+ * @param options The {@link LinkOption}s to use when checking if path is a directory
+ * @return The same input path if it is a directory
+ * @throws UnsupportedOperationException if input path not a directory
+ */
+ public static Path ensureDirectory(Path path, LinkOption ... options) {
+ if (!Files.isDirectory(path, options)) {
+ throw new UnsupportedOperationException("Not a directory: " + path);
+ }
+ return path;
+ }
}
[09/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java
deleted file mode 100644
index 0d4f826..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileChannel.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * 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.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_LOCK_CONFLICT;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.AsynchronousCloseException;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sshd.client.SftpException;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-
-public class SftpFileChannel extends FileChannel {
-
- private final SftpPath p;
- private final Collection<SftpClient.OpenMode> modes;
- private final SftpClient sftp;
- private final SftpClient.CloseableHandle handle;
- private final Object lock = new Object();
- private volatile long pos;
- private volatile Thread blockingThread;
-
- public SftpFileChannel(SftpPath p, Collection<SftpClient.OpenMode> modes) throws IOException {
- this.p = ValidateUtils.checkNotNull(p, "No target path", GenericUtils.EMPTY_OBJECT_ARRAY);
- this.modes = ValidateUtils.checkNotNull(modes, "No channel modes specified", GenericUtils.EMPTY_OBJECT_ARRAY);
-
- SftpFileSystem fs=p.getFileSystem();
- sftp = fs.getClient();
- handle = sftp.open(p.toString(), modes);
- }
-
- @Override
- public int read(ByteBuffer dst) throws IOException {
- return (int) doRead(Collections.singletonList(dst), -1);
- }
-
- @Override
- public int read(ByteBuffer dst, long position) throws IOException {
- if (position < 0) {
- throw new IllegalArgumentException("read(" + p + ") illegal position to read from: " + position);
- }
- return (int) doRead(Collections.singletonList(dst), position);
- }
-
- @Override
- public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
- List<ByteBuffer> buffers = Arrays.asList(dsts).subList(offset, offset + length);
- return doRead(buffers, -1);
- }
-
- public static final Set<SftpClient.OpenMode> READ_MODES=
- Collections.unmodifiableSet(EnumSet.of(SftpClient.OpenMode.Read));
-
- protected long doRead(List<ByteBuffer> buffers, long position) throws IOException {
- ensureOpen(READ_MODES);
- synchronized (lock) {
- boolean completed = false;
- boolean eof = false;
- long curPos = position >= 0 ? position : pos;
- try {
- long totalRead = 0;
- beginBlocking();
- loop:
- for (ByteBuffer buffer : buffers) {
- while (buffer.remaining() > 0) {
- ByteBuffer wrap = buffer;
- if (!buffer.hasArray()) {
- wrap = ByteBuffer.allocate(Math.min(8192, buffer.remaining()));
- }
- int read = sftp.read(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), wrap.remaining());
- if (read > 0) {
- if (wrap == buffer) {
- wrap.position(wrap.position() + read);
- } else {
- buffer.put(wrap.array(), wrap.arrayOffset(), read);
- }
- curPos += read;
- totalRead += read;
- } else {
- eof = read == -1;
- break loop;
- }
- }
- }
- completed = true;
- return totalRead > 0 ? totalRead : eof ? -1 : 0;
- } finally {
- if (position < 0) {
- pos = curPos;
- }
- endBlocking(completed);
- }
- }
- }
-
- @Override
- public int write(ByteBuffer src) throws IOException {
- return (int) doWrite(Collections.singletonList(src), -1);
- }
-
- @Override
- public int write(ByteBuffer src, long position) throws IOException {
- if (position < 0) {
- throw new IllegalArgumentException("write(" + p + ") illegal position to write to: " + position);
- }
- return (int) doWrite(Collections.singletonList(src), position);
- }
-
- @Override
- public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
- List<ByteBuffer> buffers = Arrays.asList(srcs).subList(offset, offset + length);
- return doWrite(buffers, -1);
- }
-
- public static final Set<SftpClient.OpenMode> WRITE_MODES=
- Collections.unmodifiableSet(
- EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Append, SftpClient.OpenMode.Create, SftpClient.OpenMode.Truncate));
-
- protected long doWrite(List<ByteBuffer> buffers, long position) throws IOException {
- ensureOpen(WRITE_MODES);
- synchronized (lock) {
- boolean completed = false;
- long curPos = position >= 0 ? position : pos;
- try {
- long totalWritten = 0;
- beginBlocking();
- for (ByteBuffer buffer : buffers) {
- while (buffer.remaining() > 0) {
- ByteBuffer wrap = buffer;
- if (!buffer.hasArray()) {
- wrap = ByteBuffer.allocate(Math.min(8192, buffer.remaining()));
- buffer.get(wrap.array(), wrap.arrayOffset(), wrap.remaining());
- }
- int written = wrap.remaining();
- sftp.write(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), written);
- if (wrap == buffer) {
- wrap.position(wrap.position() + written);
- }
- curPos += written;
- totalWritten += written;
- }
- }
- completed = true;
- return totalWritten;
- } finally {
- if (position < 0) {
- pos = curPos;
- }
- endBlocking(completed);
- }
- }
- }
-
- @Override
- public long position() throws IOException {
- ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
- return pos;
- }
-
- @Override
- public FileChannel position(long newPosition) throws IOException {
- if (newPosition < 0) {
- throw new IllegalArgumentException("position(" + p + ") illegal file channel position: " + newPosition);
- }
-
- ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
- synchronized (lock) {
- pos = newPosition;
- return this;
- }
- }
-
- @Override
- public long size() throws IOException {
- ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
- return sftp.stat(handle).size;
- }
-
- @Override
- public FileChannel truncate(long size) throws IOException {
- ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
- sftp.setStat(handle, new SftpClient.Attributes().size(size));
- return this;
- }
-
- @Override
- public void force(boolean metaData) throws IOException {
- ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
- }
-
- @Override
- public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
- if ((position < 0) || (count < 0)) {
- throw new IllegalArgumentException("transferTo(" + p + ") illegal position (" + position + ") or count (" + count + ")");
- }
- ensureOpen(READ_MODES);
- synchronized (lock) {
- boolean completed = false;
- boolean eof = false;
- long curPos = position;
- try {
- beginBlocking();
-
- int bufSize = (int) Math.min(count, 32768);
- byte[] buffer = new byte[bufSize];
- long totalRead = 0L;
- while (totalRead < count) {
- int read = sftp.read(handle, curPos, buffer, 0, buffer.length);
- if (read > 0) {
- ByteBuffer wrap = ByteBuffer.wrap(buffer);
- while (wrap.remaining() > 0) {
- target.write(wrap);
- }
- curPos += read;
- totalRead += read;
- } else {
- eof = read == -1;
- }
- }
- completed = true;
- return totalRead > 0 ? totalRead : eof ? -1 : 0;
- } finally {
- endBlocking(completed);
- }
- }
- }
-
- @Override
- public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
- if ((position < 0) || (count < 0)) {
- throw new IllegalArgumentException("transferFrom(" + p + ") illegal position (" + position + ") or count (" + count + ")");
- }
- ensureOpen(WRITE_MODES);
-
- synchronized(lock) {
- boolean completed = false;
- long curPos = position >= 0 ? position : pos;
- try {
- long totalRead = 0;
- beginBlocking();
-
- byte[] buffer = new byte[32768];
- while (totalRead < count) {
- ByteBuffer wrap = ByteBuffer.wrap(buffer, 0, (int) Math.min(buffer.length, count - totalRead));
- int read = src.read(wrap);
- if (read > 0) {
- sftp.write(handle, curPos, buffer, 0, read);
- curPos += read;
- totalRead += read;
- } else {
- break;
- }
- }
- completed = true;
- return totalRead;
- } finally {
- endBlocking(completed);
- }
- }
- }
-
- @Override
- public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
- throw new UnsupportedOperationException("map(" + p + ")[" + mode + "," + position + "," + size + "] N/A");
- }
-
- @Override
- public FileLock lock(long position, long size, boolean shared) throws IOException {
- return tryLock(position, size, shared);
- }
-
- @Override
- public FileLock tryLock(final long position, final long size, boolean shared) throws IOException {
- ensureOpen(Collections.<SftpClient.OpenMode>emptySet());
-
- try {
- sftp.lock(handle, position, size, 0);
- } catch (SftpException e) {
- if (e.getStatus() == SSH_FX_LOCK_CONFLICT) {
- throw new OverlappingFileLockException();
- }
- throw e;
- }
-
- return new FileLock(this, position, size, shared) {
- private final AtomicBoolean valid = new AtomicBoolean(true);
-
- @Override
- public boolean isValid() {
- return acquiredBy().isOpen() && valid.get();
- }
-
- @SuppressWarnings("synthetic-access")
- @Override
- public void release() throws IOException {
- if (valid.compareAndSet(true, false)) {
- sftp.unlock(handle, position, size);
- }
- }
- };
- }
-
- @Override
- protected void implCloseChannel() throws IOException {
- try {
- final Thread thread = blockingThread;
- if (thread != null) {
- thread.interrupt();
- }
- } finally {
- try {
- handle.close();
- } finally {
- sftp.close();
- }
- }
- }
-
- private void beginBlocking() {
- begin();
- blockingThread = Thread.currentThread();
- }
-
- private void endBlocking(boolean completed) throws AsynchronousCloseException {
- blockingThread = null;
- end(completed);
- }
-
- /**
- * Checks that the channel is open and that its current mode contains
- * at least one of the required ones
- * @param reqModes The required modes - ignored if {@code null}/empty
- * @throws IOException If channel not open or the required modes are not
- * satisfied
- */
- private void ensureOpen(Collection<SftpClient.OpenMode> reqModes) throws IOException {
- if (!isOpen()) {
- throw new ClosedChannelException();
- }
-
- if (GenericUtils.size(reqModes) > 0) {
- for (SftpClient.OpenMode m : reqModes) {
- if (this.modes.contains(m)) {
- return;
- }
- }
-
- throw new IOException("ensureOpen(" + p + ") current channel modes (" + this.modes + ") do contain any of the required: " + reqModes);
- }
- }
-
- @Override
- public String toString() {
- return Objects.toString(p);
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
deleted file mode 100644
index 026d13c..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystem.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * 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.client.sftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.file.util.BaseFileSystem;
-import org.apache.sshd.common.file.util.ImmutableList;
-
-public class SftpFileSystem extends BaseFileSystem<SftpPath> {
-
- private final ClientSession session;
- private final Queue<SftpClient> pool;
- private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
- private SftpPath defaultDir;
- private int readBufferSize = SftpClient.DEFAULT_READ_BUFFER_SIZE;
- private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
-
- public SftpFileSystem(SftpFileSystemProvider provider, ClientSession session) throws IOException {
- super(provider);
- this.session = session;
- this.pool = new LinkedBlockingQueue<>(8);
- try (SftpClient client = getClient()) {
- defaultDir = getPath(client.canonicalPath("."));
- }
- }
-
- public int getReadBufferSize() {
- return readBufferSize;
- }
-
- public void setReadBufferSize(int size) {
- if (size < SftpClient.MIN_READ_BUFFER_SIZE) {
- throw new IllegalArgumentException("Insufficient read buffer size: " + size + ", min.=" + SftpClient.MIN_READ_BUFFER_SIZE);
- }
-
- readBufferSize = size;
- }
-
- public int getWriteBufferSize() {
- return writeBufferSize;
- }
-
- public void setWriteBufferSize(int size) {
- if (size < SftpClient.MIN_WRITE_BUFFER_SIZE) {
- throw new IllegalArgumentException("Insufficient write buffer size: " + size + ", min.=" + SftpClient.MIN_WRITE_BUFFER_SIZE);
- }
-
- writeBufferSize = size;
- }
-
- @Override
- protected SftpPath create(String root, ImmutableList<String> names) {
- return new SftpPath(this, root, names);
- }
-
- public ClientSession getSession() {
- return session;
- }
-
- @SuppressWarnings("synthetic-access")
- public SftpClient getClient() throws IOException {
- Wrapper wrapper = wrappers.get();
- if (wrapper == null) {
- while (wrapper == null) {
- SftpClient client = pool.poll();
- if (client == null) {
- client = session.createSftpClient();
- }
- if (!client.isClosing()) {
- wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
- }
- }
- wrappers.set(wrapper);
- } else {
- wrapper.increment();
- }
- return wrapper;
- }
-
- @Override
- public void close() throws IOException {
- if (isOpen()) {
- session.close(true);
- }
- }
-
- @Override
- public boolean isOpen() {
- return !session.isClosing();
- }
-
- @Override
- public Set<String> supportedFileAttributeViews() {
- Set<String> set = new HashSet<>();
- set.addAll(Arrays.asList("basic", "posix", "owner"));
- return Collections.unmodifiableSet(set);
- }
-
- @Override
- public UserPrincipalLookupService getUserPrincipalLookupService() {
- return new DefaultUserPrincipalLookupService();
- }
-
- @Override
- public SftpPath getDefaultDir() {
- return defaultDir;
- }
-
- private class Wrapper extends AbstractSftpClient {
-
- private final SftpClient delegate;
- private final AtomicInteger count = new AtomicInteger(1);
- private final int readSize, writeSize;
-
- private Wrapper(SftpClient delegate, int readSize, int writeSize) {
- this.delegate = delegate;
- this.readSize = readSize;
- this.writeSize = writeSize;
- }
-
- @Override
- public int getVersion() {
- return delegate.getVersion();
- }
-
- @Override
- public boolean isClosing() {
- return false;
- }
-
- @SuppressWarnings("synthetic-access")
- @Override
- public void close() throws IOException {
- if (count.decrementAndGet() == 0) {
- if (!pool.offer(delegate)) {
- delegate.close();
- }
- wrappers.set(null);
- }
- }
-
- public void increment() {
- count.incrementAndGet();
- }
-
- @Override
- public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
- return delegate.open(path, options);
- }
-
- @Override
- public void close(Handle handle) throws IOException {
- delegate.close(handle);
- }
-
- @Override
- public void remove(String path) throws IOException {
- delegate.remove(path);
- }
-
- @Override
- public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
- delegate.rename(oldPath, newPath, options);
- }
-
- @Override
- public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
- return delegate.read(handle, fileOffset, dst, dstoff, len);
- }
-
- @Override
- public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
- delegate.write(handle, fileOffset, src, srcoff, len);
- }
-
- @Override
- public void mkdir(String path) throws IOException {
- delegate.mkdir(path);
- }
-
- @Override
- public void rmdir(String path) throws IOException {
- delegate.rmdir(path);
- }
-
- @Override
- public CloseableHandle openDir(String path) throws IOException {
- return delegate.openDir(path);
- }
-
- @Override
- public DirEntry[] readDir(Handle handle) throws IOException {
- return delegate.readDir(handle);
- }
-
- @Override
- public String canonicalPath(String canonical) throws IOException {
- return delegate.canonicalPath(canonical);
- }
-
- @Override
- public Attributes stat(String path) throws IOException {
- return delegate.stat(path);
- }
-
- @Override
- public Attributes lstat(String path) throws IOException {
- return delegate.lstat(path);
- }
-
- @Override
- public Attributes stat(Handle handle) throws IOException {
- return delegate.stat(handle);
- }
-
- @Override
- public void setStat(String path, Attributes attributes) throws IOException {
- delegate.setStat(path, attributes);
- }
-
- @Override
- public void setStat(Handle handle, Attributes attributes) throws IOException {
- delegate.setStat(handle, attributes);
- }
-
- @Override
- public String readLink(String path) throws IOException {
- return delegate.readLink(path);
- }
-
- @Override
- public void symLink(String linkPath, String targetPath) throws IOException {
- delegate.symLink(linkPath, targetPath);
- }
-
- @Override
- public Iterable<DirEntry> readDir(String path) throws IOException {
- return delegate.readDir(path);
- }
-
- @Override
- public InputStream read(String path) throws IOException {
- return read(path, readSize);
- }
-
- @Override
- public InputStream read(String path, OpenMode... mode) throws IOException {
- return read(path, readSize, mode);
- }
-
- @Override
- public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
- return read(path, readSize, mode);
- }
-
- @Override
- public InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
- return delegate.read(path, bufferSize, mode);
- }
-
- @Override
- public OutputStream write(String path) throws IOException {
- return write(path, writeSize);
- }
-
- @Override
- public OutputStream write(String path, OpenMode... mode) throws IOException {
- return write(path, writeSize, mode);
- }
-
- @Override
- public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
- return write(path, writeSize, mode);
- }
-
- @Override
- public OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
- return delegate.write(path, bufferSize, mode);
- }
-
- @Override
- public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
- delegate.link(linkPath, targetPath, symbolic);
- }
-
- @Override
- public void lock(Handle handle, long offset, long length, int mask) throws IOException {
- delegate.lock(handle, offset, length, mask);
- }
-
- @Override
- public void unlock(Handle handle, long offset, long length) throws IOException {
- delegate.unlock(handle, offset, length);
- }
- }
-
- protected static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
-
- @Override
- public UserPrincipal lookupPrincipalByName(String name) throws IOException {
- return new DefaultUserPrincipal(name);
- }
-
- @Override
- public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
- return new DefaultGroupPrincipal(group);
- }
- }
-
- protected static class DefaultUserPrincipal implements UserPrincipal {
-
- private final String name;
-
- public DefaultUserPrincipal(String name) {
- if (name == null) {
- throw new IllegalArgumentException("name is null");
- }
- this.name = name;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- DefaultUserPrincipal that = (DefaultUserPrincipal) o;
- return name.equals(that.name);
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public String toString() {
- return name;
- }
- }
-
- protected static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {
-
- public DefaultGroupPrincipal(String name) {
- super(name);
- }
-
- }
-
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
deleted file mode 100644
index 2ac72b1..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpFileSystemProvider.java
+++ /dev/null
@@ -1,892 +0,0 @@
-/*
- * 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.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V3;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IRGRP;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IROTH;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IRUSR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWGRP;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWOTH;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWUSR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IXGRP;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IXOTH;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IXUSR;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.nio.channels.FileChannel;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.AccessMode;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.FileSystemException;
-import java.nio.file.FileSystemNotFoundException;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.ProviderMismatchException;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.client.ClientBuilder;
-import org.apache.sshd.client.SftpException;
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.sftp.SftpClient.Attributes;
-import org.apache.sshd.common.FactoryManagerUtils;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.config.SshConfigFileReader;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class SftpFileSystemProvider extends FileSystemProvider {
- public static final String READ_BUFFER_PROP_NAME = "sftp-fs-read-buffer-size";
- public static final int DEFAULT_READ_BUFFER_SIZE = SftpClient.DEFAULT_READ_BUFFER_SIZE;
- public static final String WRITE_BUFFER_PROP_NAME = "sftp-fs-write-buffer-size";
- public static final int DEFAULT_WRITE_BUFFER_SIZE = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
- public static final String CONNECT_TIME_PROP_NAME = "sftp-fs-connect-time";
- public static final long DEFAULT_CONNECT_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
-
- private final SshClient client;
- private final Map<String, SftpFileSystem> fileSystems = new HashMap<String, SftpFileSystem>();
- protected final Logger log;
-
- public SftpFileSystemProvider() {
- this(null);
- }
-
- public SftpFileSystemProvider(SshClient client) {
- this.log = LoggerFactory.getLogger(getClass());
- if (client == null) {
- // TODO: make this configurable using system properties
- client = ClientBuilder.builder().build();
- }
- this.client = client;
- this.client.start();
- }
-
- @Override
- public String getScheme() {
- return SftpConstants.SFTP_SUBSYSTEM_NAME;
- }
-
- @Override
- public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
- synchronized (fileSystems) {
- String authority = uri.getAuthority();
- SftpFileSystem fileSystem = fileSystems.get(authority);
- if (fileSystem != null) {
- throw new FileSystemAlreadyExistsException(authority);
- }
- String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
- String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
- String[] ui = GenericUtils.split(userInfo, ':');
- int port = uri.getPort();
- if (port <= 0) {
- port = SshConfigFileReader.DEFAULT_PORT;
- }
-
- ClientSession session=null;
- try {
- session = client.connect(ui[0], host, port)
- .verify(FactoryManagerUtils.getLongProperty(env, CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME))
- .getSession()
- ;
- session.addPasswordIdentity(ui[1]);
- session.auth().verify();
- fileSystem = new SftpFileSystem(this, session);
- fileSystem.setReadBufferSize(FactoryManagerUtils.getIntProperty(env, READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
- fileSystem.setWriteBufferSize(FactoryManagerUtils.getIntProperty(env, WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
- fileSystems.put(authority, fileSystem);
- return fileSystem;
- } catch(Exception e) {
- if (session != null) {
- try {
- session.close();
- } catch(IOException t) {
- if (log.isDebugEnabled()) {
- log.debug("Failed (" + t.getClass().getSimpleName() + ")"
- + " to close session for new file system on " + host + ":" + port
- + " due to " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]"
- + ": " + t.getMessage());
- }
- }
- }
-
- if (e instanceof IOException) {
- throw (IOException) e;
- } else if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- } else {
- throw new IOException(e);
- }
- }
- }
- }
-
- @Override
- public FileSystem getFileSystem(URI uri) {
- synchronized (fileSystems) {
- String authority = uri.getAuthority();
- SftpFileSystem fileSystem = fileSystems.get(authority);
- if (fileSystem == null) {
- throw new FileSystemNotFoundException(authority);
- }
- return fileSystem;
- }
- }
-
- @Override
- public Path getPath(URI uri) {
- FileSystem fs = getFileSystem(uri);
- return fs.getPath(uri.getPath());
- }
-
- @Override
- public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
- return newFileChannel(path, options, attrs);
- }
-
- @Override
- public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
- Collection<SftpClient.OpenMode> modes = EnumSet.noneOf(SftpClient.OpenMode.class);
- for (OpenOption option : options) {
- if (option == StandardOpenOption.READ) {
- modes.add(SftpClient.OpenMode.Read);
- } else if (option == StandardOpenOption.APPEND) {
- modes.add(SftpClient.OpenMode.Append);
- } else if (option == StandardOpenOption.CREATE) {
- modes.add(SftpClient.OpenMode.Create);
- } else if (option == StandardOpenOption.TRUNCATE_EXISTING) {
- modes.add(SftpClient.OpenMode.Truncate);
- } else if (option == StandardOpenOption.WRITE) {
- modes.add(SftpClient.OpenMode.Write);
- } else if (option == StandardOpenOption.CREATE_NEW) {
- modes.add(SftpClient.OpenMode.Create);
- modes.add(SftpClient.OpenMode.Exclusive);
- } else if (option == StandardOpenOption.SPARSE) {
- /*
- * As per the Javadoc:
- *
- * The option is ignored when the file system does not
- * support the creation of sparse files
- */
- continue;
- } else {
- throw new IllegalArgumentException("newFileChannel(" + path + ") unsupported open option: " + option);
- }
- }
- if (modes.isEmpty()) {
- modes.add(SftpClient.OpenMode.Read);
- modes.add(SftpClient.OpenMode.Write);
- }
- // TODO: attrs
- return new SftpFileChannel(toSftpPath(path), modes);
- }
-
- @Override
- public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
- final SftpPath p = toSftpPath(dir);
- return new DirectoryStream<Path>() {
- private final SftpFileSystem fs = p.getFileSystem();
- private final SftpClient sftp = fs.getClient();
- private final Iterable<SftpClient.DirEntry> iter = sftp.readDir(p.toString());
-
- @Override
- public Iterator<Path> iterator() {
- return new Iterator<Path>() {
- @SuppressWarnings("synthetic-access")
- private final Iterator<SftpClient.DirEntry> it = iter.iterator();
-
- @Override
- public boolean hasNext() {
- return it.hasNext();
- }
-
- @Override
- public Path next() {
- SftpClient.DirEntry entry = it.next();
- return p.resolve(entry.filename);
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
- }
- };
- }
-
- @Override
- public void close() throws IOException {
- sftp.close();
- }
- };
- }
-
- @Override
- public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
- SftpPath p = toSftpPath(dir);
- SftpFileSystem fs = p.getFileSystem();
- try (SftpClient sftp = fs.getClient()) {
- try {
- sftp.mkdir(dir.toString());
- } catch (SftpException e) {
- int sftpStatus=e.getStatus();
- if ((sftp.getVersion() == SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
- try {
- Attributes attributes = sftp.stat(dir.toString());
- if (attributes != null) {
- throw new FileAlreadyExistsException(p.toString());
- }
- } catch (SshException e2) {
- e.addSuppressed(e2);
- }
- }
- if (sftpStatus == SftpConstants.SSH_FX_FILE_ALREADY_EXISTS) {
- throw new FileAlreadyExistsException(p.toString());
- }
- throw e;
- }
- for (FileAttribute<?> attr : attrs) {
- setAttribute(p, attr.name(), attr.value());
- }
- }
- }
-
- @Override
- public void delete(Path path) throws IOException {
- SftpPath p = toSftpPath(path);
- checkAccess(p, AccessMode.WRITE);
-
- SftpFileSystem fs = p.getFileSystem();
- try (SftpClient sftp = fs.getClient()) {
- BasicFileAttributes attributes = readAttributes(path, BasicFileAttributes.class);
- if (attributes.isDirectory()) {
- sftp.rmdir(path.toString());
- } else {
- sftp.remove(path.toString());
- }
- }
- }
-
- @Override
- public void copy(Path source, Path target, CopyOption... options) throws IOException {
- SftpPath src = toSftpPath(source);
- SftpPath dst = toSftpPath(target);
- if (src.getFileSystem() != dst.getFileSystem()) {
- throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
- }
- checkAccess(src);
-
- boolean replaceExisting = false;
- boolean copyAttributes = false;
- boolean noFollowLinks = false;
- for (CopyOption opt : options) {
- replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
- copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
- noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
- }
- LinkOption[] linkOptions = IoUtils.getLinkOptions(!noFollowLinks);
-
- // attributes of source file
- BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
- if (attrs.isSymbolicLink())
- throw new IOException("Copying of symbolic links not supported");
-
- // delete target if it exists and REPLACE_EXISTING is specified
- Boolean status=IoUtils.checkFileExists(target, linkOptions);
- if (status == null) {
- throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
- }
-
- if (replaceExisting) {
- deleteIfExists(target);
- } else {
- if (status.booleanValue()) {
- throw new FileAlreadyExistsException(target.toString());
- }
- }
-
- // create directory or copy file
- if (attrs.isDirectory()) {
- createDirectory(target);
- } else {
- try (InputStream in = newInputStream(source);
- OutputStream os = newOutputStream(target)) {
- IoUtils.copy(in, os);
- }
- }
-
- // copy basic attributes to target
- if (copyAttributes) {
- BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
- try {
- view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
- } catch (Throwable x) {
- // rollback
- try {
- delete(target);
- } catch (Throwable suppressed) {
- x.addSuppressed(suppressed);
- }
- throw x;
- }
- }
- }
-
- @Override
- public void move(Path source, Path target, CopyOption... options) throws IOException {
- SftpPath src = toSftpPath(source);
- SftpFileSystem fsSrc = src.getFileSystem();
- SftpPath dst = toSftpPath(target);
-
- if (src.getFileSystem() != dst.getFileSystem()) {
- throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
- }
- checkAccess(src);
-
- boolean replaceExisting = false;
- boolean copyAttributes = false;
- boolean noFollowLinks = false;
- for (CopyOption opt : options) {
- replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
- copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
- noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
- }
- LinkOption[] linkOptions = IoUtils.getLinkOptions(noFollowLinks);
-
- // attributes of source file
- BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
- if (attrs.isSymbolicLink()) {
- throw new IOException("Copying of symbolic links not supported");
- }
-
- // delete target if it exists and REPLACE_EXISTING is specified
- Boolean status=IoUtils.checkFileExists(target, linkOptions);
- if (status == null) {
- throw new AccessDeniedException("Existence cannot be determined for move target " + target);
- }
-
- if (replaceExisting) {
- deleteIfExists(target);
- } else if (status.booleanValue()) {
- throw new FileAlreadyExistsException(target.toString());
- }
-
- try (SftpClient sftp = fsSrc.getClient()) {
- sftp.rename(src.toString(), dst.toString());
- }
-
- // copy basic attributes to target
- if (copyAttributes) {
- BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
- try {
- view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
- } catch (Throwable x) {
- // rollback
- try {
- delete(target);
- } catch (Throwable suppressed) {
- x.addSuppressed(suppressed);
- }
- throw x;
- }
- }
- }
-
- @Override
- public boolean isSameFile(Path path1, Path path2) throws IOException {
- SftpPath p1 = toSftpPath(path1);
- SftpPath p2 = toSftpPath(path2);
- if (p1.getFileSystem() != p2.getFileSystem()) {
- throw new ProviderMismatchException("Mismatched file system providers for " + p1 + " vs. " + p2);
- }
- checkAccess(p1);
- checkAccess(p2);
- return p1.equals(p2);
- }
-
- @Override
- public boolean isHidden(Path path) throws IOException {
- return false;
- }
-
- @Override
- public FileStore getFileStore(Path path) throws IOException {
- throw new FileSystemException(path.toString(), path.toString(), "getFileStore(" + path + ") N/A");
- }
-
- @Override
- public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
- SftpPath l = toSftpPath(link);
- SftpFileSystem fsLink = l.getFileSystem();
- SftpPath t = toSftpPath(target);
- if (fsLink != t.getFileSystem()) {
- throw new ProviderMismatchException("Mismatched file system providers for " + l + " vs. " + t);
- }
- try (SftpClient client = fsLink.getClient()) {
- client.symLink(l.toString(), t.toString());
- }
- }
-
- @Override
- public Path readSymbolicLink(Path link) throws IOException {
- SftpPath l = toSftpPath(link);
- SftpFileSystem fsLink = l.getFileSystem();
- try (SftpClient client = fsLink.getClient()) {
- return fsLink.getPath(client.readLink(l.toString()));
- }
- }
-
- @Override
- public void checkAccess(Path path, AccessMode... modes) throws IOException {
- SftpPath p = toSftpPath(path);
- boolean w = false;
- boolean x = false;
- if (GenericUtils.length(modes) > 0) {
- for (AccessMode mode : modes) {
- switch (mode) {
- case READ:
- break;
- case WRITE:
- w = true;
- break;
- case EXECUTE:
- x = true;
- break;
- default:
- throw new UnsupportedOperationException("Unsupported mode: " + mode);
- }
- }
- }
-
- BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
- if ((attrs == null) && !(p.isAbsolute() && p.getNameCount() == 0)) {
- throw new NoSuchFileException(path.toString());
- }
-
- SftpFileSystem fs = p.getFileSystem();
- if (x || (w && fs.isReadOnly())) {
- throw new AccessDeniedException(path.toString());
- }
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public <V extends FileAttributeView> V getFileAttributeView(final Path path, Class<V> type, final LinkOption... options) {
- if (type.isAssignableFrom(PosixFileAttributeView.class)) {
- return (V) new PosixFileAttributeView() {
- @Override
- public String name() {
- return "view";
- }
-
- @SuppressWarnings("synthetic-access")
- @Override
- public PosixFileAttributes readAttributes() throws IOException {
- SftpPath p = toSftpPath(path);
- SftpFileSystem fs = p.getFileSystem();
- final SftpClient.Attributes attributes;
- try (SftpClient client =fs.getClient()) {
- try {
- if (followLinks(options)) {
- attributes = client.stat(p.toString());
- } else {
- attributes = client.lstat(p.toString());
- }
- } catch (SftpException e) {
- if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
- throw new NoSuchFileException(p.toString());
- }
- throw e;
- }
- }
- return new PosixFileAttributes() {
- @Override
- public UserPrincipal owner() {
- return attributes.owner != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.owner) : null;
- }
-
- @Override
- public GroupPrincipal group() {
- return attributes.group != null ? new SftpFileSystem.DefaultGroupPrincipal(attributes.group) : null;
- }
-
- @Override
- public Set<PosixFilePermission> permissions() {
- return permissionsToAttributes(attributes.perms);
- }
-
- @Override
- public FileTime lastModifiedTime() {
- return FileTime.from(attributes.mtime, TimeUnit.SECONDS);
- }
-
- @Override
- public FileTime lastAccessTime() {
- return FileTime.from(attributes.atime, TimeUnit.SECONDS);
- }
-
- @Override
- public FileTime creationTime() {
- return FileTime.from(attributes.ctime, TimeUnit.SECONDS);
- }
-
- @Override
- public boolean isRegularFile() {
- return attributes.isRegularFile();
- }
-
- @Override
- public boolean isDirectory() {
- return attributes.isDirectory();
- }
-
- @Override
- public boolean isSymbolicLink() {
- return attributes.isSymbolicLink();
- }
-
- @Override
- public boolean isOther() {
- return attributes.isOther();
- }
-
- @Override
- public long size() {
- return attributes.size;
- }
-
- @Override
- public Object fileKey() {
- // TODO
- return null;
- }
- };
- }
-
- @Override
- public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
- if (lastModifiedTime != null) {
- setAttribute(path, "lastModifiedTime", lastModifiedTime, options);
- }
- if (lastAccessTime != null) {
- setAttribute(path, "lastAccessTime", lastAccessTime, options);
- }
- if (createTime != null) {
- setAttribute(path, "createTime", createTime, options);
- }
- }
-
- @Override
- public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
- setAttribute(path, "permissions", perms, options);
- }
-
- @Override
- public void setGroup(GroupPrincipal group) throws IOException {
- setAttribute(path, "group", group, options);
- }
-
- @Override
- public UserPrincipal getOwner() throws IOException {
- return readAttributes().owner();
- }
-
- @Override
- public void setOwner(UserPrincipal owner) throws IOException {
- setAttribute(path, "owner", owner, options);
- }
- };
- } else {
- throw new UnsupportedOperationException("getFileAttributeView(" + path + ") view not supported: " + type.getSimpleName());
- }
- }
-
- @Override
- public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
- if (type.isAssignableFrom(PosixFileAttributes.class)) {
- return type.cast(getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes());
- }
-
- throw new UnsupportedOperationException("readAttributes(" + path + ")[" + type.getSimpleName() + "] N/A");
- }
-
- @Override
- public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
- String view;
- String attrs;
- int i = attributes.indexOf(':');
- if (i == -1) {
- view = "basic";
- attrs = attributes;
- } else {
- view = attributes.substring(0, i++);
- attrs = attributes.substring(i);
- }
- SftpPath p = toSftpPath(path);
- SftpFileSystem fs = p.getFileSystem();
- Collection<String> views = fs.supportedFileAttributeViews();
- if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
- throw new UnsupportedOperationException("readAttributes(" + path + ")[" + attributes + "] view " + view + " not supported: " + views);
- }
-
- PosixFileAttributes v = readAttributes(path, PosixFileAttributes.class, options);
- if ("*".equals(attrs)) {
- attrs = "lastModifiedTime,lastAccessTime,creationTime,size,isRegularFile,isDirectory,isSymbolicLink,isOther,fileKey,owner,permissions,group";
- }
- Map<String, Object> map = new HashMap<>();
- for (String attr : attrs.split(",")) {
- switch (attr) {
- case "lastModifiedTime":
- map.put(attr, v.lastModifiedTime());
- break;
- case "lastAccessTime":
- map.put(attr, v.lastAccessTime());
- break;
- case "creationTime":
- map.put(attr, v.creationTime());
- break;
- case "size":
- map.put(attr, Long.valueOf(v.size()));
- break;
- case "isRegularFile":
- map.put(attr, Boolean.valueOf(v.isRegularFile()));
- break;
- case "isDirectory":
- map.put(attr, Boolean.valueOf(v.isDirectory()));
- break;
- case "isSymbolicLink":
- map.put(attr, Boolean.valueOf(v.isSymbolicLink()));
- break;
- case "isOther":
- map.put(attr, Boolean.valueOf(v.isOther()));
- break;
- case "fileKey":
- map.put(attr, v.fileKey());
- break;
- case "owner":
- map.put(attr, v.owner());
- break;
- case "permissions":
- map.put(attr, v.permissions());
- break;
- case "group":
- map.put(attr, v.group());
- break;
- default:
- if (log.isTraceEnabled()) {
- log.trace("readAttributes({})[{}] ignored {}={}", path, attributes, attr, v);
- }
- }
- }
- return map;
- }
-
- @Override
- public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
- String view;
- String attr;
- int i = attribute.indexOf(':');
- if (i == -1) {
- view = "basic";
- attr = attribute;
- } else {
- view = attribute.substring(0, i++);
- attr = attribute.substring(i);
- }
- SftpPath p = toSftpPath(path);
- SftpFileSystem fs = p.getFileSystem();
- Collection<String> views = fs.supportedFileAttributeViews();
- if (GenericUtils.isEmpty(views) || (!view.contains(view))) {
- throw new UnsupportedOperationException("setAttribute(" + path + ")[" + attribute + "=" + value + "] view " + view + " not supported: " + views);
- }
-
- SftpClient.Attributes attributes = new SftpClient.Attributes();
- switch (attr) {
- case "lastModifiedTime":
- attributes.mtime((int) ((FileTime) value).to(TimeUnit.SECONDS));
- break;
- case "lastAccessTime":
- attributes.atime((int) ((FileTime) value).to(TimeUnit.SECONDS));
- break;
- case "creationTime":
- attributes.ctime((int) ((FileTime) value).to(TimeUnit.SECONDS));
- break;
- case "size":
- attributes.size(((Number) value).longValue());
- break;
- case "permissions": {
- @SuppressWarnings("unchecked")
- Set<PosixFilePermission> attrSet = (Set<PosixFilePermission>) value;
- attributes.perms(attributesToPermissions(path, attrSet));
- }
- break;
- case "owner":
- attributes.owner(((UserPrincipal) value).getName());
- break;
- case "group":
- attributes.group(((GroupPrincipal) value).getName());
- break;
- case "isRegularFile":
- case "isDirectory":
- case "isSymbolicLink":
- case "isOther":
- case "fileKey":
- throw new UnsupportedOperationException("setAttribute(" + path + ")[" + attribute + "=" + value + "]"
- + " unknown view=" + view + " attribute: " + attr);
- default:
- if (log.isTraceEnabled()) {
- log.trace("setAttribute({})[{}] ignore {}={}", path, attribute, attr, value);
- }
- }
-
- try (SftpClient client = fs.getClient()) {
- client.setStat(p.toString(), attributes);
- }
- }
-
- private SftpPath toSftpPath(Path path) {
- ValidateUtils.checkNotNull(path, "No path provided", GenericUtils.EMPTY_OBJECT_ARRAY);
- if (!(path instanceof SftpPath)) {
- throw new ProviderMismatchException("Path is not SFTP: " + path);
- }
- return (SftpPath) path;
- }
-
- static boolean followLinks(LinkOption... paramVarArgs) {
- boolean bool = true;
- for (LinkOption localLinkOption : paramVarArgs) {
- if (localLinkOption == LinkOption.NOFOLLOW_LINKS) {
- bool = false;
- }
- }
- return bool;
- }
-
- private Set<PosixFilePermission> permissionsToAttributes(int perms) {
- Set<PosixFilePermission> p = new HashSet<>();
- if ((perms & S_IRUSR) != 0) {
- p.add(PosixFilePermission.OWNER_READ);
- }
- if ((perms & S_IWUSR) != 0) {
- p.add(PosixFilePermission.OWNER_WRITE);
- }
- if ((perms & S_IXUSR) != 0) {
- p.add(PosixFilePermission.OWNER_EXECUTE);
- }
- if ((perms & S_IRGRP) != 0) {
- p.add(PosixFilePermission.GROUP_READ);
- }
- if ((perms & S_IWGRP) != 0) {
- p.add(PosixFilePermission.GROUP_WRITE);
- }
- if ((perms & S_IXGRP) != 0) {
- p.add(PosixFilePermission.GROUP_EXECUTE);
- }
- if ((perms & S_IROTH) != 0) {
- p.add(PosixFilePermission.OTHERS_READ);
- }
- if ((perms & S_IWOTH) != 0) {
- p.add(PosixFilePermission.OTHERS_WRITE);
- }
- if ((perms & S_IXOTH) != 0) {
- p.add(PosixFilePermission.OTHERS_EXECUTE);
- }
- return p;
- }
-
- protected int attributesToPermissions(Path path, Collection<PosixFilePermission> perms) {
- if (GenericUtils.isEmpty(perms)) {
- return 0;
- }
-
- int pf = 0;
- for (PosixFilePermission p : perms) {
- switch (p) {
- case OWNER_READ:
- pf |= S_IRUSR;
- break;
- case OWNER_WRITE:
- pf |= S_IWUSR;
- break;
- case OWNER_EXECUTE:
- pf |= S_IXUSR;
- break;
- case GROUP_READ:
- pf |= S_IRGRP;
- break;
- case GROUP_WRITE:
- pf |= S_IWGRP;
- break;
- case GROUP_EXECUTE:
- pf |= S_IXGRP;
- break;
- case OTHERS_READ:
- pf |= S_IROTH;
- break;
- case OTHERS_WRITE:
- pf |= S_IWOTH;
- break;
- case OTHERS_EXECUTE:
- pf |= S_IXOTH;
- break;
- default:
- if (log.isTraceEnabled()) {
- log.trace("attributesToPermissions(" + path + ") ignored " + p);
- }
- }
- }
-
- return pf;
- }
-
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java
deleted file mode 100644
index d4e0b0b..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpPath.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.client.sftp;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.nio.file.LinkOption;
-import java.nio.file.spi.FileSystemProvider;
-
-import org.apache.sshd.common.file.util.BasePath;
-import org.apache.sshd.common.file.util.ImmutableList;
-
-public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
- public SftpPath(SftpFileSystem fileSystem, String root, ImmutableList<String> names) {
- super(fileSystem, root, names);
- }
-
- @Override
- public SftpPath toRealPath(LinkOption... options) throws IOException {
-// try (SftpClient client = fileSystem.getClient()) {
-// client.realP
-// }
- // TODO: handle links
- SftpPath absolute = toAbsolutePath();
- FileSystem fs = getFileSystem();
- FileSystemProvider provider = fs.provider();
- provider.checkAccess(absolute);
- return absolute;
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
new file mode 100644
index 0000000..7f86bd7
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
@@ -0,0 +1,31 @@
+/*
+ * 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.client.subsystem;
+
+import java.nio.channels.Channel;
+
+import org.apache.sshd.common.NamedResource;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SubsystemClient extends NamedResource, Channel {
+ // marker interface for subsystems
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
new file mode 100644
index 0000000..fc7a02b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
@@ -0,0 +1,130 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpClient extends AbstractLoggingBean implements SftpClient {
+ protected AbstractSftpClient() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return SftpConstants.SFTP_SUBSYSTEM_NAME;
+ }
+
+ @Override
+ public CloseableHandle open(String path) throws IOException {
+ return open(path, Collections.<OpenMode>emptySet());
+ }
+
+ @Override
+ public CloseableHandle open(String path, OpenMode ... options) throws IOException {
+ return open(path, GenericUtils.of(options));
+ }
+
+ @Override
+ public void rename(String oldPath, String newPath) throws IOException {
+ rename(oldPath, newPath, Collections.<CopyMode>emptySet());
+ }
+
+ @Override
+ public void rename(String oldPath, String newPath, CopyMode ... options) throws IOException {
+ rename(oldPath, newPath, GenericUtils.of(options));
+ }
+
+ @Override
+ public InputStream read(String path) throws IOException {
+ return read(path, DEFAULT_READ_BUFFER_SIZE);
+ }
+
+ @Override
+ public InputStream read(String path, int bufferSize) throws IOException {
+ return read(path, bufferSize, EnumSet.of(OpenMode.Read));
+ }
+
+ @Override
+ public InputStream read(String path, OpenMode ... mode) throws IOException {
+ return read(path, DEFAULT_READ_BUFFER_SIZE, mode);
+ }
+
+ @Override
+ public InputStream read(String path, int bufferSize, OpenMode ... mode) throws IOException {
+ return read(path, bufferSize, GenericUtils.of(mode));
+ }
+
+ @Override
+ public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
+ return read(path, DEFAULT_READ_BUFFER_SIZE, mode);
+ }
+
+ @Override
+ public int read(Handle handle, long fileOffset, byte[] dst) throws IOException {
+ return read(handle, fileOffset, dst, 0, dst.length);
+ }
+
+ @Override
+ public OutputStream write(String path) throws IOException {
+ return write(path, DEFAULT_WRITE_BUFFER_SIZE);
+ }
+
+ @Override
+ public OutputStream write(String path, int bufferSize) throws IOException {
+ return write(path, bufferSize, EnumSet.of(OpenMode.Write, OpenMode.Create, OpenMode.Truncate));
+ }
+
+ @Override
+ public OutputStream write(String path, OpenMode ... mode) throws IOException {
+ return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode);
+ }
+
+ @Override
+ public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
+ return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode);
+ }
+
+ @Override
+ public OutputStream write(String path, int bufferSize, OpenMode ... mode) throws IOException {
+ return write(path, bufferSize, GenericUtils.of(mode));
+ }
+
+ @Override
+ public void write(Handle handle, long fileOffset, byte[] src) throws IOException {
+ write(handle, fileOffset, src, 0, src.length);
+ }
+
+ @Override
+ public void symLink(String linkPath, String targetPath) throws IOException {
+ link(linkPath, targetPath, true);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
new file mode 100644
index 0000000..868ff10
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java
@@ -0,0 +1,55 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultCloseableHandle extends CloseableHandle {
+ private final AtomicBoolean open = new AtomicBoolean(true);
+ private final SftpClient client;
+
+ public DefaultCloseableHandle(SftpClient client, String id) {
+ super(id);
+ this.client = ValidateUtils.checkNotNull(client, "No client for id=%s", id);
+ }
+
+ public final SftpClient getSftpClient() {
+ return client;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return open.get();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (open.getAndSet(false)) {
+ client.close(this);
+ }
+ }
+}
[10/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
[SSHD-509] Use targeted derived NamedFactory(ies) for the various generic parameters
* Moved SFTP related definitions to own package
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/a6e2bf9e
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/a6e2bf9e
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/a6e2bf9e
Branch: refs/heads/master
Commit: a6e2bf9e4e6f71bde5519085150109799724b579
Parents: 82791d4
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Wed Jul 1 13:56:48 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Wed Jul 1 13:56:48 2015 +0300
----------------------------------------------------------------------
.../java.nio.file.spi.FileSystemProvider | 2 +-
.../sshd/client/session/ClientSession.java | 2 +-
.../sshd/client/session/ClientSessionImpl.java | 8 +-
.../sshd/client/sftp/AbstractSftpClient.java | 124 -
.../client/sftp/DefaultCloseableHandle.java | 55 -
.../sshd/client/sftp/DefaultSftpClient.java | 1174 ---------
.../org/apache/sshd/client/sftp/SftpClient.java | 309 ---
.../sshd/client/sftp/SftpFileChannel.java | 389 ---
.../apache/sshd/client/sftp/SftpFileSystem.java | 384 ---
.../client/sftp/SftpFileSystemProvider.java | 892 -------
.../org/apache/sshd/client/sftp/SftpPath.java | 46 -
.../sshd/client/subsystem/SubsystemClient.java | 31 +
.../subsystem/sftp/AbstractSftpClient.java | 130 +
.../subsystem/sftp/DefaultCloseableHandle.java | 55 +
.../subsystem/sftp/DefaultSftpClient.java | 1179 +++++++++
.../sshd/client/subsystem/sftp/SftpClient.java | 310 +++
.../client/subsystem/sftp/SftpFileChannel.java | 389 +++
.../client/subsystem/sftp/SftpFileSystem.java | 465 ++++
.../subsystem/sftp/SftpFileSystemProvider.java | 892 +++++++
.../sshd/client/subsystem/sftp/SftpPath.java | 46 +
.../file/root/RootedFileSystemProvider.java | 66 +-
.../apache/sshd/common/sftp/SftpConstants.java | 223 --
.../common/subsystem/sftp/SftpConstants.java | 223 ++
.../org/apache/sshd/common/util/io/IoUtils.java | 13 +
.../java/org/apache/sshd/server/SshServer.java | 50 +-
.../apache/sshd/server/sftp/SftpSubsystem.java | 2280 ------------------
.../sshd/server/sftp/SftpSubsystemFactory.java | 139 --
.../server/sftp/UnsupportedAttributePolicy.java | 36 -
.../shell/InteractiveProcessShellFactory.java | 23 +-
.../sshd/server/shell/ProcessShellFactory.java | 25 +-
.../sshd/server/subsystem/SubsystemFactory.java | 38 +
.../server/subsystem/sftp/SftpSubsystem.java | 2280 ++++++++++++++++++
.../subsystem/sftp/SftpSubsystemFactory.java | 139 ++
.../sftp/UnsupportedAttributePolicy.java | 36 +
.../java/org/apache/sshd/client/ClientTest.java | 2 +-
.../client/sftp/DefaultCloseableHandleTest.java | 90 -
.../sshd/client/sftp/SftpFileSystemTest.java | 286 ---
.../org/apache/sshd/client/sftp/SftpTest.java | 651 -----
.../sftp/DefaultCloseableHandleTest.java | 92 +
.../subsystem/sftp/SftpFileSystemTest.java | 288 +++
.../sshd/client/subsystem/sftp/SftpTest.java | 652 +++++
.../java/org/apache/sshd/server/ServerTest.java | 2 +-
.../server/sftp/SftpSubsystemFactoryTest.java | 98 -
.../sftp/SftpSubsystemFactoryTest.java | 100 +
.../sshd/git/pack/GitPackCommandTest.java | 2 +-
.../apache/sshd/git/pgm/GitPgmCommandTest.java | 2 +-
46 files changed, 7453 insertions(+), 7265 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider b/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
index 7691b7c..7d90d38 100644
--- a/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
+++ b/sshd-core/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
@@ -17,5 +17,5 @@
## under the License.
##
-org.apache.sshd.client.sftp.SftpFileSystemProvider
+org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider
org.apache.sshd.common.file.root.RootedFileSystemProvider
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index 442c5d2..f74f7c3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -33,7 +33,7 @@ import org.apache.sshd.client.channel.ChannelSubsystem;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.future.AuthFuture;
import org.apache.sshd.client.scp.ScpClient;
-import org.apache.sshd.client.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
import org.apache.sshd.common.SshdSocketAddress;
import org.apache.sshd.common.future.SshFuture;
import org.apache.sshd.common.scp.ScpTransferEventListener;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index 305c53b..73f1b46 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -41,10 +41,10 @@ import org.apache.sshd.client.future.AuthFuture;
import org.apache.sshd.client.future.DefaultAuthFuture;
import org.apache.sshd.client.scp.DefaultScpClient;
import org.apache.sshd.client.scp.ScpClient;
-import org.apache.sshd.client.sftp.DefaultSftpClient;
-import org.apache.sshd.client.sftp.SftpClient;
-import org.apache.sshd.client.sftp.SftpFileSystem;
-import org.apache.sshd.client.sftp.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.DefaultSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.Service;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/AbstractSftpClient.java
deleted file mode 100644
index 2bcb9ef..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/AbstractSftpClient.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.client.sftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public abstract class AbstractSftpClient extends AbstractLoggingBean implements SftpClient {
- protected AbstractSftpClient() {
- super();
- }
-
- @Override
- public CloseableHandle open(String path) throws IOException {
- return open(path, Collections.<OpenMode>emptySet());
- }
-
- @Override
- public CloseableHandle open(String path, OpenMode ... options) throws IOException {
- return open(path, GenericUtils.of(options));
- }
-
- @Override
- public void rename(String oldPath, String newPath) throws IOException {
- rename(oldPath, newPath, Collections.<CopyMode>emptySet());
- }
-
- @Override
- public void rename(String oldPath, String newPath, CopyMode ... options) throws IOException {
- rename(oldPath, newPath, GenericUtils.of(options));
- }
-
- @Override
- public InputStream read(String path) throws IOException {
- return read(path, DEFAULT_READ_BUFFER_SIZE);
- }
-
- @Override
- public InputStream read(String path, int bufferSize) throws IOException {
- return read(path, bufferSize, EnumSet.of(OpenMode.Read));
- }
-
- @Override
- public InputStream read(String path, OpenMode ... mode) throws IOException {
- return read(path, DEFAULT_READ_BUFFER_SIZE, mode);
- }
-
- @Override
- public InputStream read(String path, int bufferSize, OpenMode ... mode) throws IOException {
- return read(path, bufferSize, GenericUtils.of(mode));
- }
-
- @Override
- public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
- return read(path, DEFAULT_READ_BUFFER_SIZE, mode);
- }
-
- @Override
- public int read(Handle handle, long fileOffset, byte[] dst) throws IOException {
- return read(handle, fileOffset, dst, 0, dst.length);
- }
-
- @Override
- public OutputStream write(String path) throws IOException {
- return write(path, DEFAULT_WRITE_BUFFER_SIZE);
- }
-
- @Override
- public OutputStream write(String path, int bufferSize) throws IOException {
- return write(path, bufferSize, EnumSet.of(OpenMode.Write, OpenMode.Create, OpenMode.Truncate));
- }
-
- @Override
- public OutputStream write(String path, OpenMode ... mode) throws IOException {
- return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode);
- }
-
- @Override
- public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
- return write(path, DEFAULT_WRITE_BUFFER_SIZE, mode);
- }
-
- @Override
- public OutputStream write(String path, int bufferSize, OpenMode ... mode) throws IOException {
- return write(path, bufferSize, GenericUtils.of(mode));
- }
-
- @Override
- public void write(Handle handle, long fileOffset, byte[] src) throws IOException {
- write(handle, fileOffset, src, 0, src.length);
- }
-
- @Override
- public void symLink(String linkPath, String targetPath) throws IOException {
- link(linkPath, targetPath, true);
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultCloseableHandle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultCloseableHandle.java
deleted file mode 100644
index f86c47e..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultCloseableHandle.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.client.sftp;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.sshd.client.sftp.SftpClient.CloseableHandle;
-import org.apache.sshd.common.util.ValidateUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class DefaultCloseableHandle extends CloseableHandle {
- private final AtomicBoolean open = new AtomicBoolean(true);
- private final SftpClient client;
-
- public DefaultCloseableHandle(SftpClient client, String id) {
- super(id);
- this.client = ValidateUtils.checkNotNull(client, "No client for id=%s", id);
- }
-
- public final SftpClient getSftpClient() {
- return client;
- }
-
- @Override
- public boolean isOpen() {
- return open.get();
- }
-
- @Override
- public void close() throws IOException {
- if (open.getAndSet(false)) {
- client.close(this);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
deleted file mode 100644
index 6b58c36..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
+++ /dev/null
@@ -1,1174 +0,0 @@
-/*
- * 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.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_APPEND_DATA;
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_READ_ATTRIBUTES;
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_READ_DATA;
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_WRITE_ATTRIBUTES;
-import static org.apache.sshd.common.sftp.SftpConstants.ACE4_WRITE_DATA;
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V3;
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V4;
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V5;
-import static org.apache.sshd.common.sftp.SftpConstants.SFTP_V6;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_ALL;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_CREATETIME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_SIZE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_ATTR_UIDGID;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_TYPE_REGULAR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FILEXFER_TYPE_SYMLINK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_APPEND;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_CREAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_CREATE_NEW;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_CREATE_TRUNCATE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_EXCL;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_OPEN_EXISTING;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_OPEN_OR_CREATE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_READ;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_TRUNC;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXF_WRITE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_ATTRS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_BLOCK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_CLOSE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_DATA;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_FSETSTAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_FSTAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_HANDLE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_INIT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_LINK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_LSTAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_MKDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_NAME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_OPEN;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_OPENDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_READ;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_READDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_READLINK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_REALPATH;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_REMOVE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_RENAME;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_RENAME_ATOMIC;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_RENAME_OVERWRITE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_RMDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_SETSTAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_STAT;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_STATUS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_SYMLINK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_UNBLOCK;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_VERSION;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FXP_WRITE;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_EOF;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_OK;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFLNK;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFREG;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InterruptedIOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.attribute.FileTime;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.SftpException;
-import org.apache.sshd.client.channel.ChannelSubsystem;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.FactoryManagerUtils;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.InputStreamWithChannel;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
-import org.apache.sshd.common.util.io.OutputStreamWithChannel;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class DefaultSftpClient extends AbstractSftpClient {
- private final ClientSession clientSession;
- private final ChannelSubsystem channel;
- private final Map<Integer, Buffer> messages;
- private final AtomicInteger cmdId = new AtomicInteger(100);
- private final Buffer receiveBuffer = new ByteArrayBuffer();
- private boolean closing;
- private int version;
- private final Map<String, byte[]> extensions = new HashMap<>();
-
- public DefaultSftpClient(ClientSession clientSession) throws IOException {
- this.clientSession = clientSession;
- this.channel = clientSession.createSubsystemChannel(SftpConstants.SFTP_SUBSYSTEM_NAME);
- this.messages = new HashMap<>();
- this.channel.setOut(new OutputStream() {
- @Override
- public void write(int b) throws IOException {
- write(new byte[] { (byte) b }, 0, 1);
- }
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- data(b, off, len);
- }
- });
- this.channel.setErr(new ByteArrayOutputStream(Byte.MAX_VALUE));
- this.channel.open().verify(FactoryManagerUtils.getLongProperty(clientSession, SFTP_CHANNEL_OPEN_TIMEOUT, DEFAULT_CHANNEL_OPEN_TIMEOUT));
- this.channel.onClose(new Runnable() {
- @SuppressWarnings("synthetic-access")
- @Override
- public void run() {
- synchronized (messages) {
- closing = true;
- messages.notifyAll();
- }
- }
- });
- init();
- }
-
- @Override
- public int getVersion() {
- return version;
- }
-
- @Override
- public boolean isClosing() {
- return closing;
- }
-
- @Override
- public void close() throws IOException {
- if (this.channel.isOpen()) {
- this.channel.close(false);
- }
- }
-
- /**
- * Receive binary data
- */
- protected int data(byte[] buf, int start, int len) throws IOException {
- Buffer incoming = new ByteArrayBuffer(buf, start, len);
- // If we already have partial data, we need to append it to the buffer and use it
- if (receiveBuffer.available() > 0) {
- receiveBuffer.putBuffer(incoming);
- incoming = receiveBuffer;
- }
- // Process commands
- int rpos = incoming.rpos();
- for (int count=0; receive(incoming); count++) {
- if (log.isTraceEnabled()) {
- log.trace("Processed " + count + " data messages");
- }
- }
-
- int read = incoming.rpos() - rpos;
- // Compact and add remaining data
- receiveBuffer.compact();
- if (receiveBuffer != incoming && incoming.available() > 0) {
- receiveBuffer.putBuffer(incoming);
- }
- return read;
- }
-
- /**
- * Read SFTP packets from buffer
- */
- protected boolean receive(Buffer incoming) throws IOException {
- int rpos = incoming.rpos();
- int wpos = incoming.wpos();
- clientSession.resetIdleTimeout();
- if ((wpos - rpos) > 4) {
- int length = incoming.getInt();
- if (length < 5) {
- throw new IOException("Illegal sftp packet length: " + length);
- }
- if ((wpos - rpos) >= (length + 4)) {
- incoming.rpos(rpos);
- incoming.wpos(rpos + 4 + length);
- process(incoming);
- incoming.rpos(rpos + 4 + length);
- incoming.wpos(wpos);
- return true;
- }
- }
- incoming.rpos(rpos);
- return false;
- }
-
- /**
- * Process an SFTP packet
- */
- protected void process(Buffer incoming) throws IOException {
- Buffer buffer = new ByteArrayBuffer(incoming.available());
- buffer.putBuffer(incoming);
- buffer.rpos(5);
- int id = buffer.getInt();
- buffer.rpos(0);
- synchronized (messages) {
- messages.put(Integer.valueOf(id), buffer);
- messages.notifyAll();
- }
- }
-
- protected int send(int cmd, Buffer buffer) throws IOException {
- int id = cmdId.incrementAndGet();
-
- try(DataOutputStream dos = new DataOutputStream(new NoCloseOutputStream(channel.getInvertedIn()))) {
- dos.writeInt(5 + buffer.available());
- dos.writeByte(cmd);
- dos.writeInt(id);
- dos.write(buffer.array(), buffer.rpos(), buffer.available());
- dos.flush();
- }
-
- return id;
- }
-
- protected Buffer receive(int id) throws IOException {
- synchronized (messages) {
- while (true) {
- if (closing) {
- throw new SshException("Channel has been closed");
- }
- Buffer buffer = messages.remove(Integer.valueOf(id));
- if (buffer != null) {
- return buffer;
- }
- try {
- messages.wait();
- } catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException("Interrupted while waiting for messages").initCause(e);
- }
- }
- }
- }
-
- protected Buffer read() throws IOException {
- try(DataInputStream dis=new DataInputStream(new NoCloseInputStream(channel.getInvertedOut()))) {
- int length = dis.readInt();
- if (length < 5) {
- throw new IllegalArgumentException("Bad length: " + length);
- }
- Buffer buffer = new ByteArrayBuffer(length + 4);
- buffer.putInt(length);
- int nb = length;
- while (nb > 0) {
- int readLen = dis.read(buffer.array(), buffer.wpos(), nb);
- if (readLen < 0) {
- throw new IllegalArgumentException("Premature EOF while read " + length + " bytes - remaining=" + nb);
- }
- buffer.wpos(buffer.wpos() + readLen);
- nb -= readLen;
- }
-
- return buffer;
- }
- }
-
- protected void init() throws IOException {
- // Init packet
- try(DataOutputStream dos = new DataOutputStream(new NoCloseOutputStream(channel.getInvertedIn()))) {
- dos.writeInt(5);
- dos.writeByte(SSH_FXP_INIT);
- dos.writeInt(SFTP_V6);
- dos.flush();
- }
-
- Buffer buffer;
- synchronized (messages) {
- while (messages.isEmpty()) {
- try {
- messages.wait();
- } catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException("Interruppted init()").initCause(e);
- }
- }
- buffer = messages.remove(messages.keySet().iterator().next());
-
- }
- int length = buffer.getInt();
- int type = buffer.getByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_VERSION) {
- if (id < SFTP_V3) {
- throw new SshException("Unsupported sftp version " + id);
- }
- version = id;
- while (buffer.available() > 0) {
- String name = buffer.getString();
- byte[] data = buffer.getBytes();
- extensions.put(name, data);
- }
- } else if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("init(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
-
- throw new SftpException(substatus, msg);
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected void checkStatus(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkStatus(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
-
- if (substatus != SSH_FX_OK) {
- throw new SftpException(substatus, msg);
- }
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected String checkHandle(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkHandle(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_HANDLE) {
- String handle = ValidateUtils.checkNotNullAndNotEmpty(buffer.getString(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
- return handle;
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected Attributes checkAttributes(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkAttributes(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_ATTRS) {
- return readAttributes(buffer);
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected String checkOneName(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkOneName(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_NAME) {
- int len = buffer.getInt();
- if (len != 1) {
- throw new SshException("SFTP error: received " + len + " names instead of 1");
- }
- String name = buffer.getString(), longName = null;
- if (version == SFTP_V3) {
- longName = buffer.getString();
- }
- Attributes attrs = readAttributes(buffer);
- if (log.isTraceEnabled()) {
- log.trace("checkOneName(id={}) ({})[{}]: {}", Integer.valueOf(id), name, longName, attrs);
- }
- return name;
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected Attributes readAttributes(Buffer buffer) throws IOException {
- Attributes attrs = new Attributes();
- int flags = buffer.getInt();
- if (version == SFTP_V3) {
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- attrs.flags.add(Attribute.Size);
- attrs.size = buffer.getLong();
- }
- if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
- attrs.flags.add(Attribute.UidGid);
- attrs.uid = buffer.getInt();
- attrs.gid = buffer.getInt();
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- attrs.flags.add(Attribute.Perms);
- attrs.perms = buffer.getInt();
- }
- if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- attrs.flags.add(Attribute.AcModTime);
- attrs.atime = buffer.getInt();
- attrs.mtime = buffer.getInt();
- }
- } else if (version >= SFTP_V4) {
- attrs.type = buffer.getByte();
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- attrs.flags.add(Attribute.Size);
- attrs.size = buffer.getLong();
- }
- if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
- attrs.flags.add(Attribute.OwnerGroup);
- attrs.owner = buffer.getString();
- attrs.group = buffer.getString();
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- attrs.flags.add(Attribute.Perms);
- attrs.perms = buffer.getInt();
- }
-
- // update the permissions according to the type
- switch (attrs.type) {
- case SSH_FILEXFER_TYPE_REGULAR:
- attrs.perms |= S_IFREG;
- break;
- case SSH_FILEXFER_TYPE_DIRECTORY:
- attrs.perms |= S_IFDIR;
- break;
- case SSH_FILEXFER_TYPE_SYMLINK:
- attrs.perms |= S_IFLNK;
- break;
- default: // do nothing
- }
-
- if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
- attrs.flags.add(Attribute.AccessTime);
- attrs.accessTime = readTime(buffer, flags);
- attrs.atime = (int) attrs.accessTime.to(TimeUnit.SECONDS);
- }
- if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
- attrs.flags.add(Attribute.CreateTime);
- attrs.createTime = readTime(buffer, flags);
- attrs.ctime = (int) attrs.createTime.to(TimeUnit.SECONDS);
- }
- if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
- attrs.flags.add(Attribute.ModifyTime);
- attrs.modifyTime = readTime(buffer, flags);
- attrs.mtime = (int) attrs.modifyTime.to(TimeUnit.SECONDS);
- }
- // TODO: acl
- } else {
- throw new IllegalStateException("readAttributes - unsupported version: " + version);
- }
- return attrs;
- }
-
- private FileTime readTime(Buffer buffer, int flags) {
- long secs = buffer.getLong();
- long millis = secs * 1000;
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- millis += buffer.getInt() / 1000000l;
- }
- return FileTime.from(millis, TimeUnit.MILLISECONDS);
- }
-
- protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
- if (version == SFTP_V3) {
- int flags = 0;
- for (Attribute a : attributes.flags) {
- switch (a) {
- case Size:
- flags |= SSH_FILEXFER_ATTR_SIZE;
- break;
- case UidGid:
- flags |= SSH_FILEXFER_ATTR_UIDGID;
- break;
- case Perms:
- flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
- break;
- case AcModTime:
- flags |= SSH_FILEXFER_ATTR_ACMODTIME;
- break;
- default: // do nothing
- }
- }
- buffer.putInt(flags);
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong(attributes.size);
- }
- if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
- buffer.putInt(attributes.uid);
- buffer.putInt(attributes.gid);
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(attributes.perms);
- }
- if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- buffer.putInt(attributes.atime);
- buffer.putInt(attributes.mtime);
- }
- } else if (version >= SFTP_V4) {
- int flags = 0;
- for (Attribute a : attributes.flags) {
- switch (a) {
- case Size:
- flags |= SSH_FILEXFER_ATTR_SIZE;
- break;
- case OwnerGroup:
- flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
- break;
- case Perms:
- flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
- break;
- case AccessTime:
- flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
- break;
- case ModifyTime:
- flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
- break;
- case CreateTime:
- flags |= SSH_FILEXFER_ATTR_CREATETIME;
- break;
- default: // do nothing
- }
- }
- buffer.putInt(flags);
- buffer.putByte(attributes.type);
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong(attributes.size);
- }
- if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
- buffer.putString(attributes.owner != null ? attributes.owner : "OWNER@", StandardCharsets.UTF_8);
- buffer.putString(attributes.group != null ? attributes.group : "GROUP@", StandardCharsets.UTF_8);
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(attributes.perms);
- }
- if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
- buffer.putLong(attributes.accessTime.to(TimeUnit.SECONDS));
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- long nanos = attributes.accessTime.to(TimeUnit.NANOSECONDS);
- nanos = nanos % TimeUnit.SECONDS.toNanos(1);
- buffer.putInt((int) nanos);
- }
- buffer.putInt(attributes.atime);
- }
- if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
- buffer.putLong(attributes.createTime.to(TimeUnit.SECONDS));
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- long nanos = attributes.createTime.to(TimeUnit.NANOSECONDS);
- nanos = nanos % TimeUnit.SECONDS.toNanos(1);
- buffer.putInt((int) nanos);
- }
- buffer.putInt(attributes.atime);
- }
- if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
- buffer.putLong(attributes.modifyTime.to(TimeUnit.SECONDS));
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- long nanos = attributes.modifyTime.to(TimeUnit.NANOSECONDS);
- nanos = nanos % TimeUnit.SECONDS.toNanos(1);
- buffer.putInt((int) nanos);
- }
- buffer.putInt(attributes.atime);
- }
- // TODO: acl
- } else {
- throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
- }
- }
-
- @Override
- public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- if (version == SFTP_V3) {
- int mode = 0;
- for (OpenMode m : options) {
- switch (m) {
- case Read:
- mode |= SSH_FXF_READ;
- break;
- case Write:
- mode |= SSH_FXF_WRITE;
- break;
- case Append:
- mode |= SSH_FXF_APPEND;
- break;
- case Create:
- mode |= SSH_FXF_CREAT;
- break;
- case Truncate:
- mode |= SSH_FXF_TRUNC;
- break;
- case Exclusive:
- mode |= SSH_FXF_EXCL;
- break;
- default: // do nothing
- }
- }
- buffer.putInt(mode);
- } else {
- int mode = 0;
- int access = 0;
- if (options.contains(OpenMode.Read)) {
- access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
- }
- if (options.contains(OpenMode.Write)) {
- access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
- }
- if (options.contains(OpenMode.Append)) {
- access |= ACE4_APPEND_DATA;
- }
- if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) {
- mode |= SSH_FXF_CREATE_NEW;
- } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) {
- mode |= SSH_FXF_CREATE_TRUNCATE;
- } else if (options.contains(OpenMode.Create)) {
- mode |= SSH_FXF_OPEN_OR_CREATE;
- } else if (options.contains(OpenMode.Truncate)) {
- mode |= SSH_FXF_TRUNCATE_EXISTING;
- } else {
- mode |= SSH_FXF_OPEN_EXISTING;
- }
- if (version >= SFTP_V5) {
- buffer.putInt(access);
- }
- buffer.putInt(mode);
- }
- writeAttributes(buffer, new Attributes());
- return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPEN, buffer))));
- }
-
- @Override
- public void close(Handle handle) throws IOException {
- Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
- buffer.putString(handle.id);
- checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
- }
-
- @Override
- public void remove(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- checkStatus(receive(send(SSH_FXP_REMOVE, buffer)));
- }
-
- @Override
- public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
- Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */);
- buffer.putString(oldPath);
- buffer.putString(newPath);
-
- int numOptions = GenericUtils.size(options);
- if (version >= SFTP_V5) {
- int opts = 0;
- if (numOptions > 0) {
- for (CopyMode opt : options) {
- switch (opt) {
- case Atomic:
- opts |= SSH_FXP_RENAME_ATOMIC;
- break;
- case Overwrite:
- opts |= SSH_FXP_RENAME_OVERWRITE;
- break;
- default: // do nothing
- }
- }
- }
- buffer.putInt(opts);
- } else if (numOptions > 0) {
- throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
- + " - copy options can not be used with this SFTP version: " + options);
- }
- checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
- }
-
- @Override
- public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
- Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
- buffer.putString(handle.id);
- buffer.putLong(fileOffset);
- buffer.putInt(len);
- return checkData(receive(send(SSH_FXP_READ, buffer)), dstoff, dst);
- }
-
- protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkData(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
-
- if (substatus == SSH_FX_EOF) {
- return -1;
- }
-
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_DATA) {
- int len = buffer.getInt();
- buffer.getRawBytes(dst, dstoff, len);
- return len;
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- @Override
- public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
- // do some bounds checking first
- if ((fileOffset < 0) || (srcoff < 0) || (len < 0)) {
- throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters "
- + " are non-negative values: file-offset=" + fileOffset
- + ", src-offset=" + srcoff + ", len=" + len);
- }
- if ((srcoff + len) > src.length) {
- throw new IllegalArgumentException("write(" + handle + ")"
- + " cannot read bytes " + srcoff + " to " + (srcoff + len)
- + " when array is only of length " + src.length);
- }
-
- Buffer buffer = new ByteArrayBuffer(handle.id.length() + len + Long.SIZE /* some extra fields */);
- buffer.putString(handle.id);
- buffer.putLong(fileOffset);
- buffer.putBytes(src, srcoff, len);
- checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
- }
-
- @Override
- public void mkdir(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path, StandardCharsets.UTF_8);
- buffer.putInt(0);
- if (version != SFTP_V3) {
- buffer.putByte((byte) 0);
- }
- checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
- }
-
- @Override
- public void rmdir(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- checkStatus(receive(send(SSH_FXP_RMDIR, buffer)));
- }
-
- @Override
- public CloseableHandle openDir(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPENDIR, buffer))));
- }
-
- @Override
- public DirEntry[] readDir(Handle handle) throws IOException {
- Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
- buffer.putString(handle.id);
- return checkDir(receive(send(SSH_FXP_READDIR, buffer)));
- }
-
- protected DirEntry[] checkDir(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkDir(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
- if (substatus == SSH_FX_EOF) {
- return null;
- }
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_NAME) {
- int len = buffer.getInt();
- DirEntry[] entries = new DirEntry[len];
- for (int i = 0; i < len; i++) {
- String name = buffer.getString();
- String longName = (version == SFTP_V3) ? buffer.getString() : null;
- Attributes attrs = readAttributes(buffer);
- if (log.isTraceEnabled()) {
- log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
- }
-
- entries[i] = new DirEntry(name, longName, attrs);
- }
- return entries;
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- @Override
- public String canonicalPath(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- return checkOneName(receive(send(SSH_FXP_REALPATH, buffer)));
- }
-
- @Override
- public Attributes stat(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- if (version >= SFTP_V4) {
- buffer.putInt(SSH_FILEXFER_ATTR_ALL);
- }
- return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
- }
-
- @Override
- public Attributes lstat(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- if (version >= SFTP_V4) {
- buffer.putInt(SSH_FILEXFER_ATTR_ALL);
- }
- return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
- }
-
- @Override
- public Attributes stat(Handle handle) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(handle.id);
- if (version >= SFTP_V4) {
- buffer.putInt(SSH_FILEXFER_ATTR_ALL);
- }
- return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
- }
-
- @Override
- public void setStat(String path, Attributes attributes) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- writeAttributes(buffer, attributes);
- checkStatus(receive(send(SSH_FXP_SETSTAT, buffer)));
- }
-
- @Override
- public void setStat(Handle handle, Attributes attributes) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(handle.id);
- writeAttributes(buffer, attributes);
- checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
- }
-
- @Override
- public String readLink(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- return checkOneName(receive(send(SSH_FXP_READLINK, buffer)));
- }
-
- @Override
- public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
- Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */);
- if (version < SFTP_V6) {
- if (!symbolic) {
- throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
- }
- buffer.putString(targetPath);
- buffer.putString(linkPath);
- checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
- } else {
- buffer.putString(targetPath);
- buffer.putString(linkPath);
- buffer.putBoolean(symbolic);
- checkStatus(receive(send(SSH_FXP_LINK, buffer)));
- }
- }
-
- @Override
- public void lock(Handle handle, long offset, long length, int mask) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(handle.id);
- buffer.putLong(offset);
- buffer.putLong(length);
- buffer.putInt(mask);
- checkStatus(receive(send(SSH_FXP_BLOCK, buffer)));
- }
-
- @Override
- public void unlock(Handle handle, long offset, long length) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(handle.id);
- buffer.putLong(offset);
- buffer.putLong(length);
- checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));
- }
-
- @Override
- public Iterable<DirEntry> readDir(final String path) throws IOException {
- return new Iterable<DirEntry>() {
- @Override
- public Iterator<DirEntry> iterator() {
- return new Iterator<DirEntry>() {
- private CloseableHandle handle;
- private DirEntry[] entries;
- private int index;
-
- {
- open();
- load();
- }
-
- @Override
- public boolean hasNext() {
- return (entries != null) && (index < entries.length);
- }
-
- @Override
- public DirEntry next() {
- DirEntry entry = entries[index++];
- if (index >= entries.length) {
- load();
- }
- return entry;
- }
-
- @SuppressWarnings("synthetic-access")
- private void open() {
- try {
- handle = openDir(path);
- if (log.isDebugEnabled()) {
- log.debug("readDir(" + path + ") handle=" + handle);
- }
- } catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("readDir(" + path + ") failed (" + e.getClass().getSimpleName() + ") to open dir: " + e.getMessage());
- }
- throw new RuntimeException(e);
- }
- }
-
- @SuppressWarnings("synthetic-access")
- private void load() {
- try {
- entries = readDir(handle);
- index = 0;
- if (entries == null) {
- handle.close();
- }
- } catch (IOException e) {
- entries = null;
- try {
- handle.close();
- } catch (IOException t) {
- if (log.isTraceEnabled()) {
- log.trace(t.getClass().getSimpleName() + " while close handle=" + handle
- + " due to " + e.getClass().getSimpleName() + " [" + e.getMessage() + "]"
- + ": " + t.getMessage());
- }
- }
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("readDir(" + path + ") Iterator#remove() N/A");
- }
- };
- }
- };
- }
-
- @Override
- public InputStream read(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
- if (bufferSize < MIN_READ_BUFFER_SIZE) {
- throw new IllegalArgumentException("Insufficient read buffer size: " + bufferSize + ", min.=" + MIN_READ_BUFFER_SIZE);
- }
-
- return new InputStreamWithChannel() {
- private byte[] bb = new byte[1];
- private byte[] buffer = new byte[bufferSize];
- private int index;
- private int available;
- private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
- private long offset;
-
- @Override
- public boolean isOpen() {
- return (handle != null) && handle.isOpen();
- }
-
- @Override
- public int read() throws IOException {
- int read = read(bb, 0, 1);
- if (read > 0) {
- return bb[0];
- }
-
- return read;
- }
-
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- if (!isOpen()) {
- throw new IOException("read(" + path + ") stream closed");
- }
-
- int idx = off;
- while (len > 0) {
- if (index >= available) {
- available = DefaultSftpClient.this.read(handle, offset, buffer, 0, buffer.length);
- if (available < 0) {
- if (idx == off) {
- return -1;
- } else {
- break;
- }
- }
- offset += available;
- index = 0;
- }
- if (index >= available) {
- break;
- }
- int nb = Math.min(len, available - index);
- System.arraycopy(buffer, index, b, idx, nb);
- index += nb;
- idx += nb;
- len -= nb;
- }
- return idx - off;
- }
-
- @Override
- public void close() throws IOException {
- if (isOpen()) {
- try {
- handle.close();
- } finally {
- handle = null;
- }
- }
- }
- };
- }
-
- @Override
- public OutputStream write(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
- if (bufferSize < MIN_WRITE_BUFFER_SIZE) {
- throw new IllegalArgumentException("Insufficient write buffer size: " + bufferSize + ", min.=" + MIN_WRITE_BUFFER_SIZE);
- }
-
- return new OutputStreamWithChannel() {
- private byte[] bb = new byte[1];
- private byte[] buffer = new byte[bufferSize];
- private int index;
- private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
- private long offset;
-
- @Override
- public boolean isOpen() {
- return (handle != null) && handle.isOpen();
- }
-
- @Override
- public void write(int b) throws IOException {
- bb[0] = (byte) b;
- write(bb, 0, 1);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- if (!isOpen()) {
- throw new IOException("write(" + path + ")[len=" + len + "] stream is closed");
- }
-
- do {
- int nb = Math.min(len, buffer.length - index);
- System.arraycopy(b, off, buffer, index, nb);
- index += nb;
- if (index == buffer.length) {
- flush();
- }
- off += nb;
- len -= nb;
- } while (len > 0);
- }
-
- @Override
- public void flush() throws IOException {
- if (!isOpen()) {
- throw new IOException("flush(" + path + ") stream is closed");
- }
-
- DefaultSftpClient.this.write(handle, offset, buffer, 0, index);
- offset += index;
- index = 0;
- }
-
- @Override
- public void close() throws IOException {
- if (isOpen()) {
- try {
- try {
- if (index > 0) {
- flush();
- }
- } finally {
- handle.close();
- }
- } finally {
- handle = null;
- }
- }
- }
- };
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpClient.java
deleted file mode 100644
index 739a408..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/SftpClient.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * 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.client.sftp;
-
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFDIR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFLNK;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFMT;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IFREG;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.Channel;
-import java.nio.file.attribute.FileTime;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-
-/**
- * @author <a href="http://mina.apache.org">Apache MINA Project</a>
- */
-public interface SftpClient extends Closeable {
-
- enum OpenMode {
- Read,
- Write,
- Append,
- Create,
- Truncate,
- Exclusive
- }
-
- enum CopyMode {
- Atomic,
- Overwrite
- }
-
- enum Attribute {
- Size,
- UidGid,
- Perms,
- AcModTime,
- OwnerGroup,
- AccessTime,
- ModifyTime,
- CreateTime,
- }
-
- public static class Handle {
- public final String id;
- public Handle(String id) {
- this.id = ValidateUtils.checkNotNullAndNotEmpty(id, "No handle ID", GenericUtils.EMPTY_OBJECT_ARRAY);
- }
-
- @Override
- public String toString() {
- return id;
- }
- }
-
- public static abstract class CloseableHandle extends Handle implements Channel, Closeable {
- protected CloseableHandle(String id) {
- super(id);
- }
- }
-
- public static class Attributes {
- public final Set<Attribute> flags = EnumSet.noneOf(Attribute.class);
- public long size;
- public byte type;
- public int uid;
- public int gid;
- public int perms;
- public int atime;
- public int ctime;
- public int mtime;
- public String owner;
- public String group;
- public FileTime accessTime;
- public FileTime createTime;
- public FileTime modifyTime;
-
- @Override
- public String toString() {
- return "type=" + type
- + ";size=" + size
- + ";uid=" + uid
- + ";gid=" + gid
- + ";perms=0x" + Integer.toHexString(perms)
- + ";flags=" + flags
- + ";owner=" + owner
- + ";group=" + group
- + ";aTime=(" + atime + ")[" + accessTime + "]"
- + ";cTime=(" + ctime + ")[" + createTime + "]"
- + ";mTime=(" + mtime + ")[" + modifyTime + "]"
- ;
- }
-
- public Attributes size(long size) {
- flags.add(Attribute.Size);
- this.size = size;
- return this;
- }
- public Attributes owner(String owner) {
- flags.add(Attribute.OwnerGroup);
- this.owner = owner;
- if (GenericUtils.isEmpty(group)) {
- group = "GROUP@";
- }
- return this;
- }
- public Attributes group(String group) {
- flags.add(Attribute.OwnerGroup);
- this.group = group;
- if (GenericUtils.isEmpty(owner)) {
- owner = "OWNER@";
- }
- return this;
- }
- public Attributes owner(int uid, int gid) {
- flags.add(Attribute.UidGid);
- this.uid = uid;
- this.gid = gid;
- return this;
- }
- public Attributes perms(int perms) {
- flags.add(Attribute.Perms);
- this.perms = perms;
- return this;
- }
- public Attributes atime(int atime) {
- flags.add(Attribute.AccessTime);
- this.atime = atime;
- this.accessTime = FileTime.from(atime, TimeUnit.SECONDS);
- return this;
- }
- public Attributes ctime(int ctime) {
- flags.add(Attribute.CreateTime);
- this.ctime = ctime;
- this.createTime = FileTime.from(atime, TimeUnit.SECONDS);
- return this;
- }
- public Attributes mtime(int mtime) {
- flags.add(Attribute.ModifyTime);
- this.mtime = mtime;
- this.modifyTime = FileTime.from(atime, TimeUnit.SECONDS);
- return this;
- }
- public Attributes time(int atime, int mtime) {
- flags.add(Attribute.AcModTime);
- this.atime = atime;
- this.mtime = mtime;
- return this;
- }
- public Attributes accessTime(FileTime atime) {
- flags.add(Attribute.AccessTime);
- this.atime = (int) atime.to(TimeUnit.SECONDS);
- this.accessTime = atime;
- return this;
- }
- public Attributes createTime(FileTime ctime) {
- flags.add(Attribute.CreateTime);
- this.ctime = (int) ctime.to(TimeUnit.SECONDS);
- this.createTime = ctime;
- return this;
- }
- public Attributes modifyTime(FileTime mtime) {
- flags.add(Attribute.ModifyTime);
- this.mtime = (int) mtime.to(TimeUnit.SECONDS);
- this.modifyTime = mtime;
- return this;
- }
- public boolean isRegularFile() {
- return (perms & S_IFMT) == S_IFREG;
- }
- public boolean isDirectory() {
- return (perms & S_IFMT) == S_IFDIR;
- }
- public boolean isSymbolicLink() {
- return (perms & S_IFMT) == S_IFLNK;
- }
- public boolean isOther() {
- return !isRegularFile() && !isDirectory() && !isSymbolicLink();
- }
- }
-
- public static class DirEntry {
- public String filename;
- public String longFilename;
- public Attributes attributes;
- public DirEntry(String filename, String longFilename, Attributes attributes) {
- this.filename = filename;
- this.longFilename = longFilename;
- this.attributes = attributes;
- }
- }
-
- int getVersion();
-
- boolean isClosing();
-
- //
- // Low level API
- //
-
- CloseableHandle open(String path) throws IOException;
- CloseableHandle open(String path, OpenMode ... options) throws IOException;
- CloseableHandle open(String path, Collection<OpenMode> options) throws IOException;
-
- void close(Handle handle) throws IOException;
-
- void remove(String path) throws IOException;
-
- void rename(String oldPath, String newPath) throws IOException;
- void rename(String oldPath, String newPath, CopyMode... options) throws IOException;
- void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException;
-
- int read(Handle handle, long fileOffset, byte[] dst) throws IOException;
- int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException;
-
- void write(Handle handle, long fileOffset, byte[] src) throws IOException;
- void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException;
-
- void mkdir(String path) throws IOException;
-
- void rmdir(String path) throws IOException;
-
- CloseableHandle openDir(String path) throws IOException;
-
- DirEntry[] readDir(Handle handle) throws IOException;
-
- String canonicalPath(String canonical) throws IOException;
-
- Attributes stat(String path) throws IOException;
-
- Attributes lstat(String path) throws IOException;
-
- Attributes stat(Handle handle) throws IOException;
-
- void setStat(String path, Attributes attributes) throws IOException;
-
- void setStat(Handle handle, Attributes attributes) throws IOException;
-
- String readLink(String path) throws IOException;
-
- void symLink(String linkPath, String targetPath) throws IOException;
-
- void link(String linkPath, String targetPath, boolean symbolic) throws IOException;
-
- void lock(Handle handle, long offset, long length, int mask) throws IOException;
-
- void unlock(Handle handle, long offset, long length) throws IOException;
-
- //
- // High level API
- //
-
- Iterable<DirEntry> readDir(String path) throws IOException;
-
- // default values used if none specified
- int MIN_BUFFER_SIZE=Byte.MAX_VALUE, MIN_READ_BUFFER_SIZE=MIN_BUFFER_SIZE, MIN_WRITE_BUFFER_SIZE=MIN_BUFFER_SIZE;
- int IO_BUFFER_SIZE=32 * 1024, DEFAULT_READ_BUFFER_SIZE=IO_BUFFER_SIZE, DEFAULT_WRITE_BUFFER_SIZE=IO_BUFFER_SIZE;
- long DEFAULT_WAIT_TIMEOUT=TimeUnit.SECONDS.toMillis(30L);
-
- /**
- * Property that can be used on the {@link org.apache.sshd.common.FactoryManager}
- * to control the internal timeout used by the client to open a channel.
- * If not specified then {@link #DEFAULT_CHANNEL_OPEN_TIMEOUT} value
- * is used
- */
- String SFTP_CHANNEL_OPEN_TIMEOUT = "sftp-channel-open-timeout";
- long DEFAULT_CHANNEL_OPEN_TIMEOUT = DEFAULT_WAIT_TIMEOUT;
-
- InputStream read(String path) throws IOException;
- InputStream read(String path, int bufferSize) throws IOException;
- InputStream read(String path, OpenMode ... mode) throws IOException;
- InputStream read(String path, int bufferSize, OpenMode ... mode) throws IOException;
- InputStream read(String path, Collection<OpenMode> mode) throws IOException;
- InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
-
- OutputStream write(String path) throws IOException;
- OutputStream write(String path, int bufferSize) throws IOException;
- OutputStream write(String path, OpenMode ... mode) throws IOException;
- OutputStream write(String path, int bufferSize, OpenMode ... mode) throws IOException;
- OutputStream write(String path, Collection<OpenMode> mode) throws IOException;
- OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
-
-}
[06/10] mina-sshd git commit: [SSHD-509] Use targeted derived
NamedFactory(ies) for the various generic parameters
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
index 85f5d6d..1b60133 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
@@ -25,7 +25,6 @@ import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -43,7 +42,6 @@ import org.apache.sshd.common.io.mina.MinaServiceFactory;
import org.apache.sshd.common.io.nio2.Nio2ServiceFactory;
import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.server.auth.UserAuth;
@@ -63,9 +61,9 @@ import org.apache.sshd.server.session.ServerConnectionServiceFactory;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.session.ServerUserAuthServiceFactory;
import org.apache.sshd.server.session.SessionFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
import org.apache.sshd.server.shell.ProcessShellFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
/**
* The SshServer class is the main entry point for the server side of the SSH protocol.
@@ -390,16 +388,18 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
boolean error = false;
Map<String, String> options = new LinkedHashMap<String, String>();
- for (int i = 0; i < args.length; i++) {
- if ("-p".equals(args[i])) {
- if (i + 1 >= args.length) {
- System.err.println("option requires an argument: " + args[i]);
+ int numArgs = GenericUtils.length(args);
+ for (int i = 0; i < numArgs; i++) {
+ String argName = args[i];
+ if ("-p".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
break;
}
port = Integer.parseInt(args[++i]);
- } else if ("-io".equals(args[i])) {
- if (i + 1 >= args.length) {
- System.err.println("option requires an argument: " + args[i]);
+ } else if ("-io".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
break;
}
provider = args[++i];
@@ -408,12 +408,12 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
} else if ("nio2".endsWith(provider)) {
System.setProperty(IoServiceFactory.class.getName(), Nio2ServiceFactory.class.getName());
} else {
- System.err.println("provider should be mina or nio2: " + args[i]);
+ System.err.println("provider should be mina or nio2: " + argName);
break;
}
- } else if ("-o".equals(args[i])) {
- if (i + 1 >= args.length) {
- System.err.println("option requires and argument: " + args[i]);
+ } else if ("-o".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ System.err.println("option requires and argument: " + argName);
error = true;
break;
}
@@ -425,12 +425,12 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
break;
}
options.put(opt.substring(0, idx), opt.substring(idx + 1));
- } else if (args[i].startsWith("-")) {
- System.err.println("illegal option: " + args[i]);
+ } else if (argName.startsWith("-")) {
+ System.err.println("illegal option: " + argName);
error = true;
break;
} else {
- System.err.println("extra argument: " + args[i]);
+ System.err.println("extra argument: " + argName);
error = true;
break;
}
@@ -458,23 +458,17 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
- return username != null && username.equals(password);
+ return (username != null) && username.equals(password);
}
});
sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
sshd.setTcpipForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
sshd.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(new CommandFactory() {
- @Override
- public Command createCommand(String command) {
- EnumSet<ProcessShellFactory.TtyOptions> ttyOptions;
- if (OsUtils.isUNIX()) {
- ttyOptions = EnumSet.of(ProcessShellFactory.TtyOptions.ONlCr);
- } else {
- ttyOptions = EnumSet.of(ProcessShellFactory.TtyOptions.Echo, ProcessShellFactory.TtyOptions.ICrNl, ProcessShellFactory.TtyOptions.ONlCr);
+ @Override
+ public Command createCommand(String command) {
+ return new ProcessShellFactory(GenericUtils.split(command, ' ')).create();
}
- return new ProcessShellFactory(command.split(" "), ttyOptions).create();
- }
- }).build());
+ }).build());
sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
sshd.start();