Skip to content

Commit 3835cb4

Browse files
authored
LUCENE-9686: Fix read past EOF handling in DirectIODirectory (#2258)
1 parent 15aaec6 commit 3835cb4

File tree

2 files changed

+67
-10
lines changed

2 files changed

+67
-10
lines changed

lucene/misc/src/java/org/apache/lucene/misc/store/DirectIODirectory.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,8 @@ public void seek(long pos) throws IOException {
359359
filePos = alignedPos - buffer.capacity();
360360

361361
final int delta = (int) (pos - alignedPos);
362-
refill();
363-
try {
364-
buffer.position(delta);
365-
} catch (IllegalArgumentException iae) {
366-
throw new EOFException("read past EOF: " + this);
367-
}
362+
refill(delta);
363+
buffer.position(delta);
368364
}
369365
assert pos == getFilePointer();
370366
}
@@ -381,17 +377,18 @@ public long length() {
381377
@Override
382378
public byte readByte() throws IOException {
383379
if (!buffer.hasRemaining()) {
384-
refill();
380+
refill(1);
385381
}
382+
386383
return buffer.get();
387384
}
388385

389-
private void refill() throws IOException {
386+
private void refill(int bytesToRead) throws IOException {
390387
filePos += buffer.capacity();
391388

392389
// BaseDirectoryTestCase#testSeekPastEOF test for consecutive read past EOF,
393390
// hence throwing EOFException early to maintain buffer state (position in particular)
394-
if (filePos > channel.size()) {
391+
if (filePos > channel.size() || (channel.size() - filePos < bytesToRead)) {
395392
throw new EOFException("read past EOF: " + this);
396393
}
397394

@@ -417,7 +414,7 @@ public void readBytes(byte[] dst, int offset, int len) throws IOException {
417414
buffer.get(dst, offset, left);
418415
toRead -= left;
419416
offset += left;
420-
refill();
417+
refill(toRead);
421418
} else {
422419
buffer.get(dst, offset, toRead);
423420
break;

lucene/misc/src/test/org/apache/lucene/misc/store/TestDirectIODirectory.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package org.apache.lucene.misc.store;
1818

19+
import com.carrotsearch.randomizedtesting.RandomizedTest;
20+
import java.io.EOFException;
1921
import java.io.IOException;
2022
import java.nio.file.Files;
2123
import java.nio.file.Path;
@@ -77,10 +79,68 @@ public void testIllegalEOFWithFileSizeMultipleOfBlockSize() throws Exception {
7779
o.close();
7880
IndexInput i = dir.openInput("out", newIOContext(random()));
7981
i.seek(fileSize);
82+
83+
// Seeking past EOF should always throw EOFException
84+
expectThrows(
85+
EOFException.class, () -> i.seek(fileSize + RandomizedTest.randomIntBetween(1, 2048)));
86+
87+
// Reading immediately after seeking past EOF should throw EOFException
88+
expectThrows(EOFException.class, () -> i.readByte());
8089
i.close();
8190
}
8291
}
8392

93+
public void testReadPastEOFShouldThrowEOFExceptionWithEmptyFile() throws Exception {
94+
// fileSize needs to be 0 to test this condition. Do not randomized.
95+
final int fileSize = 0;
96+
try (Directory dir = getDirectory(createTempDir("testReadPastEOF"))) {
97+
try (IndexOutput o = dir.createOutput("out", newIOContext(random()))) {
98+
o.writeBytes(new byte[fileSize], 0, fileSize);
99+
}
100+
101+
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
102+
i.seek(fileSize);
103+
expectThrows(EOFException.class, () -> i.readByte());
104+
expectThrows(EOFException.class, () -> i.readBytes(new byte[1], 0, 1));
105+
}
106+
107+
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
108+
expectThrows(
109+
EOFException.class, () -> i.seek(fileSize + RandomizedTest.randomIntBetween(1, 2048)));
110+
expectThrows(EOFException.class, () -> i.readByte());
111+
expectThrows(EOFException.class, () -> i.readBytes(new byte[1], 0, 1));
112+
}
113+
114+
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
115+
expectThrows(EOFException.class, () -> i.readByte());
116+
}
117+
118+
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
119+
expectThrows(EOFException.class, () -> i.readBytes(new byte[1], 0, 1));
120+
}
121+
}
122+
}
123+
124+
public void testSeekPastEOFAndRead() throws Exception {
125+
try (Directory dir = getDirectory(createTempDir("testSeekPastEOF"))) {
126+
final int len = random().nextInt(2048);
127+
128+
try (IndexOutput o = dir.createOutput("out", newIOContext(random()))) {
129+
byte[] b = new byte[len];
130+
o.writeBytes(b, 0, len);
131+
}
132+
133+
try (IndexInput i = dir.openInput("out", newIOContext(random()))) {
134+
// Seeking past EOF should always throw EOFException
135+
expectThrows(
136+
EOFException.class, () -> i.seek(len + RandomizedTest.randomIntBetween(1, 2048)));
137+
138+
// Reading immediately after seeking past EOF should throw EOFException
139+
expectThrows(EOFException.class, () -> i.readByte());
140+
}
141+
}
142+
}
143+
84144
public void testUseDirectIODefaults() throws Exception {
85145
Path path = createTempDir("testUseDirectIODefaults");
86146
try (DirectIODirectory dir = new DirectIODirectory(FSDirectory.open(path))) {

0 commit comments

Comments
 (0)