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