/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package libcore.java.io; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.system.StructStatVfs; import android.util.MutableInt; import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.junit.junit3.TestCaseWithRules; import libcore.junit.util.ResourceLeakageDetector; import org.junit.Rule; import org.junit.rules.TestRule; public final class FileInputStreamTest extends TestCaseWithRules { @Rule public TestRule guardRule = ResourceLeakageDetector.getRule(); private static final int TOTAL_SIZE = 1024; private static final int SKIP_SIZE = 100; private static class DataFeeder extends Thread { private FileDescriptor mOutFd; public DataFeeder(FileDescriptor fd) { mOutFd = fd; } @Override public void run() { try { FileOutputStream fos = new FileOutputStream(mOutFd); try { byte[] buffer = new byte[TOTAL_SIZE]; for (int i = 0; i < buffer.length; ++i) { buffer[i] = (byte) i; } fos.write(buffer); } finally { IoUtils.closeQuietly(fos); IoUtils.close(mOutFd); } } catch (IOException e) { throw new RuntimeException(e); } } } private void verifyData(FileInputStream is, int start, int count) throws IOException { byte buffer[] = new byte[count]; assertEquals(count, is.read(buffer)); for (int i = 0; i < count; ++i) { assertEquals((byte) (i + start), buffer[i]); } } public void testSkipInPipes() throws Exception { FileDescriptor[] pipe = Libcore.os.pipe2(0); DataFeeder feeder = new DataFeeder(pipe[1]); try { feeder.start(); FileInputStream fis = new FileInputStream(pipe[0]); fis.skip(SKIP_SIZE); verifyData(fis, SKIP_SIZE, TOTAL_SIZE - SKIP_SIZE); assertEquals(-1, fis.read()); feeder.join(1000); assertFalse(feeder.isAlive()); } finally { IoUtils.closeQuietly(pipe[0]); } } public void testDirectories() throws Exception { try { new FileInputStream("."); fail(); } catch (FileNotFoundException expected) { } } private File makeFile() throws Exception { File tmp = File.createTempFile("FileOutputStreamTest", "tmp"); FileOutputStream fos = new FileOutputStream(tmp); fos.write(1); fos.write(1); fos.close(); return tmp; } public void testFileDescriptorOwnership() throws Exception { File tmp = makeFile(); FileInputStream fis1 = new FileInputStream(tmp); FileInputStream fis2 = new FileInputStream(fis1.getFD()); // Close the second FileDescriptor and check we can't use it... fis2.close(); try { fis2.available(); fail(); } catch (IOException expected) { } try { fis2.read(); fail(); } catch (IOException expected) { } try { fis2.read(new byte[1], 0, 1); fail(); } catch (IOException expected) { } try { fis2.skip(1); fail(); } catch (IOException expected) { } // ...but that we can still use the first. assertTrue(fis1.getFD().valid()); assertFalse(fis1.read() == -1); // Close the first FileDescriptor and check we can't use it... fis1.close(); try { fis1.available(); fail(); } catch (IOException expected) { } try { fis1.read(); fail(); } catch (IOException expected) { } try { fis1.read(new byte[1], 0, 1); fail(); } catch (IOException expected) { } try { fis1.skip(1); fail(); } catch (IOException expected) { } // FD is no longer owned by any stream, should be invalidated. assertFalse(fis1.getFD().valid()); } public void testClose() throws Exception { File tmp = makeFile(); FileInputStream fis = new FileInputStream(tmp); // Closing an already-closed stream is a no-op... fis.close(); fis.close(); // But any explicit activity is an error. try { fis.available(); fail(); } catch (IOException expected) { } try { fis.read(); fail(); } catch (IOException expected) { } try { fis.read(new byte[1], 0, 1); fail(); } catch (IOException expected) { } try { fis.skip(1); fail(); } catch (IOException expected) { } // Including 0-byte skips... try { fis.skip(0); fail(); } catch (IOException expected) { } // ...but not 0-byte reads... fis.read(new byte[0], 0, 0); } // http://b/26117827 // // Return 0 (the conservative estimate) for files for which ioctl is not implemented. public void test_available_on_nonIOCTL_supported_file() throws Exception { File file = new File("/dev/zero"); try (FileInputStream input = new FileInputStream(file)) { assertEquals(0, input.available()); } try (FileInputStream input = new FileInputStream(file)) { android.system.Os.ioctlInt(input.getFD(), OsConstants.FIONREAD, new MutableInt(0)); fail(); } catch (ErrnoException expected) { assertEquals("FIONREAD should have returned ENOTTY for the file. If it doesn't return" + " FIONREAD, the test is no longer valid.", OsConstants.ENOTTY, expected.errno); } } // http://b/25695227 public void testFdLeakWhenOpeningDirectory() throws Exception { File phile = IoUtils.createTemporaryDirectory("test_bug_25695227"); try { new FileInputStream(phile); fail(); } catch (FileNotFoundException expected) { } assertTrue(getOpenFdsForPrefix("test_bug_25695227").isEmpty()); } // http://b/28192631 public void testSkipOnLargeFiles() throws Exception { File largeFile = File.createTempFile("FileInputStreamTest_testSkipOnLargeFiles", ""); // Required space is 3.1 GB: 3GB for file plus 100M headroom. final long requiredFreeSpaceBytes = 3172L * 1024 * 1024; long fileSize = 3 * 1024L * 1024 * 1024; // 3 GiB // If system doesn't have enough space free for this test, skip it. final StructStatVfs statVfs = Os.statvfs(largeFile.getPath()); final long freeSpaceAvailableBytes = statVfs.f_bsize * statVfs.f_bavail; if (freeSpaceAvailableBytes < requiredFreeSpaceBytes) { return; } try { allocateEmptyFile(largeFile, fileSize); assertEquals(fileSize, largeFile.length()); try (FileInputStream fis = new FileInputStream(largeFile)) { long lastByte = fileSize - 1; assertEquals(0, Libcore.os.lseek(fis.getFD(), 0, OsConstants.SEEK_CUR)); assertEquals(lastByte, fis.skip(lastByte)); } } finally { // Proactively cleanup - it's a pretty large file. assertTrue(largeFile.delete()); } } /** * Allocates a file to the specified size using fallocate, falling back to ftruncate. */ private static void allocateEmptyFile(File file, long fileSize) throws IOException, InterruptedException { // fallocate is much faster than ftruncate (<<1sec rather than 24sec for 3 GiB on Nexus 6P) try (FileOutputStream fos = new FileOutputStream(file)) { try { Os.posix_fallocate(fos.getFD(), 0, fileSize); return; } catch (ErrnoException e) { // Fall back to ftruncate, which works on all filesystems but is slower } } // Need to reopen the file to get a valid FileDescriptor try (FileOutputStream fos = new FileOutputStream(file)) { Os.ftruncate(fos.getFD(), fileSize); } catch (ErrnoException e2) { throw new IOException("Failed to truncate: " + file, e2); } } private static List getOpenFdsForPrefix(String path) throws Exception { File[] fds = new File("/proc/self/fd").listFiles(); List list = new ArrayList<>(); for (File fd : fds) { try { File fdPath = new File(android.system.Os.readlink(fd.getAbsolutePath())); if (fdPath.getName().startsWith(path)) { list.add(Integer.valueOf(fd.getName())); } } catch (ErrnoException e) { if (e.errno != OsConstants.ENOENT) { throw e.rethrowAsIOException(); } } } return list; } }