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      */
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      *
1279      * @deprecated This method may suppress potentially significant exceptions, particularly when
1280      *   closing writable resources. With a writable resource, a failure thrown from {@code close()}
1281      *   should be considered as significant as a failure thrown from a write method because it may
1282      *   indicate a failure to flush bytes to the underlying resource.
1283      */
1284     @Deprecated
closeQuietly(@ullable AutoCloseable closeable)1285     public static void closeQuietly(@Nullable AutoCloseable closeable) {
1286         IoUtils.closeQuietly(closeable);
1287     }
1288 
1289     /**
1290      * Closes the given object quietly, ignoring any checked exceptions. Does
1291      * nothing if the given object is {@code null}.
1292      *
1293      * @deprecated This method may suppress potentially significant exceptions, particularly when
1294      *   closing writable resources. With a writable resource, a failure thrown from {@code close()}
1295      *   should be considered as significant as a failure thrown from a write method because it may
1296      *   indicate a failure to flush bytes to the underlying resource.
1297      */
1298     @Deprecated
closeQuietly(@ullable FileDescriptor fd)1299     public static void closeQuietly(@Nullable FileDescriptor fd) {
1300         IoUtils.closeQuietly(fd);
1301     }
1302 
1303     /** {@hide} */
translateModeStringToPosix(String mode)1304     public static int translateModeStringToPosix(String mode) {
1305         // Quick check for invalid chars
1306         for (int i = 0; i < mode.length(); i++) {
1307             switch (mode.charAt(i)) {
1308                 case 'r':
1309                 case 'w':
1310                 case 't':
1311                 case 'a':
1312                     break;
1313                 default:
1314                     throw new IllegalArgumentException("Bad mode: " + mode);
1315             }
1316         }
1317 
1318         int res = 0;
1319         if (mode.startsWith("rw")) {
1320             res = O_RDWR | O_CREAT;
1321         } else if (mode.startsWith("w")) {
1322             res = O_WRONLY | O_CREAT;
1323         } else if (mode.startsWith("r")) {
1324             res = O_RDONLY;
1325         } else {
1326             throw new IllegalArgumentException("Bad mode: " + mode);
1327         }
1328         if (mode.indexOf('t') != -1) {
1329             res |= O_TRUNC;
1330         }
1331         if (mode.indexOf('a') != -1) {
1332             res |= O_APPEND;
1333         }
1334         return res;
1335     }
1336 
1337     /** {@hide} */
translateModePosixToString(int mode)1338     public static String translateModePosixToString(int mode) {
1339         String res = "";
1340         if ((mode & O_ACCMODE) == O_RDWR) {
1341             res = "rw";
1342         } else if ((mode & O_ACCMODE) == O_WRONLY) {
1343             res = "w";
1344         } else if ((mode & O_ACCMODE) == O_RDONLY) {
1345             res = "r";
1346         } else {
1347             throw new IllegalArgumentException("Bad mode: " + mode);
1348         }
1349         if ((mode & O_TRUNC) == O_TRUNC) {
1350             res += "t";
1351         }
1352         if ((mode & O_APPEND) == O_APPEND) {
1353             res += "a";
1354         }
1355         return res;
1356     }
1357 
1358     /** {@hide} */
translateModePosixToPfd(int mode)1359     public static int translateModePosixToPfd(int mode) {
1360         int res = 0;
1361         if ((mode & O_ACCMODE) == O_RDWR) {
1362             res = MODE_READ_WRITE;
1363         } else if ((mode & O_ACCMODE) == O_WRONLY) {
1364             res = MODE_WRITE_ONLY;
1365         } else if ((mode & O_ACCMODE) == O_RDONLY) {
1366             res = MODE_READ_ONLY;
1367         } else {
1368             throw new IllegalArgumentException("Bad mode: " + mode);
1369         }
1370         if ((mode & O_CREAT) == O_CREAT) {
1371             res |= MODE_CREATE;
1372         }
1373         if ((mode & O_TRUNC) == O_TRUNC) {
1374             res |= MODE_TRUNCATE;
1375         }
1376         if ((mode & O_APPEND) == O_APPEND) {
1377             res |= MODE_APPEND;
1378         }
1379         return res;
1380     }
1381 
1382     /** {@hide} */
translateModePfdToPosix(int mode)1383     public static int translateModePfdToPosix(int mode) {
1384         int res = 0;
1385         if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
1386             res = O_RDWR;
1387         } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
1388             res = O_WRONLY;
1389         } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) {
1390             res = O_RDONLY;
1391         } else {
1392             throw new IllegalArgumentException("Bad mode: " + mode);
1393         }
1394         if ((mode & MODE_CREATE) == MODE_CREATE) {
1395             res |= O_CREAT;
1396         }
1397         if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) {
1398             res |= O_TRUNC;
1399         }
1400         if ((mode & MODE_APPEND) == MODE_APPEND) {
1401             res |= O_APPEND;
1402         }
1403         return res;
1404     }
1405 
1406     /** {@hide} */
translateModeAccessToPosix(int mode)1407     public static int translateModeAccessToPosix(int mode) {
1408         if (mode == F_OK) {
1409             // There's not an exact mapping, so we attempt a read-only open to
1410             // determine if a file exists
1411             return O_RDONLY;
1412         } else if ((mode & (R_OK | W_OK)) == (R_OK | W_OK)) {
1413             return O_RDWR;
1414         } else if ((mode & R_OK) == R_OK) {
1415             return O_RDONLY;
1416         } else if ((mode & W_OK) == W_OK) {
1417             return O_WRONLY;
1418         } else {
1419             throw new IllegalArgumentException("Bad mode: " + mode);
1420         }
1421     }
1422 
1423     /** {@hide} */
1424     @VisibleForTesting
1425     public static class MemoryPipe extends Thread implements AutoCloseable {
1426         private final FileDescriptor[] pipe;
1427         private final byte[] data;
1428         private final boolean sink;
1429 
MemoryPipe(byte[] data, boolean sink)1430         private MemoryPipe(byte[] data, boolean sink) throws IOException {
1431             try {
1432                 this.pipe = Os.pipe();
1433             } catch (ErrnoException e) {
1434                 throw e.rethrowAsIOException();
1435             }
1436             this.data = data;
1437             this.sink = sink;
1438         }
1439 
startInternal()1440         private MemoryPipe startInternal() {
1441             super.start();
1442             return this;
1443         }
1444 
createSource(byte[] data)1445         public static MemoryPipe createSource(byte[] data) throws IOException {
1446             return new MemoryPipe(data, false).startInternal();
1447         }
1448 
createSink(byte[] data)1449         public static MemoryPipe createSink(byte[] data) throws IOException {
1450             return new MemoryPipe(data, true).startInternal();
1451         }
1452 
getFD()1453         public FileDescriptor getFD() {
1454             return sink ? pipe[1] : pipe[0];
1455         }
1456 
getInternalFD()1457         public FileDescriptor getInternalFD() {
1458             return sink ? pipe[0] : pipe[1];
1459         }
1460 
1461         @Override
run()1462         public void run() {
1463             final FileDescriptor fd = getInternalFD();
1464             try {
1465                 int i = 0;
1466                 while (i < data.length) {
1467                     if (sink) {
1468                         i += Os.read(fd, data, i, data.length - i);
1469                     } else {
1470                         i += Os.write(fd, data, i, data.length - i);
1471                     }
1472                 }
1473             } catch (IOException | ErrnoException e) {
1474                 // Ignored
1475             } finally {
1476                 if (sink) {
1477                     SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
1478                 }
1479                 IoUtils.closeQuietly(fd);
1480             }
1481         }
1482 
1483         @Override
close()1484         public void close() throws Exception {
1485             IoUtils.closeQuietly(getFD());
1486         }
1487     }
1488 }
1489