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