Skip to content

Commit da87069

Browse files
author
Ciaran Evans
committed
Ensure we read streams fully
Read method makes an assumption that the stream will return the amount of data specified by the length parameter which isn't the case for streams. The length parameter indicates how much it may read up to. A stream will block until it has read at least one byte, after that it may return at any point indicating how much it actually read. Also fixing a bug in the EntryInputStream which made the same assumption that the number of bytes read will always be the same as the length requested.
1 parent e63a323 commit da87069

File tree

3 files changed

+67
-20
lines changed

3 files changed

+67
-20
lines changed

loop-fs-iso-impl/src/main/java/com/github/stephenc/javaisotools/loopfs/iso9660/EntryInputStream.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
/*
22
* Copyright (c) 2010. Stephen Connolly.
33
* Copyright (c) 2006-2007. loopy project (http://loopy.sourceforge.net).
4-
*
4+
*
55
* This library is free software; you can redistribute it and/or
66
* modify it under the terms of the GNU Lesser General Public
77
* License as published by the Free Software Foundation; either
88
* version 2.1 of the License, or (at your option) any later version.
9-
*
9+
*
1010
* This library is distributed in the hope that it will be useful,
1111
* but WITHOUT ANY WARRANTY; without even the implied warranty of
1212
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1313
* Lesser General Public License for more details.
14-
*
14+
*
1515
* You should have received a copy of the GNU Lesser General Public
1616
* License along with this library; if not, write to the Free Software
1717
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
@@ -72,8 +72,8 @@ public int read(final byte b[], final int off, final int len) throws IOException
7272
}
7373

7474
if (read > 0) {
75-
this.pos += len;
76-
this.rem -= len;
75+
this.pos += read;
76+
this.rem -= read;
7777
}
7878

7979
return read;
@@ -127,4 +127,4 @@ private void ensureOpen() {
127127
throw new IllegalStateException("stream has been closed");
128128
}
129129
}
130-
}
130+
}

loop-fs-iso-impl/src/test/java/com/github/stephenc/javaisotools/loopfs/iso9660/Iso9660FileSystemTest.java

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,42 @@
11
/*
22
* Copyright (c) 2010. Stephen Connolly.
3-
*
3+
*
44
* This library is free software; you can redistribute it and/or
55
* modify it under the terms of the GNU Lesser General Public
66
* License as published by the Free Software Foundation; either
77
* version 2.1 of the License, or (at your option) any later version.
8-
*
8+
*
99
* This library is distributed in the hope that it will be useful,
1010
* but WITHOUT ANY WARRANTY; without even the implied warranty of
1111
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1212
* Lesser General Public License for more details.
13-
*
13+
*
1414
* You should have received a copy of the GNU Lesser General Public
1515
* License along with this library; if not, write to the Free Software
1616
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1717
*/
1818

1919
package com.github.stephenc.javaisotools.loopfs.iso9660;
2020

21-
import java.io.File;
22-
import java.io.FileInputStream;
23-
import java.io.InputStream;
24-
import java.util.Properties;
21+
import static org.hamcrest.CoreMatchers.is;
22+
import static org.junit.Assert.assertThat;
23+
import static org.junit.Assume.assumeTrue;
2524

25+
import com.github.stephenc.javaisotools.loopfs.spi.SeekableInputFile;
2626
import com.github.stephenc.javaisotools.loopfs.spi.SeekableInputFileHadoop;
27+
import com.google.common.collect.Iterables;
2728
import org.apache.hadoop.conf.Configuration;
2829
import org.apache.hadoop.fs.Path;
2930
import org.apache.hadoop.hdfs.MiniDFSCluster;
30-
import org.junit.*;
31-
3231
import org.codehaus.plexus.util.IOUtil;
32+
import org.junit.BeforeClass;
33+
import org.junit.Test;
3334

34-
import static org.hamcrest.CoreMatchers.*;
35-
import static org.junit.Assert.*;
36-
import static org.junit.Assume.assumeTrue;
35+
import java.io.File;
36+
import java.io.FileInputStream;
37+
import java.io.IOException;
38+
import java.io.InputStream;
39+
import java.util.Properties;
3740

3841
/**
3942
* Tests the Iso9660 implementation.
@@ -65,13 +68,25 @@ public void smokes() throws Exception {
6568
this.runCheck(image);
6669
}
6770

71+
@Test
72+
public void shouldReadAllBytesWhenSeekableInputPartiallyReads() throws IOException {
73+
// Create seekeable input which does not read up to specified length
74+
SeekableInputFile input = new PartiallyReadSeekableInput();
75+
Iso9660FileSystem fs = new Iso9660FileSystem(input, true);
76+
Iso9660FileEntry entry = Iterables.getLast(fs);
77+
78+
byte[] bytes = fs.getBytes(entry);
79+
80+
assertThat("All bytes should have been read", new String(bytes), is("Goodbye"));
81+
}
82+
6883
@Test
6984
public void hdfsSmokes() throws Exception {
7085
assumeTrue(isNotWindows());
7186
//Creating a Mini DFS Cluster as the default File System does not return a Seekable Stream
7287
MiniDFSCluster.Builder builder = new MiniDFSCluster.Builder(new Configuration());
7388
MiniDFSCluster hdfsCluster = builder.build();
74-
String hdfsTestFile = "hdfs://127.0.0.1:"+ hdfsCluster.getNameNodePort() + "/test/" + filePath;
89+
String hdfsTestFile = "hdfs://127.0.0.1:" + hdfsCluster.getNameNodePort() + "/test/" + filePath;
7590
hdfsCluster.getFileSystem()
7691
.copyFromLocalFile(new Path(filePath), new Path(hdfsTestFile));
7792
InputStream is = hdfsCluster.getFileSystem().open(new Path(hdfsTestFile));
@@ -97,4 +112,22 @@ private boolean isNotWindows() {
97112
String os = System.getProperty("os.name");
98113
return !os.startsWith("Windows");
99114
}
115+
116+
private static class PartiallyReadSeekableInput extends SeekableInputFile {
117+
118+
private byte[] bytes;
119+
120+
public PartiallyReadSeekableInput() throws IOException {
121+
super(new File(filePath));
122+
}
123+
124+
@Override
125+
public int read(byte[] b, int off, int len) throws IOException {
126+
// Deliberately miss last byte on first pass
127+
boolean firstPass = b != bytes;
128+
int length = firstPass ? len - 1 : len;
129+
bytes = b;
130+
return super.read(b, off, length);
131+
}
132+
}
100133
}

loop-fs-spi/src/main/java/com/github/stephenc/javaisotools/loopfs/spi/AbstractFileSystem.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,21 @@ protected final void seek(long pos) throws IOException {
8585
*/
8686
protected final int read(byte[] buffer, int offset, int length) throws IOException {
8787
ensureOpen();
88-
return this.channel.read(buffer, offset, length);
88+
return readFully(buffer, offset, length);
89+
}
90+
91+
private int readFully(byte[] buffer, int offset, int length) throws IOException {
92+
int bytesRead;
93+
int remaining = length;
94+
95+
// read doesn't guarantee a full buffer is read. Reading until we have a full buffer or end of stream
96+
while (remaining != 0 &&
97+
(bytesRead = this.channel.read(buffer, offset, remaining)) != -1) {
98+
offset += bytesRead;
99+
remaining -= bytesRead;
100+
}
101+
int totalBytesRead = length - remaining;
102+
return totalBytesRead;
89103
}
90104

91105
}

0 commit comments

Comments
 (0)