1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.os; 18 19 import static android.os.ParcelFileDescriptor.MODE_APPEND; 20 import static android.os.ParcelFileDescriptor.MODE_CREATE; 21 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; 22 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; 23 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE; 24 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; 25 import static android.system.OsConstants.F_OK; 26 import static android.system.OsConstants.O_ACCMODE; 27 import static android.system.OsConstants.O_APPEND; 28 import static android.system.OsConstants.O_CREAT; 29 import static android.system.OsConstants.O_RDONLY; 30 import static android.system.OsConstants.O_RDWR; 31 import static android.system.OsConstants.O_TRUNC; 32 import static android.system.OsConstants.O_WRONLY; 33 import static android.system.OsConstants.R_OK; 34 import static android.system.OsConstants.SPLICE_F_MORE; 35 import static android.system.OsConstants.SPLICE_F_MOVE; 36 import static android.system.OsConstants.S_ISFIFO; 37 import static android.system.OsConstants.S_ISREG; 38 import static android.system.OsConstants.W_OK; 39 40 import android.annotation.NonNull; 41 import android.annotation.Nullable; 42 import android.annotation.TestApi; 43 import android.annotation.UnsupportedAppUsage; 44 import android.content.ContentResolver; 45 import android.provider.DocumentsContract.Document; 46 import android.system.ErrnoException; 47 import android.system.Os; 48 import android.system.StructStat; 49 import android.text.TextUtils; 50 import android.util.Log; 51 import android.util.Slog; 52 import android.webkit.MimeTypeMap; 53 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.internal.util.ArrayUtils; 56 import com.android.internal.util.SizedInputStream; 57 58 import libcore.io.IoUtils; 59 import libcore.util.EmptyArray; 60 61 import java.io.BufferedInputStream; 62 import java.io.ByteArrayOutputStream; 63 import java.io.File; 64 import java.io.FileDescriptor; 65 import java.io.FileInputStream; 66 import java.io.FileNotFoundException; 67 import java.io.FileOutputStream; 68 import java.io.FilenameFilter; 69 import java.io.IOException; 70 import java.io.InputStream; 71 import java.io.OutputStream; 72 import java.nio.charset.StandardCharsets; 73 import java.security.DigestInputStream; 74 import java.security.MessageDigest; 75 import java.security.NoSuchAlgorithmException; 76 import java.util.Arrays; 77 import java.util.Collection; 78 import java.util.Comparator; 79 import java.util.Objects; 80 import java.util.concurrent.Executor; 81 import java.util.concurrent.TimeUnit; 82 import java.util.regex.Pattern; 83 import java.util.zip.CRC32; 84 import java.util.zip.CheckedInputStream; 85 86 /** 87 * Utility methods useful for working with files. 88 */ 89 public final class FileUtils { 90 private static final String TAG = "FileUtils"; 91 92 /** {@hide} */ public static final int S_IRWXU = 00700; 93 /** {@hide} */ public static final int S_IRUSR = 00400; 94 /** {@hide} */ public static final int S_IWUSR = 00200; 95 /** {@hide} */ public static final int S_IXUSR = 00100; 96 97 /** {@hide} */ public static final int S_IRWXG = 00070; 98 /** {@hide} */ public static final int S_IRGRP = 00040; 99 /** {@hide} */ public static final int S_IWGRP = 00020; 100 /** {@hide} */ public static final int S_IXGRP = 00010; 101 102 /** {@hide} */ public static final int S_IRWXO = 00007; 103 /** {@hide} */ public static final int S_IROTH = 00004; 104 /** {@hide} */ public static final int S_IWOTH = 00002; 105 /** {@hide} */ public static final int S_IXOTH = 00001; 106 107 @UnsupportedAppUsage 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 */ digest(@onNull File file, @NonNull String algorithm)746 public static byte[] digest(@NonNull File file, @NonNull String algorithm) 747 throws IOException, NoSuchAlgorithmException { 748 try (FileInputStream in = new FileInputStream(file)) { 749 return digest(in, algorithm); 750 } 751 } 752 753 /** 754 * Compute the digest of the given file using the requested algorithm. 755 * 756 * @param algorithm Any valid algorithm accepted by 757 * {@link MessageDigest#getInstance(String)}. 758 * @hide 759 */ digest(@onNull InputStream in, @NonNull String algorithm)760 public static byte[] digest(@NonNull InputStream in, @NonNull String algorithm) 761 throws IOException, NoSuchAlgorithmException { 762 // TODO: implement kernel optimizations 763 return digestInternalUserspace(in, algorithm); 764 } 765 766 /** 767 * Compute the digest of the given file using the requested algorithm. 768 * 769 * @param algorithm Any valid algorithm accepted by 770 * {@link MessageDigest#getInstance(String)}. 771 * @hide 772 */ digest(FileDescriptor fd, String algorithm)773 public static byte[] digest(FileDescriptor fd, String algorithm) 774 throws IOException, NoSuchAlgorithmException { 775 // TODO: implement kernel optimizations 776 return digestInternalUserspace(new FileInputStream(fd), algorithm); 777 } 778 digestInternalUserspace(InputStream in, String algorithm)779 private static byte[] digestInternalUserspace(InputStream in, String algorithm) 780 throws IOException, NoSuchAlgorithmException { 781 final MessageDigest digest = MessageDigest.getInstance(algorithm); 782 try (DigestInputStream digestStream = new DigestInputStream(in, digest)) { 783 final byte[] buffer = new byte[8192]; 784 while (digestStream.read(buffer) != -1) { 785 } 786 } 787 return digest.digest(); 788 } 789 790 /** 791 * Delete older files in a directory until only those matching the given 792 * constraints remain. 793 * 794 * @param minCount Always keep at least this many files. 795 * @param minAgeMs Always keep files younger than this age, in milliseconds. 796 * @return if any files were deleted. 797 * @hide 798 */ 799 @UnsupportedAppUsage deleteOlderFiles(File dir, int minCount, long minAgeMs)800 public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) { 801 if (minCount < 0 || minAgeMs < 0) { 802 throw new IllegalArgumentException("Constraints must be positive or 0"); 803 } 804 805 final File[] files = dir.listFiles(); 806 if (files == null) return false; 807 808 // Sort with newest files first 809 Arrays.sort(files, new Comparator<File>() { 810 @Override 811 public int compare(File lhs, File rhs) { 812 return Long.compare(rhs.lastModified(), lhs.lastModified()); 813 } 814 }); 815 816 // Keep at least minCount files 817 boolean deleted = false; 818 for (int i = minCount; i < files.length; i++) { 819 final File file = files[i]; 820 821 // Keep files newer than minAgeMs 822 final long age = System.currentTimeMillis() - file.lastModified(); 823 if (age > minAgeMs) { 824 if (file.delete()) { 825 Log.d(TAG, "Deleted old file " + file); 826 deleted = true; 827 } 828 } 829 } 830 return deleted; 831 } 832 833 /** 834 * Test if a file lives under the given directory, either as a direct child 835 * or a distant grandchild. 836 * <p> 837 * Both files <em>must</em> have been resolved using 838 * {@link File#getCanonicalFile()} to avoid symlink or path traversal 839 * attacks. 840 * 841 * @hide 842 */ contains(File[] dirs, File file)843 public static boolean contains(File[] dirs, File file) { 844 for (File dir : dirs) { 845 if (contains(dir, file)) { 846 return true; 847 } 848 } 849 return false; 850 } 851 852 /** {@hide} */ contains(Collection<File> dirs, File file)853 public static boolean contains(Collection<File> dirs, File file) { 854 for (File dir : dirs) { 855 if (contains(dir, file)) { 856 return true; 857 } 858 } 859 return false; 860 } 861 862 /** 863 * Test if a file lives under the given directory, either as a direct child 864 * or a distant grandchild. 865 * <p> 866 * Both files <em>must</em> have been resolved using 867 * {@link File#getCanonicalFile()} to avoid symlink or path traversal 868 * attacks. 869 * 870 * @hide 871 */ 872 @TestApi contains(File dir, File file)873 public static boolean contains(File dir, File file) { 874 if (dir == null || file == null) return false; 875 return contains(dir.getAbsolutePath(), file.getAbsolutePath()); 876 } 877 878 /** 879 * Test if a file lives under the given directory, either as a direct child 880 * or a distant grandchild. 881 * <p> 882 * Both files <em>must</em> have been resolved using 883 * {@link File#getCanonicalFile()} to avoid symlink or path traversal 884 * attacks. 885 * 886 * @hide 887 */ contains(String dirPath, String filePath)888 public static boolean contains(String dirPath, String filePath) { 889 if (dirPath.equals(filePath)) { 890 return true; 891 } 892 if (!dirPath.endsWith("/")) { 893 dirPath += "/"; 894 } 895 return filePath.startsWith(dirPath); 896 } 897 898 /** {@hide} */ deleteContentsAndDir(File dir)899 public static boolean deleteContentsAndDir(File dir) { 900 if (deleteContents(dir)) { 901 return dir.delete(); 902 } else { 903 return false; 904 } 905 } 906 907 /** {@hide} */ 908 @UnsupportedAppUsage deleteContents(File dir)909 public static boolean deleteContents(File dir) { 910 File[] files = dir.listFiles(); 911 boolean success = true; 912 if (files != null) { 913 for (File file : files) { 914 if (file.isDirectory()) { 915 success &= deleteContents(file); 916 } 917 if (!file.delete()) { 918 Log.w(TAG, "Failed to delete " + file); 919 success = false; 920 } 921 } 922 } 923 return success; 924 } 925 isValidExtFilenameChar(char c)926 private static boolean isValidExtFilenameChar(char c) { 927 switch (c) { 928 case '\0': 929 case '/': 930 return false; 931 default: 932 return true; 933 } 934 } 935 936 /** 937 * Check if given filename is valid for an ext4 filesystem. 938 * 939 * @hide 940 */ isValidExtFilename(String name)941 public static boolean isValidExtFilename(String name) { 942 return (name != null) && name.equals(buildValidExtFilename(name)); 943 } 944 945 /** 946 * Mutate the given filename to make it valid for an ext4 filesystem, 947 * replacing any invalid characters with "_". 948 * 949 * @hide 950 */ buildValidExtFilename(String name)951 public static String buildValidExtFilename(String name) { 952 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { 953 return "(invalid)"; 954 } 955 final StringBuilder res = new StringBuilder(name.length()); 956 for (int i = 0; i < name.length(); i++) { 957 final char c = name.charAt(i); 958 if (isValidExtFilenameChar(c)) { 959 res.append(c); 960 } else { 961 res.append('_'); 962 } 963 } 964 trimFilename(res, 255); 965 return res.toString(); 966 } 967 isValidFatFilenameChar(char c)968 private static boolean isValidFatFilenameChar(char c) { 969 if ((0x00 <= c && c <= 0x1f)) { 970 return false; 971 } 972 switch (c) { 973 case '"': 974 case '*': 975 case '/': 976 case ':': 977 case '<': 978 case '>': 979 case '?': 980 case '\\': 981 case '|': 982 case 0x7F: 983 return false; 984 default: 985 return true; 986 } 987 } 988 989 /** 990 * Check if given filename is valid for a FAT filesystem. 991 * 992 * @hide 993 */ isValidFatFilename(String name)994 public static boolean isValidFatFilename(String name) { 995 return (name != null) && name.equals(buildValidFatFilename(name)); 996 } 997 998 /** 999 * Mutate the given filename to make it valid for a FAT filesystem, 1000 * replacing any invalid characters with "_". 1001 * 1002 * @hide 1003 */ buildValidFatFilename(String name)1004 public static String buildValidFatFilename(String name) { 1005 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { 1006 return "(invalid)"; 1007 } 1008 final StringBuilder res = new StringBuilder(name.length()); 1009 for (int i = 0; i < name.length(); i++) { 1010 final char c = name.charAt(i); 1011 if (isValidFatFilenameChar(c)) { 1012 res.append(c); 1013 } else { 1014 res.append('_'); 1015 } 1016 } 1017 // Even though vfat allows 255 UCS-2 chars, we might eventually write to 1018 // ext4 through a FUSE layer, so use that limit. 1019 trimFilename(res, 255); 1020 return res.toString(); 1021 } 1022 1023 /** {@hide} */ 1024 @VisibleForTesting trimFilename(String str, int maxBytes)1025 public static String trimFilename(String str, int maxBytes) { 1026 final StringBuilder res = new StringBuilder(str); 1027 trimFilename(res, maxBytes); 1028 return res.toString(); 1029 } 1030 1031 /** {@hide} */ trimFilename(StringBuilder res, int maxBytes)1032 private static void trimFilename(StringBuilder res, int maxBytes) { 1033 byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8); 1034 if (raw.length > maxBytes) { 1035 maxBytes -= 3; 1036 while (raw.length > maxBytes) { 1037 res.deleteCharAt(res.length() / 2); 1038 raw = res.toString().getBytes(StandardCharsets.UTF_8); 1039 } 1040 res.insert(res.length() / 2, "..."); 1041 } 1042 } 1043 1044 /** {@hide} */ rewriteAfterRename(File beforeDir, File afterDir, String path)1045 public static String rewriteAfterRename(File beforeDir, File afterDir, String path) { 1046 if (path == null) return null; 1047 final File result = rewriteAfterRename(beforeDir, afterDir, new File(path)); 1048 return (result != null) ? result.getAbsolutePath() : null; 1049 } 1050 1051 /** {@hide} */ rewriteAfterRename(File beforeDir, File afterDir, String[] paths)1052 public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) { 1053 if (paths == null) return null; 1054 final String[] result = new String[paths.length]; 1055 for (int i = 0; i < paths.length; i++) { 1056 result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]); 1057 } 1058 return result; 1059 } 1060 1061 /** 1062 * Given a path under the "before" directory, rewrite it to live under the 1063 * "after" directory. For example, {@code /before/foo/bar.txt} would become 1064 * {@code /after/foo/bar.txt}. 1065 * 1066 * @hide 1067 */ rewriteAfterRename(File beforeDir, File afterDir, File file)1068 public static File rewriteAfterRename(File beforeDir, File afterDir, File file) { 1069 if (file == null || beforeDir == null || afterDir == null) return null; 1070 if (contains(beforeDir, file)) { 1071 final String splice = file.getAbsolutePath().substring( 1072 beforeDir.getAbsolutePath().length()); 1073 return new File(afterDir, splice); 1074 } 1075 return null; 1076 } 1077 1078 /** {@hide} */ buildUniqueFileWithExtension(File parent, String name, String ext)1079 private static File buildUniqueFileWithExtension(File parent, String name, String ext) 1080 throws FileNotFoundException { 1081 File file = buildFile(parent, name, ext); 1082 1083 // If conflicting file, try adding counter suffix 1084 int n = 0; 1085 while (file.exists()) { 1086 if (n++ >= 32) { 1087 throw new FileNotFoundException("Failed to create unique file"); 1088 } 1089 file = buildFile(parent, name + " (" + n + ")", ext); 1090 } 1091 1092 return file; 1093 } 1094 1095 /** 1096 * Generates a unique file name under the given parent directory. If the display name doesn't 1097 * have an extension that matches the requested MIME type, the default extension for that MIME 1098 * type is appended. If a file already exists, the name is appended with a numerical value to 1099 * make it unique. 1100 * 1101 * For example, the display name 'example' with 'text/plain' MIME might produce 1102 * 'example.txt' or 'example (1).txt', etc. 1103 * 1104 * @throws FileNotFoundException 1105 * @hide 1106 */ buildUniqueFile(File parent, String mimeType, String displayName)1107 public static File buildUniqueFile(File parent, String mimeType, String displayName) 1108 throws FileNotFoundException { 1109 final String[] parts = splitFileName(mimeType, displayName); 1110 return buildUniqueFileWithExtension(parent, parts[0], parts[1]); 1111 } 1112 1113 /** {@hide} */ buildNonUniqueFile(File parent, String mimeType, String displayName)1114 public static File buildNonUniqueFile(File parent, String mimeType, String displayName) { 1115 final String[] parts = splitFileName(mimeType, displayName); 1116 return buildFile(parent, parts[0], parts[1]); 1117 } 1118 1119 /** 1120 * Generates a unique file name under the given parent directory, keeping 1121 * any extension intact. 1122 * 1123 * @hide 1124 */ buildUniqueFile(File parent, String displayName)1125 public static File buildUniqueFile(File parent, String displayName) 1126 throws FileNotFoundException { 1127 final String name; 1128 final String ext; 1129 1130 // Extract requested extension from display name 1131 final int lastDot = displayName.lastIndexOf('.'); 1132 if (lastDot >= 0) { 1133 name = displayName.substring(0, lastDot); 1134 ext = displayName.substring(lastDot + 1); 1135 } else { 1136 name = displayName; 1137 ext = null; 1138 } 1139 1140 return buildUniqueFileWithExtension(parent, name, ext); 1141 } 1142 1143 /** 1144 * Splits file name into base name and extension. 1145 * If the display name doesn't have an extension that matches the requested MIME type, the 1146 * extension is regarded as a part of filename and default extension for that MIME type is 1147 * appended. 1148 * 1149 * @hide 1150 */ splitFileName(String mimeType, String displayName)1151 public static String[] splitFileName(String mimeType, String displayName) { 1152 String name; 1153 String ext; 1154 1155 if (Document.MIME_TYPE_DIR.equals(mimeType)) { 1156 name = displayName; 1157 ext = null; 1158 } else { 1159 String mimeTypeFromExt; 1160 1161 // Extract requested extension from display name 1162 final int lastDot = displayName.lastIndexOf('.'); 1163 if (lastDot >= 0) { 1164 name = displayName.substring(0, lastDot); 1165 ext = displayName.substring(lastDot + 1); 1166 mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension( 1167 ext.toLowerCase()); 1168 } else { 1169 name = displayName; 1170 ext = null; 1171 mimeTypeFromExt = null; 1172 } 1173 1174 if (mimeTypeFromExt == null) { 1175 mimeTypeFromExt = ContentResolver.MIME_TYPE_DEFAULT; 1176 } 1177 1178 final String extFromMimeType; 1179 if (ContentResolver.MIME_TYPE_DEFAULT.equals(mimeType)) { 1180 extFromMimeType = null; 1181 } else { 1182 extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); 1183 } 1184 1185 if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) { 1186 // Extension maps back to requested MIME type; allow it 1187 } else { 1188 // No match; insist that create file matches requested MIME 1189 name = displayName; 1190 ext = extFromMimeType; 1191 } 1192 } 1193 1194 if (ext == null) { 1195 ext = ""; 1196 } 1197 1198 return new String[] { name, ext }; 1199 } 1200 1201 /** {@hide} */ buildFile(File parent, String name, String ext)1202 private static File buildFile(File parent, String name, String ext) { 1203 if (TextUtils.isEmpty(ext)) { 1204 return new File(parent, name); 1205 } else { 1206 return new File(parent, name + "." + ext); 1207 } 1208 } 1209 1210 /** {@hide} */ listOrEmpty(@ullable File dir)1211 public static @NonNull String[] listOrEmpty(@Nullable File dir) { 1212 return (dir != null) ? ArrayUtils.defeatNullable(dir.list()) 1213 : EmptyArray.STRING; 1214 } 1215 1216 /** {@hide} */ listFilesOrEmpty(@ullable File dir)1217 public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) { 1218 return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles()) 1219 : ArrayUtils.EMPTY_FILE; 1220 } 1221 1222 /** {@hide} */ listFilesOrEmpty(@ullable File dir, FilenameFilter filter)1223 public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) { 1224 return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter)) 1225 : ArrayUtils.EMPTY_FILE; 1226 } 1227 1228 /** {@hide} */ newFileOrNull(@ullable String path)1229 public static @Nullable File newFileOrNull(@Nullable String path) { 1230 return (path != null) ? new File(path) : null; 1231 } 1232 1233 /** 1234 * Creates a directory with name {@code name} under an existing directory {@code baseDir}. 1235 * Returns a {@code File} object representing the directory on success, {@code null} on 1236 * failure. 1237 * 1238 * @hide 1239 */ createDir(File baseDir, String name)1240 public static @Nullable File createDir(File baseDir, String name) { 1241 final File dir = new File(baseDir, name); 1242 1243 return createDir(dir) ? dir : null; 1244 } 1245 1246 /** @hide */ createDir(File dir)1247 public static boolean createDir(File dir) { 1248 if (dir.exists()) { 1249 return dir.isDirectory(); 1250 } 1251 1252 return dir.mkdir(); 1253 } 1254 1255 /** 1256 * Round the given size of a storage device to a nice round power-of-two 1257 * value, such as 256MB or 32GB. This avoids showing weird values like 1258 * "29.5GB" in UI. 1259 * 1260 * @hide 1261 */ roundStorageSize(long size)1262 public static long roundStorageSize(long size) { 1263 long val = 1; 1264 long pow = 1; 1265 while ((val * pow) < size) { 1266 val <<= 1; 1267 if (val > 512) { 1268 val = 1; 1269 pow *= 1000; 1270 } 1271 } 1272 return val * pow; 1273 } 1274 1275 /** 1276 * Closes the given object quietly, ignoring any checked exceptions. Does 1277 * nothing if the given object is {@code null}. 1278 */ closeQuietly(@ullable AutoCloseable closeable)1279 public static void closeQuietly(@Nullable AutoCloseable closeable) { 1280 IoUtils.closeQuietly(closeable); 1281 } 1282 1283 /** 1284 * Closes the given object quietly, ignoring any checked exceptions. Does 1285 * nothing if the given object is {@code null}. 1286 */ closeQuietly(@ullable FileDescriptor fd)1287 public static void closeQuietly(@Nullable FileDescriptor fd) { 1288 IoUtils.closeQuietly(fd); 1289 } 1290 1291 /** {@hide} */ translateModeStringToPosix(String mode)1292 public static int translateModeStringToPosix(String mode) { 1293 // Sanity check for invalid chars 1294 for (int i = 0; i < mode.length(); i++) { 1295 switch (mode.charAt(i)) { 1296 case 'r': 1297 case 'w': 1298 case 't': 1299 case 'a': 1300 break; 1301 default: 1302 throw new IllegalArgumentException("Bad mode: " + mode); 1303 } 1304 } 1305 1306 int res = 0; 1307 if (mode.startsWith("rw")) { 1308 res = O_RDWR | O_CREAT; 1309 } else if (mode.startsWith("w")) { 1310 res = O_WRONLY | O_CREAT; 1311 } else if (mode.startsWith("r")) { 1312 res = O_RDONLY; 1313 } else { 1314 throw new IllegalArgumentException("Bad mode: " + mode); 1315 } 1316 if (mode.indexOf('t') != -1) { 1317 res |= O_TRUNC; 1318 } 1319 if (mode.indexOf('a') != -1) { 1320 res |= O_APPEND; 1321 } 1322 return res; 1323 } 1324 1325 /** {@hide} */ translateModePosixToString(int mode)1326 public static String translateModePosixToString(int mode) { 1327 String res = ""; 1328 if ((mode & O_ACCMODE) == O_RDWR) { 1329 res = "rw"; 1330 } else if ((mode & O_ACCMODE) == O_WRONLY) { 1331 res = "w"; 1332 } else if ((mode & O_ACCMODE) == O_RDONLY) { 1333 res = "r"; 1334 } else { 1335 throw new IllegalArgumentException("Bad mode: " + mode); 1336 } 1337 if ((mode & O_TRUNC) == O_TRUNC) { 1338 res += "t"; 1339 } 1340 if ((mode & O_APPEND) == O_APPEND) { 1341 res += "a"; 1342 } 1343 return res; 1344 } 1345 1346 /** {@hide} */ translateModePosixToPfd(int mode)1347 public static int translateModePosixToPfd(int mode) { 1348 int res = 0; 1349 if ((mode & O_ACCMODE) == O_RDWR) { 1350 res = MODE_READ_WRITE; 1351 } else if ((mode & O_ACCMODE) == O_WRONLY) { 1352 res = MODE_WRITE_ONLY; 1353 } else if ((mode & O_ACCMODE) == O_RDONLY) { 1354 res = MODE_READ_ONLY; 1355 } else { 1356 throw new IllegalArgumentException("Bad mode: " + mode); 1357 } 1358 if ((mode & O_CREAT) == O_CREAT) { 1359 res |= MODE_CREATE; 1360 } 1361 if ((mode & O_TRUNC) == O_TRUNC) { 1362 res |= MODE_TRUNCATE; 1363 } 1364 if ((mode & O_APPEND) == O_APPEND) { 1365 res |= MODE_APPEND; 1366 } 1367 return res; 1368 } 1369 1370 /** {@hide} */ translateModePfdToPosix(int mode)1371 public static int translateModePfdToPosix(int mode) { 1372 int res = 0; 1373 if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) { 1374 res = O_RDWR; 1375 } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) { 1376 res = O_WRONLY; 1377 } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) { 1378 res = O_RDONLY; 1379 } else { 1380 throw new IllegalArgumentException("Bad mode: " + mode); 1381 } 1382 if ((mode & MODE_CREATE) == MODE_CREATE) { 1383 res |= O_CREAT; 1384 } 1385 if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) { 1386 res |= O_TRUNC; 1387 } 1388 if ((mode & MODE_APPEND) == MODE_APPEND) { 1389 res |= O_APPEND; 1390 } 1391 return res; 1392 } 1393 1394 /** {@hide} */ translateModeAccessToPosix(int mode)1395 public static int translateModeAccessToPosix(int mode) { 1396 if (mode == F_OK) { 1397 // There's not an exact mapping, so we attempt a read-only open to 1398 // determine if a file exists 1399 return O_RDONLY; 1400 } else if ((mode & (R_OK | W_OK)) == (R_OK | W_OK)) { 1401 return O_RDWR; 1402 } else if ((mode & R_OK) == R_OK) { 1403 return O_RDONLY; 1404 } else if ((mode & W_OK) == W_OK) { 1405 return O_WRONLY; 1406 } else { 1407 throw new IllegalArgumentException("Bad mode: " + mode); 1408 } 1409 } 1410 1411 /** {@hide} */ 1412 @VisibleForTesting 1413 public static class MemoryPipe extends Thread implements AutoCloseable { 1414 private final FileDescriptor[] pipe; 1415 private final byte[] data; 1416 private final boolean sink; 1417 MemoryPipe(byte[] data, boolean sink)1418 private MemoryPipe(byte[] data, boolean sink) throws IOException { 1419 try { 1420 this.pipe = Os.pipe(); 1421 } catch (ErrnoException e) { 1422 throw e.rethrowAsIOException(); 1423 } 1424 this.data = data; 1425 this.sink = sink; 1426 } 1427 startInternal()1428 private MemoryPipe startInternal() { 1429 super.start(); 1430 return this; 1431 } 1432 createSource(byte[] data)1433 public static MemoryPipe createSource(byte[] data) throws IOException { 1434 return new MemoryPipe(data, false).startInternal(); 1435 } 1436 createSink(byte[] data)1437 public static MemoryPipe createSink(byte[] data) throws IOException { 1438 return new MemoryPipe(data, true).startInternal(); 1439 } 1440 getFD()1441 public FileDescriptor getFD() { 1442 return sink ? pipe[1] : pipe[0]; 1443 } 1444 getInternalFD()1445 public FileDescriptor getInternalFD() { 1446 return sink ? pipe[0] : pipe[1]; 1447 } 1448 1449 @Override run()1450 public void run() { 1451 final FileDescriptor fd = getInternalFD(); 1452 try { 1453 int i = 0; 1454 while (i < data.length) { 1455 if (sink) { 1456 i += Os.read(fd, data, i, data.length - i); 1457 } else { 1458 i += Os.write(fd, data, i, data.length - i); 1459 } 1460 } 1461 } catch (IOException | ErrnoException e) { 1462 // Ignored 1463 } finally { 1464 if (sink) { 1465 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); 1466 } 1467 IoUtils.closeQuietly(fd); 1468 } 1469 } 1470 1471 @Override close()1472 public void close() throws Exception { 1473 IoUtils.closeQuietly(getFD()); 1474 } 1475 } 1476 } 1477