1 /* 2 * Copyright (C) 2006 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 17 package android.os; 18 19 import android.system.ErrnoException; 20 import android.text.TextUtils; 21 import android.system.Os; 22 import android.system.OsConstants; 23 import android.util.Log; 24 import android.util.Slog; 25 26 import java.io.BufferedInputStream; 27 import java.io.ByteArrayOutputStream; 28 import java.io.File; 29 import java.io.FileDescriptor; 30 import java.io.FileInputStream; 31 import java.io.FileNotFoundException; 32 import java.io.FileOutputStream; 33 import java.io.FileWriter; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.util.Arrays; 37 import java.util.Comparator; 38 import java.util.regex.Pattern; 39 import java.util.zip.CRC32; 40 import java.util.zip.CheckedInputStream; 41 42 /** 43 * Tools for managing files. Not for public consumption. 44 * @hide 45 */ 46 public class FileUtils { 47 private static final String TAG = "FileUtils"; 48 49 public static final int S_IRWXU = 00700; 50 public static final int S_IRUSR = 00400; 51 public static final int S_IWUSR = 00200; 52 public static final int S_IXUSR = 00100; 53 54 public static final int S_IRWXG = 00070; 55 public static final int S_IRGRP = 00040; 56 public static final int S_IWGRP = 00020; 57 public static final int S_IXGRP = 00010; 58 59 public static final int S_IRWXO = 00007; 60 public static final int S_IROTH = 00004; 61 public static final int S_IWOTH = 00002; 62 public static final int S_IXOTH = 00001; 63 64 /** Regular expression for safe filenames: no spaces or metacharacters */ 65 private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+"); 66 67 /** 68 * Set owner and mode of of given {@link File}. 69 * 70 * @param mode to apply through {@code chmod} 71 * @param uid to apply through {@code chown}, or -1 to leave unchanged 72 * @param gid to apply through {@code chown}, or -1 to leave unchanged 73 * @return 0 on success, otherwise errno. 74 */ 75 public static int setPermissions(File path, int mode, int uid, int gid) { 76 return setPermissions(path.getAbsolutePath(), mode, uid, gid); 77 } 78 79 /** 80 * Set owner and mode of of given path. 81 * 82 * @param mode to apply through {@code chmod} 83 * @param uid to apply through {@code chown}, or -1 to leave unchanged 84 * @param gid to apply through {@code chown}, or -1 to leave unchanged 85 * @return 0 on success, otherwise errno. 86 */ 87 public static int setPermissions(String path, int mode, int uid, int gid) { 88 try { 89 Os.chmod(path, mode); 90 } catch (ErrnoException e) { 91 Slog.w(TAG, "Failed to chmod(" + path + "): " + e); 92 return e.errno; 93 } 94 95 if (uid >= 0 || gid >= 0) { 96 try { 97 Os.chown(path, uid, gid); 98 } catch (ErrnoException e) { 99 Slog.w(TAG, "Failed to chown(" + path + "): " + e); 100 return e.errno; 101 } 102 } 103 104 return 0; 105 } 106 107 /** 108 * Set owner and mode of of given {@link FileDescriptor}. 109 * 110 * @param mode to apply through {@code chmod} 111 * @param uid to apply through {@code chown}, or -1 to leave unchanged 112 * @param gid to apply through {@code chown}, or -1 to leave unchanged 113 * @return 0 on success, otherwise errno. 114 */ 115 public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) { 116 try { 117 Os.fchmod(fd, mode); 118 } catch (ErrnoException e) { 119 Slog.w(TAG, "Failed to fchmod(): " + e); 120 return e.errno; 121 } 122 123 if (uid >= 0 || gid >= 0) { 124 try { 125 Os.fchown(fd, uid, gid); 126 } catch (ErrnoException e) { 127 Slog.w(TAG, "Failed to fchown(): " + e); 128 return e.errno; 129 } 130 } 131 132 return 0; 133 } 134 135 /** 136 * Return owning UID of given path, otherwise -1. 137 */ 138 public static int getUid(String path) { 139 try { 140 return Os.stat(path).st_uid; 141 } catch (ErrnoException e) { 142 return -1; 143 } 144 } 145 146 /** 147 * Perform an fsync on the given FileOutputStream. The stream at this 148 * point must be flushed but not yet closed. 149 */ 150 public static boolean sync(FileOutputStream stream) { 151 try { 152 if (stream != null) { 153 stream.getFD().sync(); 154 } 155 return true; 156 } catch (IOException e) { 157 } 158 return false; 159 } 160 161 // copy a file from srcFile to destFile, return true if succeed, return 162 // false if fail 163 public static boolean copyFile(File srcFile, File destFile) { 164 boolean result = false; 165 try { 166 InputStream in = new FileInputStream(srcFile); 167 try { 168 result = copyToFile(in, destFile); 169 } finally { 170 in.close(); 171 } 172 } catch (IOException e) { 173 result = false; 174 } 175 return result; 176 } 177 178 /** 179 * Copy data from a source stream to destFile. 180 * Return true if succeed, return false if failed. 181 */ 182 public static boolean copyToFile(InputStream inputStream, File destFile) { 183 try { 184 if (destFile.exists()) { 185 destFile.delete(); 186 } 187 FileOutputStream out = new FileOutputStream(destFile); 188 try { 189 byte[] buffer = new byte[4096]; 190 int bytesRead; 191 while ((bytesRead = inputStream.read(buffer)) >= 0) { 192 out.write(buffer, 0, bytesRead); 193 } 194 } finally { 195 out.flush(); 196 try { 197 out.getFD().sync(); 198 } catch (IOException e) { 199 } 200 out.close(); 201 } 202 return true; 203 } catch (IOException e) { 204 return false; 205 } 206 } 207 208 /** 209 * Check if a filename is "safe" (no metacharacters or spaces). 210 * @param file The file to check 211 */ 212 public static boolean isFilenameSafe(File file) { 213 // Note, we check whether it matches what's known to be safe, 214 // rather than what's known to be unsafe. Non-ASCII, control 215 // characters, etc. are all unsafe by default. 216 return SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches(); 217 } 218 219 /** 220 * Read a text file into a String, optionally limiting the length. 221 * @param file to read (will not seek, so things like /proc files are OK) 222 * @param max length (positive for head, negative of tail, 0 for no limit) 223 * @param ellipsis to add of the file was truncated (can be null) 224 * @return the contents of the file, possibly truncated 225 * @throws IOException if something goes wrong reading the file 226 */ 227 public static String readTextFile(File file, int max, String ellipsis) throws IOException { 228 InputStream input = new FileInputStream(file); 229 // wrapping a BufferedInputStream around it because when reading /proc with unbuffered 230 // input stream, bytes read not equal to buffer size is not necessarily the correct 231 // indication for EOF; but it is true for BufferedInputStream due to its implementation. 232 BufferedInputStream bis = new BufferedInputStream(input); 233 try { 234 long size = file.length(); 235 if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes 236 if (size > 0 && (max == 0 || size < max)) max = (int) size; 237 byte[] data = new byte[max + 1]; 238 int length = bis.read(data); 239 if (length <= 0) return ""; 240 if (length <= max) return new String(data, 0, length); 241 if (ellipsis == null) return new String(data, 0, max); 242 return new String(data, 0, max) + ellipsis; 243 } else if (max < 0) { // "tail" mode: keep the last N 244 int len; 245 boolean rolled = false; 246 byte[] last = null; 247 byte[] data = null; 248 do { 249 if (last != null) rolled = true; 250 byte[] tmp = last; last = data; data = tmp; 251 if (data == null) data = new byte[-max]; 252 len = bis.read(data); 253 } while (len == data.length); 254 255 if (last == null && len <= 0) return ""; 256 if (last == null) return new String(data, 0, len); 257 if (len > 0) { 258 rolled = true; 259 System.arraycopy(last, len, last, 0, last.length - len); 260 System.arraycopy(data, 0, last, last.length - len, len); 261 } 262 if (ellipsis == null || !rolled) return new String(last); 263 return ellipsis + new String(last); 264 } else { // "cat" mode: size unknown, read it all in streaming fashion 265 ByteArrayOutputStream contents = new ByteArrayOutputStream(); 266 int len; 267 byte[] data = new byte[1024]; 268 do { 269 len = bis.read(data); 270 if (len > 0) contents.write(data, 0, len); 271 } while (len == data.length); 272 return contents.toString(); 273 } 274 } finally { 275 bis.close(); 276 input.close(); 277 } 278 } 279 280 /** 281 * Writes string to file. Basically same as "echo -n $string > $filename" 282 * 283 * @param filename 284 * @param string 285 * @throws IOException 286 */ 287 public static void stringToFile(String filename, String string) throws IOException { 288 FileWriter out = new FileWriter(filename); 289 try { 290 out.write(string); 291 } finally { 292 out.close(); 293 } 294 } 295 296 /** 297 * Computes the checksum of a file using the CRC32 checksum routine. 298 * The value of the checksum is returned. 299 * 300 * @param file the file to checksum, must not be null 301 * @return the checksum value or an exception is thrown. 302 */ 303 public static long checksumCrc32(File file) throws FileNotFoundException, IOException { 304 CRC32 checkSummer = new CRC32(); 305 CheckedInputStream cis = null; 306 307 try { 308 cis = new CheckedInputStream( new FileInputStream(file), checkSummer); 309 byte[] buf = new byte[128]; 310 while(cis.read(buf) >= 0) { 311 // Just read for checksum to get calculated. 312 } 313 return checkSummer.getValue(); 314 } finally { 315 if (cis != null) { 316 try { 317 cis.close(); 318 } catch (IOException e) { 319 } 320 } 321 } 322 } 323 324 /** 325 * Delete older files in a directory until only those matching the given 326 * constraints remain. 327 * 328 * @param minCount Always keep at least this many files. 329 * @param minAge Always keep files younger than this age. 330 * @return if any files were deleted. 331 */ 332 public static boolean deleteOlderFiles(File dir, int minCount, long minAge) { 333 if (minCount < 0 || minAge < 0) { 334 throw new IllegalArgumentException("Constraints must be positive or 0"); 335 } 336 337 final File[] files = dir.listFiles(); 338 if (files == null) return false; 339 340 // Sort with newest files first 341 Arrays.sort(files, new Comparator<File>() { 342 @Override 343 public int compare(File lhs, File rhs) { 344 return (int) (rhs.lastModified() - lhs.lastModified()); 345 } 346 }); 347 348 // Keep at least minCount files 349 boolean deleted = false; 350 for (int i = minCount; i < files.length; i++) { 351 final File file = files[i]; 352 353 // Keep files newer than minAge 354 final long age = System.currentTimeMillis() - file.lastModified(); 355 if (age > minAge) { 356 if (file.delete()) { 357 Log.d(TAG, "Deleted old file " + file); 358 deleted = true; 359 } 360 } 361 } 362 return deleted; 363 } 364 365 /** 366 * Test if a file lives under the given directory, either as a direct child 367 * or a distant grandchild. 368 * <p> 369 * Both files <em>must</em> have been resolved using 370 * {@link File#getCanonicalFile()} to avoid symlink or path traversal 371 * attacks. 372 */ 373 public static boolean contains(File dir, File file) { 374 if (file == null) return false; 375 376 String dirPath = dir.getAbsolutePath(); 377 String filePath = file.getAbsolutePath(); 378 379 if (dirPath.equals(filePath)) { 380 return true; 381 } 382 383 if (!dirPath.endsWith("/")) { 384 dirPath += "/"; 385 } 386 return filePath.startsWith(dirPath); 387 } 388 389 public static boolean deleteContents(File dir) { 390 File[] files = dir.listFiles(); 391 boolean success = true; 392 if (files != null) { 393 for (File file : files) { 394 if (file.isDirectory()) { 395 success &= deleteContents(file); 396 } 397 if (!file.delete()) { 398 Log.w(TAG, "Failed to delete " + file); 399 success = false; 400 } 401 } 402 } 403 return success; 404 } 405 406 /** 407 * Assert that given filename is valid on ext4. 408 */ 409 public static boolean isValidExtFilename(String name) { 410 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { 411 return false; 412 } 413 for (int i = 0; i < name.length(); i++) { 414 final char c = name.charAt(i); 415 if (c == '\0' || c == '/') { 416 return false; 417 } 418 } 419 return true; 420 } 421 422 public static String rewriteAfterRename(File beforeDir, File afterDir, String path) { 423 if (path == null) return null; 424 final File result = rewriteAfterRename(beforeDir, afterDir, new File(path)); 425 return (result != null) ? result.getAbsolutePath() : null; 426 } 427 428 public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) { 429 if (paths == null) return null; 430 final String[] result = new String[paths.length]; 431 for (int i = 0; i < paths.length; i++) { 432 result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]); 433 } 434 return result; 435 } 436 437 /** 438 * Given a path under the "before" directory, rewrite it to live under the 439 * "after" directory. For example, {@code /before/foo/bar.txt} would become 440 * {@code /after/foo/bar.txt}. 441 */ 442 public static File rewriteAfterRename(File beforeDir, File afterDir, File file) { 443 if (file == null) return null; 444 if (contains(beforeDir, file)) { 445 final String splice = file.getAbsolutePath().substring( 446 beforeDir.getAbsolutePath().length()); 447 return new File(afterDir, splice); 448 } 449 return null; 450 } 451 } 452