1/*
2 * Copyright (C) 2016 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.dalvik.system;
18
19import android.system.Os;
20import android.system.OsConstants;
21import junit.framework.TestCase;
22
23import java.io.File;
24import java.io.FileDescriptor;
25import java.io.FileInputStream;
26import java.io.FileOutputStream;
27import java.io.RandomAccessFile;
28import java.util.ArrayList;
29import java.util.EnumSet;
30import java.util.List;
31import java.util.Set;
32
33import dalvik.system.BlockGuard;
34
35public class BlockGuardTest extends TestCase {
36
37    private BlockGuard.Policy oldPolicy;
38    private RecordingPolicy recorder = new RecordingPolicy();
39
40    @Override
41    public void setUp() {
42        recorder.setChecks(EnumSet.allOf(RecordingPolicy.Check.class));
43        oldPolicy = BlockGuard.getThreadPolicy();
44        BlockGuard.setThreadPolicy(recorder);
45    }
46
47    @Override
48    public void tearDown() {
49        BlockGuard.setThreadPolicy(oldPolicy);
50        recorder.clear();
51    }
52
53    public void testFile() throws Exception {
54        File f = File.createTempFile("foo", "bar");
55        recorder.expectAndClear("onReadFromDisk", "onWriteToDisk");
56
57        f.getAbsolutePath();
58        f.getParentFile();
59        f.getName();
60        f.getParent();
61        f.getPath();
62        f.isAbsolute();
63        recorder.expectNoViolations();
64
65        f.mkdir();
66        recorder.expectAndClear("onWriteToDisk");
67
68        f.listFiles();
69        recorder.expectAndClear("onReadFromDisk");
70
71        f.list();
72        recorder.expectAndClear("onReadFromDisk");
73
74        f.length();
75        recorder.expectAndClear("onReadFromDisk");
76
77        f.lastModified();
78        recorder.expectAndClear("onReadFromDisk");
79
80        f.canExecute();
81        recorder.expectAndClear("onReadFromDisk");
82
83        f.canRead();
84        recorder.expectAndClear("onReadFromDisk");
85
86        f.canWrite();
87        recorder.expectAndClear("onReadFromDisk");
88
89        f.isFile();
90        recorder.expectAndClear("onReadFromDisk");
91
92        f.isDirectory();
93        recorder.expectAndClear("onReadFromDisk");
94
95        f.setExecutable(true, false);
96        recorder.expectAndClear("onWriteToDisk");
97
98        f.setReadable(true, false);
99        recorder.expectAndClear("onWriteToDisk");
100
101        f.setWritable(true, false);
102        recorder.expectAndClear("onWriteToDisk");
103
104        f.delete();
105        recorder.expectAndClear("onWriteToDisk");
106    }
107
108    public void testFileInputStream() throws Exception {
109        // The file itself doesn't matter: it just has to exist and allow the creation of the
110        // FileInputStream. The BlockGuard should have the same behavior towards a normal file and
111        // system file.
112        File tmpFile = File.createTempFile("inputFile", ".txt");
113        try (FileOutputStream fos = new FileOutputStream(tmpFile)) {
114            fos.write("01234567890".getBytes());
115        }
116
117        try {
118            recorder.clear();
119
120            FileInputStream fis = new FileInputStream(tmpFile);
121            recorder.expectAndClear("onReadFromDisk");
122
123            fis.read(new byte[4], 0, 4);
124            recorder.expectAndClear("onReadFromDisk");
125
126            fis.read();
127            recorder.expectAndClear("onReadFromDisk");
128
129            fis.skip(1);
130            recorder.expectAndClear("onReadFromDisk");
131
132            fis.close();
133        } finally {
134            tmpFile.delete();
135        }
136    }
137
138    public void testFileOutputStream() throws Exception {
139        File f = File.createTempFile("foo", "bar");
140        recorder.clear();
141
142        FileOutputStream fos = new FileOutputStream(f);
143        recorder.expectAndClear("onWriteToDisk");
144
145        fos.write(new byte[3]);
146        recorder.expectAndClear("onWriteToDisk");
147
148        fos.write(4);
149        recorder.expectAndClear("onWriteToDisk");
150
151        fos.flush();
152        recorder.expectNoViolations();
153
154        fos.close();
155        recorder.expectNoViolations();
156    }
157
158    public void testUnbufferedIO() throws Exception {
159        File f = File.createTempFile("foo", "bar");
160        recorder.setChecks(EnumSet.of(RecordingPolicy.Check.UNBUFFERED_IO));
161        recorder.clear();
162
163        try (FileOutputStream fos = new FileOutputStream(f)) {
164            recorder.expectNoViolations();
165            for (int i = 0; i < 11; i++) {
166                recorder.expectNoViolations();
167                fos.write("a".getBytes());
168            }
169            recorder.expectAndClear("onUnbufferedIO");
170        }
171
172        try (FileInputStream fis = new FileInputStream(new File("/dev/null"))) {
173            recorder.expectNoViolations();
174            byte[] b = new byte[1];
175            for (int i = 0; i < 11; i++) {
176                recorder.expectNoViolations();
177                fis.read(b);
178            }
179            recorder.expectAndClear("onUnbufferedIO");
180        }
181
182        try (RandomAccessFile ras = new RandomAccessFile(f, "rw")) {
183            // seek should reset the IoTracker.
184            ras.seek(0);
185            recorder.expectNoViolations();
186            for (int i = 0; i < 11; i++) {
187                recorder.expectNoViolations();
188                ras.read("a".getBytes());
189            }
190            recorder.expectAndClear("onUnbufferedIO");
191        }
192
193        try (RandomAccessFile ras = new RandomAccessFile(f, "rw")) {
194            // No violation is expected as a write is called while reading which should reset the
195            // IoTracker counter.
196            for (int i = 0; i < 11; i++) {
197                recorder.expectNoViolations();
198                if (i == 5) {
199                    ras.write("a".getBytes());
200                }
201                ras.read("a".getBytes());
202            }
203            recorder.expectNoViolations();
204        }
205
206        try (RandomAccessFile ras = new RandomAccessFile(f, "rw")) {
207            // No violation is expected as a seek is called while reading which should reset the
208            // IoTracker counter.
209            for (int i = 0; i < 11; i++) {
210                recorder.expectNoViolations();
211                if (i == 5) {
212                    ras.seek(0);
213                }
214                ras.read("a".getBytes());
215            }
216            recorder.expectNoViolations();
217        }
218
219        try (RandomAccessFile ras = new RandomAccessFile(f, "rw")) {
220            // seek should reset the IoTracker.
221            for (int i = 0; i < 11; i++) {
222                recorder.expectNoViolations();
223                ras.write("a".getBytes());
224            }
225            recorder.expectAndClear("onUnbufferedIO");
226        }
227
228        try (RandomAccessFile ras = new RandomAccessFile(f, "rw")) {
229            // No violation is expected as a read is called while writing which should reset the
230            // IoTracker counter.
231            for (int i = 0; i < 11; i++) {
232                recorder.expectNoViolations();
233                if (i == 5) {
234                    ras.read("a".getBytes());
235                }
236                ras.write("a".getBytes());
237            }
238            recorder.expectNoViolations();
239        }
240
241        try (RandomAccessFile ras = new RandomAccessFile(f, "rw")) {
242            for (int i = 0; i < 11; i++) {
243                recorder.expectNoViolations();
244                if (i == 5) {
245                    ras.seek(0);
246                }
247                ras.write("a".getBytes());
248            }
249            recorder.expectNoViolations();
250        }
251    }
252
253    public void testOpen() throws Exception {
254        File temp = File.createTempFile("foo", "bar");
255        recorder.clear();
256
257        // Open in read/write mode : should be recorded as a read and a write to disk.
258        FileDescriptor fd = Os.open(temp.getPath(), OsConstants.O_RDWR, 0);
259        recorder.expectAndClear("onReadFromDisk", "onWriteToDisk");
260        Os.close(fd);
261
262        // Open in read only mode : should be recorded as a read from disk.
263        recorder.clear();
264        fd = Os.open(temp.getPath(), OsConstants.O_RDONLY, 0);
265        recorder.expectAndClear("onReadFromDisk");
266        Os.close(fd);
267    }
268
269    public static class RecordingPolicy implements BlockGuard.Policy {
270        private final List<String> violations = new ArrayList<>();
271        private Set<Check> checksList;
272
273        public enum Check {
274            WRITE_TO_DISK,
275            READ_FROM_DISK,
276            NETWORK,
277            UNBUFFERED_IO,
278        }
279
280        public void setChecks(EnumSet<Check> checksList) {
281            this.checksList = checksList;
282        }
283
284        @Override
285        public void onWriteToDisk() {
286            if (checksList != null && checksList.contains(Check.WRITE_TO_DISK)) {
287                addViolation("onWriteToDisk");
288            }
289        }
290
291        @Override
292        public void onReadFromDisk() {
293            if (checksList != null && checksList.contains(Check.READ_FROM_DISK)) {
294                addViolation("onReadFromDisk");
295            }
296        }
297
298        @Override
299        public void onNetwork() {
300            if (checksList != null && checksList.contains(Check.NETWORK)) {
301                addViolation("onNetwork");
302            }
303        }
304
305        @Override
306        public void onUnbufferedIO() {
307            if (checksList != null && checksList.contains(Check.UNBUFFERED_IO)) {
308                addViolation("onUnbufferedIO");
309            }
310        }
311
312        private void addViolation(String type) {
313            StackTraceElement[] threadTrace = Thread.currentThread().getStackTrace();
314
315            final StackTraceElement violator = threadTrace[4];
316            violations.add(type + " [caller= " + violator.getMethodName() + "]");
317        }
318
319        public void clear() {
320            violations.clear();
321        }
322
323        public void expectNoViolations() {
324            if (violations.size() != 0) {
325                throw new AssertionError("Expected 0 violations but found " + violations.size());
326            }
327        }
328
329        public void expectAndClear(String... expected) {
330            if (expected.length != violations.size()) {
331                throw new AssertionError("Expected " + expected.length + " violations but found "
332                        + violations.size());
333            }
334
335            for (int i = 0; i < expected.length; ++i) {
336                if (!violations.get(i).startsWith(expected[i])) {
337                    throw new AssertionError("Expected: " + expected[i] + " but was "
338                            + violations.get(i));
339                }
340            }
341
342            clear();
343        }
344
345        @Override
346        public int getPolicyMask() {
347            return 0;
348        }
349    }
350}
351