Due to the upload requirements of implementing a message protocol over sftp
where truncated
messages aren't easily detected by the receiving system, the extension is
required to avoid
data corruption. In particular, the operation permits us to upload to a scratch
name to be
ignored by the pickup process, and then rename to a name that will pick up,
while atomically
erroring if the pickup name already exists.
In particular, the only safe upload sequence is:
rm working.@tmp@
put working.@tmp@
do {
ln working.@tmp@ serial.txt
increment serial
} while (ln reported file exists)
The ln operation is [email protected]. We cannot substitute a ren operation
here because
ren is allowed to clobber an existing file. Even if your code checks for this,
there is beneath
it a race condition because the rename() system call doesn't check, and we must
be race condition
free.
Anyway, I didn't think there were any more sftp providers left standing that
didn't implement
[email protected]; turns out I was wrong. I whipped up a quick patch to the
hosting provider
involved to demonstrate how easy this is to implement. The patch probably
works, but I have no
place to try it. I'm fairly confident because I just implemented a client side
version a few
months ago.
---
sshd-core_src_main_java_org_apache_sshd_server_subsystem_sftp_SftpSubsystem.orig.java
Thu Feb 25 11:35:59 2016
+++
sshd-core_src_main_java_org_apache_sshd_server_subsystem_sftp_SftpSubsystem.java
Thu Feb 25 12:02:38 2016
@@ -214,6 +214,7 @@
Collections.unmodifiableList(
Arrays.asList(
new OpenSSHExtension(FsyncExtensionParser.NAME,
"1")
+ new OpenSSHExtension(HardLinkExtensionParser.NAME,
"1")
));
public static final List<String> DEFAULT_OPEN_SSH_EXTENSIONS_NAMES =
@@ -599,6 +600,9 @@
case SftpConstants.EXT_SPACE_AVAILABLE:
doSpaceAvailable(buffer, id);
break;
+ case HardLinkExtensionParser.NAME:
+ doHardLink(buffer, id);
+ break;
default:
if (log.isDebugEnabled()) {
log.debug("executeExtendedCommand({}) received unsupported
SSH_FXP_EXTENDED({})", getServerSession(), extension);
@@ -606,6 +610,32 @@
sendStatus(BufferUtils.clear(buffer), id,
SftpConstants.SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(" + extension +
") is unsupported or not implemented");
break;
}
+ }
+
+ protected void doHardLink(Buffer buffer, int id) throws IOException {
+ String srcFile = buffer.getString();
+ String dstFile = buffer.getString();
+
+ try {
+ doHardLink(id, srcFile, dstFile);
+ } catch (IOException | RuntimeException e) {
+ sendStatus(BufferUtils.clear(buffer), id, e);
+ return;
+ }
+
+ sendStatus(BufferUtils.clear(buffer), id, SftpConstants.SSH_FX_OK, "");
+ }
+
+ protected void doHardLink(int id, String srcFile, String dstFile) throws
IOException {
+ if (log.isDebugEnabled()) {
+ log.debug("doHardLink({})[id={}] SSH_FXP_EXTENDED[{}] (src={},
dst={})",
+ getServerSession(), id, HardLinkExtensionParser.NAME;,
+ srcFile, dstFile);
+ }
+
+ Path src = resolveFile(srcFile);
+ Path dst = resolveFile(dstFile);
+ Files.createLink(src, dst);
}
protected void doSpaceAvailable(Buffer buffer, int id) throws IOException {