1/*
2 * Copyright (C) 2015 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 */
16package libcore.tzdata.update;
17
18import junit.framework.TestCase;
19
20import android.system.Os;
21import android.system.OsConstants;
22import android.system.StructStat;
23
24import java.io.BufferedWriter;
25import java.io.File;
26import java.io.FileOutputStream;
27import java.io.IOException;
28import java.io.OutputStreamWriter;
29import java.nio.charset.StandardCharsets;
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.Collections;
33import java.util.List;
34import libcore.io.IoUtils;
35import libcore.io.Libcore;
36
37/**
38 * Tests for {@link FileUtils}.
39 */
40public class FileUtilsTest extends TestCase {
41
42    private List<File> testFiles = new ArrayList<>();
43
44    @Override
45    public void tearDown() throws Exception {
46        // Delete in reverse order
47        Collections.reverse(testFiles);
48        for (File tempFile : testFiles) {
49            tempFile.delete();
50        }
51        super.tearDown();
52    }
53
54    public void testCalculateChecksum() throws Exception {
55        final String content = "Content";
56        File file1 = createTextFile(content);
57        File file2 = createTextFile(content);
58        File file3 = createTextFile(content + "!");
59
60        long file1CheckSum = FileUtils.calculateChecksum(file1);
61        long file2CheckSum = FileUtils.calculateChecksum(file2);
62        long file3Checksum = FileUtils.calculateChecksum(file3);
63
64        assertEquals(file1CheckSum, file2CheckSum);
65        assertTrue(file1CheckSum != file3Checksum);
66    }
67
68    public void testDeleteRecursive() throws Exception {
69        File dir = createTempDir();
70        File file1 = createRegularFile(dir, "file1");
71        File file2 = createRegularFile(dir, "file2");
72        File symLink1 = createSymlink(file1, dir, "symLink1");
73        File subDir = createDir(dir, "subDir");
74        File file3 = createRegularFile(subDir, "subFile1");
75        File file4 = createRegularFile(subDir, "subFile2");
76        File symLink2 = createSymlink(file1, dir, "symLink2");
77
78        File otherDir = createTempDir();
79        File otherFile = createRegularFile(otherDir, "kept");
80
81        File linkToOtherDir = createSymlink(otherDir, subDir, "linkToOtherDir");
82        File linkToOtherFile = createSymlink(otherFile, subDir, "linkToOtherFile");
83
84        File[] filesToDelete = { dir, file1, file2, symLink1, subDir, file3, file4, symLink2,
85                linkToOtherDir, linkToOtherFile };
86        File[] filesToKeep = { otherDir, otherFile };
87        assertFilesExist(filesToDelete);
88        assertFilesExist(filesToKeep);
89
90        FileUtils.deleteRecursive(dir);
91        assertFilesDoNotExist(filesToDelete);
92        assertFilesExist(filesToKeep);
93    }
94
95    public void testIsSymlink() throws Exception {
96        File dir = createTempDir();
97        File subDir = createDir(dir, "subDir");
98        File fileInSubDir = createRegularFile(subDir, "fileInSubDir");
99        File normalFile = createRegularFile(dir, "normalFile");
100        File symlinkToDir = createSymlink(subDir, dir, "symlinkToDir");
101        File symlinkToFile = createSymlink(fileInSubDir, dir, "symlinkToFile");
102        File symlinkToFileInSubDir = createSymlink(fileInSubDir, dir, "symlinkToFileInSubDir");
103        File normalFileViaSymlink = new File(symlinkToDir, "normalFile");
104
105        assertFalse(FileUtils.isSymlink(dir));
106        assertFalse(FileUtils.isSymlink(subDir));
107        assertFalse(FileUtils.isSymlink(fileInSubDir));
108        assertFalse(FileUtils.isSymlink(normalFile));
109        assertTrue(FileUtils.isSymlink(symlinkToDir));
110        assertTrue(FileUtils.isSymlink(symlinkToFile));
111        assertTrue(FileUtils.isSymlink(symlinkToFileInSubDir));
112        assertFalse(FileUtils.isSymlink(normalFileViaSymlink));
113    }
114
115    public void testCreateSubFile() throws Exception {
116        File dir1 = createTempDir().getCanonicalFile();
117
118        File actualSubFile = FileUtils.createSubFile(dir1, "file");
119        assertEquals(new File(dir1, "file"), actualSubFile);
120
121        File existingSubFile = createRegularFile(dir1, "file");
122        actualSubFile = FileUtils.createSubFile(dir1, "file");
123        assertEquals(existingSubFile, actualSubFile);
124
125        File existingSubDir = createDir(dir1, "subdir");
126        actualSubFile = FileUtils.createSubFile(dir1, "subdir");
127        assertEquals(existingSubDir, actualSubFile);
128
129        assertCreateSubFileThrows(dir1, "../file");
130        assertCreateSubFileThrows(dir1, "../../file");
131        assertCreateSubFileThrows(dir1, "../otherdir/file");
132
133        File dir2 = createTempDir().getCanonicalFile();
134        createSymlink(dir2, dir1, "symlinkToDir2");
135        assertCreateSubFileThrows(dir1, "symlinkToDir2");
136
137        assertCreateSubFileThrows(dir1, "symlinkToDir2/fileInSymlinkedDir");
138
139        createRegularFile(dir1, "symlinkToDir2/fileInSymlinkedDir");
140        assertCreateSubFileThrows(dir1, "symlinkToDir2/fileInSymlinkedDir");
141    }
142
143    public void testEnsureDirectoryExists() throws Exception {
144        File dir = createTempDir();
145
146        File exists = new File(dir, "exists");
147        assertTrue(exists.mkdir());
148        assertTrue(exists.setReadable(true /* readable */, true /* ownerOnly */));
149        assertTrue(exists.setExecutable(true /* readable */, true /* ownerOnly */));
150        FileUtils.ensureDirectoriesExist(exists, true /* makeWorldReadable */);
151        assertDirExistsAndIsAccessible(exists, false /* requireWorldReadable */);
152
153        File subDir = new File(dir, "subDir");
154        assertFalse(subDir.exists());
155        FileUtils.ensureDirectoriesExist(subDir, true /* makeWorldReadable */);
156        assertDirExistsAndIsAccessible(subDir, true /* requireWorldReadable */);
157
158        File one = new File(dir, "one");
159        File two = new File(one, "two");
160        File three = new File(two, "three");
161        FileUtils.ensureDirectoriesExist(three, true /* makeWorldReadable */);
162        assertDirExistsAndIsAccessible(one, true /* requireWorldReadable */);
163        assertDirExistsAndIsAccessible(two, true /* requireWorldReadable */);
164        assertDirExistsAndIsAccessible(three, true /* requireWorldReadable */);
165    }
166
167    public void testEnsureDirectoriesExist_noPermissions() throws Exception {
168        File dir = createTempDir();
169        assertDirExistsAndIsAccessible(dir, false /* requireWorldReadable */);
170
171        File unreadableSubDir = new File(dir, "unreadableSubDir");
172        assertTrue(unreadableSubDir.mkdir());
173        assertTrue(unreadableSubDir.setReadable(false /* readable */, true /* ownerOnly */));
174        assertTrue(unreadableSubDir.setExecutable(false /* readable */, true /* ownerOnly */));
175
176        File toCreate = new File(unreadableSubDir, "toCreate");
177        try {
178            FileUtils.ensureDirectoriesExist(toCreate, true /* makeWorldReadable */);
179            fail();
180        } catch (IOException expected) {
181        }
182        assertDirExistsAndIsAccessible(dir, false /* requireWorldReadable */);
183        assertFalse(unreadableSubDir.canRead() && unreadableSubDir.canExecute());
184        assertFalse(toCreate.exists());
185    }
186
187    public void testEnsureFileDoesNotExist() throws Exception {
188        File dir = createTempDir();
189
190        FileUtils.ensureFileDoesNotExist(new File(dir, "doesNotExist"));
191
192        File exists1 = createRegularFile(dir, "exists1");
193        assertTrue(exists1.exists());
194        FileUtils.ensureFileDoesNotExist(exists1);
195        assertFalse(exists1.exists());
196
197        exists1 = createRegularFile(dir, "exists1");
198        File symlink = createSymlink(exists1, dir, "symlinkToFile");
199        assertTrue(symlink.exists());
200        FileUtils.ensureFileDoesNotExist(symlink);
201        assertFalse(symlink.exists());
202        assertTrue(exists1.exists());
203
204        // Only files and symlinks supported. We do not delete directories.
205        File emptyDir = createTempDir();
206        try {
207            FileUtils.ensureFileDoesNotExist(emptyDir);
208            fail();
209        } catch (IOException expected) {
210        }
211        assertTrue(emptyDir.exists());
212    }
213
214    // This test does not pass when run as root because root can do anything even if the permissions
215    // don't allow it.
216    public void testEnsureFileDoesNotExist_noPermission() throws Exception {
217        File dir = createTempDir();
218
219        File protectedDir = createDir(dir, "protected");
220        File undeletable = createRegularFile(protectedDir, "undeletable");
221        assertTrue(protectedDir.setWritable(false));
222        assertTrue(undeletable.exists());
223        try {
224            FileUtils.ensureFileDoesNotExist(undeletable);
225            fail();
226        } catch (IOException expected) {
227        } finally {
228            assertTrue(protectedDir.setWritable(true)); // Reset for clean-up
229        }
230        assertTrue(undeletable.exists());
231    }
232
233    public void testCheckFilesExist() throws Exception {
234        File dir = createTempDir();
235        createRegularFile(dir, "exists1");
236        File subDir = createDir(dir, "subDir");
237        createRegularFile(subDir, "exists2");
238        assertTrue(FileUtils.filesExist(dir, "exists1", "subDir/exists2"));
239        assertFalse(FileUtils.filesExist(dir, "doesNotExist"));
240        assertFalse(FileUtils.filesExist(dir, "subDir/doesNotExist"));
241    }
242
243    public void testReadLines() throws Exception {
244        File file = createTextFile("One\nTwo\nThree\n");
245
246        List<String> lines = FileUtils.readLines(file);
247        assertEquals(3, lines.size());
248        assertEquals(lines, Arrays.asList("One", "Two", "Three"));
249    }
250
251    private File createTextFile(String contents) throws IOException {
252        File file = File.createTempFile(getClass().getSimpleName(), ".txt");
253        try (FileOutputStream fos = new FileOutputStream(file)) {
254            BufferedWriter writer = new BufferedWriter(
255                    new OutputStreamWriter(fos, StandardCharsets.UTF_8));
256            writer.write(contents);
257            writer.close();
258        }
259        return file;
260    }
261
262    private File createSymlink(File file, File symlinkDir, String symlinkName) throws Exception {
263        assertTrue(file.exists());
264
265        File symlink = new File(symlinkDir, symlinkName);
266        Os.symlink(file.getAbsolutePath(), symlink.getAbsolutePath());
267        testFiles.add(symlink);
268        return symlink;
269    }
270
271    private static void assertCreateSubFileThrows(File parentDir, String name) {
272        try {
273            FileUtils.createSubFile(parentDir, name);
274            fail();
275        } catch (IOException expected) {
276            assertTrue(expected.getMessage().contains("must exist beneath"));
277        }
278    }
279
280    private static void assertFilesDoNotExist(File... files) {
281        for (File f : files) {
282            assertFalse(f + " unexpectedly exists", f.exists());
283        }
284    }
285
286    private static void assertFilesExist(File... files) {
287        for (File f : files) {
288            assertTrue(f + " expected to exist", f.exists());
289        }
290    }
291
292    private static void assertDirExistsAndIsAccessible(File dir, boolean requireWorldReadable)
293            throws Exception {
294        assertTrue(dir.exists() && dir.isDirectory() && dir.canRead() && dir.canExecute());
295
296        String path = dir.getCanonicalPath();
297        StructStat sb = Libcore.os.stat(path);
298        int mask = OsConstants.S_IXUSR | OsConstants.S_IRUSR;
299        if (requireWorldReadable) {
300            mask = mask | OsConstants.S_IXGRP | OsConstants.S_IRGRP
301                    | OsConstants.S_IXOTH | OsConstants.S_IROTH;
302        }
303        assertTrue("Permission mask required: " + Integer.toOctalString(mask),
304                (sb.st_mode & mask) == mask);
305    }
306
307    private File createTempDir() {
308        final String tempPrefix = getClass().getSimpleName();
309        File tempDir = IoUtils.createTemporaryDirectory(tempPrefix);
310        testFiles.add(tempDir);
311        return tempDir;
312    }
313
314    private File createDir(File parentDir, String name) {
315        File dir = new File(parentDir, name);
316        assertTrue(dir.mkdir());
317        testFiles.add(dir);
318        return dir;
319    }
320
321    private File createRegularFile(File dir, String name) throws Exception {
322        File file = new File(dir, name);
323        try (FileOutputStream fos = new FileOutputStream(file)) {
324            fos.write("Hello".getBytes());
325        }
326        testFiles.add(file);
327        return file;
328    }
329}
330