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