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 static android.os.ParcelFileDescriptor.MODE_APPEND; 20 import static android.os.ParcelFileDescriptor.MODE_CREATE; 21 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; 22 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; 23 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; 24 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; 25 import static android.system.OsConstants.F_OK; 26 import static android.system.OsConstants.O_ACCMODE; 27 import static android.system.OsConstants.O_APPEND; 28 import static android.system.OsConstants.O_CREAT; 29 import static android.system.OsConstants.O_RDONLY; 30 import static android.system.OsConstants.O_RDWR; 31 import static android.system.OsConstants.O_TRUNC; 32 import static android.system.OsConstants.O_WRONLY; 33 import static android.system.OsConstants.R_OK; 34 import static android.system.OsConstants.SPLICE_F_MORE; 35 import static android.system.OsConstants.SPLICE_F_MOVE; 36 import static android.system.OsConstants.S_ISFIFO; 37 import static android.system.OsConstants.S_ISREG; 38 import static android.system.OsConstants.W_OK; 39 40 import android.annotation.NonNull; 41 import android.annotation.Nullable; 42 import android.annotation.TestApi; 43 import android.annotation.UnsupportedAppUsage; 44 import android.content.ContentResolver; 45 import android.provider.DocumentsContract.Document; 46 import android.system.ErrnoException; 47 import android.system.Os; 48 import android.system.StructStat; 49 import android.text.TextUtils; 50 import android.util.Log; 51 import android.util.Slog; 52 import android.webkit.MimeTypeMap; 53 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.internal.util.ArrayUtils; 56 import com.android.internal.util.SizedInputStream; 57 58 import libcore.io.IoUtils; 59 import libcore.util.EmptyArray; 60 61 import java.io.BufferedInputStream; 62 import java.io.ByteArrayOutputStream; 63 import java.io.File; 64 import java.io.FileDescriptor; 65 import java.io.FileInputStream; 66 import java.io.FileNotFoundException; 67 import java.io.FileOutputStream; 68 import java.io.FilenameFilter; 69 import java.io.IOException; 70 import java.io.InputStream; 71 import java.io.OutputStream; 72 import java.nio.charset.StandardCharsets; 73 import java.security.DigestInputStream; 74 import java.security.MessageDigest; 75 import java.security.NoSuchAlgorithmException; 76 import java.util.Arrays; 77 import java.util.Collection; 78 import java.util.Comparator; 79 import java.util.Objects; 80 import java.util.concurrent.Executor; 81 import java.util.concurrent.TimeUnit; 82 import java.util.regex.Pattern; 83 import java.util.zip.CRC32; 84 import java.util.zip.CheckedInputStream; 85 86 /** 87 * Utility methods useful for working with files. 88 */ 89 public final class FileUtils { 90 private static final String TAG = "FileUtils"; 91 92 /** {@hide} */ public static final int S_IRWXU = 00700; 93 /** {@hide} */ public static final int S_IRUSR = 00400; 94 /** {@hide} */ public static final int S_IWUSR = 00200; 95 /** {@hide} */ public static final int S_IXUSR = 00100; 96 97 /** {@hide} */ public static final int S_IRWXG = 00070; 98 /** {@hide} */ public static final int S_IRGRP = 00040; 99 /** {@hide} */ public static final int S_IWGRP = 00020; 100 /** {@hide} */ public static final int S_IXGRP = 00010; 101 102 /** {@hide} */ public static final int S_IRWXO = 00007; 103 /** {@hide} */ public static final int S_IROTH = 00004; 104 /** {@hide} */ public static final int S_IWOTH = 00002; 105 /** {@hide} */ public static final int S_IXOTH = 00001; 106 107 @UnsupportedAppUsage 108 private FileUtils() { 109 } 110 111 /** Regular expression for safe filenames: no spaces or metacharacters. 112 * 113 * Use a preload holder so that FileUtils can be compile-time initialized. 114 */ 115 private static class NoImagePreloadHolder { 116 public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+"); 117 } 118 119 // non-final so it can be toggled by Robolectric's ShadowFileUtils 120 private static boolean sEnableCopyOptimizations = true; 121 122 private static final long COPY_CHECKPOINT_BYTES = 524288; 123 124 /** 125 * Listener that is called periodically as progress is made. 126 */ 127 public interface ProgressListener { 128 public void onProgress(long progress); 129 } 130 131 /** 132 * Set owner and mode of of given {@link File}. 133 * 134 * @param mode to apply through {@code chmod} 135 * @param uid to apply through {@code chown}, or -1 to leave unchanged 136 * @param gid to apply through {@code chown}, or -1 to leave unchanged 137 * @return 0 on success, otherwise errno. 138 * @hide 139 */ 140 @UnsupportedAppUsage 141 public static int setPermissions(File path, int mode, int uid, int gid) { 142 return setPermissions(path.getAbsolutePath(), mode, uid, gid); 143 } 144 145 /** 146 * Set owner and mode of of given path. 147 * 148 * @param mode to apply through {@code chmod} 149 * @param uid to apply through {@code chown}, or -1 to leave unchanged 150 * @param gid to apply through {@code chown}, or -1 to leave unchanged 151 * @return 0 on success, otherwise errno. 152 * @hide 153 */ 154 @UnsupportedAppUsage 155 public static int setPermissions(String path, int mode, int uid, int gid) { 156 try { 157 Os.chmod(path, mode); 158 } catch (ErrnoException e) { 159 Slog.w(TAG, "Failed to chmod(" + path + "): " + e); 160 return e.errno; 161 } 162 163 if (uid >= 0 || gid >= 0) { 164 try { 165 Os.chown(path, uid, gid); 166 } catch (ErrnoException e) { 167 Slog.w(TAG, "Failed to chown(" + path + "): " + e); 168 return e.errno; 169 } 170 } 171 172 return 0; 173 } 174 175 /** 176 * Set owner and mode of of given {@link FileDescriptor}. 177 * 178 * @param mode to apply through {@code chmod} 179 * @param uid to apply through {@code chown}, or -1 to leave unchanged 180 * @param gid to apply through {@code chown}, or -1 to leave unchanged 181 * @return 0 on success, otherwise errno. 182 * @hide 183 */ 184 @UnsupportedAppUsage 185 public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) { 186 try { 187 Os.fchmod(fd, mode); 188 } catch (ErrnoException e) { 189 Slog.w(TAG, "Failed to fchmod(): " + e); 190 return e.errno; 191 } 192 193 if (uid >= 0 || gid >= 0) { 194 try { 195 Os.fchown(fd, uid, gid); 196 } catch (ErrnoException e) { 197 Slog.w(TAG, "Failed to fchown(): " + e); 198 return e.errno; 199 } 200 } 201 202 return 0; 203 } 204 205 /** 206 * Copy the owner UID, owner GID, and mode bits from one file to another. 207 * 208 * @param from File where attributes should be copied from. 209 * @param to File where attributes should be copied to. 210 * @hide 211 */ 212 public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException { 213 try { 214 final StructStat stat = Os.stat(from.getAbsolutePath()); 215 Os.chmod(to.getAbsolutePath(), stat.st_mode); 216 Os.chown(to.getAbsolutePath(), stat.st_uid, stat.st_gid); 217 } catch (ErrnoException e) { 218 throw e.rethrowAsIOException(); 219 } 220 } 221 222 /** 223 * @deprecated use {@link Os#stat(String)} instead. 224 * @hide 225 */ 226 @Deprecated 227 public static int getUid(String path) { 228 try { 229 return Os.stat(path).st_uid; 230 } catch (ErrnoException e) { 231 return -1; 232 } 233 } 234 235 /** 236 * Perform an fsync on the given FileOutputStream. The stream at this 237 * point must be flushed but not yet closed. 238 * 239 * @hide 240 */ 241 @UnsupportedAppUsage 242 public static boolean sync(FileOutputStream stream) { 243 try { 244 if (stream != null) { 245 stream.getFD().sync(); 246 } 247 return true; 248 } catch (IOException e) { 249 } 250 return false; 251 } 252 253 /** 254 * @deprecated use {@link #copy(File, File)} instead. 255 * @hide 256 */ 257 @UnsupportedAppUsage 258 @Deprecated 259 public static boolean copyFile(File srcFile, File destFile) { 260 try { 261 copyFileOrThrow(srcFile, destFile); 262 return true; 263 } catch (IOException e) { 264 return false; 265 } 266 } 267 268 /** 269 * @deprecated use {@link #copy(File, File)} instead. 270 * @hide 271 */ 272 @Deprecated 273 public static void copyFileOrThrow(File srcFile, File destFile) throws IOException { 274 try (InputStream in = new FileInputStream(srcFile)) { 275 copyToFileOrThrow(in, destFile); 276 } 277 } 278 279 /** 280 * @deprecated use {@link #copy(InputStream, OutputStream)} instead. 281 * @hide 282 */ 283 @UnsupportedAppUsage 284 @Deprecated 285 public static boolean copyToFile(InputStream inputStream, File destFile) { 286 try { 287 copyToFileOrThrow(inputStream, destFile); 288 return true; 289 } catch (IOException e) { 290 return false; 291 } 292 } 293 294 /** 295 * @deprecated use {@link #copy(InputStream, OutputStream)} instead. 296 * @hide 297 */ 298 @Deprecated 299 public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException { 300 if (destFile.exists()) { 301 destFile.delete(); 302 } 303 try (FileOutputStream out = new FileOutputStream(destFile)) { 304 copy(in, out); 305 try { 306 Os.fsync(out.getFD()); 307 } catch (ErrnoException e) { 308 throw e.rethrowAsIOException(); 309 } 310 } 311 } 312 313 /** 314 * Copy the contents of one file to another, replacing any existing content. 315 * <p> 316 * Attempts to use several optimization strategies to copy the data in the 317 * kernel before falling back to a userspace copy as a last resort. 318 * 319 * @return number of bytes copied. 320 * @hide 321 */ 322 public static long copy(@NonNull File from, @NonNull File to) throws IOException { 323 return copy(from, to, null, null, null); 324 } 325 326 /** 327 * Copy the contents of one file to another, replacing any existing content. 328 * <p> 329 * Attempts to use several optimization strategies to copy the data in the 330 * kernel before falling back to a userspace copy as a last resort. 331 * 332 * @param signal to signal if the copy should be cancelled early. 333 * @param executor that listener events should be delivered via. 334 * @param listener to be periodically notified as the copy progresses. 335 * @return number of bytes copied. 336 * @hide 337 */ 338 public static long copy(@NonNull File from, @NonNull File to, 339 @Nullable CancellationSignal signal, @Nullable Executor executor, 340 @Nullable ProgressListener listener) throws IOException { 341 try (FileInputStream in = new FileInputStream(from); 342 FileOutputStream out = new FileOutputStream(to)) { 343 return copy(in, out, signal, executor, listener); 344 } 345 } 346 347 /** 348 * Copy the contents of one stream to another. 349 * <p> 350 * Attempts to use several optimization strategies to copy the data in the 351 * kernel before falling back to a userspace copy as a last resort. 352 * 353 * @return number of bytes copied. 354 */ 355 public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException { 356 return copy(in, out, null, null, null); 357 } 358 359 /** 360 * Copy the contents of one stream to another. 361 * <p> 362 * Attempts to use several optimization strategies to copy the data in the 363 * kernel before falling back to a userspace copy as a last resort. 364 * 365 * @param signal to signal if the copy should be cancelled early. 366 * @param executor that listener events should be delivered via. 367 * @param listener to be periodically notified as the copy progresses. 368 * @return number of bytes copied. 369 */ 370 public static long copy(@NonNull InputStream in, @NonNull OutputStream out, 371 @Nullable CancellationSignal signal, @Nullable Executor executor, 372 @Nullable ProgressListener listener) throws IOException { 373 if (sEnableCopyOptimizations) { 374 if (in instanceof FileInputStream && out instanceof FileOutputStream) { 375 return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(), 376 signal, executor, listener); 377 } 378 } 379 380 // Worse case fallback to userspace 381 return copyInternalUserspace(in, out, signal, executor, listener); 382 } 383 384 /** 385 * Copy the contents of one FD to another. 386 * <p> 387 * Attempts to use several optimization strategies to copy the data in the 388 * kernel before falling back to a userspace copy as a last resort. 389 * 390 * @return number of bytes copied. 391 */ 392 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out) 393 throws IOException { 394 return copy(in, out, null, null, null); 395 } 396 397 /** 398 * Copy the contents of one FD to another. 399 * <p> 400 * Attempts to use several optimization strategies to copy the data in the 401 * kernel before falling back to a userspace copy as a last resort. 402 * 403 * @param signal to signal if the copy should be cancelled early. 404 * @param executor that listener events should be delivered via. 405 * @param listener to be periodically notified as the copy progresses. 406 * @return number of bytes copied. 407 */ 408 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, 409 @Nullable CancellationSignal signal, @Nullable Executor executor, 410 @Nullable ProgressListener listener) throws IOException { 411 return copy(in, out, Long.MAX_VALUE, signal, executor, listener); 412 } 413 414 /** 415 * Copy the contents of one FD to another. 416 * <p> 417 * Attempts to use several optimization strategies to copy the data in the 418 * kernel before falling back to a userspace copy as a last resort. 419 * 420 * @param count the number of bytes to copy. 421 * @param signal to signal if the copy should be cancelled early. 422 * @param executor that listener events should be delivered via. 423 * @param listener to be periodically notified as the copy progresses. 424 * @return number of bytes copied. 425 * @hide 426 */ 427 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count, 428 @Nullable CancellationSignal signal, @Nullable Executor executor, 429 @Nullable ProgressListener listener) throws IOException { 430 if (sEnableCopyOptimizations) { 431 try { 432 final StructStat st_in = Os.fstat(in); 433 final StructStat st_out = Os.fstat(out); 434 if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) { 435 return copyInternalSendfile(in, out, count, signal, executor, listener); 436 } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) { 437 return copyInternalSplice(in, out, count, signal, executor, listener); 438 } 439 } catch (ErrnoException e) { 440 throw e.rethrowAsIOException(); 441 } 442 } 443 444 // Worse case fallback to userspace 445 return copyInternalUserspace(in, out, count, signal, executor, listener); 446 } 447 448 /** 449 * Requires one of input or output to be a pipe. 450 * 451 * @hide 452 */ 453 @VisibleForTesting 454 public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count, 455 CancellationSignal signal, Executor executor, ProgressListener listener) 456 throws ErrnoException { 457 long progress = 0; 458 long checkpoint = 0; 459 460 long t; 461 while ((t = Os.splice(in, null, out, null, Math.min(count, COPY_CHECKPOINT_BYTES), 462 SPLICE_F_MOVE | SPLICE_F_MORE)) != 0) { 463 progress += t; 464 checkpoint += t; 465 count -= t; 466 467 if (checkpoint >= COPY_CHECKPOINT_BYTES) { 468 if (signal != null) { 469 signal.throwIfCanceled(); 470 } 471 if (executor != null && listener != null) { 472 final long progressSnapshot = progress; 473 executor.execute(() -> { 474 listener.onProgress(progressSnapshot); 475 }); 476 } 477 checkpoint = 0; 478 } 479 } 480 if (executor != null && listener != null) { 481 final long progressSnapshot = progress; 482 executor.execute(() -> { 483 listener.onProgress(progressSnapshot); 484 }); 485 } 486 return progress; 487 } 488 489 /** 490 * Requires both input and output to be a regular file. 491 * 492 * @hide 493 */ 494 @VisibleForTesting 495 public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count, 496 CancellationSignal signal, Executor executor, ProgressListener listener) 497 throws ErrnoException { 498 long progress = 0; 499 long checkpoint = 0; 500 501 long t; 502 while ((t = Os.sendfile(out, in, null, Math.min(count, COPY_CHECKPOINT_BYTES))) != 0) { 503 progress += t; 504 checkpoint += t; 505 count -= t; 506 507 if (checkpoint >= COPY_CHECKPOINT_BYTES) { 508 if (signal != null) { 509 signal.throwIfCanceled(); 510 } 511 if (executor != null && listener != null) { 512 final long progressSnapshot = progress; 513 executor.execute(() -> { 514 listener.onProgress(progressSnapshot); 515 }); 516 } 517 checkpoint = 0; 518 } 519 } 520 if (executor != null && listener != null) { 521 final long progressSnapshot = progress; 522 executor.execute(() -> { 523 listener.onProgress(progressSnapshot); 524 }); 525 } 526 return progress; 527 } 528 529 /** {@hide} */ 530 @Deprecated 531 @VisibleForTesting 532 public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, 533 ProgressListener listener, CancellationSignal signal, long count) 534 throws IOException { 535 return copyInternalUserspace(in, out, count, signal, Runnable::run, listener); 536 } 537 538 /** {@hide} */ 539 @VisibleForTesting 540 public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count, 541 CancellationSignal signal, Executor executor, ProgressListener listener) 542 throws IOException { 543 if (count != Long.MAX_VALUE) { 544 return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count), 545 new FileOutputStream(out), signal, executor, listener); 546 } else { 547 return copyInternalUserspace(new FileInputStream(in), 548 new FileOutputStream(out), signal, executor, listener); 549 } 550 } 551 552 /** {@hide} */ 553 @VisibleForTesting 554 public static long copyInternalUserspace(InputStream in, OutputStream out, 555 CancellationSignal signal, Executor executor, ProgressListener listener) 556 throws IOException { 557 long progress = 0; 558 long checkpoint = 0; 559 byte[] buffer = new byte[8192]; 560 561 int t; 562 while ((t = in.read(buffer)) != -1) { 563 out.write(buffer, 0, t); 564 565 progress += t; 566 checkpoint += t; 567 568 if (checkpoint >= COPY_CHECKPOINT_BYTES) { 569 if (signal != null) { 570 signal.throwIfCanceled(); 571 } 572 if (executor != null && listener != null) { 573 final long progressSnapshot = progress; 574 executor.execute(() -> { 575 listener.onProgress(progressSnapshot); 576 }); 577 } 578 checkpoint = 0; 579 } 580 } 581 if (executor != null && listener != null) { 582 final long progressSnapshot = progress; 583 executor.execute(() -> { 584 listener.onProgress(progressSnapshot); 585 }); 586 } 587 return progress; 588 } 589 590 /** 591 * Check if a filename is "safe" (no metacharacters or spaces). 592 * @param file The file to check 593 * @hide 594 */ 595 @UnsupportedAppUsage 596 public static boolean isFilenameSafe(File file) { 597 // Note, we check whether it matches what's known to be safe, 598 // rather than what's known to be unsafe. Non-ASCII, control 599 // characters, etc. are all unsafe by default. 600 return NoImagePreloadHolder.SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches(); 601 } 602 603 /** 604 * Read a text file into a String, optionally limiting the length. 605 * @param file to read (will not seek, so things like /proc files are OK) 606 * @param max length (positive for head, negative of tail, 0 for no limit) 607 * @param ellipsis to add of the file was truncated (can be null) 608 * @return the contents of the file, possibly truncated 609 * @throws IOException if something goes wrong reading the file 610 * @hide 611 */ 612 @UnsupportedAppUsage 613 public static String readTextFile(File file, int max, String ellipsis) throws IOException { 614 InputStream input = new FileInputStream(file); 615 // wrapping a BufferedInputStream around it because when reading /proc with unbuffered 616 // input stream, bytes read not equal to buffer size is not necessarily the correct 617 // indication for EOF; but it is true for BufferedInputStream due to its implementation. 618 BufferedInputStream bis = new BufferedInputStream(input); 619 try { 620 long size = file.length(); 621 if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes 622 if (size > 0 && (max == 0 || size < max)) max = (int) size; 623 byte[] data = new byte[max + 1]; 624 int length = bis.read(data); 625 if (length <= 0) return ""; 626 if (length <= max) return new String(data, 0, length); 627 if (ellipsis == null) return new String(data, 0, max); 628 return new String(data, 0, max) + ellipsis; 629 } else if (max < 0) { // "tail" mode: keep the last N 630 int len; 631 boolean rolled = false; 632 byte[] last = null; 633 byte[] data = null; 634 do { 635 if (last != null) rolled = true; 636 byte[] tmp = last; last = data; data = tmp; 637 if (data == null) data = new byte[-max]; 638 len = bis.read(data); 639 } while (len == data.length); 640 641 if (last == null && len <= 0) return ""; 642 if (last == null) return new String(data, 0, len); 643 if (len > 0) { 644 rolled = true; 645 System.arraycopy(last, len, last, 0, last.length - len); 646 System.arraycopy(data, 0, last, last.length - len, len); 647 } 648 if (ellipsis == null || !rolled) return new String(last); 649 return ellipsis + new String(last); 650 } else { // "cat" mode: size unknown, read it all in streaming fashion 651 ByteArrayOutputStream contents = new ByteArrayOutputStream(); 652 int len; 653 byte[] data = new byte[1024]; 654 do { 655 len = bis.read(data); 656 if (len > 0) contents.write(data, 0, len); 657 } while (len == data.length); 658 return contents.toString(); 659 } 660 } finally { 661 bis.close(); 662 input.close(); 663 } 664 } 665 666 /** {@hide} */ 667 @UnsupportedAppUsage 668 public static void stringToFile(File file, String string) throws IOException { 669 stringToFile(file.getAbsolutePath(), string); 670 } 671 672 /** 673 * Writes the bytes given in {@code content} to the file whose absolute path 674 * is {@code filename}. 675 * 676 * @hide 677 */ 678 public static void bytesToFile(String filename, byte[] content) throws IOException { 679 if (filename.startsWith("/proc/")) { 680 final int oldMask = StrictMode.allowThreadDiskWritesMask(); 681 try (FileOutputStream fos = new FileOutputStream(filename)) { 682 fos.write(content); 683 } finally { 684 StrictMode.setThreadPolicyMask(oldMask); 685 } 686 } else { 687 try (FileOutputStream fos = new FileOutputStream(filename)) { 688 fos.write(content); 689 } 690 } 691 } 692 693 /** 694 * Writes string to file. Basically same as "echo -n $string > $filename" 695 * 696 * @param filename 697 * @param string 698 * @throws IOException 699 * @hide 700 */ 701 @UnsupportedAppUsage 702 public static void stringToFile(String filename, String string) throws IOException { 703 bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8)); 704 } 705 706 /** 707 * Computes the checksum of a file using the CRC32 checksum routine. The 708 * value of the checksum is returned. 709 * 710 * @param file the file to checksum, must not be null 711 * @return the checksum value or an exception is thrown. 712 * @deprecated this is a weak hashing algorithm, and should not be used due 713 * to its potential for collision. 714 * @hide 715 */ 716 @UnsupportedAppUsage 717 @Deprecated 718 public static long checksumCrc32(File file) throws FileNotFoundException, IOException { 719 CRC32 checkSummer = new CRC32(); 720 CheckedInputStream cis = null; 721 722 try { 723 cis = new CheckedInputStream( new FileInputStream(file), checkSummer); 724 byte[] buf = new byte[128]; 725 while(cis.read(buf) >= 0) { 726 // Just read for checksum to get calculated. 727 } 728 return checkSummer.getValue(); 729 } finally { 730 if (cis != null) { 731 try { 732 cis.close(); 733 } catch (IOException e) { 734 } 735 } 736 } 737 } 738 739 /** 740 * Compute the digest of the given file using the requested algorithm. 741 * 742 * @param algorithm Any valid algorithm accepted by 743 * {@link MessageDigest#getInstance(String)}. 744 * @hide 745 */ 746 public static byte[] digest(@NonNull File file, @NonNull String algorithm) 747 throws IOException, NoSuchAlgorithmException { 748 try (FileInputStream in = new FileInputStream(file)) { 749 return digest(in, algorithm); 750 } 751 } 752 753 /** 754 * Compute the digest of the given file using the requested algorithm. 755 * 756 * @param algorithm Any valid algorithm accepted by 757 * {@link MessageDigest#getInstance(String)}. 758 * @hide 759 */ 760 public static byte[] digest(@NonNull InputStream in, @NonNull String algorithm) 761 throws IOException, NoSuchAlgorithmException { 762 // TODO: implement kernel optimizations 763 return digestInternalUserspace(in, algorithm); 764 } 765 766 /** 767 * Compute the digest of the given file using the requested algorithm. 768 * 769 * @param algorithm Any valid algorithm accepted by 770 * {@link MessageDigest#getInstance(String)}. 771 * @hide 772 */ 773 public static byte[] digest(FileDescriptor fd, String algorithm) 774 throws IOException, NoSuchAlgorithmException { 775 // TODO: implement kernel optimizations 776 return digestInternalUserspace(new FileInputStream(fd), algorithm); 777 } 778 779 private static byte[] digestInternalUserspace(InputStream in, String algorithm) 780 throws IOException, NoSuchAlgorithmException { 781 final MessageDigest digest = MessageDigest.getInstance(algorithm); 782 try (DigestInputStream digestStream = new DigestInputStream(in, digest)) { 783 final byte[] buffer = new byte[8192]; 784 while (digestStream.read(buffer) != -1) { 785 } 786 } 787 return digest.digest(); 788 } 789 790 /** 791 * Delete older files in a directory until only those matching the given 792 * constraints remain. 793 * 794 * @param minCount Always keep at least this many files. 795 * @param minAgeMs Always keep files younger than this age, in milliseconds. 796 * @return if any files were deleted. 797 * @hide 798 */ 799 @UnsupportedAppUsage 800 public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) { 801 if (minCount < 0 || minAgeMs < 0) { 802 throw new IllegalArgumentException("Constraints must be positive or 0"); 803 } 804 805 final File[] files = dir.listFiles(); 806 if (files == null) return false; 807 808 // Sort with newest files first 809 Arrays.sort(files, new Comparator<File>() { 810 @Override 811 public int compare(File lhs, File rhs) { 812 return Long.compare(rhs.lastModified(), lhs.lastModified()); 813 } 814 }); 815 816 // Keep at least minCount files 817 boolean deleted = false; 818 for (int i = minCount; i < files.length; i++) { 819 final File file = files[i]; 820 821 // Keep files newer than minAgeMs 822 final long age = System.currentTimeMillis() - file.lastModified(); 823 if (age > minAgeMs) { 824 if (file.delete()) { 825 Log.d(TAG, "Deleted old file " + file); 826 deleted = true; 827 } 828 } 829 } 830 return deleted; 831 } 832 833 /** 834 * Test if a file lives under the given directory, either as a direct child 835 * or a distant grandchild. 836 * <p> 837 * Both files <em>must</em> have been resolved using 838 * {@link File#getCanonicalFile()} to avoid symlink or path traversal 839 * attacks. 840 * 841 * @hide 842 */ 843 public static boolean contains(File[] dirs, File file) { 844 for (File dir : dirs) { 845 if (contains(dir, file)) { 846 return true; 847 } 848 } 849 return false; 850 } 851 852 /** {@hide} */ 853 public static boolean contains(Collection<File> dirs, File file) { 854 for (File dir : dirs) { 855 if (contains(dir, file)) { 856 return true; 857 } 858 } 859 return false; 860 } 861 862 /** 863 * Test if a file lives under the given directory, either as a direct child 864 * or a distant grandchild. 865 * <p> 866 * Both files <em>must</em> have been resolved using 867 * {@link File#getCanonicalFile()} to avoid symlink or path traversal 868 * attacks. 869 * 870 * @hide 871 */ 872 @TestApi 873 public static boolean contains(File dir, File file) { 874 if (dir == null || file == null) return false; 875 return contains(dir.getAbsolutePath(), file.getAbsolutePath()); 876 } 877 878 /** 879 * Test if a file lives under the given directory, either as a direct child 880 * or a distant grandchild. 881 * <p> 882 * Both files <em>must</em> have been resolved using 883 * {@link File#getCanonicalFile()} to avoid symlink or path traversal 884 * attacks. 885 * 886 * @hide 887 */ 888 public static boolean contains(String dirPath, String filePath) { 889 if (dirPath.equals(filePath)) { 890 return true; 891 } 892 if (!dirPath.endsWith("/")) { 893 dirPath += "/"; 894 } 895 return filePath.startsWith(dirPath); 896 } 897 898 /** {@hide} */ 899 public static boolean deleteContentsAndDir(File dir) { 900 if (deleteContents(dir)) { 901 return dir.delete(); 902 } else { 903 return false; 904 } 905 } 906 907 /** {@hide} */ 908 @UnsupportedAppUsage 909 public static boolean deleteContents(File dir) { 910 File[] files = dir.listFiles(); 911 boolean success = true; 912 if (files != null) { 913 for (File file : files) { 914 if (file.isDirectory()) { 915 success &= deleteContents(file); 916 } 917 if (!file.delete()) { 918 Log.w(TAG, "Failed to delete " + file); 919 success = false; 920 } 921 } 922 } 923 return success; 924 } 925 926 private static boolean isValidExtFilenameChar(char c) { 927 switch (c) { 928 case '\0': 929 case '/': 930 return false; 931 default: 932 return true; 933 } 934 } 935 936 /** 937 * Check if given filename is valid for an ext4 filesystem. 938 * 939 * @hide 940 */ 941 public static boolean isValidExtFilename(String name) { 942 return (name != null) && name.equals(buildValidExtFilename(name)); 943 } 944 945 /** 946 * Mutate the given filename to make it valid for an ext4 filesystem, 947 * replacing any invalid characters with "_". 948 * 949 * @hide 950 */ 951 public static String buildValidExtFilename(String name) { 952 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { 953 return "(invalid)"; 954 } 955 final StringBuilder res = new StringBuilder(name.length()); 956 for (int i = 0; i < name.length(); i++) { 957 final char c = name.charAt(i); 958 if (isValidExtFilenameChar(c)) { 959 res.append(c); 960 } else { 961 res.append('_'); 962 } 963 } 964 trimFilename(res, 255); 965 return res.toString(); 966 } 967 968 private static boolean isValidFatFilenameChar(char c) { 969 if ((0x00 <= c && c <= 0x1f)) { 970 return false; 971 } 972 switch (c) { 973 case '"': 974 case '*': 975 case '/': 976 case ':': 977 case '<': 978 case '>': 979 case '?': 980 case '\\': 981 case '|': 982 case 0x7F: 983 return false; 984 default: 985 return true; 986 } 987 } 988 989 /** 990 * Check if given filename is valid for a FAT filesystem. 991 * 992 * @hide 993 */ 994 public static boolean isValidFatFilename(String name) { 995 return (name != null) && name.equals(buildValidFatFilename(name)); 996 } 997 998 /** 999 * Mutate the given filename to make it valid for a FAT filesystem, 1000 * replacing any invalid characters with "_". 1001 * 1002 * @hide 1003 */ 1004 public static String buildValidFatFilename(String name) { 1005 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { 1006 return "(invalid)"; 1007 } 1008 final StringBuilder res = new StringBuilder(name.length()); 1009 for (int i = 0; i < name.length(); i++) { 1010 final char c = name.charAt(i); 1011 if (isValidFatFilenameChar(c)) { 1012 res.append(c); 1013 } else { 1014 res.append('_'); 1015 } 1016 } 1017 // Even though vfat allows 255 UCS-2 chars, we might eventually write to 1018 // ext4 through a FUSE layer, so use that limit. 1019 trimFilename(res, 255); 1020 return res.toString(); 1021 } 1022 1023 /** {@hide} */ 1024 @VisibleForTesting 1025 public static String trimFilename(String str, int maxBytes) { 1026 final StringBuilder res = new StringBuilder(str); 1027 trimFilename(res, maxBytes); 1028 return res.toString(); 1029 } 1030 1031 /** {@hide} */ 1032 private static void trimFilename(StringBuilder res, int maxBytes) { 1033 byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8); 1034 if (raw.length > maxBytes) { 1035 maxBytes -= 3; 1036 while (raw.length > maxBytes) { 1037 res.deleteCharAt(res.length() / 2); 1038 raw = res.toString().getBytes(StandardCharsets.UTF_8); 1039 } 1040 res.insert(res.length() / 2, "..."); 1041 } 1042 } 1043 1044 /** {@hide} */ 1045 public static String rewriteAfterRename(File beforeDir, File afterDir, String path) { 1046 if (path == null) return null; 1047 final File result = rewriteAfterRename(beforeDir, afterDir, new File(path)); 1048 return (result != null) ? result.getAbsolutePath() : null; 1049 } 1050 1051 /** {@hide} */ 1052 public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) { 1053 if (paths == null) return null; 1054 final String[] result = new String[paths.length]; 1055 for (int i = 0; i < paths.length; i++) { 1056 result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]); 1057 } 1058 return result; 1059 } 1060 1061 /** 1062 * Given a path under the "before" directory, rewrite it to live under the 1063 * "after" directory. For example, {@code /before/foo/bar.txt} would become 1064 * {@code /after/foo/bar.txt}. 1065 * 1066 * @hide 1067 */ 1068 public static File rewriteAfterRename(File beforeDir, File afterDir, File file) { 1069 if (file == null || beforeDir == null || afterDir == null) return null; 1070 if (contains(beforeDir, file)) { 1071 final String splice = file.getAbsolutePath().substring( 1072 beforeDir.getAbsolutePath().length()); 1073 return new File(afterDir, splice); 1074 } 1075 return null; 1076 } 1077 1078 /** {@hide} */ 1079 private static File buildUniqueFileWithExtension(File parent, String name, String ext) 1080 throws FileNotFoundException { 1081 File file = buildFile(parent, name, ext); 1082 1083 // If conflicting file, try adding counter suffix 1084 int n = 0; 1085 while (file.exists()) { 1086 if (n++ >= 32) { 1087 throw new FileNotFoundException("Failed to create unique file"); 1088 } 1089 file = buildFile(parent, name + " (" + n + ")", ext); 1090 } 1091 1092 return file; 1093 } 1094 1095 /** 1096 * Generates a unique file name under the given parent directory. If the display name doesn't 1097 * have an extension that matches the requested MIME type, the default extension for that MIME 1098 * type is appended. If a file already exists, the name is appended with a numerical value to 1099 * make it unique. 1100 * 1101 * For example, the display name 'example' with 'text/plain' MIME might produce 1102 * 'example.txt' or 'example (1).txt', etc. 1103 * 1104 * @throws FileNotFoundException 1105 * @hide 1106 */ 1107 public static File buildUniqueFile(File parent, String mimeType, String displayName) 1108 throws FileNotFoundException { 1109 final String[] parts = splitFileName(mimeType, displayName); 1110 return buildUniqueFileWithExtension(parent, parts[0], parts[1]); 1111 } 1112 1113 /** {@hide} */ 1114 public static File buildNonUniqueFile(File parent, String mimeType, String displayName) { 1115 final String[] parts = splitFileName(mimeType, displayName); 1116 return buildFile(parent, parts[0], parts[1]); 1117 } 1118 1119 /** 1120 * Generates a unique file name under the given parent directory, keeping 1121 * any extension intact. 1122 * 1123 * @hide 1124 */ 1125 public static File buildUniqueFile(File parent, String displayName) 1126 throws FileNotFoundException { 1127 final String name; 1128 final String ext; 1129 1130 // Extract requested extension from display name 1131 final int lastDot = displayName.lastIndexOf('.'); 1132 if (lastDot >= 0) { 1133 name = displayName.substring(0, lastDot); 1134 ext = displayName.substring(lastDot + 1); 1135 } else { 1136 name = displayName; 1137 ext = null; 1138 } 1139 1140 return buildUniqueFileWithExtension(parent, name, ext); 1141 } 1142 1143 /** 1144 * Splits file name into base name and extension. 1145 * If the display name doesn't have an extension that matches the requested MIME type, the 1146 * extension is regarded as a part of filename and default extension for that MIME type is 1147 * appended. 1148 * 1149 * @hide 1150 */ 1151 public static String[] splitFileName(String mimeType, String displayName) { 1152 String name; 1153 String ext; 1154 1155 if (Document.MIME_TYPE_DIR.equals(mimeType)) { 1156 name = displayName; 1157 ext = null; 1158 } else { 1159 String mimeTypeFromExt; 1160 1161 // Extract requested extension from display name 1162 final int lastDot = displayName.lastIndexOf('.'); 1163 if (lastDot >= 0) { 1164 name = displayName.substring(0, lastDot); 1165 ext = displayName.substring(lastDot + 1); 1166 mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension( 1167 ext.toLowerCase()); 1168 } else { 1169 name = displayName; 1170 ext = null; 1171 mimeTypeFromExt = null; 1172 } 1173 1174 if (mimeTypeFromExt == null) { 1175 mimeTypeFromExt = ContentResolver.MIME_TYPE_DEFAULT; 1176 } 1177 1178 final String extFromMimeType; 1179 if (ContentResolver.MIME_TYPE_DEFAULT.equals(mimeType)) { 1180 extFromMimeType = null; 1181 } else { 1182 extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); 1183 } 1184 1185 if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) { 1186 // Extension maps back to requested MIME type; allow it 1187 } else { 1188 // No match; insist that create file matches requested MIME 1189 name = displayName; 1190 ext = extFromMimeType; 1191 } 1192 } 1193 1194 if (ext == null) { 1195 ext = ""; 1196 } 1197 1198 return new String[] { name, ext }; 1199 } 1200 1201 /** {@hide} */ 1202 private static File buildFile(File parent, String name, String ext) { 1203 if (TextUtils.isEmpty(ext)) { 1204 return new File(parent, name); 1205 } else { 1206 return new File(parent, name + "." + ext); 1207 } 1208 } 1209 1210 /** {@hide} */ 1211 public static @NonNull String[] listOrEmpty(@Nullable File dir) { 1212 return (dir != null) ? ArrayUtils.defeatNullable(dir.list()) 1213 : EmptyArray.STRING; 1214 } 1215 1216 /** {@hide} */ 1217 public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) { 1218 return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles()) 1219 : ArrayUtils.EMPTY_FILE; 1220 } 1221 1222 /** {@hide} */ 1223 public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) { 1224 return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter)) 1225 : ArrayUtils.EMPTY_FILE; 1226 } 1227 1228 /** {@hide} */ 1229 public static @Nullable File newFileOrNull(@Nullable String path) { 1230 return (path != null) ? new File(path) : null; 1231 } 1232 1233 /** 1234 * Creates a directory with name {@code name} under an existing directory {@code baseDir}. 1235 * Returns a {@code File} object representing the directory on success, {@code null} on 1236 * failure. 1237 * 1238 * @hide 1239 */ 1240 public static @Nullable File createDir(File baseDir, String name) { 1241 final File dir = new File(baseDir, name); 1242 1243 return createDir(dir) ? dir : null; 1244 } 1245 1246 /** @hide */ 1247 public static boolean createDir(File dir) { 1248 if (dir.exists()) { 1249 return dir.isDirectory(); 1250 } 1251 1252 return dir.mkdir(); 1253 } 1254 1255 /** 1256 * Round the given size of a storage device to a nice round power-of-two 1257 * value, such as 256MB or 32GB. This avoids showing weird values like 1258 * "29.5GB" in UI. 1259 * 1260 * @hide 1261 */ 1262 public static long roundStorageSize(long size) { 1263 long val = 1; 1264 long pow = 1; 1265 while ((val * pow) < size) { 1266 val <<= 1; 1267 if (val > 512) { 1268 val = 1; 1269 pow *= 1000; 1270 } 1271 } 1272 return val * pow; 1273 } 1274 1275 /** 1276 * Closes the given object quietly, ignoring any checked exceptions. Does 1277 * nothing if the given object is {@code null}. 1278 */ 1279 public static void closeQuietly(@Nullable AutoCloseable closeable) { 1280 IoUtils.closeQuietly(closeable); 1281 } 1282 1283 /** 1284 * Closes the given object quietly, ignoring any checked exceptions. Does 1285 * nothing if the given object is {@code null}. 1286 */ 1287 public static void closeQuietly(@Nullable FileDescriptor fd) { 1288 IoUtils.closeQuietly(fd); 1289 } 1290 1291 /** {@hide} */ 1292 public static int translateModeStringToPosix(String mode) { 1293 // Sanity check for invalid chars 1294 for (int i = 0; i < mode.length(); i++) { 1295 switch (mode.charAt(i)) { 1296 case 'r': 1297 case 'w': 1298 case 't': 1299 case 'a': 1300 break; 1301 default: 1302 throw new IllegalArgumentException("Bad mode: " + mode); 1303 } 1304 } 1305 1306 int res = 0; 1307 if (mode.startsWith("rw")) { 1308 res = O_RDWR | O_CREAT; 1309 } else if (mode.startsWith("w")) { 1310 res = O_WRONLY | O_CREAT; 1311 } else if (mode.startsWith("r")) { 1312 res = O_RDONLY; 1313 } else { 1314 throw new IllegalArgumentException("Bad mode: " + mode); 1315 } 1316 if (mode.indexOf('t') != -1) { 1317 res |= O_TRUNC; 1318 } 1319 if (mode.indexOf('a') != -1) { 1320 res |= O_APPEND; 1321 } 1322 return res; 1323 } 1324 1325 /** {@hide} */ 1326 public static String translateModePosixToString(int mode) { 1327 String res = ""; 1328 if ((mode & O_ACCMODE) == O_RDWR) { 1329 res = "rw"; 1330 } else if ((mode & O_ACCMODE) == O_WRONLY) { 1331 res = "w"; 1332 } else if ((mode & O_ACCMODE) == O_RDONLY) { 1333 res = "r"; 1334 } else { 1335 throw new IllegalArgumentException("Bad mode: " + mode); 1336 } 1337 if ((mode & O_TRUNC) == O_TRUNC) { 1338 res += "t"; 1339 } 1340 if ((mode & O_APPEND) == O_APPEND) { 1341 res += "a"; 1342 } 1343 return res; 1344 } 1345 1346 /** {@hide} */ 1347 public static int translateModePosixToPfd(int mode) { 1348 int res = 0; 1349 if ((mode & O_ACCMODE) == O_RDWR) { 1350 res = MODE_READ_WRITE; 1351 } else if ((mode & O_ACCMODE) == O_WRONLY) { 1352 res = MODE_WRITE_ONLY; 1353 } else if ((mode & O_ACCMODE) == O_RDONLY) { 1354 res = MODE_READ_ONLY; 1355 } else { 1356 throw new IllegalArgumentException("Bad mode: " + mode); 1357 } 1358 if ((mode & O_CREAT) == O_CREAT) { 1359 res |= MODE_CREATE; 1360 } 1361 if ((mode & O_TRUNC) == O_TRUNC) { 1362 res |= MODE_TRUNCATE; 1363 } 1364 if ((mode & O_APPEND) == O_APPEND) { 1365 res |= MODE_APPEND; 1366 } 1367 return res; 1368 } 1369 1370 /** {@hide} */ 1371 public static int translateModePfdToPosix(int mode) { 1372 int res = 0; 1373 if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) { 1374 res = O_RDWR; 1375 } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) { 1376 res = O_WRONLY; 1377 } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) { 1378 res = O_RDONLY; 1379 } else { 1380 throw new IllegalArgumentException("Bad mode: " + mode); 1381 } 1382 if ((mode & MODE_CREATE) == MODE_CREATE) { 1383 res |= O_CREAT; 1384 } 1385 if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) { 1386 res |= O_TRUNC; 1387 } 1388 if ((mode & MODE_APPEND) == MODE_APPEND) { 1389 res |= O_APPEND; 1390 } 1391 return res; 1392 } 1393 1394 /** {@hide} */ 1395 public static int translateModeAccessToPosix(int mode) { 1396 if (mode == F_OK) { 1397 // There's not an exact mapping, so we attempt a read-only open to 1398 // determine if a file exists 1399 return O_RDONLY; 1400 } else if ((mode & (R_OK | W_OK)) == (R_OK | W_OK)) { 1401 return O_RDWR; 1402 } else if ((mode & R_OK) == R_OK) { 1403 return O_RDONLY; 1404 } else if ((mode & W_OK) == W_OK) { 1405 return O_WRONLY; 1406 } else { 1407 throw new IllegalArgumentException("Bad mode: " + mode); 1408 } 1409 } 1410 1411 /** {@hide} */ 1412 @VisibleForTesting 1413 public static class MemoryPipe extends Thread implements AutoCloseable { 1414 private final FileDescriptor[] pipe; 1415 private final byte[] data; 1416 private final boolean sink; 1417 1418 private MemoryPipe(byte[] data, boolean sink) throws IOException { 1419 try { 1420 this.pipe = Os.pipe(); 1421 } catch (ErrnoException e) { 1422 throw e.rethrowAsIOException(); 1423 } 1424 this.data = data; 1425 this.sink = sink; 1426 } 1427 1428 private MemoryPipe startInternal() { 1429 super.start(); 1430 return this; 1431 } 1432 1433 public static MemoryPipe createSource(byte[] data) throws IOException { 1434 return new MemoryPipe(data, false).startInternal(); 1435 } 1436 1437 public static MemoryPipe createSink(byte[] data) throws IOException { 1438 return new MemoryPipe(data, true).startInternal(); 1439 } 1440 1441 public FileDescriptor getFD() { 1442 return sink ? pipe[1] : pipe[0]; 1443 } 1444 1445 public FileDescriptor getInternalFD() { 1446 return sink ? pipe[0] : pipe[1]; 1447 } 1448 1449 @Override 1450 public void run() { 1451 final FileDescriptor fd = getInternalFD(); 1452 try { 1453 int i = 0; 1454 while (i < data.length) { 1455 if (sink) { 1456 i += Os.read(fd, data, i, data.length - i); 1457 } else { 1458 i += Os.write(fd, data, i, data.length - i); 1459 } 1460 } 1461 } catch (IOException | ErrnoException e) { 1462 // Ignored 1463 } finally { 1464 if (sink) { 1465 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); 1466 } 1467 IoUtils.closeQuietly(fd); 1468 } 1469 } 1470 1471 @Override 1472 public void close() throws Exception { 1473 IoUtils.closeQuietly(getFD()); 1474 } 1475 } 1476 } 1477