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