FileInputStreamTest.java revision 27604018f783bf6354a13870b3e7785edca69b5f
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.io;
18
19import java.io.File;
20import java.io.FileDescriptor;
21import java.io.FileInputStream;
22import java.io.FileNotFoundException;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.util.ArrayList;
26import java.util.List;
27
28import android.system.ErrnoException;
29import android.system.Os;
30import android.system.OsConstants;
31import android.system.StructStatVfs;
32
33import libcore.io.IoUtils;
34import libcore.io.Libcore;
35import libcore.junit.junit3.TestCaseWithRules;
36import libcore.junit.util.ResourceLeakageDetector;
37import org.junit.Rule;
38import org.junit.rules.TestRule;
39
40public final class FileInputStreamTest extends TestCaseWithRules {
41    @Rule
42    public TestRule guardRule = ResourceLeakageDetector.getRule();
43
44    private static final int TOTAL_SIZE = 1024;
45    private static final int SKIP_SIZE = 100;
46
47    private static class DataFeeder extends Thread {
48        private FileDescriptor mOutFd;
49
50        public DataFeeder(FileDescriptor fd) {
51            mOutFd = fd;
52        }
53
54        @Override
55        public void run() {
56            try {
57                FileOutputStream fos = new FileOutputStream(mOutFd);
58                try {
59                    byte[] buffer = new byte[TOTAL_SIZE];
60                    for (int i = 0; i < buffer.length; ++i) {
61                        buffer[i] = (byte) i;
62                    }
63                    fos.write(buffer);
64                } finally {
65                    IoUtils.closeQuietly(fos);
66                    IoUtils.close(mOutFd);
67                }
68            } catch (IOException e) {
69                throw new RuntimeException(e);
70            }
71        }
72    }
73
74    private void verifyData(FileInputStream is, int start, int count) throws IOException {
75        byte buffer[] = new byte[count];
76        assertEquals(count, is.read(buffer));
77        for (int i = 0; i < count; ++i) {
78            assertEquals((byte) (i + start), buffer[i]);
79        }
80    }
81
82    public void testSkipInPipes() throws Exception {
83        FileDescriptor[] pipe = Libcore.os.pipe2(0);
84        DataFeeder feeder = new DataFeeder(pipe[1]);
85        try {
86            feeder.start();
87            FileInputStream fis = new FileInputStream(pipe[0]);
88            fis.skip(SKIP_SIZE);
89            verifyData(fis, SKIP_SIZE, TOTAL_SIZE - SKIP_SIZE);
90            assertEquals(-1, fis.read());
91            feeder.join(1000);
92            assertFalse(feeder.isAlive());
93        } finally {
94            IoUtils.closeQuietly(pipe[0]);
95        }
96    }
97
98    public void testDirectories() throws Exception {
99        try {
100            new FileInputStream(".");
101            fail();
102        } catch (FileNotFoundException expected) {
103        }
104    }
105
106    private File makeFile() throws Exception {
107        File tmp = File.createTempFile("FileOutputStreamTest", "tmp");
108        FileOutputStream fos = new FileOutputStream(tmp);
109        fos.write(1);
110        fos.write(1);
111        fos.close();
112        return tmp;
113    }
114
115    public void testFileDescriptorOwnership() throws Exception {
116        File tmp = makeFile();
117
118        FileInputStream fis1 = new FileInputStream(tmp);
119        FileInputStream fis2 = new FileInputStream(fis1.getFD());
120
121        // Close the second FileDescriptor and check we can't use it...
122        fis2.close();
123
124        try {
125            fis2.available();
126            fail();
127        } catch (IOException expected) {
128        }
129        try {
130            fis2.read();
131            fail();
132        } catch (IOException expected) {
133        }
134        try {
135            fis2.read(new byte[1], 0, 1);
136            fail();
137        } catch (IOException expected) {
138        }
139        try {
140            fis2.skip(1);
141            fail();
142        } catch (IOException expected) {
143        }
144        // ...but that we can still use the first.
145        assertTrue(fis1.getFD().valid());
146        assertFalse(fis1.read() == -1);
147
148        // Close the first FileDescriptor and check we can't use it...
149        fis1.close();
150        try {
151            fis1.available();
152            fail();
153        } catch (IOException expected) {
154        }
155        try {
156            fis1.read();
157            fail();
158        } catch (IOException expected) {
159        }
160        try {
161            fis1.read(new byte[1], 0, 1);
162            fail();
163        } catch (IOException expected) {
164        }
165        try {
166            fis1.skip(1);
167            fail();
168        } catch (IOException expected) {
169        }
170
171        // FD is no longer owned by any stream, should be invalidated.
172        assertFalse(fis1.getFD().valid());
173    }
174
175    public void testClose() throws Exception {
176        File tmp = makeFile();
177        FileInputStream fis = new FileInputStream(tmp);
178
179        // Closing an already-closed stream is a no-op...
180        fis.close();
181        fis.close();
182
183        // But any explicit activity is an error.
184        try {
185            fis.available();
186            fail();
187        } catch (IOException expected) {
188        }
189        try {
190            fis.read();
191            fail();
192        } catch (IOException expected) {
193        }
194        try {
195            fis.read(new byte[1], 0, 1);
196            fail();
197        } catch (IOException expected) {
198        }
199        try {
200            fis.skip(1);
201            fail();
202        } catch (IOException expected) {
203        }
204        // Including 0-byte skips...
205        try {
206            fis.skip(0);
207            fail();
208        } catch (IOException expected) {
209        }
210        // ...but not 0-byte reads...
211        fis.read(new byte[0], 0, 0);
212    }
213
214    // http://b/26117827
215    public void testReadProcVersion() throws IOException {
216        File file = new File("/proc/version");
217        try (FileInputStream input = new FileInputStream(file)) {
218            assertTrue(input.available() == 0);
219        }
220    }
221
222    // http://b/25695227
223    public void testFdLeakWhenOpeningDirectory() throws Exception {
224        File phile = IoUtils.createTemporaryDirectory("test_bug_25695227");
225
226        try {
227            new FileInputStream(phile);
228            fail();
229        } catch (FileNotFoundException expected) {
230        }
231
232        assertTrue(getOpenFdsForPrefix("test_bug_25695227").isEmpty());
233    }
234
235    // http://b/28192631
236    public void testSkipOnLargeFiles() throws Exception {
237        File largeFile = File.createTempFile("FileInputStreamTest_testSkipOnLargeFiles", "");
238        // Required space is 3.1 GB: 3GB for file plus 100M headroom.
239        final long requiredFreeSpaceBytes = 3172L * 1024 * 1024;
240        long fileSize = 3 * 1024L * 1024 * 1024; // 3 GiB
241        // If system doesn't have enough space free for this test, skip it.
242        final StructStatVfs statVfs = Os.statvfs(largeFile.getPath());
243        final long freeSpaceAvailableBytes = statVfs.f_bsize * statVfs.f_bavail;
244        if (freeSpaceAvailableBytes < requiredFreeSpaceBytes) {
245            return;
246        }
247        try {
248            allocateEmptyFile(largeFile, fileSize);
249            assertEquals(fileSize, largeFile.length());
250            try (FileInputStream fis = new FileInputStream(largeFile)) {
251                long lastByte = fileSize - 1;
252                assertEquals(0, Libcore.os.lseek(fis.getFD(), 0, OsConstants.SEEK_CUR));
253                assertEquals(lastByte, fis.skip(lastByte));
254            }
255        } finally {
256            // Proactively cleanup - it's a pretty large file.
257            assertTrue(largeFile.delete());
258        }
259    }
260
261    /**
262     * Allocates a file to the specified size using fallocate, falling back to ftruncate.
263     */
264    private static void allocateEmptyFile(File file, long fileSize)
265            throws IOException, InterruptedException {
266        // fallocate is much faster than ftruncate (<<1sec rather than 24sec for 3 GiB on Nexus 6P)
267        try (FileOutputStream fos = new FileOutputStream(file)) {
268            try {
269                Os.posix_fallocate(fos.getFD(), 0, fileSize);
270                return;
271            } catch (ErrnoException e) {
272                // Fall back to ftruncate, which works on all filesystems but is slower
273            }
274        }
275        // Need to reopen the file to get a valid FileDescriptor
276        try (FileOutputStream fos = new FileOutputStream(file)) {
277            Os.ftruncate(fos.getFD(), fileSize);
278        } catch (ErrnoException e2) {
279            throw new IOException("Failed to truncate: " + file, e2);
280        }
281    }
282
283    private static List<Integer> getOpenFdsForPrefix(String path) throws Exception {
284        File[] fds = new File("/proc/self/fd").listFiles();
285        List<Integer> list = new ArrayList<>();
286        for (File fd : fds) {
287            try {
288                File fdPath = new File(android.system.Os.readlink(fd.getAbsolutePath()));
289                if (fdPath.getName().startsWith(path)) {
290                    list.add(Integer.valueOf(fd.getName()));
291                }
292            } catch (ErrnoException e) {
293                if (e.errno != OsConstants.ENOENT) {
294                    throw e.rethrowAsIOException();
295                }
296            }
297        }
298
299        return list;
300    }
301}
302