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 */ 16 package libcore.tzdata.update; 17 18 import java.io.BufferedReader; 19 import java.io.File; 20 import java.io.FileInputStream; 21 import java.io.IOException; 22 import java.io.InputStreamReader; 23 import java.nio.charset.StandardCharsets; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.LinkedList; 27 import java.util.List; 28 import java.util.zip.CRC32; 29 30 /** 31 * Utility methods for files operations. 32 */ 33 public final class FileUtils { 34 FileUtils()35 private FileUtils() { 36 } 37 38 /** 39 * Creates a new {@link java.io.File} from the {@code parentDir} and {@code name}, but only if 40 * the resulting file would exist beneath {@code parentDir}. Useful if {@code name} could 41 * contain "/../" or symlinks. The returned object has a canonicalized path. 42 * 43 * @throws java.io.IOException if the file would not exist beneath {@code parentDir} 44 */ createSubFile(File parentDir, String name)45 public static File createSubFile(File parentDir, String name) throws IOException { 46 // The subFile must exist beneath parentDir. If name contains "/../" this may not be the 47 // case so we check. 48 File subFile = new File(parentDir, name).getCanonicalFile(); 49 if (!subFile.getPath().startsWith(parentDir.getCanonicalPath())) { 50 throw new IOException(name + " must exist beneath " + parentDir + 51 ". Canonicalized subpath: " + subFile); 52 } 53 return subFile; 54 } 55 56 /** 57 * Makes sure a directory exists. If it doesn't exist, it is created. Parent directories are 58 * also created as needed. If {@code makeWorldReadable} is {@code true} the directory's default 59 * permissions will be set. Even when {@code makeWorldReadable} is {@code true}, only 60 * directories explicitly created will have their permissions set; existing directories are 61 * untouched. 62 * 63 * @throws IOException if the directory or one of its parents did not already exist and could 64 * not be created 65 */ ensureDirectoriesExist(File dir, boolean makeWorldReadable)66 public static void ensureDirectoriesExist(File dir, boolean makeWorldReadable) 67 throws IOException { 68 LinkedList<File> dirs = new LinkedList<>(); 69 File currentDir = dir; 70 do { 71 dirs.addFirst(currentDir); 72 currentDir = currentDir.getParentFile(); 73 } while (currentDir != null); 74 75 for (File dirToCheck : dirs) { 76 if (!dirToCheck.exists()) { 77 if (!dirToCheck.mkdir()) { 78 throw new IOException("Unable to create directory: " + dir); 79 } 80 if (makeWorldReadable) { 81 makeDirectoryWorldAccessible(dirToCheck); 82 } 83 } else if (!dirToCheck.isDirectory()) { 84 throw new IOException(dirToCheck + " exists but is not a directory"); 85 } 86 } 87 } 88 makeDirectoryWorldAccessible(File directory)89 public static void makeDirectoryWorldAccessible(File directory) throws IOException { 90 if (!directory.isDirectory()) { 91 throw new IOException(directory + " must be a directory"); 92 } 93 makeWorldReadable(directory); 94 if (!directory.setExecutable(true, false /* ownerOnly */)) { 95 throw new IOException("Unable to make " + directory + " world-executable"); 96 } 97 } 98 makeWorldReadable(File file)99 public static void makeWorldReadable(File file) throws IOException { 100 if (!file.setReadable(true, false /* ownerOnly */)) { 101 throw new IOException("Unable to make " + file + " world-readable"); 102 } 103 } 104 105 /** 106 * Calculates the checksum from the contents of a file. 107 */ calculateChecksum(File file)108 public static long calculateChecksum(File file) throws IOException { 109 final int BUFFER_SIZE = 8196; 110 CRC32 crc32 = new CRC32(); 111 try (FileInputStream fis = new FileInputStream(file)) { 112 byte[] buffer = new byte[BUFFER_SIZE]; 113 int count; 114 while ((count = fis.read(buffer)) != -1) { 115 crc32.update(buffer, 0, count); 116 } 117 } 118 return crc32.getValue(); 119 } 120 rename(File from, File to)121 public static void rename(File from, File to) throws IOException { 122 ensureFileDoesNotExist(to); 123 if (!from.renameTo(to)) { 124 throw new IOException("Unable to rename " + from + " to " + to); 125 } 126 } 127 ensureFileDoesNotExist(File file)128 public static void ensureFileDoesNotExist(File file) throws IOException { 129 if (file.exists()) { 130 if (!file.isFile()) { 131 throw new IOException(file + " is not a file"); 132 } 133 doDelete(file); 134 } 135 } 136 doDelete(File file)137 public static void doDelete(File file) throws IOException { 138 if (!file.delete()) { 139 throw new IOException("Unable to delete: " + file); 140 } 141 } 142 isSymlink(File file)143 public static boolean isSymlink(File file) throws IOException { 144 String baseName = file.getName(); 145 String canonicalPathExceptBaseName = 146 new File(file.getParentFile().getCanonicalFile(), baseName).getPath(); 147 return !file.getCanonicalPath().equals(canonicalPathExceptBaseName); 148 } 149 deleteRecursive(File toDelete)150 public static void deleteRecursive(File toDelete) throws IOException { 151 if (toDelete.isDirectory()) { 152 for (File file : toDelete.listFiles()) { 153 if (file.isDirectory() && !FileUtils.isSymlink(file)) { 154 // The isSymlink() check is important so that we don't delete files in other 155 // directories: only the symlink itself. 156 deleteRecursive(file); 157 } else { 158 // Delete symlinks to directories or files. 159 FileUtils.doDelete(file); 160 } 161 } 162 String[] remainingFiles = toDelete.list(); 163 if (remainingFiles.length != 0) { 164 throw new IOException("Unable to delete files: " + Arrays 165 .toString(remainingFiles)); 166 } 167 } 168 FileUtils.doDelete(toDelete); 169 } 170 filesExist(File rootDir, String... fileNames)171 public static boolean filesExist(File rootDir, String... fileNames) throws IOException { 172 for (String fileName : fileNames) { 173 File file = new File(rootDir, fileName); 174 if (!file.exists()) { 175 return false; 176 } 177 } 178 return true; 179 } 180 181 /** 182 * Read all lines from a UTF-8 encoded file, returning them as a list of strings. 183 */ readLines(File file)184 public static List<String> readLines(File file) throws IOException { 185 FileInputStream in = new FileInputStream(file); 186 try (BufferedReader fileReader = new BufferedReader( 187 new InputStreamReader(in, StandardCharsets.UTF_8)); 188 ) { 189 List<String> lines = new ArrayList<>(); 190 String line; 191 while ((line = fileReader.readLine()) != null) { 192 lines.add(line); 193 } 194 return lines; 195 } 196 } 197 } 198