[SSHD-1288] SFTP: fix reading files that are being written
If data is appended while a file is read via SFTP, SftpInputStreamAsync
would enter an infinite loop if requestOffset >= fileSize + bufferSize.
Fix this and issue only sequential read requests once we detect that
we're beyond the expected EOF.
diff --git a/CHANGES.md b/CHANGES.md
index 842a961..a4d57fd 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -22,4 +22,5 @@
* [SSHD-1281](https://issues.apache.org/jira/browse/SSHD-1281) ClientSession.auth().verify() is terminated with timeout
* [SSHD-1285](https://issues.apache.org/jira/browse/SSHD-1285) 2.9.0 release broken on Java 8
+* [SSHD-1288](https://issues.apache.org/jira/browse/SSHD-1288) SFTP: fix reading files that are being written
* [SSHD-1289](https://issues.apache.org/jira/browse/SSHD-1289) Deadlock during session exit
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpInputStreamAsync.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpInputStreamAsync.java
index 12ea4e5..d846c66 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpInputStreamAsync.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpInputStreamAsync.java
@@ -228,8 +228,11 @@
Session session = client.getSession();
byte[] id = handle.getIdentifier();
boolean traceEnabled = log.isTraceEnabled();
- while (pendingReads.size() < Math.max(1, windowSize / bufferSize)
- && (fileSize <= 0 || requestOffset < fileSize + bufferSize)) {
+ if (fileSize > 0 && requestOffset > fileSize && !pendingReads.isEmpty()) {
+ // We'd be issuing requests for reading beyond the expected EOF. Do that only one by one.
+ return;
+ }
+ while (pendingReads.size() < Math.max(1, windowSize / bufferSize)) {
Buffer buf = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_DATA,
23 /* sftp packet */ + 16 + id.length);
buf.rpos(23);
@@ -244,6 +247,9 @@
}
pendingReads.add(ack);
requestOffset += bufferSize;
+ if (fileSize > 0 && requestOffset > fileSize) {
+ break;
+ }
}
}
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
index f247ed5..4351040 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
@@ -244,6 +244,51 @@
}
}
+ @Test // see SSHD-1288
+ public void testReadWriteDownload() throws Exception {
+ Assume.assumeTrue("Not sure appending to a file opened for reading works on Windows",
+ OsUtils.isUNIX() || OsUtils.isOSX());
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path lclSftp = CommonTestSupportUtils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
+ getCurrentTestName());
+ Path testFile = assertHierarchyTargetFolderExists(lclSftp).resolve("file.bin");
+ byte[] expected = new byte[1024 * 1024];
+
+ Factory<? extends Random> factory = sshd.getRandomFactory();
+ Random rnd = factory.create();
+ rnd.fill(expected);
+ Files.write(testFile, expected);
+
+ String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
+ try (SftpClient sftp = createSingleSessionClient()) {
+ byte[] actual;
+ try (InputStream in = sftp.read(file); ByteArrayOutputStream buf = new ByteArrayOutputStream(2 * expected.length)) {
+ Files.write(testFile, expected, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
+ byte[] data = new byte[4096];
+ for (int n = 0; n >= 0;) {
+ n = in.read(data, 0, data.length);
+ if (n > 0) {
+ buf.write(data, 0, n);
+ }
+ }
+ actual = buf.toByteArray();
+ }
+ assertEquals("Short read", 2 * expected.length, actual.length);
+ for (int i = 0, j = 0; i < actual.length; i++, j++) {
+ if (j >= expected.length) {
+ j = 0;
+ }
+ byte expByte = expected[j];
+ byte actByte = actual[i];
+ if (expByte != actByte) {
+ fail("Mismatched values at index=" + i + ": expected=0x" + Integer.toHexString(expByte & 0xFF)
+ + ", actual=0x" + Integer.toHexString(actByte & 0xFF));
+ }
+ }
+ }
+ }
+
@Test // see extra fix for SSHD-538
public void testNavigateBeyondRootFolder() throws Exception {
Path rootLocation = Paths.get(OsUtils.isUNIX() ? "/" : "C:\\");