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.OsConstants;
30import junit.framework.TestCase;
31
32import libcore.io.IoUtils;
33import libcore.io.Libcore;
34
35public final class FileInputStreamTest extends TestCase {
36    private static final int TOTAL_SIZE = 1024;
37    private static final int SKIP_SIZE = 100;
38
39    private static class DataFeeder extends Thread {
40        private FileDescriptor mOutFd;
41
42        public DataFeeder(FileDescriptor fd) {
43            mOutFd = fd;
44        }
45
46        @Override
47        public void run() {
48            try {
49                FileOutputStream fos = new FileOutputStream(mOutFd);
50                try {
51                    byte[] buffer = new byte[TOTAL_SIZE];
52                    for (int i = 0; i < buffer.length; ++i) {
53                        buffer[i] = (byte) i;
54                    }
55                    fos.write(buffer);
56                } finally {
57                    IoUtils.closeQuietly(fos);
58                    IoUtils.close(mOutFd);
59                }
60            } catch (IOException e) {
61                throw new RuntimeException(e);
62            }
63        }
64    }
65
66    private void verifyData(FileInputStream is, int start, int count) throws IOException {
67        byte buffer[] = new byte[count];
68        assertEquals(count, is.read(buffer));
69        for (int i = 0; i < count; ++i) {
70            assertEquals((byte) (i + start), buffer[i]);
71        }
72    }
73
74    public void testSkipInPipes() throws Exception {
75        FileDescriptor[] pipe = Libcore.os.pipe2(0);
76        DataFeeder feeder = new DataFeeder(pipe[1]);
77        try {
78            feeder.start();
79            FileInputStream fis = new FileInputStream(pipe[0]);
80            fis.skip(SKIP_SIZE);
81            verifyData(fis, SKIP_SIZE, TOTAL_SIZE - SKIP_SIZE);
82            assertEquals(-1, fis.read());
83            feeder.join(1000);
84            assertFalse(feeder.isAlive());
85        } finally {
86            IoUtils.closeQuietly(pipe[0]);
87        }
88    }
89
90    public void testDirectories() throws Exception {
91        try {
92            new FileInputStream(".");
93            fail();
94        } catch (FileNotFoundException expected) {
95        }
96    }
97
98    private File makeFile() throws Exception {
99        File tmp = File.createTempFile("FileOutputStreamTest", "tmp");
100        FileOutputStream fos = new FileOutputStream(tmp);
101        fos.write(1);
102        fos.write(1);
103        fos.close();
104        return tmp;
105    }
106
107    public void testFileDescriptorOwnership() throws Exception {
108        File tmp = makeFile();
109
110        FileInputStream fis1 = new FileInputStream(tmp);
111        FileInputStream fis2 = new FileInputStream(fis1.getFD());
112
113        // Close the second FileDescriptor and check we can't use it...
114        fis2.close();
115
116        try {
117            fis2.available();
118            fail();
119        } catch (IOException expected) {
120        }
121        try {
122            fis2.read();
123            fail();
124        } catch (IOException expected) {
125        }
126        try {
127            fis2.read(new byte[1], 0, 1);
128            fail();
129        } catch (IOException expected) {
130        }
131        try {
132            fis2.skip(1);
133            fail();
134        } catch (IOException expected) {
135        }
136        // ...but that we can still use the first.
137        assertTrue(fis1.getFD().valid());
138        assertFalse(fis1.read() == -1);
139
140        // Close the first FileDescriptor and check we can't use it...
141        fis1.close();
142        try {
143            fis1.available();
144            fail();
145        } catch (IOException expected) {
146        }
147        try {
148            fis1.read();
149            fail();
150        } catch (IOException expected) {
151        }
152        try {
153            fis1.read(new byte[1], 0, 1);
154            fail();
155        } catch (IOException expected) {
156        }
157        try {
158            fis1.skip(1);
159            fail();
160        } catch (IOException expected) {
161        }
162
163        // FD is no longer owned by any stream, should be invalidated.
164        assertFalse(fis1.getFD().valid());
165    }
166
167    public void testClose() throws Exception {
168        File tmp = makeFile();
169        FileInputStream fis = new FileInputStream(tmp);
170
171        // Closing an already-closed stream is a no-op...
172        fis.close();
173        fis.close();
174
175        // But any explicit activity is an error.
176        try {
177            fis.available();
178            fail();
179        } catch (IOException expected) {
180        }
181        try {
182            fis.read();
183            fail();
184        } catch (IOException expected) {
185        }
186        try {
187            fis.read(new byte[1], 0, 1);
188            fail();
189        } catch (IOException expected) {
190        }
191        try {
192            fis.skip(1);
193            fail();
194        } catch (IOException expected) {
195        }
196        // Including 0-byte skips...
197        try {
198            fis.skip(0);
199            fail();
200        } catch (IOException expected) {
201        }
202        // ...but not 0-byte reads...
203        fis.read(new byte[0], 0, 0);
204    }
205
206    // http://b/26117827
207    public void testReadProcVersion() throws IOException {
208        File file = new File("/proc/version");
209        FileInputStream input = new FileInputStream(file);
210        assertTrue(input.available() == 0);
211    }
212
213    // http://b/25695227
214    public void testFdLeakWhenOpeningDirectory() throws Exception {
215        File phile = IoUtils.createTemporaryDirectory("test_bug_25695227");
216
217        try {
218            new FileInputStream(phile);
219            fail();
220        } catch (FileNotFoundException expected) {
221        }
222
223        assertTrue(getOpenFdsForPrefix("test_bug_25695227").isEmpty());
224    }
225
226    // http://b/28192631
227    public void testSkipOnLargeFiles() throws Exception {
228        File largeFile = File.createTempFile("FileInputStreamTest_testSkipOnLargeFiles", "");
229        FileOutputStream fos = new FileOutputStream(largeFile);
230        try {
231            byte[] buffer = new byte[1024 * 1024]; // 1 MB
232            for (int i = 0; i < 3 * 1024; i++) { // 3 GB
233                fos.write(buffer);
234            }
235        } finally {
236            fos.close();
237        }
238
239        FileInputStream fis = new FileInputStream(largeFile);
240        long lastByte = 3 * 1024 * 1024 * 1024L - 1;
241        assertEquals(0, Libcore.os.lseek(fis.getFD(), 0, OsConstants.SEEK_CUR));
242        assertEquals(lastByte, fis.skip(lastByte));
243
244        // Proactively cleanup - it's a pretty large file.
245        assertTrue(largeFile.delete());
246    }
247
248    private static List<Integer> getOpenFdsForPrefix(String path) throws Exception {
249        File[] fds = new File("/proc/self/fd").listFiles();
250        List<Integer> list = new ArrayList<>();
251        for (File fd : fds) {
252            try {
253                File fdPath = new File(android.system.Os.readlink(fd.getAbsolutePath()));
254                if (fdPath.getName().startsWith(path)) {
255                    list.add(Integer.valueOf(fd.getName()));
256                }
257            } catch (ErrnoException e) {
258                if (e.errno != OsConstants.ENOENT) {
259                    throw e.rethrowAsIOException();
260                }
261            }
262        }
263
264        return list;
265    }
266}
267