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