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/05/26 14:08:08 UTC
[2/2] mina-sshd git commit: [SSHD-476] Allow direct SCP file
upload/download to/from stream
[SSHD-476] Allow direct SCP file upload/download to/from stream
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/9b60dcc5
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/9b60dcc5
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/9b60dcc5
Branch: refs/heads/master
Commit: 9b60dcc53b4b2363b1923aa2e4bb033d3e49f03d
Parents: d7939e2
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Tue May 26 15:07:53 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Tue May 26 15:07:53 2015 +0300
----------------------------------------------------------------------
.../java/org/apache/sshd/ClientSession.java | 2 +-
.../sshd/agent/local/AgentForwardedChannel.java | 2 +-
.../org/apache/sshd/agent/unix/AgentClient.java | 2 +-
.../java/org/apache/sshd/client/ScpClient.java | 58 ---
.../sshd/client/scp/AbstractScpClient.java | 128 ++++++-
.../sshd/client/scp/DefaultScpClient.java | 189 +++++-----
.../org/apache/sshd/client/scp/ScpClient.java | 73 ++++
.../sshd/client/session/ClientSessionImpl.java | 2 +-
.../sshd/client/sftp/DefaultSftpClient.java | 2 +-
.../apache/sshd/client/sftp/SftpFileSystem.java | 4 +-
.../client/sftp/SftpFileSystemProvider.java | 59 +--
.../common/channel/ChannelOutputStream.java | 2 +-
.../common/channel/ChannelPipedInputStream.java | 2 +-
.../sshd/common/file/util/MockFileSystem.java | 113 ++++++
.../apache/sshd/common/file/util/MockPath.java | 186 ++++++++++
.../scp/LocalFileScpSourceStreamResolver.java | 90 +++++
.../scp/LocalFileScpTargetStreamResolver.java | 150 ++++++++
.../org/apache/sshd/common/scp/ScpHelper.java | 355 ++++++++-----------
.../sshd/common/scp/ScpReceiveLineHandler.java | 35 ++
.../common/scp/ScpSourceStreamResolver.java | 68 ++++
.../common/scp/ScpTargetStreamResolver.java | 59 +++
.../apache/sshd/common/scp/ScpTimestamp.java | 60 ++++
.../sshd/common/session/AbstractSession.java | 2 +-
.../sshd/common/util/io/LimitInputStream.java | 111 ++++++
.../sshd/server/command/ScpCommandFactory.java | 9 +-
.../java/org/apache/sshd/client/ScpTest.java | 92 +++--
.../common/util/io/LimitInputStreamTest.java | 111 ++++++
.../test/java/org/apache/sshd/util/Utils.java | 14 +-
28 files changed, 1565 insertions(+), 415 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
index 822e072..0f43cf1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java
@@ -24,13 +24,13 @@ import java.security.KeyPair;
import java.util.Map;
import org.apache.sshd.client.ClientFactoryManager;
-import org.apache.sshd.client.ScpClient;
import org.apache.sshd.client.UserInteraction;
import org.apache.sshd.client.channel.ChannelDirectTcpip;
import org.apache.sshd.client.channel.ChannelExec;
import org.apache.sshd.client.channel.ChannelShell;
import org.apache.sshd.client.channel.ChannelSubsystem;
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.common.Session;
import org.apache.sshd.common.SshdSocketAddress;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java b/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java
index 7901af2..ea0779a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentForwardedChannel.java
@@ -65,7 +65,7 @@ public class AgentForwardedChannel extends AbstractClientChannel {
}
return messages.poll();
} catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException().initCause(e);
+ throw (IOException) new InterruptedIOException("Interrupted while polling for messages").initCause(e);
}
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentClient.java b/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentClient.java
index 45b33e1..a8a5271 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/unix/AgentClient.java
@@ -134,7 +134,7 @@ public class AgentClient extends AbstractAgentProxy implements Runnable {
}
return messages.poll();
} catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException().initCause(e);
+ throw (IOException) new InterruptedIOException(authSocket + ": Interrupted while polling for messages").initCause(e);
}
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
deleted file mode 100644
index ed273d3..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
+++ /dev/null
@@ -1,58 +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;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collection;
-
-/**
- */
-public interface ScpClient {
-
- enum Option {
- Recursive,
- PreserveAttributes,
- TargetIsDirectory
- }
-
- void download(String remote, String local, Option... options) throws IOException;
- void download(String remote, String local, Collection<Option> options) throws IOException;
-
- void download(String remote, Path local, Option... options) throws IOException;
- void download(String remote, Path local, Collection<Option> options) throws IOException;
-
- void download(String[] remote, String local, Option... options) throws IOException;
- void download(String[] remote, String local, Collection<Option> options) throws IOException;
-
- void download(String[] remote, Path local, Option... options) throws IOException;
- void download(String[] remote, Path local, Collection<Option> options) throws IOException;
-
- void upload(String local, String remote, Option... options) throws IOException;
- void upload(String local, String remote, Collection<Option> options) throws IOException;
-
- void upload(Path local, String remote, Option... options) throws IOException;
- void upload(Path local, String remote, Collection<Option> options) throws IOException;
-
- void upload(String[] local, String remote, Option... options) throws IOException;
- void upload(String[] local, String remote, Collection<Option> options) throws IOException;
-
- void upload(Path[] local, String remote, Option... options) throws IOException;
- void upload(Path[] local, String remote, Collection<Option> options) throws IOException;
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/client/scp/AbstractScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/AbstractScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/AbstractScpClient.java
index f005d2f..d59000d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/AbstractScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/AbstractScpClient.java
@@ -19,20 +19,32 @@
package org.apache.sshd.client.scp;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
-import org.apache.sshd.client.ScpClient;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.scp.ScpTimestamp;
+import org.apache.sshd.common.util.AbstractLoggingBean;
import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public abstract class AbstractScpClient implements ScpClient {
+public abstract class AbstractScpClient extends AbstractLoggingBean implements ScpClient {
protected AbstractScpClient() {
super();
}
@@ -81,12 +93,50 @@ public abstract class AbstractScpClient implements ScpClient {
@Override
public void download(String remote, Path local, Option... options) throws IOException {
- download(remote, local, GenericUtils.isEmpty(options) ? Collections.<Option>emptySet() : GenericUtils.of(options));
+ download(remote, local, GenericUtils.of(options));
+ }
+
+ @Override
+ public void download(String remote, Path local, Collection<Option> options) throws IOException {
+ local = ValidateUtils.checkNotNull(local, "Invalid argument local: %s", local);
+ remote = ValidateUtils.checkNotNullAndNotEmpty(remote, "Invalid argument remote: %s", remote);
+
+ LinkOption[] opts = IoUtils.getLinkOptions(false);
+ if (Files.isDirectory(local, opts)) {
+ options = addTargetIsDirectory(options);
+ }
+
+ if (options.contains(Option.TargetIsDirectory)) {
+ Boolean status = IoUtils.checkFileExists(local, opts);
+ if (status == null) {
+ throw new SshException("Target directory " + local.toString() + " is probaly inaccesible");
+ }
+
+ if (!status.booleanValue()) {
+ throw new SshException("Target directory " + local.toString() + " does not exist");
+ }
+
+ if (!Files.isDirectory(local, opts)) {
+ throw new SshException("Target directory " + local.toString() + " is not a directory");
+ }
+ }
+
+ download(remote, local.getFileSystem(), local, options);
+ }
+
+ protected abstract void download(String remote, FileSystem fs, Path local, Collection<Option> options) throws IOException;
+
+ @Override
+ public byte[] downloadBytes(String remote) throws IOException {
+ try(ByteArrayOutputStream local = new ByteArrayOutputStream()) {
+ download(remote, local);
+ return local.toByteArray();
+ }
}
@Override
public void upload(String local, String remote, Option... options) throws IOException {
- upload(local, remote, GenericUtils.isEmpty(options) ? Collections.<Option>emptySet() : GenericUtils.of(options));
+ upload(local, remote, GenericUtils.of(options));
}
@Override
@@ -114,6 +164,42 @@ public abstract class AbstractScpClient implements ScpClient {
upload(local, remote, GenericUtils.isEmpty(options) ? Collections.<Option>emptySet() : GenericUtils.of(options));
}
+ @Override
+ public void upload(byte[] data, String remote, Collection<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
+ upload(data, 0, data.length, remote, perms, time);
+ }
+
+ @Override
+ public void upload(byte[] data, int offset, int len, String remote, Collection<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
+ try(InputStream local = new ByteArrayInputStream(data, offset, len)) {
+ upload(local, remote, len, perms, time);
+ }
+ }
+
+ @Override
+ public void upload(String[] local, String remote, Collection<Option> options) throws IOException {
+ final Collection<String> paths=Arrays.asList(ValidateUtils.checkNotNullAndNotEmpty(local, "Invalid argument local: %s", (Object) local));
+ runUpload(remote, options, paths, new ScpOperationExecutor<String>() {
+ @Override
+ public void execute(ScpHelper helper, Collection<String> local, Collection<Option> sendOptions) throws IOException {
+ helper.send(local, sendOptions.contains(Option.Recursive), sendOptions.contains(Option.PreserveAttributes), ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
+ }
+ });
+ }
+
+ @Override
+ public void upload(Path[] local, String remote, Collection<Option> options) throws IOException {
+ final Collection<Path> paths=Arrays.asList(ValidateUtils.checkNotNullAndNotEmpty(local, "Invalid argument local: %s", (Object) local));
+ runUpload(remote, options, paths, new ScpOperationExecutor<Path>() {
+ @Override
+ public void execute(ScpHelper helper, Collection<Path> local, Collection<Option> sendOptions) throws IOException {
+ helper.sendPaths(local, sendOptions.contains(Option.Recursive), sendOptions.contains(Option.PreserveAttributes), ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
+ }
+ });
+ }
+
+ protected abstract <T> void runUpload(String remote, Collection<Option> options, Collection<T> local, AbstractScpClient.ScpOperationExecutor<T> executor) throws IOException;
+
protected Collection<Option> addTargetIsDirectory(Collection<Option> options) {
if (GenericUtils.isEmpty(options) || (!options.contains(Option.TargetIsDirectory))) {
// create a copy in case the original collection is un-modifiable
@@ -123,4 +209,38 @@ public abstract class AbstractScpClient implements ScpClient {
return options;
}
+
+ public static String createSendCommand(String remote, Collection<Option> options) {
+ StringBuilder sb = new StringBuilder(remote.length() + Long.SIZE).append(ScpHelper.SCP_COMMAND_PREFIX);
+ if (options.contains(Option.Recursive)) {
+ sb.append(" -r");
+ }
+ if (options.contains(Option.TargetIsDirectory)) {
+ sb.append(" -d");
+ }
+ if (options.contains(Option.PreserveAttributes)) {
+ sb.append(" -p");
+ }
+
+ sb.append(" -t").append(" --").append(" ").append(remote);
+ return sb.toString();
+ }
+
+ public static String createReceiveCommand(String remote, Collection<Option> options) {
+ ValidateUtils.checkNotNullAndNotEmpty(remote, "No remote location specified", GenericUtils.EMPTY_OBJECT_ARRAY);
+ StringBuilder sb = new StringBuilder(remote.length() + Long.SIZE).append(ScpHelper.SCP_COMMAND_PREFIX);
+ if (options.contains(Option.Recursive)) {
+ sb.append(" -r");
+ }
+ if (options.contains(Option.PreserveAttributes)) {
+ sb.append(" -p");
+ }
+
+ sb.append(" -f").append(" --").append(' ').append(remote);
+ return sb.toString();
+ }
+
+ public static interface ScpOperationExecutor<T> {
+ void execute(ScpHelper helper, Collection<T> local, Collection<Option> options) throws IOException;
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
index 9c72dda..c4a977a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
@@ -19,21 +19,27 @@
package org.apache.sshd.client.scp;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InterruptedIOException;
+import java.io.OutputStream;
import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
import java.nio.file.Path;
-import java.util.Arrays;
+import java.nio.file.attribute.PosixFilePermission;
import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
import org.apache.sshd.ClientSession;
import org.apache.sshd.client.channel.ChannelExec;
-import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.util.MockFileSystem;
+import org.apache.sshd.common.file.util.MockPath;
import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.scp.ScpSourceStreamResolver;
+import org.apache.sshd.common.scp.ScpTimestamp;
import org.apache.sshd.common.scp.ScpTransferEventListener;
-import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
@@ -57,7 +63,8 @@ public class DefaultScpClient extends AbstractScpClient {
public void download(String remote, String local, Collection<Option> options) throws IOException {
local = ValidateUtils.checkNotNullAndNotEmpty(local, "Invalid argument local: %s", local);
- FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
+ FactoryManager manager = clientSession.getFactoryManager();
+ FileSystemFactory factory = manager.getFileSystemFactory();
FileSystem fs = factory.createFileSystem(clientSession);
try {
download(remote, fs, fs.getPath(local), options);
@@ -71,56 +78,36 @@ public class DefaultScpClient extends AbstractScpClient {
}
@Override
- public void download(String remote, Path local, Collection<Option> options) throws IOException {
- local = ValidateUtils.checkNotNull(local, "Invalid argument local: %s", local);
- download(remote, local.getFileSystem(), local, options);
- }
-
- protected void download(String remote, FileSystem fs, Path local, Collection<Option> options) throws IOException {
- local = ValidateUtils.checkNotNull(local, "Invalid argument local: %s", local);
- remote = ValidateUtils.checkNotNullAndNotEmpty(remote, "Invalid argument remote: %s", remote);
-
- LinkOption[] opts = IoUtils.getLinkOptions(false);
- if (Files.isDirectory(local, opts)) {
- options = addTargetIsDirectory(options);
- }
-
- if (options.contains(Option.TargetIsDirectory)) {
- Boolean status = IoUtils.checkFileExists(local, opts);
- if (status == null) {
- throw new SshException("Target directory " + local.toString() + " is probaly inaccesible");
- }
-
- if (!status.booleanValue()) {
- throw new SshException("Target directory " + local.toString() + " does not exist");
- }
- if (!Files.isDirectory(local, opts)) {
- throw new SshException("Target directory " + local.toString() + " is not a directory");
+ public void download(String remote, OutputStream local) throws IOException {
+ String cmd = createReceiveCommand(remote, Collections.<Option>emptyList());
+ ChannelExec channel = clientSession.createExecChannel(cmd);
+ try {
+ try {
+ channel.open().await();
+ } catch (InterruptedException e) {
+ throw (IOException) new InterruptedIOException("Interrupted while await channel open for download of " + remote).initCause(e);
}
- }
- StringBuilder sb = new StringBuilder("scp");
- if (options.contains(Option.Recursive)) {
- sb.append(" -r");
- }
- if (options.contains(Option.PreserveAttributes)) {
- sb.append(" -p");
+ // NOTE: we use a mock file system since we expect no invocations for it
+ ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), new MockFileSystem(remote), listener);
+ helper.receiveFileStream(local, ScpHelper.DEFAULT_RECEIVE_BUFFER_SIZE);
+ } finally {
+ channel.close(false);
}
- sb.append(" -f");
- sb.append(" --");
- sb.append(" ");
- sb.append(remote);
+ }
- ChannelExec channel = clientSession.createExecChannel(sb.toString());
+ @Override
+ protected void download(String remote, FileSystem fs, Path local, Collection<Option> options) throws IOException {
+ String cmd = createReceiveCommand(remote, options);
+ ChannelExec channel = clientSession.createExecChannel(cmd);
try {
try {
channel.open().await();
} catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException().initCause(e);
+ throw (IOException) new InterruptedIOException("Interrupted while await channel open for download of " + remote + " to " + local).initCause(e);
}
ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs, listener);
-
helper.receive(local,
options.contains(Option.Recursive),
options.contains(Option.TargetIsDirectory),
@@ -132,64 +119,84 @@ public class DefaultScpClient extends AbstractScpClient {
}
@Override
- public void upload(String[] local, String remote, Collection<Option> options) throws IOException {
- final Collection<String> paths=Arrays.asList(ValidateUtils.checkNotNullAndNotEmpty(local, "Invalid argument local: %s", (Object) local));
- runUpload(remote, options, paths, new ScpOperationExecutor<String>() {
- @Override
- public void execute(ScpHelper helper, Collection<String> local, Collection<Option> options) throws IOException {
- helper.send(local,
- options.contains(Option.Recursive),
- options.contains(Option.PreserveAttributes),
- ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
- }
- });
- }
+ public void upload(final InputStream local, final String remote, final long size, final Collection<PosixFilePermission> perms, final ScpTimestamp time) throws IOException {
+ int namePos = ValidateUtils.checkNotNullAndNotEmpty(remote, "No remote location specified", GenericUtils.EMPTY_OBJECT_ARRAY).lastIndexOf('/');
+ final String name = (namePos < 0)
+ ? remote
+ : ValidateUtils.checkNotNullAndNotEmpty(remote.substring(namePos + 1), "No name value in remote=%s", remote)
+ ;
+ final String cmd = createSendCommand(remote, (time != null) ? EnumSet.of(Option.PreserveAttributes) : Collections.<Option>emptySet());
+ ChannelExec channel = clientSession.createExecChannel(cmd);
+ try {
+ channel.open().await();
+ } catch (InterruptedException e) {
+ throw (IOException) new InterruptedIOException("Interrupted while await channel open for stream upload to " + remote).initCause(e);
+ }
- @Override
- public void upload(Path[] local, String remote, Collection<Option> options) throws IOException {
- final Collection<Path> paths=Arrays.asList(ValidateUtils.checkNotNullAndNotEmpty(local, "Invalid argument local: %s", (Object) local));
- runUpload(remote, options, paths, new ScpOperationExecutor<Path>() {
- @Override
- public void execute(ScpHelper helper, Collection<Path> local, Collection<Option> options) throws IOException {
- helper.sendPaths(local,
- options.contains(Option.Recursive),
- options.contains(Option.PreserveAttributes),
- ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
- }
- });
+ try {
+ ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), new MockFileSystem(remote), listener);
+ final Path mockPath = new MockPath(remote);
+ helper.sendStream(new ScpSourceStreamResolver() {
+ @Override
+ public String getFileName() throws IOException {
+ return name;
+ }
+
+ @Override
+ public Path getEventListenerFilePath() {
+ return mockPath;
+ }
+
+ @Override
+ public Collection<PosixFilePermission> getPermissions() throws IOException {
+ return perms;
+ }
+
+ @Override
+ public ScpTimestamp getTimestamp() throws IOException {
+ return time;
+ }
+
+ @Override
+ public long getSize() throws IOException {
+ return size;
+ }
+
+ @Override
+ public InputStream resolveSourceStream() throws IOException {
+ return local;
+ }
+
+ @Override
+ public String toString() {
+ return cmd;
+ }
+ },
+ (time != null), ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
+ } finally {
+ channel.close(false);
+ }
}
- protected <T> void runUpload(String remote, Collection<Option> options, Collection<T> local, ScpOperationExecutor<T> executor) throws IOException {
+ @Override
+ protected <T> void runUpload(String remote, Collection<Option> options, Collection<T> local, AbstractScpClient.ScpOperationExecutor<T> executor) throws IOException {
local = ValidateUtils.checkNotNullAndNotEmpty(local, "Invalid argument local: %s", local);
remote = ValidateUtils.checkNotNullAndNotEmpty(remote, "Invalid argument remote: %s", remote);
if (local.size() > 1) {
options = addTargetIsDirectory(options);
}
- StringBuilder sb = new StringBuilder("scp");
- if (options.contains(Option.Recursive)) {
- sb.append(" -r");
- }
- if (options.contains(Option.TargetIsDirectory)) {
- sb.append(" -d");
- }
- if (options.contains(Option.PreserveAttributes)) {
- sb.append(" -p");
- }
- sb.append(" -t");
- sb.append(" --");
- sb.append(" ");
- sb.append(remote);
-
- ChannelExec channel = clientSession.createExecChannel(sb.toString());
+ String cmd = createSendCommand(remote, options);
+ ChannelExec channel = clientSession.createExecChannel(cmd);
try {
channel.open().await();
} catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException().initCause(e);
+ throw (IOException) new InterruptedIOException("Interrupted while await channel open for upload to " + remote).initCause(e);
}
try {
- FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
+ FactoryManager manager = clientSession.getFactoryManager();
+ FileSystemFactory factory = manager.getFileSystemFactory();
FileSystem fs = factory.createFileSystem(clientSession);
try {
ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs, listener);
@@ -197,7 +204,7 @@ public class DefaultScpClient extends AbstractScpClient {
} finally {
try {
fs.close();
- } catch (UnsupportedOperationException e) {
+ } catch(UnsupportedOperationException e) {
// Ignore
}
}
@@ -205,8 +212,4 @@ public class DefaultScpClient extends AbstractScpClient {
channel.close(false);
}
}
-
- public static interface ScpOperationExecutor<T> {
- void execute(ScpHelper helper, Collection<T> local, Collection<Option> options) throws IOException;
- }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClient.java
new file mode 100644
index 0000000..ae7bb1e
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClient.java
@@ -0,0 +1,73 @@
+/*
+ * 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.scp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Collection;
+
+import org.apache.sshd.common.scp.ScpTimestamp;
+
+/**
+ */
+public interface ScpClient {
+
+ enum Option {
+ Recursive,
+ PreserveAttributes,
+ TargetIsDirectory
+ }
+
+ void download(String remote, String local, Option... options) throws IOException;
+ void download(String remote, String local, Collection<Option> options) throws IOException;
+
+ void download(String remote, Path local, Option... options) throws IOException;
+ void download(String remote, Path local, Collection<Option> options) throws IOException;
+
+ // NOTE: the remote location MUST be a file or an exception is generated
+ void download(String remote, OutputStream local) throws IOException;
+ byte[] downloadBytes(String remote) throws IOException;
+
+ void download(String[] remote, String local, Option... options) throws IOException;
+ void download(String[] remote, String local, Collection<Option> options) throws IOException;
+
+ void download(String[] remote, Path local, Option... options) throws IOException;
+ void download(String[] remote, Path local, Collection<Option> options) throws IOException;
+
+ void upload(String local, String remote, Option... options) throws IOException;
+ void upload(String local, String remote, Collection<Option> options) throws IOException;
+
+ void upload(Path local, String remote, Option... options) throws IOException;
+ void upload(Path local, String remote, Collection<Option> options) throws IOException;
+
+ void upload(String[] local, String remote, Option... options) throws IOException;
+ void upload(String[] local, String remote, Collection<Option> options) throws IOException;
+
+ void upload(Path[] local, String remote, Option... options) throws IOException;
+ void upload(Path[] local, String remote, Collection<Option> options) throws IOException;
+
+ // NOTE: due to SCP command limitations, the amount of data to be uploaded must be known a-priori
+ // To upload a dynamic amount of data use SFTP
+ void upload(byte[] data, String remote, Collection<PosixFilePermission> perms, ScpTimestamp time) throws IOException;
+ void upload(byte[] data, int offset, int len, String remote, Collection<PosixFilePermission> perms, ScpTimestamp time) throws IOException;
+ void upload(InputStream local, String remote, long size, Collection<PosixFilePermission> perms, ScpTimestamp time) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/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 c9a1656..09286d8 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
@@ -30,7 +30,6 @@ import java.util.Map;
import org.apache.sshd.ClientChannel;
import org.apache.sshd.ClientSession;
import org.apache.sshd.client.ClientFactoryManager;
-import org.apache.sshd.client.ScpClient;
import org.apache.sshd.client.ServerKeyVerifier;
import org.apache.sshd.client.UserInteraction;
import org.apache.sshd.client.channel.ChannelDirectTcpip;
@@ -40,6 +39,7 @@ import org.apache.sshd.client.channel.ChannelSubsystem;
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;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/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
index 3ee81c2..ebf80ed 100644
--- 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
@@ -269,7 +269,7 @@ public class DefaultSftpClient extends AbstractSftpClient {
try {
messages.wait();
} catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException().initCause(e);
+ throw (IOException) new InterruptedIOException("Interrupted while waiting for messages").initCause(e);
}
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/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
index dfc88f4..820099c 100644
--- 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
@@ -84,7 +84,9 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
@Override
public void close() throws IOException {
- session.close(true);
+ if (isOpen()) {
+ session.close(true);
+ }
}
@Override
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/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
index fc53d04..6eb2e61 100644
--- 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
@@ -75,14 +75,14 @@ import java.util.concurrent.TimeUnit;
import org.apache.sshd.ClientSession;
import org.apache.sshd.SshBuilder;
import org.apache.sshd.SshClient;
-import org.apache.sshd.client.sftp.SftpClient.Attributes;
import org.apache.sshd.client.SftpException;
+import org.apache.sshd.client.sftp.SftpClient.Attributes;
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.IoUtils;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
+import org.apache.sshd.common.util.ValidateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -118,27 +118,46 @@ public class SftpFileSystemProvider extends FileSystemProvider {
if (fileSystem != null) {
throw new FileSystemAlreadyExistsException(authority);
}
- String host = uri.getHost();
- String userInfo = uri.getUserInfo();
- if (host == null) {
- throw new IllegalArgumentException("Host not provided");
- }
- if (userInfo == null) {
- throw new IllegalArgumentException("UserInfo not provided");
+ 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;
}
- String[] ui = userInfo.split(":");
- ClientSession session;
+
+ ClientSession session=null;
try {
- session = client.connect(ui[0], host, uri.getPort() > 0 ? uri.getPort() : SshConfigFileReader.DEFAULT_PORT)
- .await().getSession();
- } catch (InterruptedException e) {
- throw new InterruptedIOException();
+ session = client.connect(ui[0], host, port).await().getSession();
+ session.addPasswordIdentity(ui[1]);
+ session.auth().verify();
+ fileSystem = new SftpFileSystem(this, session);
+ 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 InterruptedException) {
+ throw (IOException) new InterruptedIOException("Interrupted while waiting for connection to " + host + ":" + port).initCause(e);
+ } else if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else {
+ throw new IOException(e);
+ }
}
- session.addPasswordIdentity(ui[1]);
- session.auth().verify();
- fileSystem = new SftpFileSystem(this, session);
- fileSystems.put(authority, fileSystem);
- return fileSystem;
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java
index 92fee57..83aaaff 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelOutputStream.java
@@ -98,7 +98,7 @@ public class ChannelOutputStream extends OutputStream implements Channel {
closed = true;
throw e;
} catch (InterruptedException e) {
- throw (IOException)new InterruptedIOException().initCause(e);
+ throw (IOException)new InterruptedIOException("Interrupted while waiting for remote space").initCause(e);
}
}
continue;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
index e42b12c..f9fd7e1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
@@ -119,7 +119,7 @@ public class ChannelPipedInputStream extends InputStream implements ChannelPiped
dataAvailable.await();
}
} catch (InterruptedException e) {
- throw (IOException) new InterruptedIOException().initCause(e);
+ throw (IOException) new InterruptedIOException("Interrupted while waiting for data to become available").initCause(e);
}
}
if (len > buffer.available()) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/common/file/util/MockFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/util/MockFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/common/file/util/MockFileSystem.java
new file mode 100644
index 0000000..db5fab8
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/util/MockFileSystem.java
@@ -0,0 +1,113 @@
+/*
+ * 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.file.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MockFileSystem extends FileSystem {
+ private final AtomicBoolean open = new AtomicBoolean(true);
+ private final String name;
+
+ public MockFileSystem(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public FileSystemProvider provider() {
+ throw new UnsupportedOperationException("provider() N/A");
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (open.getAndSet(false)) {
+ return; // debug breakpoint
+ }
+ }
+
+ @Override
+ public boolean isOpen() {
+ return open.get();
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return true;
+ }
+
+ @Override
+ public String getSeparator() {
+ return File.separator;
+ }
+
+ @Override
+ public Iterable<Path> getRootDirectories() {
+ return Collections.<Path>emptyList();
+ }
+
+ @Override
+ public Iterable<FileStore> getFileStores() {
+ return Collections.<FileStore>emptyList();
+ }
+
+ @Override
+ public Set<String> supportedFileAttributeViews() {
+ return Collections.<String>emptySet();
+ }
+
+ @Override
+ public Path getPath(String first, String... more) {
+ throw new UnsupportedOperationException("getPath(" + first + ") " + Arrays.toString(more));
+ }
+
+ @Override
+ public PathMatcher getPathMatcher(String syntaxAndPattern) {
+ throw new UnsupportedOperationException("getPathMatcher(" + syntaxAndPattern + ")");
+ }
+
+ @Override
+ public UserPrincipalLookupService getUserPrincipalLookupService() {
+ throw new UnsupportedOperationException("getUserPrincipalLookupService() N/A");
+ }
+
+ @Override
+ public WatchService newWatchService() throws IOException {
+ throw new IOException("newWatchService() N/A");
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/common/file/util/MockPath.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/util/MockPath.java b/sshd-core/src/main/java/org/apache/sshd/common/file/util/MockPath.java
new file mode 100644
index 0000000..85711d4
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/util/MockPath.java
@@ -0,0 +1,186 @@
+/*
+ * 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.file.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent.Kind;
+import java.nio.file.WatchEvent.Modifier;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MockPath implements Path {
+ private final String path;
+ private final FileSystem fs;
+
+ public MockPath(String path) {
+ this.path = path;
+ this.fs = new MockFileSystem(path);
+ }
+
+ @Override
+ public FileSystem getFileSystem() {
+ return fs;
+ }
+
+ @Override
+ public boolean isAbsolute() {
+ return true;
+ }
+
+ @Override
+ public Path getRoot() {
+ return this;
+ }
+
+ @Override
+ public Path getFileName() {
+ return this;
+ }
+
+ @Override
+ public Path getParent() {
+ return null;
+ }
+
+ @Override
+ public int getNameCount() {
+ return 0;
+ }
+
+ @Override
+ public Path getName(int index) {
+ if (index == 0) {
+ return this;
+ } else {
+ throw new IllegalArgumentException("getName - bad index: " + index);
+ }
+ }
+
+ @Override
+ public Path subpath(int beginIndex, int endIndex) {
+ throw new UnsupportedOperationException("subPath(" + beginIndex + "," + endIndex + ") N/A");
+ }
+
+ @Override
+ public boolean startsWith(Path other) {
+ return startsWith(other.toString());
+ }
+
+ @Override
+ public boolean startsWith(String other) {
+ return path.startsWith(other);
+ }
+
+ @Override
+ public boolean endsWith(Path other) {
+ return endsWith(other.toString());
+ }
+
+ @Override
+ public boolean endsWith(String other) {
+ return path.endsWith(other);
+ }
+
+ @Override
+ public Path normalize() {
+ return this;
+ }
+
+ @Override
+ public Path resolve(Path other) {
+ return resolve(other.toString());
+ }
+
+ @Override
+ public Path resolve(String other) {
+ throw new UnsupportedOperationException("resolve(" + other + ") N/A");
+ }
+
+ @Override
+ public Path resolveSibling(Path other) {
+ return resolveSibling(other.toString());
+ }
+
+ @Override
+ public Path resolveSibling(String other) {
+ throw new UnsupportedOperationException("resolveSibling(" + other + ") N/A");
+ }
+
+ @Override
+ public Path relativize(Path other) {
+ throw new UnsupportedOperationException("relativize(" + other + ") N/A");
+ }
+
+ @Override
+ public URI toUri() {
+ throw new UnsupportedOperationException("toUri() N/A");
+ }
+
+ @Override
+ public Path toAbsolutePath() {
+ return this;
+ }
+
+ @Override
+ public Path toRealPath(LinkOption... options) throws IOException {
+ return this;
+ }
+
+ @Override
+ public File toFile() {
+ throw new UnsupportedOperationException("toFile() N/A");
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
+ return register(watcher, events, (Modifier[]) null);
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
+ throw new IOException("register(" + path + ") N/A");
+ }
+
+ @Override
+ public Iterator<Path> iterator() {
+ return Collections.<Path>singleton(this).iterator();
+ }
+
+ @Override
+ public int compareTo(Path other) {
+ return path.compareTo(other.toString());
+ }
+
+ @Override
+ public String toString() {
+ return path;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpSourceStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpSourceStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpSourceStreamResolver.java
new file mode 100644
index 0000000..7c2311e
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpSourceStreamResolver.java
@@ -0,0 +1,90 @@
+/*
+ * 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.scp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.sshd.common.util.AbstractLoggingBean;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LocalFileScpSourceStreamResolver extends AbstractLoggingBean implements ScpSourceStreamResolver {
+ private final Path path, name;
+ private final Set<PosixFilePermission> perms;
+ private final long size;
+ private final ScpTimestamp time;
+
+ public LocalFileScpSourceStreamResolver(Path path) throws IOException {
+ this.path = ValidateUtils.checkNotNull(path, "No path specified", GenericUtils.EMPTY_OBJECT_ARRAY);
+ this.name = path.getFileName();
+ this.perms = IoUtils.getPermissions(path);
+
+ BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
+ this.size = basic.size();
+ this.time = new ScpTimestamp(basic.lastModifiedTime().toMillis(), basic.lastAccessTime().toMillis());
+ }
+
+ @Override
+ public String getFileName() throws IOException {
+ return name.toString();
+ }
+
+ @Override
+ public Collection<PosixFilePermission> getPermissions() throws IOException {
+ return perms;
+ }
+
+ @Override
+ public ScpTimestamp getTimestamp() throws IOException {
+ return time;
+ }
+
+ @Override
+ public long getSize() throws IOException {
+ return size;
+ }
+
+ @Override
+ public Path getEventListenerFilePath() {
+ return path;
+ }
+
+ @Override
+ public InputStream resolveSourceStream() throws IOException {
+ return Files.newInputStream(getEventListenerFilePath());
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(getEventListenerFilePath());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpTargetStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpTargetStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpTargetStreamResolver.java
new file mode 100644
index 0000000..efd7f46
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpTargetStreamResolver.java
@@ -0,0 +1,150 @@
+/*
+ * 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.scp;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.util.AbstractLoggingBean;
+import org.apache.sshd.common.util.IoUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LocalFileScpTargetStreamResolver extends AbstractLoggingBean implements ScpTargetStreamResolver {
+ private final Path path;
+ private final Boolean status;
+ private Path file;
+
+ public LocalFileScpTargetStreamResolver(Path path) throws IOException {
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ this.status = IoUtils.checkFileExists(path, options);
+ if (status == null) {
+ throw new AccessDeniedException("Receive target file path existence status cannot be determined: " + path);
+ }
+
+ this.path = path;
+ }
+
+ @Override
+ public OutputStream resolveTargetStream(String name, long length, Set<PosixFilePermission> perms) throws IOException {
+ if (file != null) {
+ throw new StreamCorruptedException("resolveTargetStream(" + name + ")[" + perms + "] already resolved: " + file);
+ }
+
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ if (status.booleanValue() && Files.isDirectory(path, options)) {
+ String localName = name.replace('/', File.separatorChar); // in case we are running on Windows
+ file = path.resolve(localName);
+ } else if (status.booleanValue() && Files.isRegularFile(path, options)) {
+ file = path;
+ } else if (!status.booleanValue()) {
+ Path parent = path.getParent();
+
+ Boolean parentStatus = IoUtils.checkFileExists(parent, options);
+ if (parentStatus == null) {
+ throw new AccessDeniedException("Receive file parent (" + parent + ") existence status cannot be determined for " + path);
+ }
+
+ if (parentStatus.booleanValue() && Files.isDirectory(parent, options)) {
+ file = path;
+ }
+ }
+
+ if (file == null) {
+ throw new IOException("Can not write to " + path);
+ }
+
+ Boolean fileStatus = IoUtils.checkFileExists(file, options);
+ if (fileStatus == null) {
+ throw new AccessDeniedException("Receive file existence status cannot be determined: " + file);
+ }
+
+ if (fileStatus.booleanValue()) {
+ if (Files.isDirectory(file, options)) {
+ throw new IOException("File is a directory: " + file);
+ }
+
+ if (!Files.isWritable(file)) {
+ throw new IOException("Can not write to file: " + file);
+ }
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("resolveTargetStream(" + name + "): " + file);
+ }
+
+ return Files.newOutputStream(file);
+ }
+
+ @Override
+ public Path getEventListenerFilePath() {
+ if (file == null) {
+ return path;
+ } else {
+ return file;
+ }
+ }
+
+ @Override
+ public void postProcessReceivedData(String name, boolean preserve, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
+ if (file == null) {
+ throw new StreamCorruptedException("postProcessReceivedData(" + name + ")[" + perms + "] No currently resolved data");
+ }
+
+ if (preserve) {
+ updateFileProperties(name, file, perms, time);
+ }
+ }
+
+ protected void updateFileProperties(String name, Path path, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
+ if (log.isTraceEnabled()) {
+ log.trace("updateFileProperties(" + name + ")[" + path + "] permissions: " + perms);
+ }
+ IoUtils.setPermissions(path, perms);
+
+ if (time != null) {
+ BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class);
+ FileTime lastModified = FileTime.from(time.lastModifiedTime, TimeUnit.MILLISECONDS);
+ FileTime lastAccess = FileTime.from(time.lastAccessTime, TimeUnit.MILLISECONDS);
+ if (log.isTraceEnabled()) {
+ log.trace("updateFileProperties(" + name + ")[" + path + "] last-modified=" + lastModified + ", last-access=" + lastAccess);
+ }
+
+ view.setTimes(lastModified, lastAccess, null);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(getEventListenerFilePath());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/9b60dcc5/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index d1ee197..b7d244e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -21,10 +21,10 @@ package org.apache.sshd.common.scp;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
-import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.StreamCorruptedException;
import java.nio.file.AccessDeniedException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
@@ -41,16 +41,23 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.file.util.MockPath;
import org.apache.sshd.common.scp.ScpTransferEventListener.FileOperation;
import org.apache.sshd.common.util.AbstractLoggingBean;
import org.apache.sshd.common.util.DirectoryScanner;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.io.LimitInputStream;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class ScpHelper extends AbstractLoggingBean {
+ /**
+ * Command prefix used to identify SCP commands
+ */
+ public static final String SCP_COMMAND_PREFIX = "scp";
+
public static final int OK = 0;
public static final int WARNING = 1;
public static final int ERROR = 2;
@@ -91,46 +98,98 @@ public class ScpHelper extends AbstractLoggingBean {
this.listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
}
- public void receive(Path path, boolean recursive, boolean shouldBeDir, boolean preserve, int bufferSize) throws IOException {
+ public void receiveFileStream(final OutputStream local, final int bufferSize) throws IOException {
+ receive(new ScpReceiveLineHandler() {
+ @Override
+ public void process(final String line, boolean isDir, ScpTimestamp timestamp) throws IOException {
+ if (isDir) {
+ throw new StreamCorruptedException("Cannot download a directory into a file stream: " + line);
+ }
+
+ final Path path = new MockPath(line);
+ receiveStream(line, new ScpTargetStreamResolver() {
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public OutputStream resolveTargetStream(String name, long length, Set<PosixFilePermission> perms) throws IOException {
+ if (log.isDebugEnabled()) {
+ log.debug("resolveTargetStream(" + name + ")[" + perms + "][len=" + length + "] started local stream download");
+ }
+ return local;
+ }
+
+ @Override
+ public Path getEventListenerFilePath() {
+ return path;
+ }
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void postProcessReceivedData(String name, boolean preserve, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
+ if (log.isDebugEnabled()) {
+ log.debug("postProcessReceivedData(" + name + ")[" + perms + "][time=" + time + "] ended local stream download");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return line;
+ }
+ }, timestamp, false, bufferSize);
+ }
+ });
+ }
+
+ public void receive(final Path path, final boolean recursive, boolean shouldBeDir, final boolean preserve, final int bufferSize) throws IOException {
if (shouldBeDir) {
- LinkOption[] options=IoUtils.getLinkOptions(false);
- Boolean status=IoUtils.checkFileExists(path, options);
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ Boolean status = IoUtils.checkFileExists(path, options);
if (status == null) {
- throw new SshException("Target directory " + path.toString() + " is most like inaccessible");
+ throw new SshException("Target directory " + path + " is most like inaccessible");
}
if (!status.booleanValue()) {
- throw new SshException("Target directory " + path.toString() + " does not exist");
+ throw new SshException("Target directory " + path + " does not exist");
}
if (!Files.isDirectory(path, options)) {
- throw new SshException("Target directory " + path.toString() + " is not a directory");
+ throw new SshException("Target directory " + path + " is not a directory");
}
}
+ receive(new ScpReceiveLineHandler() {
+ @Override
+ public void process(String line, boolean isDir, ScpTimestamp time) throws IOException {
+ if (recursive && isDir) {
+ receiveDir(line, path, time, preserve, bufferSize);
+ } else {
+ receiveFile(line, path, time, preserve, bufferSize);
+ }
+ }
+ });
+ }
+
+ protected void receive(ScpReceiveLineHandler handler) throws IOException {
ack();
- long[] time = null;
- for (;;)
- {
+ ScpTimestamp time = null;
+ for (;;) {
String line;
boolean isDir = false;
int c = readAck(true);
- switch (c)
- {
+ switch (c) {
case -1:
return;
case 'D':
isDir = true;
case 'C':
- line = ((char) c) + readLine();
+ line = String.valueOf((char) c) + readLine();
log.debug("Received header: " + line);
break;
case 'T':
- line = ((char) c) + readLine();
+ line = String.valueOf((char) c) + readLine();
log.debug("Received header: " + line);
- time = parseTime(line);
+ time = ScpTimestamp.parseTime(line);
ack();
continue;
case 'E':
- line = ((char) c) + readLine();
+ line = String.valueOf((char) c) + readLine();
log.debug("Received header: " + line);
ack();
return;
@@ -139,21 +198,14 @@ public class ScpHelper extends AbstractLoggingBean {
continue;
}
- if (recursive && isDir)
- {
- receiveDir(line, path, time, preserve, bufferSize);
- time = null;
- }
- else
- {
- receiveFile(line, path, time, preserve, bufferSize);
+ try {
+ handler.process(line, isDir, time);
+ } finally {
time = null;
}
}
}
-
-
- public void receiveDir(String header, Path path, long[] time, boolean preserve, int bufferSize) throws IOException {
+ public void receiveDir(String header, Path path, ScpTimestamp time, boolean preserve, int bufferSize) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Receiving directory {}", path);
}
@@ -169,8 +221,8 @@ public class ScpHelper extends AbstractLoggingBean {
throw new IOException("Expected 0 length for directory but got " + length);
}
- LinkOption[] options=IoUtils.getLinkOptions(false);
- Boolean status=IoUtils.checkFileExists(path, options);
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ Boolean status = IoUtils.checkFileExists(path, options);
if (status == null) {
throw new AccessDeniedException("Receive directory existence status cannot be determined: " + path);
}
@@ -180,7 +232,7 @@ public class ScpHelper extends AbstractLoggingBean {
String localName = name.replace('/', File.separatorChar);
file = path.resolve(localName);
} else if (!status.booleanValue()) {
- Path parent=path.getParent();
+ Path parent = path.getParent();
status = IoUtils.checkFileExists(parent, options);
if (status == null) {
@@ -206,13 +258,7 @@ public class ScpHelper extends AbstractLoggingBean {
}
if (preserve) {
- IoUtils.setPermissions(path, perms);
- if (time != null) {
- Files.getFileAttributeView(file, BasicFileAttributeView.class)
- .setTimes(FileTime.from(time[0], TimeUnit.SECONDS),
- FileTime.from(time[1], TimeUnit.SECONDS),
- null);
- }
+ updateFileProperties(file, perms, time);
}
ack();
@@ -235,7 +281,7 @@ public class ScpHelper extends AbstractLoggingBean {
ack();
break;
} else if (header.startsWith("T")) {
- time = parseTime(header);
+ time = ScpTimestamp.parseTime(header);
ack();
} else {
throw new IOException("Unexpected message: '" + header + "'");
@@ -247,30 +293,35 @@ public class ScpHelper extends AbstractLoggingBean {
}
}
- public void receiveFile(String header, Path path, long[] time, boolean preserve, int bufferSize) throws IOException {
+ public void receiveFile(String header, Path path, ScpTimestamp time, boolean preserve, int bufferSize) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Receiving file {}", path);
}
+
+ receiveStream(header, new LocalFileScpTargetStreamResolver(path), time, preserve, bufferSize);
+ }
+
+ public void receiveStream(String header, ScpTargetStreamResolver resolver, ScpTimestamp time, boolean preserve, int bufferSize) throws IOException {
if (!header.startsWith("C")) {
- throw new IOException("Expected a C message but got '" + header + "'");
+ throw new IOException("receiveStream(" + resolver + ") Expected a C message but got '" + header + "'");
}
if (bufferSize < MIN_RECEIVE_BUFFER_SIZE) {
- throw new IOException("receiveFile(" + path + ") buffer size (" + bufferSize + ") below minimum (" + MIN_RECEIVE_BUFFER_SIZE + ")");
+ throw new IOException("receiveStream(" + resolver + ") buffer size (" + bufferSize + ") below minimum (" + MIN_RECEIVE_BUFFER_SIZE + ")");
}
Set<PosixFilePermission> perms = parseOctalPerms(header.substring(1, 5));
final long length = Long.parseLong(header.substring(6, header.indexOf(' ', 6)));
String name = header.substring(header.indexOf(' ', 6) + 1);
if (length < 0L) { // TODO consider throwing an exception...
- log.warn("receiveFile(" + path + ") bad length in header: " + header);
+ log.warn("receiveStream(" + resolver + ") bad length in header: " + header);
}
// if file size is less than buffer size allocate only expected file size
int bufSize;
if (length == 0L) {
if (log.isDebugEnabled()) {
- log.debug("receiveFile(" + path + ") zero file size (perhaps special file) using copy buffer size=" + MIN_RECEIVE_BUFFER_SIZE);
+ log.debug("receiveStream(" + resolver + ") zero file size (perhaps special file) using copy buffer size=" + MIN_RECEIVE_BUFFER_SIZE);
}
bufSize = MIN_RECEIVE_BUFFER_SIZE;
} else {
@@ -278,60 +329,17 @@ public class ScpHelper extends AbstractLoggingBean {
}
if (bufSize < 0) { // TODO consider throwing an exception
- log.warn("receiveFile(" + path + ") bad buffer size (" + bufSize + ") using default (" + MIN_RECEIVE_BUFFER_SIZE + ")");
+ log.warn("receiveFile(" + resolver + ") bad buffer size (" + bufSize + ") using default (" + MIN_RECEIVE_BUFFER_SIZE + ")");
bufSize = MIN_RECEIVE_BUFFER_SIZE;
}
- LinkOption[] options=IoUtils.getLinkOptions(false);
- Boolean status=IoUtils.checkFileExists(path, options);
- if (status == null) {
- throw new AccessDeniedException("Receive target file path existence status cannot be determined: " + path);
- }
-
- Path file=null;
- if (status.booleanValue() && Files.isDirectory(path, options)) {
- String localName = name.replace('/', File.separatorChar);
- file = path.resolve(localName);
- } else if (status.booleanValue() && Files.isRegularFile(path, options)) {
- file = path;
- } else if (!status.booleanValue()) {
- Path parent=path.getParent();
-
- status = IoUtils.checkFileExists(parent, options);
- if (status == null) {
- throw new AccessDeniedException("Receive file parent (" + parent + ") existence status cannot be determined for " + path);
- }
-
- if (status.booleanValue() && Files.isDirectory(parent, options)) {
- file = path;
- }
- }
-
- if (file == null) {
- throw new IOException("Can not write to " + path);
- }
-
- status = IoUtils.checkFileExists(file, options);
- if (status == null) {
- throw new AccessDeniedException("Receive file existence status cannot be determined: " + file);
- }
-
- if (status.booleanValue()) {
- if (Files.isDirectory(file, options)) {
- throw new IOException("File is a directory: " + file);
- }
-
- if (!Files.isWritable(file)) {
- throw new IOException("Can not write to file: " + file);
- }
- }
-
try (
InputStream is = new LimitInputStream(this.in, length);
- OutputStream os = Files.newOutputStream(file)
+ OutputStream os = resolver.resolveTargetStream(name, length, perms)
) {
ack();
+ Path file = resolver.getEventListenerFilePath();
try {
listener.startFileEvent(FileOperation.RECEIVE, file, length, perms);
IoUtils.copy(is, os, bufSize);
@@ -342,18 +350,28 @@ public class ScpHelper extends AbstractLoggingBean {
}
}
- if (preserve) {
- IoUtils.setPermissions(file, perms);
- if (time != null) {
- Files.getFileAttributeView(file, BasicFileAttributeView.class)
- .setTimes(FileTime.from(time[0], TimeUnit.SECONDS),
- FileTime.from(time[1], TimeUnit.SECONDS),
- null);
- }
- }
+ resolver.postProcessReceivedData(name, preserve, perms, time);
ack();
readAck(false);
+
+ }
+
+ protected void updateFileProperties(Path file, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
+ if (log.isTraceEnabled()) {
+ log.trace("updateFileProperties(" + file + ") permissions: " + perms);
+ }
+ IoUtils.setPermissions(file, perms);
+
+ if (time != null) {
+ BasicFileAttributeView view = Files.getFileAttributeView(file, BasicFileAttributeView.class);
+ FileTime lastModified = FileTime.from(time.lastModifiedTime, TimeUnit.MILLISECONDS);
+ FileTime lastAccess = FileTime.from(time.lastAccessTime, TimeUnit.MILLISECONDS);
+ if (log.isTraceEnabled()) {
+ log.trace("updateFileProperties(" + file + ") last-modified=" + lastModified + ", last-access=" + lastAccess);
+ }
+ view.setTimes(lastModified, lastAccess, null);
+ }
}
public String readLine() throws IOException {
@@ -472,50 +490,20 @@ public class ScpHelper extends AbstractLoggingBean {
log.debug("Sending file {}", path);
}
- if (bufferSize < MIN_SEND_BUFFER_SIZE) {
- throw new IOException("sendFile(" + path + ") buffer size (" + bufferSize + ") below minimum (" + MIN_SEND_BUFFER_SIZE + ")");
- }
-
- BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
- if (preserve) {
- StringBuilder buf = new StringBuilder();
- buf.append("T");
- buf.append(basic.lastModifiedTime().to(TimeUnit.SECONDS));
- buf.append(" ");
- buf.append("0");
- buf.append(" ");
- buf.append(basic.lastAccessTime().to(TimeUnit.SECONDS));
- buf.append(" ");
- buf.append("0");
- buf.append("\n");
- out.write(buf.toString().getBytes());
- out.flush();
- readAck(false);
- }
-
- Set<PosixFilePermission> perms = IoUtils.getPermissions(path);
- StringBuilder buf = new StringBuilder();
- buf.append("C");
- buf.append(preserve ? getOctalPerms(perms) : "0644");
- buf.append(" ");
- buf.append(basic.size()); // length
- buf.append(" ");
- buf.append(path.getFileName().toString());
- buf.append("\n");
- out.write(buf.toString().getBytes());
- out.flush();
- readAck(false);
+ sendStream(new LocalFileScpSourceStreamResolver(path), preserve, bufferSize);
+ }
- long fileSize = Files.size(path);
- if (fileSize < 0L) { // TODO consider throwing an exception...
- log.warn("sendFile(" + path + ") bad file size: " + fileSize);
+ public void sendStream(ScpSourceStreamResolver resolver, boolean preserve, int bufferSize) throws IOException {
+ if (bufferSize < MIN_SEND_BUFFER_SIZE) {
+ throw new IOException("sendStream(" + resolver + ") buffer size (" + bufferSize + ") below minimum (" + MIN_SEND_BUFFER_SIZE + ")");
}
+ long fileSize = resolver.getSize();
// if file size is less than buffer size allocate only expected file size
int bufSize;
- if (fileSize == 0L) {
+ if (fileSize <= 0L) {
if (log.isDebugEnabled()) {
- log.debug("sendFile(" + path + ") zero file size (perhaps special file) using copy buffer size=" + MIN_SEND_BUFFER_SIZE);
+ log.debug("sendStream(" + resolver + ") unknown file size (" + fileSize + ") perhaps special file - using copy buffer size=" + MIN_SEND_BUFFER_SIZE);
}
bufSize = MIN_SEND_BUFFER_SIZE;
} else {
@@ -523,11 +511,37 @@ public class ScpHelper extends AbstractLoggingBean {
}
if (bufSize < 0) { // TODO consider throwing an exception
- log.warn("sendFile(" + path + ") bad buffer size (" + bufSize + ") using default (" + MIN_SEND_BUFFER_SIZE + ")");
+ log.warn("sendStream(" + resolver + ") bad buffer size (" + bufSize + ") using default (" + MIN_SEND_BUFFER_SIZE + ")");
bufSize = MIN_SEND_BUFFER_SIZE;
}
- try (InputStream in = Files.newInputStream(path)) {
+ ScpTimestamp time = resolver.getTimestamp();
+ if (preserve && (time != null)) {
+ String cmd = new StringBuilder(Long.SIZE)
+ .append('T').append(TimeUnit.MILLISECONDS.toSeconds(time.lastModifiedTime)).append(' ').append('0')
+ .append(' ').append(TimeUnit.MILLISECONDS.toSeconds(time.lastAccessTime)).append(' ').append('0')
+ .append('\n')
+ .toString();
+ out.write(cmd.getBytes());
+ out.flush();
+ readAck(false);
+ }
+
+ Set<PosixFilePermission> perms = EnumSet.copyOf(resolver.getPermissions());
+ String octalPerms = preserve ? getOctalPerms(perms) : "0644";
+ String fileName = resolver.getFileName();
+ String cmd = new StringBuilder(octalPerms.length() + fileName.length() + Long.SIZE /* some extra delimiters */)
+ .append('C').append(octalPerms)
+ .append(' ').append(fileSize)
+ .append(' ').append(fileName)
+ .append('\n')
+ .toString();
+ out.write(cmd.getBytes());
+ out.flush();
+ readAck(false);
+
+ try (InputStream in = resolver.resolveSourceStream()) {
+ Path path = resolver.getEventListenerFilePath();
try {
listener.startFileEvent(FileOperation.SEND, path, fileSize, perms);
IoUtils.copy(in, out, bufSize);
@@ -545,6 +559,7 @@ public class ScpHelper extends AbstractLoggingBean {
if (log.isDebugEnabled()) {
log.debug("Sending directory {}", path);
}
+
BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
if (preserve) {
StringBuilder buf = new StringBuilder();
@@ -579,7 +594,7 @@ public class ScpHelper extends AbstractLoggingBean {
listener.startFolderEvent(FileOperation.SEND, path, perms);
try {
- LinkOption[] options = IoUtils.getLinkOptions(false);
+ LinkOption[] options = IoUtils.getLinkOptions(false);
for (Path child : children) {
if (Files.isRegularFile(child, options)) {
sendFile(child, preserve, bufferSize);
@@ -600,11 +615,6 @@ public class ScpHelper extends AbstractLoggingBean {
readAck(false);
}
- private long[] parseTime(String line) {
- String[] numbers = line.substring(1).split(" ");
- return new long[]{Long.parseLong(numbers[0]), Long.parseLong(numbers[2])};
- }
-
public static String getOctalPerms(Path path) throws IOException {
return getOctalPerms(IoUtils.getPermissions(path));
}
@@ -713,61 +723,4 @@ public class ScpHelper extends AbstractLoggingBean {
}
return c;
}
-
- private static class LimitInputStream extends FilterInputStream {
-
- private long remaining;
-
- public LimitInputStream(InputStream in, long length) {
- super(in);
- remaining = length;
- }
-
- @Override
- public int read() throws IOException {
- if (remaining > 0) {
- remaining--;
- return super.read();
- } else {
- return -1;
- }
- }
-
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- int nb = len;
- if (nb > remaining) {
- nb = (int) remaining;
- }
- if (nb > 0) {
- int read = super.read(b, off, nb);
- remaining -= read;
- return read;
- } else {
- return -1;
- }
- }
-
- @Override
- public long skip(long n) throws IOException {
- long skipped = super.skip(n);
- remaining -= skipped;
- return skipped;
- }
-
- @Override
- public int available() throws IOException {
- int av = super.available();
- if (av > remaining) {
- return (int) remaining;
- } else {
- return av;
- }
- }
-
- @Override
- public void close() throws IOException {
- // do not close the original input stream since it serves for ACK(s)
- }
- }
}