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