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