8020669: (fs) Files.readAllBytes() does not read any data when Files.size() is 0
authorigerasim
Mon, 29 Jul 2013 12:35:42 +0400
changeset 19185 34aa83720203
parent 19184 927725c23203
child 19186 2b66b27d9ea9
8020669: (fs) Files.readAllBytes() does not read any data when Files.size() is 0 Reviewed-by: alanb, chegar, martin, rriggs
jdk/src/share/classes/java/nio/file/Files.java
jdk/test/java/nio/file/Files/BytesAndLines.java
--- a/jdk/src/share/classes/java/nio/file/Files.java	Tue Jul 30 21:11:08 2013 +0400
+++ b/jdk/src/share/classes/java/nio/file/Files.java	Mon Jul 29 12:35:42 2013 +0400
@@ -25,10 +25,10 @@
 
 package java.nio.file;
 
-import java.nio.ByteBuffer;
 import java.nio.file.attribute.*;
 import java.nio.file.spi.FileSystemProvider;
 import java.nio.file.spi.FileTypeDetector;
+import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
 import java.nio.channels.SeekableByteChannel;
 import java.io.Closeable;
@@ -2965,7 +2965,63 @@
     }
 
     /**
-     * Read all the bytes from a file. The method ensures that the file is
+     * The maximum size of array to allocate.
+     * Some VMs reserve some header words in an array.
+     * Attempts to allocate larger arrays may result in
+     * OutOfMemoryError: Requested array size exceeds VM limit
+     */
+    private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
+
+    /**
+     * Reads all the bytes from an input stream. Uses {@code initialSize} as a hint
+     * about how many bytes the stream will have.
+     *
+     * @param   source
+     *          the input stream to read from
+     * @param   initialSize
+     *          the initial size of the byte array to allocate
+     *
+     * @return  a byte array containing the bytes read from the file
+     *
+     * @throws  IOException
+     *          if an I/O error occurs reading from the stream
+     * @throws  OutOfMemoryError
+     *          if an array of the required size cannot be allocated
+     */
+    private static byte[] read(InputStream source, int initialSize)
+            throws IOException
+    {
+        int capacity = initialSize;
+        byte[] buf = new byte[capacity];
+        int nread = 0;
+        int n;
+        for (;;) {
+            // read to EOF which may read more or less than initialSize (eg: file
+            // is truncated while we are reading)
+            while ((n = source.read(buf, nread, capacity - nread)) > 0)
+                nread += n;
+
+            // if last call to source.read() returned -1, we are done
+            // otherwise, try to read one more byte; if that failed we're done too
+            if (n < 0 || (n = source.read()) < 0)
+                break;
+
+            // one more byte was read; need to allocate a larger buffer
+            if (capacity <= MAX_BUFFER_SIZE - capacity) {
+                capacity = Math.max(capacity << 1, BUFFER_SIZE);
+            } else {
+                if (capacity == MAX_BUFFER_SIZE)
+                    throw new OutOfMemoryError("Required array size too large");
+                capacity = MAX_BUFFER_SIZE;
+            }
+            buf = Arrays.copyOf(buf, capacity);
+            buf[nread++] = (byte)n;
+        }
+        return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
+    }
+
+    /**
+     * Reads all the bytes from a file. The method ensures that the file is
      * closed when all bytes have been read or an I/O error, or other runtime
      * exception, is thrown.
      *
@@ -2989,22 +3045,13 @@
      *          method is invoked to check read access to the file.
      */
     public static byte[] readAllBytes(Path path) throws IOException {
-        try (FileChannel fc = FileChannel.open(path)) {
+        try (FileChannel fc = FileChannel.open(path);
+             InputStream is = Channels.newInputStream(fc)) {
             long size = fc.size();
-            if (size > (long)Integer.MAX_VALUE)
+            if (size > (long)MAX_BUFFER_SIZE)
                 throw new OutOfMemoryError("Required array size too large");
 
-            byte[] arr = new byte[(int)size];
-            ByteBuffer bb = ByteBuffer.wrap(arr);
-            while (bb.hasRemaining()) {
-                if (fc.read(bb) < 0) {
-                    // truncated
-                    break;
-                }
-            }
-
-            int nread = bb.position();
-            return (nread == size) ? arr : Arrays.copyOf(arr, nread);
+            return read(is, (int)size);
         }
     }
 
--- a/jdk/test/java/nio/file/Files/BytesAndLines.java	Tue Jul 30 21:11:08 2013 +0400
+++ b/jdk/test/java/nio/file/Files/BytesAndLines.java	Mon Jul 29 12:35:42 2013 +0400
@@ -22,7 +22,7 @@
  */
 
 /* @test
- * @bug 7006126
+ * @bug 7006126 8020669
  * @summary Unit test for methods for Files readAllBytes, readAllLines and
  *     and write methods.
  */
@@ -82,6 +82,16 @@
             write(file, lines, Charset.defaultCharset(), opts);
             throw new RuntimeException("NullPointerException expected");
         } catch (NullPointerException ignore) { }
+
+        // read from procfs
+        if (System.getProperty("os.name").equals("Linux")) {
+            // Refer to the Linux proc(5) man page for details about /proc/self/stat file
+            // procfs reports it to be zero sized, even though data can be read from it
+            String statFile = "/proc/self/stat";
+            Path pathStat = Paths.get(statFile);
+            byte[] data = Files.readAllBytes(pathStat);
+            assertTrue(data.length > 0, "Files.readAllBytes('" + statFile + "') failed to read");
+        }
     }
 
 
@@ -174,6 +184,16 @@
                 throw new RuntimeException("NullPointerException expected");
             } catch (NullPointerException ignore) { }
 
+            // read from procfs
+            if (System.getProperty("os.name").equals("Linux")) {
+                // Refer to the Linux proc(5) man page for details about /proc/self/status file
+                // procfs reports this file to be zero sized, even though data can be read from it
+                String statusFile = "/proc/self/status";
+                Path pathStatus = Paths.get(statusFile);
+                lines = Files.readAllLines(pathStatus, US_ASCII);
+                assertTrue(lines.size() > 0, "Files.readAllLines('" + pathStatus + "') failed to read");
+            }
+
         } finally {
             delete(tmpfile);
         }
@@ -242,7 +262,6 @@
         } finally {
             delete(tmpfile);
         }
-
     }
 
     static void assertTrue(boolean expr, String errmsg) {