1 /*
   2  * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 6322678 7082769
  27  * @summary FileInputStream/FileOutputStream/RandomAccessFile allow file descriptor
  28  *          to be closed while still in use.
  29  * @run main/othervm FileDescriptorSharing
  30  */
  31 
  32 import java.io.*;
  33 import java.nio.channels.FileChannel;
  34 import java.nio.channels.FileLock;
  35 import java.util.concurrent.CountDownLatch;
  36 
  37 public class FileDescriptorSharing {
  38 
  39     final static int numFiles = 10;
  40     volatile static boolean fail;
  41 
  42     public static void main(String[] args) throws Exception {
  43         TestFinalizer();
  44         TestMultipleFD();
  45         TestIsValid();
  46         MultiThreadedFD();
  47     }
  48 
  49     /**
  50      * We shouldn't discard a file descriptor until all streams have
  51      * finished with it
  52      */
  53     private static void TestFinalizer() throws Exception {
  54         FileDescriptor fd = null;
  55         File tempFile = new File("TestFinalizer1.txt");
  56         tempFile.deleteOnExit();
  57         try (Writer writer = new FileWriter(tempFile)) {
  58             for (int i=0; i<5; i++) {
  59                 writer.write("test file content test file content");
  60             }
  61         }
  62 
  63         FileInputStream fis1 = new FileInputStream(tempFile);
  64         fd = fis1.getFD();
  65         // Create a new FIS based on the existing FD (so the two FIS's share the same native fd)
  66         try (FileInputStream fis2 = new FileInputStream(fd)) {
  67             // allow fis1 to be gc'ed
  68             fis1 = null;
  69             int ret = 0;
  70             while(ret >= 0) {
  71                 // encourage gc
  72                 System.gc();
  73                 // read from fis2 - when fis1 is gc'ed and finalizer is run, read will fail
  74                 System.out.print(".");
  75                 ret = fis2.read();
  76             }
  77         }
  78 
  79         // variation of above. Use RandomAccessFile to obtain a filedescriptor
  80         File testFinalizerFile = new File("TestFinalizer");
  81         RandomAccessFile raf = new RandomAccessFile(testFinalizerFile, "rw");
  82         raf.writeBytes("test file content test file content");
  83         raf.seek(0L);
  84         fd = raf.getFD();
  85         try (FileInputStream fis3 = new FileInputStream(fd)) {
  86             // allow raf to be gc'ed
  87             raf = null;
  88             int ret = 0;
  89             while (ret >= 0) {
  90                 // encourage gc
  91                 System.gc();
  92                 /*
  93                  * read from fis3 - when raf is gc'ed and finalizer is run,
  94                  * fd should still be valid.
  95                  */
  96                 System.out.print(".");
  97                 ret = fis3.read();
  98             }
  99             if(!fd.valid()) {
 100                 throw new RuntimeException("TestFinalizer() : FileDescriptor should be valid");
 101             }
 102         } finally {
 103             testFinalizerFile.delete();
 104         }
 105     }
 106 
 107     /**
 108      * Exercise FileDispatcher close()/preClose()
 109      */
 110     private static void TestMultipleFD() throws Exception {
 111         RandomAccessFile raf = null;
 112         FileOutputStream fos = null;
 113         FileInputStream fis = null;
 114         FileChannel fc = null;
 115         FileLock fileLock = null;
 116 
 117         File test1 = new File("test1");
 118         try {
 119             raf = new RandomAccessFile(test1, "rw");
 120             fos = new FileOutputStream(raf.getFD());
 121             fis = new FileInputStream(raf.getFD());
 122             fc = raf.getChannel();
 123             fileLock = fc.lock();
 124             raf.setLength(0L);
 125             fos.flush();
 126             fos.write("TEST".getBytes());
 127         } finally {
 128             if (fileLock != null) fileLock.release();
 129             if (fis != null) fis.close();
 130             if (fos != null) fos.close();
 131             if (raf != null) raf.close();
 132             test1.delete();
 133         }
 134 
 135         /*
 136          * Close out in different order to ensure FD is not
 137          * closed out too early
 138          */
 139         File test2 = new File("test2");
 140         try {
 141             raf = new RandomAccessFile(test2, "rw");
 142             fos = new FileOutputStream(raf.getFD());
 143             fis = new FileInputStream(raf.getFD());
 144             fc = raf.getChannel();
 145             fileLock = fc.lock();
 146             raf.setLength(0L);
 147             fos.flush();
 148             fos.write("TEST".getBytes());
 149         } finally {
 150             if (fileLock != null) fileLock.release();
 151             if (raf != null) raf.close();
 152             if (fos != null) fos.close();
 153             if (fis != null) fis.close();
 154             test2.delete();
 155         }
 156 
 157         // one more time, fos first this time
 158         File test3 = new File("test3");
 159         try {
 160             raf = new RandomAccessFile(test3, "rw");
 161             fos = new FileOutputStream(raf.getFD());
 162             fis = new FileInputStream(raf.getFD());
 163             fc = raf.getChannel();
 164             fileLock = fc.lock();
 165             raf.setLength(0L);
 166             fos.flush();
 167             fos.write("TEST".getBytes());
 168         } finally {
 169             if (fileLock != null) fileLock.release();
 170             if (fos != null) fos.close();
 171             if (raf != null) raf.close();
 172             if (fis != null) fis.close();
 173             test3.delete();
 174         }
 175     }
 176 
 177     /**
 178      * Similar to TestMultipleFD() but this time we
 179      * just get and use FileDescriptor.valid() for testing.
 180      */
 181     private static void TestIsValid() throws Exception {
 182         FileDescriptor fd = null;
 183         RandomAccessFile raf = null;
 184         FileOutputStream fos = null;
 185         FileInputStream fis = null;
 186         FileChannel fc = null;
 187 
 188         File test1 = new File("test1");
 189         try {
 190             raf = new RandomAccessFile(test1, "rw");
 191             fd = raf.getFD();
 192             fos = new FileOutputStream(fd);
 193             fis = new FileInputStream(fd);
 194         } finally {
 195             try {
 196                 if (fis != null) fis.close();
 197                 if (fos != null) fos.close();
 198                 if (!fd.valid()) {
 199                     throw new RuntimeException("FileDescriptor should be valid");
 200                 }
 201                 if (raf != null) raf.close();
 202                 if (fd.valid()) {
 203                     throw new RuntimeException("close() called and FileDescriptor still valid");
 204                 }
 205             } finally {
 206                 if (raf != null) raf.close();
 207                 test1.delete();
 208             }
 209         }
 210 
 211         /*
 212          * Close out in different order to ensure FD is not
 213          * closed out too early
 214          */
 215         File test2 = new File("test2");
 216         try {
 217             raf = new RandomAccessFile(test2, "rw");
 218             fd = raf.getFD();
 219             fos = new FileOutputStream(fd);
 220             fis = new FileInputStream(fd);
 221         } finally {
 222             try {
 223                 if (raf != null) raf.close();
 224                 if (fos != null) fos.close();
 225                 if (!fd.valid()) {
 226                     throw new RuntimeException("FileDescriptor should be valid");
 227                 }
 228                 if (fis != null) fis.close();
 229                 if (fd.valid()) {
 230                     throw new RuntimeException("close() called and FileDescriptor still valid");
 231                 }
 232             } finally {
 233                 test2.delete();
 234             }
 235         }
 236 
 237         // one more time, fos first this time
 238         File test3 = new File("test3");
 239         try {
 240             raf = new RandomAccessFile(test3, "rw");
 241             fd = raf.getFD();
 242             fos = new FileOutputStream(fd);
 243             fis = new FileInputStream(fd);
 244         } finally {
 245             try {
 246                 if (fos != null) fos.close();
 247                 if (raf != null) raf.close();
 248                 if (!fd.valid()) {
 249                     throw new RuntimeException("FileDescriptor should be valid");
 250                 }
 251                 if (fis != null) fis.close();
 252                 if (fd.valid()) {
 253                     throw new RuntimeException("close() called and FileDescriptor still valid");
 254                 }
 255             } finally {
 256                 test3.delete();
 257             }
 258         }
 259     }
 260 
 261     /**
 262      * Test concurrent access to the same fd.useCount field
 263      */
 264     private static void MultiThreadedFD() throws Exception {
 265         RandomAccessFile raf = null;
 266         FileDescriptor fd = null;
 267         int numThreads = 2;
 268         CountDownLatch done = new CountDownLatch(numThreads);
 269         OpenClose[] fileOpenClose = new OpenClose[numThreads];
 270         File MultipleThreadedFD = new File("MultipleThreadedFD");
 271         try {
 272             raf = new RandomAccessFile(MultipleThreadedFD, "rw");
 273             fd = raf.getFD();
 274             for(int count=0;count<numThreads;count++) {
 275                 fileOpenClose[count] = new OpenClose(fd, done);
 276                 fileOpenClose[count].start();
 277             }
 278             done.await();
 279         } finally {
 280             try {
 281                 if(raf != null) raf.close();
 282                 // fd should now no longer be valid
 283                 if(fd.valid()) {
 284                     throw new RuntimeException("FileDescriptor should not be valid");
 285                 }
 286                 // OpenClose thread tests failed
 287                 if(fail) {
 288                     throw new RuntimeException("OpenClose thread tests failed.");
 289                 }
 290             } finally {
 291                 MultipleThreadedFD.delete();
 292             }
 293         }
 294     }
 295 
 296     /**
 297      * A thread which will open and close a number of FileInputStreams and
 298      * FileOutputStreams referencing the same native file descriptor.
 299      */
 300     private static class OpenClose extends Thread {
 301         private FileDescriptor fd = null;
 302         private CountDownLatch done;
 303         FileInputStream[] fisArray = new FileInputStream[numFiles];
 304         FileOutputStream[] fosArray = new FileOutputStream[numFiles];
 305 
 306         OpenClose(FileDescriptor filedescriptor, CountDownLatch done) {
 307             this.fd = filedescriptor;
 308             this.done = done;
 309         }
 310 
 311         public void run() {
 312              try {
 313                  for(int i=0;i<numFiles;i++) {
 314                      fisArray[i] = new FileInputStream(fd);
 315                      fosArray[i] = new FileOutputStream(fd);
 316                  }
 317 
 318                  // Now close out
 319                  for(int i=0;i<numFiles;i++) {
 320                      if(fisArray[i] != null) fisArray[i].close();
 321                      if(fosArray[i] != null) fosArray[i].close();
 322                  }
 323 
 324              } catch(IOException ioe) {
 325                  System.out.println("OpenClose encountered IO issue :" + ioe);
 326                  fail = true;
 327              } finally {
 328                  if (!fd.valid()) { // fd should still be valid given RAF reference
 329                      System.out.println("OpenClose: FileDescriptor should be valid");
 330                      fail = true;
 331                  }
 332                  done.countDown();
 333              }
 334          }
 335     }
 336 }