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