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