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