blob: a47fbbaab9dd443ace1098cb556789b3d66abf97 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.os;
18
Jeff Sharkey63280e02018-09-12 11:47:07 -060019import static android.os.ParcelFileDescriptor.MODE_APPEND;
20import static android.os.ParcelFileDescriptor.MODE_CREATE;
21import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
22import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
23import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
24import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
Jeff Sharkeybc2ae002018-07-31 10:45:37 -060025import static android.system.OsConstants.F_OK;
Nick Kralevich52671a72018-12-17 12:46:08 -080026import static android.system.OsConstants.O_ACCMODE;
Jeff Sharkey63280e02018-09-12 11:47:07 -060027import static android.system.OsConstants.O_APPEND;
28import static android.system.OsConstants.O_CREAT;
29import static android.system.OsConstants.O_RDONLY;
30import static android.system.OsConstants.O_RDWR;
31import static android.system.OsConstants.O_TRUNC;
32import static android.system.OsConstants.O_WRONLY;
Jeff Sharkeybc2ae002018-07-31 10:45:37 -060033import static android.system.OsConstants.R_OK;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -070034import static android.system.OsConstants.SPLICE_F_MORE;
35import static android.system.OsConstants.SPLICE_F_MOVE;
36import static android.system.OsConstants.S_ISFIFO;
37import static android.system.OsConstants.S_ISREG;
Jeff Sharkeybc2ae002018-07-31 10:45:37 -060038import static android.system.OsConstants.W_OK;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -070039
Jeff Sharkey85ced632015-07-22 11:11:46 -070040import android.annotation.NonNull;
Jeff Sharkey15447792015-11-05 16:18:51 -080041import android.annotation.Nullable;
Jeff Sharkeycb269aac2019-01-25 11:15:38 -070042import android.annotation.TestApi;
Artur Satayevafdb23a2019-12-10 17:47:53 +000043import android.compat.annotation.UnsupportedAppUsage;
Jeff Sharkey91e3cd42018-08-27 18:03:33 -060044import android.content.ContentResolver;
Ben Kwa62539a22015-04-22 15:43:17 -070045import android.provider.DocumentsContract.Document;
Elliott Hughes34385d32014-04-28 11:11:32 -070046import android.system.ErrnoException;
47import android.system.Os;
Jeff Sharkey35871f22016-01-29 17:13:29 -070048import android.system.StructStat;
Jeff Sharkey0cce5352014-11-26 13:38:26 -080049import android.text.TextUtils;
Jeff Sharkeyd9526902013-03-14 14:11:57 -070050import android.util.Log;
Jeff Sharkey184a0102013-07-10 16:19:52 -070051import android.util.Slog;
Ben Kwa62539a22015-04-22 15:43:17 -070052import android.webkit.MimeTypeMap;
Jeff Sharkey184a0102013-07-10 16:19:52 -070053
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -070054import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeyb0c363b22018-12-15 11:53:03 -070055import com.android.internal.util.ArrayUtils;
Jeff Sharkey45c97df2018-02-01 16:01:52 -070056import com.android.internal.util.SizedInputStream;
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -070057
Jeff Sharkeyb18f8992018-01-31 21:47:09 -070058import libcore.io.IoUtils;
Jeff Sharkeyc4bab982016-02-01 10:16:01 -070059import libcore.util.EmptyArray;
60
Guang Zhu90619812012-10-12 15:50:44 -070061import java.io.BufferedInputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import java.io.ByteArrayOutputStream;
63import java.io.File;
Jeff Sharkey184a0102013-07-10 16:19:52 -070064import java.io.FileDescriptor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065import java.io.FileInputStream;
Wink Saville6d25a992011-06-03 17:03:51 -070066import java.io.FileNotFoundException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067import java.io.FileOutputStream;
Jeff Sharkey35871f22016-01-29 17:13:29 -070068import java.io.FilenameFilter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069import java.io.IOException;
70import java.io.InputStream;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -070071import java.io.OutputStream;
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -070072import java.nio.charset.StandardCharsets;
Jeff Sharkey47ad1862018-12-11 12:24:16 -070073import java.security.DigestInputStream;
74import java.security.MessageDigest;
75import java.security.NoSuchAlgorithmException;
Jeff Sharkeyd9526902013-03-14 14:11:57 -070076import java.util.Arrays;
Jeff Sharkey3f64ec52019-01-22 13:19:40 -070077import java.util.Collection;
Jeff Sharkeyd9526902013-03-14 14:11:57 -070078import java.util.Comparator;
Ben Kwa62539a22015-04-22 15:43:17 -070079import java.util.Objects;
Jeff Sharkey5aae0c92018-07-09 16:38:20 -060080import java.util.concurrent.Executor;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -070081import java.util.concurrent.TimeUnit;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082import java.util.regex.Pattern;
Wink Saville6d25a992011-06-03 17:03:51 -070083import java.util.zip.CRC32;
84import java.util.zip.CheckedInputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086/**
Jeff Sharkey5aae0c92018-07-09 16:38:20 -060087 * Utility methods useful for working with files.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 */
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -070089public final class FileUtils {
Jeff Sharkeyd9526902013-03-14 14:11:57 -070090 private static final String TAG = "FileUtils";
91
Jeff Sharkey5aae0c92018-07-09 16:38:20 -060092 /** {@hide} */ public static final int S_IRWXU = 00700;
93 /** {@hide} */ public static final int S_IRUSR = 00400;
94 /** {@hide} */ public static final int S_IWUSR = 00200;
95 /** {@hide} */ public static final int S_IXUSR = 00100;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096
Jeff Sharkey5aae0c92018-07-09 16:38:20 -060097 /** {@hide} */ public static final int S_IRWXG = 00070;
98 /** {@hide} */ public static final int S_IRGRP = 00040;
99 /** {@hide} */ public static final int S_IWGRP = 00020;
100 /** {@hide} */ public static final int S_IXGRP = 00010;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600102 /** {@hide} */ public static final int S_IRWXO = 00007;
103 /** {@hide} */ public static final int S_IROTH = 00004;
104 /** {@hide} */ public static final int S_IWOTH = 00002;
105 /** {@hide} */ public static final int S_IXOTH = 00001;
106
Andrei Onea24ec3212019-03-15 17:35:05 +0000107 @UnsupportedAppUsage
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600108 private FileUtils() {
109 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110
Andreas Gampe0693fd82016-04-28 19:33:05 -0700111 /** Regular expression for safe filenames: no spaces or metacharacters.
112 *
113 * Use a preload holder so that FileUtils can be compile-time initialized.
114 */
115 private static class NoImagePreloadHolder {
116 public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
117 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118
Brett Chabot968a5322018-07-11 16:26:55 -0700119 // non-final so it can be toggled by Robolectric's ShadowFileUtils
120 private static boolean sEnableCopyOptimizations = true;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700121
122 private static final long COPY_CHECKPOINT_BYTES = 524288;
123
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600124 /**
125 * Listener that is called periodically as progress is made.
126 */
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700127 public interface ProgressListener {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700128 public void onProgress(long progress);
129 }
130
Jeff Sharkey184a0102013-07-10 16:19:52 -0700131 /**
132 * Set owner and mode of of given {@link File}.
133 *
134 * @param mode to apply through {@code chmod}
135 * @param uid to apply through {@code chown}, or -1 to leave unchanged
136 * @param gid to apply through {@code chown}, or -1 to leave unchanged
137 * @return 0 on success, otherwise errno.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600138 * @hide
Jeff Sharkey184a0102013-07-10 16:19:52 -0700139 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000140 @UnsupportedAppUsage
Jeff Sharkey184a0102013-07-10 16:19:52 -0700141 public static int setPermissions(File path, int mode, int uid, int gid) {
142 return setPermissions(path.getAbsolutePath(), mode, uid, gid);
143 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144
Jeff Sharkey184a0102013-07-10 16:19:52 -0700145 /**
146 * Set owner and mode of of given path.
147 *
148 * @param mode to apply through {@code chmod}
149 * @param uid to apply through {@code chown}, or -1 to leave unchanged
150 * @param gid to apply through {@code chown}, or -1 to leave unchanged
151 * @return 0 on success, otherwise errno.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600152 * @hide
Jeff Sharkey184a0102013-07-10 16:19:52 -0700153 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000154 @UnsupportedAppUsage
Jeff Sharkey184a0102013-07-10 16:19:52 -0700155 public static int setPermissions(String path, int mode, int uid, int gid) {
156 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700157 Os.chmod(path, mode);
Jeff Sharkey184a0102013-07-10 16:19:52 -0700158 } catch (ErrnoException e) {
159 Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
160 return e.errno;
161 }
162
163 if (uid >= 0 || gid >= 0) {
164 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700165 Os.chown(path, uid, gid);
Jeff Sharkey184a0102013-07-10 16:19:52 -0700166 } catch (ErrnoException e) {
167 Slog.w(TAG, "Failed to chown(" + path + "): " + e);
168 return e.errno;
169 }
170 }
171
172 return 0;
173 }
174
175 /**
176 * Set owner and mode of of given {@link FileDescriptor}.
177 *
178 * @param mode to apply through {@code chmod}
179 * @param uid to apply through {@code chown}, or -1 to leave unchanged
180 * @param gid to apply through {@code chown}, or -1 to leave unchanged
181 * @return 0 on success, otherwise errno.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600182 * @hide
Jeff Sharkey184a0102013-07-10 16:19:52 -0700183 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000184 @UnsupportedAppUsage
Jeff Sharkey184a0102013-07-10 16:19:52 -0700185 public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
186 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700187 Os.fchmod(fd, mode);
Jeff Sharkey184a0102013-07-10 16:19:52 -0700188 } catch (ErrnoException e) {
189 Slog.w(TAG, "Failed to fchmod(): " + e);
190 return e.errno;
191 }
192
193 if (uid >= 0 || gid >= 0) {
194 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700195 Os.fchown(fd, uid, gid);
Jeff Sharkey184a0102013-07-10 16:19:52 -0700196 } catch (ErrnoException e) {
197 Slog.w(TAG, "Failed to fchown(): " + e);
198 return e.errno;
199 }
200 }
201
202 return 0;
203 }
204
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600205 /**
206 * Copy the owner UID, owner GID, and mode bits from one file to another.
207 *
208 * @param from File where attributes should be copied from.
209 * @param to File where attributes should be copied to.
210 * @hide
211 */
212 public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
Jeff Sharkey35871f22016-01-29 17:13:29 -0700213 try {
214 final StructStat stat = Os.stat(from.getAbsolutePath());
215 Os.chmod(to.getAbsolutePath(), stat.st_mode);
216 Os.chown(to.getAbsolutePath(), stat.st_uid, stat.st_gid);
217 } catch (ErrnoException e) {
218 throw e.rethrowAsIOException();
219 }
220 }
221
Jeff Sharkey184a0102013-07-10 16:19:52 -0700222 /**
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600223 * @deprecated use {@link Os#stat(String)} instead.
224 * @hide
Jeff Sharkey184a0102013-07-10 16:19:52 -0700225 */
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600226 @Deprecated
Jeff Sharkey184a0102013-07-10 16:19:52 -0700227 public static int getUid(String path) {
228 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700229 return Os.stat(path).st_uid;
Jeff Sharkey184a0102013-07-10 16:19:52 -0700230 } catch (ErrnoException e) {
231 return -1;
232 }
233 }
Dianne Hackborn053f61d2013-06-26 18:07:43 -0700234
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700235 /**
236 * Perform an fsync on the given FileOutputStream. The stream at this
237 * point must be flushed but not yet closed.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600238 *
239 * @hide
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700240 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000241 @UnsupportedAppUsage
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700242 public static boolean sync(FileOutputStream stream) {
243 try {
244 if (stream != null) {
245 stream.getFD().sync();
246 }
247 return true;
248 } catch (IOException e) {
249 }
250 return false;
251 }
252
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700253 /**
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700254 * @deprecated use {@link #copy(File, File)} instead.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600255 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700256 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000257 @UnsupportedAppUsage
Jeff Sharkey35871f22016-01-29 17:13:29 -0700258 @Deprecated
259 public static boolean copyFile(File srcFile, File destFile) {
260 try {
261 copyFileOrThrow(srcFile, destFile);
262 return true;
263 } catch (IOException e) {
264 return false;
265 }
266 }
267
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700268 /**
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700269 * @deprecated use {@link #copy(File, File)} instead.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600270 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700271 */
272 @Deprecated
Jeff Sharkey35871f22016-01-29 17:13:29 -0700273 public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
274 try (InputStream in = new FileInputStream(srcFile)) {
275 copyToFileOrThrow(in, destFile);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 }
Jeff Sharkey35871f22016-01-29 17:13:29 -0700277 }
278
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700279 /**
280 * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600281 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700282 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000283 @UnsupportedAppUsage
Jeff Sharkey35871f22016-01-29 17:13:29 -0700284 @Deprecated
285 public static boolean copyToFile(InputStream inputStream, File destFile) {
286 try {
287 copyToFileOrThrow(inputStream, destFile);
288 return true;
289 } catch (IOException e) {
290 return false;
291 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 }
Guang Zhu90619812012-10-12 15:50:44 -0700293
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 /**
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700295 * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600296 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 */
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700298 @Deprecated
299 public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException {
Jeff Sharkey35871f22016-01-29 17:13:29 -0700300 if (destFile.exists()) {
301 destFile.delete();
302 }
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700303 try (FileOutputStream out = new FileOutputStream(destFile)) {
304 copy(in, out);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 try {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700306 Os.fsync(out.getFD());
307 } catch (ErrnoException e) {
308 throw e.rethrowAsIOException();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 }
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700310 }
311 }
312
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700313 /**
314 * Copy the contents of one file to another, replacing any existing content.
315 * <p>
316 * Attempts to use several optimization strategies to copy the data in the
317 * kernel before falling back to a userspace copy as a last resort.
318 *
319 * @return number of bytes copied.
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700320 * @hide
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700321 */
322 public static long copy(@NonNull File from, @NonNull File to) throws IOException {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600323 return copy(from, to, null, null, null);
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700324 }
325
326 /**
327 * Copy the contents of one file to another, replacing any existing content.
328 * <p>
329 * Attempts to use several optimization strategies to copy the data in the
330 * kernel before falling back to a userspace copy as a last resort.
331 *
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700332 * @param signal to signal if the copy should be cancelled early.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600333 * @param executor that listener events should be delivered via.
334 * @param listener to be periodically notified as the copy progresses.
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700335 * @return number of bytes copied.
Jeff Sharkeyb91eaa52019-02-19 11:09:13 -0700336 * @hide
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700337 */
338 public static long copy(@NonNull File from, @NonNull File to,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600339 @Nullable CancellationSignal signal, @Nullable Executor executor,
340 @Nullable ProgressListener listener) throws IOException {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700341 try (FileInputStream in = new FileInputStream(from);
342 FileOutputStream out = new FileOutputStream(to)) {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600343 return copy(in, out, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700344 }
345 }
346
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700347 /**
348 * Copy the contents of one stream to another.
349 * <p>
350 * Attempts to use several optimization strategies to copy the data in the
351 * kernel before falling back to a userspace copy as a last resort.
352 *
353 * @return number of bytes copied.
354 */
355 public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600356 return copy(in, out, null, null, null);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700357 }
358
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700359 /**
360 * Copy the contents of one stream to another.
361 * <p>
362 * Attempts to use several optimization strategies to copy the data in the
363 * kernel before falling back to a userspace copy as a last resort.
364 *
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700365 * @param signal to signal if the copy should be cancelled early.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600366 * @param executor that listener events should be delivered via.
367 * @param listener to be periodically notified as the copy progresses.
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700368 * @return number of bytes copied.
369 */
370 public static long copy(@NonNull InputStream in, @NonNull OutputStream out,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600371 @Nullable CancellationSignal signal, @Nullable Executor executor,
372 @Nullable ProgressListener listener) throws IOException {
Brett Chabot968a5322018-07-11 16:26:55 -0700373 if (sEnableCopyOptimizations) {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700374 if (in instanceof FileInputStream && out instanceof FileOutputStream) {
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700375 return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(),
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600376 signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700377 }
378 }
379
380 // Worse case fallback to userspace
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600381 return copyInternalUserspace(in, out, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700382 }
383
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700384 /**
385 * Copy the contents of one FD to another.
386 * <p>
387 * Attempts to use several optimization strategies to copy the data in the
388 * kernel before falling back to a userspace copy as a last resort.
389 *
390 * @return number of bytes copied.
391 */
392 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out)
393 throws IOException {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600394 return copy(in, out, null, null, null);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700395 }
396
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700397 /**
398 * Copy the contents of one FD to another.
399 * <p>
400 * Attempts to use several optimization strategies to copy the data in the
401 * kernel before falling back to a userspace copy as a last resort.
402 *
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700403 * @param signal to signal if the copy should be cancelled early.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600404 * @param executor that listener events should be delivered via.
405 * @param listener to be periodically notified as the copy progresses.
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700406 * @return number of bytes copied.
407 */
408 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600409 @Nullable CancellationSignal signal, @Nullable Executor executor,
410 @Nullable ProgressListener listener) throws IOException {
411 return copy(in, out, Long.MAX_VALUE, signal, executor, listener);
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700412 }
413
414 /**
415 * Copy the contents of one FD to another.
416 * <p>
417 * Attempts to use several optimization strategies to copy the data in the
418 * kernel before falling back to a userspace copy as a last resort.
419 *
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700420 * @param count the number of bytes to copy.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600421 * @param signal to signal if the copy should be cancelled early.
422 * @param executor that listener events should be delivered via.
423 * @param listener to be periodically notified as the copy progresses.
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700424 * @return number of bytes copied.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600425 * @hide
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700426 */
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600427 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count,
428 @Nullable CancellationSignal signal, @Nullable Executor executor,
429 @Nullable ProgressListener listener) throws IOException {
Brett Chabot968a5322018-07-11 16:26:55 -0700430 if (sEnableCopyOptimizations) {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700431 try {
432 final StructStat st_in = Os.fstat(in);
433 final StructStat st_out = Os.fstat(out);
434 if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600435 return copyInternalSendfile(in, out, count, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700436 } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600437 return copyInternalSplice(in, out, count, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700438 }
439 } catch (ErrnoException e) {
440 throw e.rethrowAsIOException();
441 }
442 }
443
444 // Worse case fallback to userspace
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600445 return copyInternalUserspace(in, out, count, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700446 }
447
448 /**
449 * Requires one of input or output to be a pipe.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600450 *
451 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700452 */
453 @VisibleForTesting
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600454 public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
455 CancellationSignal signal, Executor executor, ProgressListener listener)
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700456 throws ErrnoException {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700457 long progress = 0;
458 long checkpoint = 0;
459
460 long t;
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700461 while ((t = Os.splice(in, null, out, null, Math.min(count, COPY_CHECKPOINT_BYTES),
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700462 SPLICE_F_MOVE | SPLICE_F_MORE)) != 0) {
463 progress += t;
464 checkpoint += t;
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700465 count -= t;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700466
467 if (checkpoint >= COPY_CHECKPOINT_BYTES) {
468 if (signal != null) {
469 signal.throwIfCanceled();
470 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600471 if (executor != null && listener != null) {
472 final long progressSnapshot = progress;
473 executor.execute(() -> {
474 listener.onProgress(progressSnapshot);
475 });
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700476 }
477 checkpoint = 0;
478 }
479 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600480 if (executor != null && listener != null) {
481 final long progressSnapshot = progress;
482 executor.execute(() -> {
483 listener.onProgress(progressSnapshot);
484 });
Jeff Sharkeyacbde7d2018-03-26 16:54:36 -0600485 }
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700486 return progress;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700487 }
488
489 /**
490 * Requires both input and output to be a regular file.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600491 *
492 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700493 */
494 @VisibleForTesting
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600495 public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
496 CancellationSignal signal, Executor executor, ProgressListener listener)
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700497 throws ErrnoException {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700498 long progress = 0;
499 long checkpoint = 0;
500
501 long t;
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700502 while ((t = Os.sendfile(out, in, null, Math.min(count, COPY_CHECKPOINT_BYTES))) != 0) {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700503 progress += t;
504 checkpoint += t;
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700505 count -= t;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700506
507 if (checkpoint >= COPY_CHECKPOINT_BYTES) {
508 if (signal != null) {
509 signal.throwIfCanceled();
510 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600511 if (executor != null && listener != null) {
512 final long progressSnapshot = progress;
513 executor.execute(() -> {
514 listener.onProgress(progressSnapshot);
515 });
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700516 }
517 checkpoint = 0;
518 }
519 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600520 if (executor != null && listener != null) {
521 final long progressSnapshot = progress;
522 executor.execute(() -> {
523 listener.onProgress(progressSnapshot);
524 });
Jeff Sharkeyacbde7d2018-03-26 16:54:36 -0600525 }
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700526 return progress;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700527 }
528
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600529 /** {@hide} */
530 @Deprecated
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700531 @VisibleForTesting
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700532 public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600533 ProgressListener listener, CancellationSignal signal, long count)
534 throws IOException {
535 return copyInternalUserspace(in, out, count, signal, Runnable::run, listener);
536 }
537
538 /** {@hide} */
539 @VisibleForTesting
540 public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count,
541 CancellationSignal signal, Executor executor, ProgressListener listener)
542 throws IOException {
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700543 if (count != Long.MAX_VALUE) {
544 return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count),
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600545 new FileOutputStream(out), signal, executor, listener);
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700546 } else {
547 return copyInternalUserspace(new FileInputStream(in),
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600548 new FileOutputStream(out), signal, executor, listener);
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700549 }
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700550 }
551
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600552 /** {@hide} */
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700553 @VisibleForTesting
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700554 public static long copyInternalUserspace(InputStream in, OutputStream out,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600555 CancellationSignal signal, Executor executor, ProgressListener listener)
556 throws IOException {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700557 long progress = 0;
558 long checkpoint = 0;
559 byte[] buffer = new byte[8192];
560
561 int t;
562 while ((t = in.read(buffer)) != -1) {
563 out.write(buffer, 0, t);
564
565 progress += t;
566 checkpoint += t;
567
568 if (checkpoint >= COPY_CHECKPOINT_BYTES) {
569 if (signal != null) {
570 signal.throwIfCanceled();
571 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600572 if (executor != null && listener != null) {
573 final long progressSnapshot = progress;
574 executor.execute(() -> {
575 listener.onProgress(progressSnapshot);
576 });
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700577 }
578 checkpoint = 0;
579 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600581 if (executor != null && listener != null) {
582 final long progressSnapshot = progress;
583 executor.execute(() -> {
584 listener.onProgress(progressSnapshot);
585 });
Jeff Sharkeyacbde7d2018-03-26 16:54:36 -0600586 }
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700587 return progress;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800588 }
589
590 /**
591 * Check if a filename is "safe" (no metacharacters or spaces).
592 * @param file The file to check
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600593 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800594 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000595 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 public static boolean isFilenameSafe(File file) {
597 // Note, we check whether it matches what's known to be safe,
598 // rather than what's known to be unsafe. Non-ASCII, control
599 // characters, etc. are all unsafe by default.
Andreas Gampe0693fd82016-04-28 19:33:05 -0700600 return NoImagePreloadHolder.SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 }
602
603 /**
604 * Read a text file into a String, optionally limiting the length.
605 * @param file to read (will not seek, so things like /proc files are OK)
606 * @param max length (positive for head, negative of tail, 0 for no limit)
607 * @param ellipsis to add of the file was truncated (can be null)
608 * @return the contents of the file, possibly truncated
609 * @throws IOException if something goes wrong reading the file
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600610 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000612 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 public static String readTextFile(File file, int max, String ellipsis) throws IOException {
614 InputStream input = new FileInputStream(file);
Guang Zhu90619812012-10-12 15:50:44 -0700615 // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
616 // input stream, bytes read not equal to buffer size is not necessarily the correct
617 // indication for EOF; but it is true for BufferedInputStream due to its implementation.
618 BufferedInputStream bis = new BufferedInputStream(input);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 try {
Dan Egnor42471dd2010-01-07 17:25:22 -0800620 long size = file.length();
621 if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
622 if (size > 0 && (max == 0 || size < max)) max = (int) size;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800623 byte[] data = new byte[max + 1];
Guang Zhu90619812012-10-12 15:50:44 -0700624 int length = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625 if (length <= 0) return "";
626 if (length <= max) return new String(data, 0, length);
627 if (ellipsis == null) return new String(data, 0, max);
628 return new String(data, 0, max) + ellipsis;
Dan Egnor42471dd2010-01-07 17:25:22 -0800629 } else if (max < 0) { // "tail" mode: keep the last N
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800630 int len;
631 boolean rolled = false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700632 byte[] last = null;
633 byte[] data = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800634 do {
635 if (last != null) rolled = true;
636 byte[] tmp = last; last = data; data = tmp;
637 if (data == null) data = new byte[-max];
Guang Zhu90619812012-10-12 15:50:44 -0700638 len = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 } while (len == data.length);
640
641 if (last == null && len <= 0) return "";
642 if (last == null) return new String(data, 0, len);
643 if (len > 0) {
644 rolled = true;
645 System.arraycopy(last, len, last, 0, last.length - len);
646 System.arraycopy(data, 0, last, last.length - len, len);
647 }
648 if (ellipsis == null || !rolled) return new String(last);
649 return ellipsis + new String(last);
Dan Egnor42471dd2010-01-07 17:25:22 -0800650 } else { // "cat" mode: size unknown, read it all in streaming fashion
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 ByteArrayOutputStream contents = new ByteArrayOutputStream();
652 int len;
653 byte[] data = new byte[1024];
654 do {
Guang Zhu90619812012-10-12 15:50:44 -0700655 len = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 if (len > 0) contents.write(data, 0, len);
657 } while (len == data.length);
658 return contents.toString();
659 }
660 } finally {
Guang Zhu90619812012-10-12 15:50:44 -0700661 bis.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 input.close();
663 }
664 }
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400665
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600666 /** {@hide} */
Andrei Onea24ec3212019-03-15 17:35:05 +0000667 @UnsupportedAppUsage
Jeff Sharkey2271ba32016-02-01 17:57:08 -0700668 public static void stringToFile(File file, String string) throws IOException {
669 stringToFile(file.getAbsolutePath(), string);
670 }
671
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600672 /**
Narayan Kamath6d051fc2016-11-22 20:20:00 +0000673 * Writes the bytes given in {@code content} to the file whose absolute path
674 * is {@code filename}.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600675 *
676 * @hide
Narayan Kamath6d051fc2016-11-22 20:20:00 +0000677 */
678 public static void bytesToFile(String filename, byte[] content) throws IOException {
Jeff Sharkey89182982017-11-01 19:02:56 -0600679 if (filename.startsWith("/proc/")) {
680 final int oldMask = StrictMode.allowThreadDiskWritesMask();
681 try (FileOutputStream fos = new FileOutputStream(filename)) {
682 fos.write(content);
683 } finally {
684 StrictMode.setThreadPolicyMask(oldMask);
685 }
686 } else {
687 try (FileOutputStream fos = new FileOutputStream(filename)) {
688 fos.write(content);
689 }
Narayan Kamath6d051fc2016-11-22 20:20:00 +0000690 }
691 }
692
Jeff Sharkey2271ba32016-02-01 17:57:08 -0700693 /**
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400694 * Writes string to file. Basically same as "echo -n $string > $filename"
695 *
696 * @param filename
697 * @param string
698 * @throws IOException
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600699 * @hide
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400700 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000701 @UnsupportedAppUsage
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400702 public static void stringToFile(String filename, String string) throws IOException {
Jeff Sharkey032c08a2017-01-19 17:05:10 -0700703 bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400704 }
Wink Saville1b9a6a62011-06-04 07:31:35 -0700705
Wink Saville6d25a992011-06-03 17:03:51 -0700706 /**
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600707 * Computes the checksum of a file using the CRC32 checksum routine. The
708 * value of the checksum is returned.
Wink Saville6d25a992011-06-03 17:03:51 -0700709 *
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600710 * @param file the file to checksum, must not be null
Wink Saville6d25a992011-06-03 17:03:51 -0700711 * @return the checksum value or an exception is thrown.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600712 * @deprecated this is a weak hashing algorithm, and should not be used due
713 * to its potential for collision.
714 * @hide
Wink Saville6d25a992011-06-03 17:03:51 -0700715 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000716 @UnsupportedAppUsage
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600717 @Deprecated
Wink Saville6d25a992011-06-03 17:03:51 -0700718 public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
719 CRC32 checkSummer = new CRC32();
720 CheckedInputStream cis = null;
721
722 try {
723 cis = new CheckedInputStream( new FileInputStream(file), checkSummer);
724 byte[] buf = new byte[128];
725 while(cis.read(buf) >= 0) {
726 // Just read for checksum to get calculated.
727 }
728 return checkSummer.getValue();
729 } finally {
730 if (cis != null) {
731 try {
732 cis.close();
733 } catch (IOException e) {
734 }
735 }
736 }
737 }
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700738
739 /**
Jeff Sharkey47ad1862018-12-11 12:24:16 -0700740 * Compute the digest of the given file using the requested algorithm.
741 *
742 * @param algorithm Any valid algorithm accepted by
743 * {@link MessageDigest#getInstance(String)}.
744 * @hide
745 */
746 public static byte[] digest(@NonNull File file, @NonNull String algorithm)
747 throws IOException, NoSuchAlgorithmException {
748 try (FileInputStream in = new FileInputStream(file)) {
749 return digest(in, algorithm);
750 }
751 }
752
753 /**
754 * Compute the digest of the given file using the requested algorithm.
755 *
756 * @param algorithm Any valid algorithm accepted by
757 * {@link MessageDigest#getInstance(String)}.
758 * @hide
759 */
760 public static byte[] digest(@NonNull InputStream in, @NonNull String algorithm)
761 throws IOException, NoSuchAlgorithmException {
762 // TODO: implement kernel optimizations
763 return digestInternalUserspace(in, algorithm);
764 }
765
766 /**
767 * Compute the digest of the given file using the requested algorithm.
768 *
769 * @param algorithm Any valid algorithm accepted by
770 * {@link MessageDigest#getInstance(String)}.
771 * @hide
772 */
773 public static byte[] digest(FileDescriptor fd, String algorithm)
774 throws IOException, NoSuchAlgorithmException {
775 // TODO: implement kernel optimizations
776 return digestInternalUserspace(new FileInputStream(fd), algorithm);
777 }
778
779 private static byte[] digestInternalUserspace(InputStream in, String algorithm)
780 throws IOException, NoSuchAlgorithmException {
781 final MessageDigest digest = MessageDigest.getInstance(algorithm);
782 try (DigestInputStream digestStream = new DigestInputStream(in, digest)) {
783 final byte[] buffer = new byte[8192];
784 while (digestStream.read(buffer) != -1) {
785 }
786 }
787 return digest.digest();
788 }
789
790 /**
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700791 * Delete older files in a directory until only those matching the given
792 * constraints remain.
793 *
794 * @param minCount Always keep at least this many files.
Makoto Onukia9dca242017-06-21 17:06:49 -0700795 * @param minAgeMs Always keep files younger than this age, in milliseconds.
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800796 * @return if any files were deleted.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600797 * @hide
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700798 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000799 @UnsupportedAppUsage
Makoto Onukia9dca242017-06-21 17:06:49 -0700800 public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) {
801 if (minCount < 0 || minAgeMs < 0) {
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700802 throw new IllegalArgumentException("Constraints must be positive or 0");
803 }
804
805 final File[] files = dir.listFiles();
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800806 if (files == null) return false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700807
808 // Sort with newest files first
809 Arrays.sort(files, new Comparator<File>() {
810 @Override
811 public int compare(File lhs, File rhs) {
Ian Rogers660e6de2016-05-17 11:50:54 -0700812 return Long.compare(rhs.lastModified(), lhs.lastModified());
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700813 }
814 });
815
816 // Keep at least minCount files
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800817 boolean deleted = false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700818 for (int i = minCount; i < files.length; i++) {
819 final File file = files[i];
820
Makoto Onukia9dca242017-06-21 17:06:49 -0700821 // Keep files newer than minAgeMs
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700822 final long age = System.currentTimeMillis() - file.lastModified();
Makoto Onukia9dca242017-06-21 17:06:49 -0700823 if (age > minAgeMs) {
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800824 if (file.delete()) {
825 Log.d(TAG, "Deleted old file " + file);
826 deleted = true;
827 }
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700828 }
829 }
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800830 return deleted;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700831 }
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800832
833 /**
834 * Test if a file lives under the given directory, either as a direct child
835 * or a distant grandchild.
836 * <p>
837 * Both files <em>must</em> have been resolved using
838 * {@link File#getCanonicalFile()} to avoid symlink or path traversal
839 * attacks.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600840 *
841 * @hide
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800842 */
Jeff Sharkey48877892015-03-18 11:27:19 -0700843 public static boolean contains(File[] dirs, File file) {
844 for (File dir : dirs) {
845 if (contains(dir, file)) {
846 return true;
847 }
848 }
849 return false;
850 }
851
Jeff Sharkey3f64ec52019-01-22 13:19:40 -0700852 /** {@hide} */
853 public static boolean contains(Collection<File> dirs, File file) {
854 for (File dir : dirs) {
855 if (contains(dir, file)) {
856 return true;
857 }
858 }
859 return false;
860 }
861
Jeff Sharkey48877892015-03-18 11:27:19 -0700862 /**
863 * Test if a file lives under the given directory, either as a direct child
864 * or a distant grandchild.
865 * <p>
866 * Both files <em>must</em> have been resolved using
867 * {@link File#getCanonicalFile()} to avoid symlink or path traversal
868 * attacks.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600869 *
870 * @hide
Jeff Sharkey48877892015-03-18 11:27:19 -0700871 */
Jeff Sharkeycb269aac2019-01-25 11:15:38 -0700872 @TestApi
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800873 public static boolean contains(File dir, File file) {
Jeff Sharkey50a05452015-04-29 11:24:52 -0700874 if (dir == null || file == null) return false;
Jeff Sharkeyd5d5e922017-02-21 10:51:23 -0700875 return contains(dir.getAbsolutePath(), file.getAbsolutePath());
876 }
Jeff Sharkeyd7460572014-07-06 20:44:55 -0700877
Alan Stokes2a7f8e62019-01-18 17:07:07 +0000878 /**
879 * Test if a file lives under the given directory, either as a direct child
880 * or a distant grandchild.
881 * <p>
882 * Both files <em>must</em> have been resolved using
883 * {@link File#getCanonicalFile()} to avoid symlink or path traversal
884 * attacks.
885 *
886 * @hide
887 */
Jeff Sharkeyd5d5e922017-02-21 10:51:23 -0700888 public static boolean contains(String dirPath, String filePath) {
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800889 if (dirPath.equals(filePath)) {
890 return true;
891 }
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800892 if (!dirPath.endsWith("/")) {
893 dirPath += "/";
894 }
895 return filePath.startsWith(dirPath);
896 }
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700897
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600898 /** {@hide} */
Jeff Sharkeyfcf1e552016-04-14 20:44:58 -0600899 public static boolean deleteContentsAndDir(File dir) {
900 if (deleteContents(dir)) {
901 return dir.delete();
902 } else {
903 return false;
904 }
905 }
906
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600907 /** {@hide} */
Andrei Onea24ec3212019-03-15 17:35:05 +0000908 @UnsupportedAppUsage
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700909 public static boolean deleteContents(File dir) {
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700910 File[] files = dir.listFiles();
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700911 boolean success = true;
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700912 if (files != null) {
913 for (File file : files) {
914 if (file.isDirectory()) {
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700915 success &= deleteContents(file);
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700916 }
Jeff Sharkey73767b92014-07-04 20:18:13 -0700917 if (!file.delete()) {
918 Log.w(TAG, "Failed to delete " + file);
919 success = false;
920 }
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700921 }
922 }
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700923 return success;
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700924 }
925
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800926 private static boolean isValidExtFilenameChar(char c) {
927 switch (c) {
928 case '\0':
929 case '/':
930 return false;
931 default:
932 return true;
933 }
934 }
935
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700936 /**
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800937 * Check if given filename is valid for an ext4 filesystem.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600938 *
939 * @hide
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700940 */
941 public static boolean isValidExtFilename(String name) {
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800942 return (name != null) && name.equals(buildValidExtFilename(name));
943 }
944
945 /**
946 * Mutate the given filename to make it valid for an ext4 filesystem,
947 * replacing any invalid characters with "_".
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600948 *
949 * @hide
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800950 */
951 public static String buildValidExtFilename(String name) {
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700952 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800953 return "(invalid)";
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700954 }
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800955 final StringBuilder res = new StringBuilder(name.length());
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700956 for (int i = 0; i < name.length(); i++) {
957 final char c = name.charAt(i);
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800958 if (isValidExtFilenameChar(c)) {
959 res.append(c);
960 } else {
961 res.append('_');
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700962 }
963 }
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -0700964 trimFilename(res, 255);
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800965 return res.toString();
966 }
967
968 private static boolean isValidFatFilenameChar(char c) {
969 if ((0x00 <= c && c <= 0x1f)) {
970 return false;
971 }
972 switch (c) {
973 case '"':
974 case '*':
975 case '/':
976 case ':':
977 case '<':
978 case '>':
979 case '?':
980 case '\\':
981 case '|':
982 case 0x7F:
983 return false;
984 default:
985 return true;
986 }
987 }
988
989 /**
990 * Check if given filename is valid for a FAT filesystem.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600991 *
992 * @hide
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800993 */
994 public static boolean isValidFatFilename(String name) {
995 return (name != null) && name.equals(buildValidFatFilename(name));
996 }
997
998 /**
999 * Mutate the given filename to make it valid for a FAT filesystem,
1000 * replacing any invalid characters with "_".
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001001 *
1002 * @hide
Jeff Sharkey0cce5352014-11-26 13:38:26 -08001003 */
1004 public static String buildValidFatFilename(String name) {
1005 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
1006 return "(invalid)";
1007 }
1008 final StringBuilder res = new StringBuilder(name.length());
1009 for (int i = 0; i < name.length(); i++) {
1010 final char c = name.charAt(i);
1011 if (isValidFatFilenameChar(c)) {
1012 res.append(c);
1013 } else {
1014 res.append('_');
1015 }
1016 }
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -07001017 // Even though vfat allows 255 UCS-2 chars, we might eventually write to
1018 // ext4 through a FUSE layer, so use that limit.
1019 trimFilename(res, 255);
Jeff Sharkey0cce5352014-11-26 13:38:26 -08001020 return res.toString();
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -07001021 }
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001022
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001023 /** {@hide} */
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -07001024 @VisibleForTesting
1025 public static String trimFilename(String str, int maxBytes) {
1026 final StringBuilder res = new StringBuilder(str);
1027 trimFilename(res, maxBytes);
1028 return res.toString();
1029 }
1030
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001031 /** {@hide} */
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -07001032 private static void trimFilename(StringBuilder res, int maxBytes) {
1033 byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
1034 if (raw.length > maxBytes) {
1035 maxBytes -= 3;
1036 while (raw.length > maxBytes) {
1037 res.deleteCharAt(res.length() / 2);
1038 raw = res.toString().getBytes(StandardCharsets.UTF_8);
1039 }
1040 res.insert(res.length() / 2, "...");
1041 }
1042 }
1043
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001044 /** {@hide} */
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001045 public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
Jeff Sharkeyd7460572014-07-06 20:44:55 -07001046 if (path == null) return null;
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001047 final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
1048 return (result != null) ? result.getAbsolutePath() : null;
1049 }
1050
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001051 /** {@hide} */
Jeff Sharkeyd7460572014-07-06 20:44:55 -07001052 public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
1053 if (paths == null) return null;
1054 final String[] result = new String[paths.length];
1055 for (int i = 0; i < paths.length; i++) {
1056 result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]);
1057 }
1058 return result;
1059 }
1060
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001061 /**
1062 * Given a path under the "before" directory, rewrite it to live under the
1063 * "after" directory. For example, {@code /before/foo/bar.txt} would become
1064 * {@code /after/foo/bar.txt}.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001065 *
1066 * @hide
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001067 */
1068 public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
Jeff Sharkey41be35d2015-05-13 12:38:16 -07001069 if (file == null || beforeDir == null || afterDir == null) return null;
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001070 if (contains(beforeDir, file)) {
1071 final String splice = file.getAbsolutePath().substring(
1072 beforeDir.getAbsolutePath().length());
1073 return new File(afterDir, splice);
1074 }
1075 return null;
1076 }
Ben Kwa62539a22015-04-22 15:43:17 -07001077
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001078 /** {@hide} */
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001079 private static File buildUniqueFileWithExtension(File parent, String name, String ext)
1080 throws FileNotFoundException {
1081 File file = buildFile(parent, name, ext);
1082
1083 // If conflicting file, try adding counter suffix
1084 int n = 0;
1085 while (file.exists()) {
1086 if (n++ >= 32) {
1087 throw new FileNotFoundException("Failed to create unique file");
1088 }
1089 file = buildFile(parent, name + " (" + n + ")", ext);
1090 }
1091
1092 return file;
1093 }
1094
Ben Kwa62539a22015-04-22 15:43:17 -07001095 /**
1096 * Generates a unique file name under the given parent directory. If the display name doesn't
1097 * have an extension that matches the requested MIME type, the default extension for that MIME
1098 * type is appended. If a file already exists, the name is appended with a numerical value to
1099 * make it unique.
1100 *
1101 * For example, the display name 'example' with 'text/plain' MIME might produce
1102 * 'example.txt' or 'example (1).txt', etc.
1103 *
1104 * @throws FileNotFoundException
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001105 * @hide
Ben Kwa62539a22015-04-22 15:43:17 -07001106 */
1107 public static File buildUniqueFile(File parent, String mimeType, String displayName)
1108 throws FileNotFoundException {
Daichi Hironofc7fb752016-03-15 19:19:31 +09001109 final String[] parts = splitFileName(mimeType, displayName);
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001110 return buildUniqueFileWithExtension(parent, parts[0], parts[1]);
1111 }
Daichi Hironofc7fb752016-03-15 19:19:31 +09001112
Jeff Sharkey88050332019-02-11 11:07:09 -07001113 /** {@hide} */
1114 public static File buildNonUniqueFile(File parent, String mimeType, String displayName) {
1115 final String[] parts = splitFileName(mimeType, displayName);
1116 return buildFile(parent, parts[0], parts[1]);
1117 }
1118
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001119 /**
1120 * Generates a unique file name under the given parent directory, keeping
1121 * any extension intact.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001122 *
1123 * @hide
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001124 */
1125 public static File buildUniqueFile(File parent, String displayName)
1126 throws FileNotFoundException {
1127 final String name;
1128 final String ext;
1129
1130 // Extract requested extension from display name
1131 final int lastDot = displayName.lastIndexOf('.');
1132 if (lastDot >= 0) {
1133 name = displayName.substring(0, lastDot);
1134 ext = displayName.substring(lastDot + 1);
1135 } else {
1136 name = displayName;
1137 ext = null;
Daichi Hironofc7fb752016-03-15 19:19:31 +09001138 }
1139
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001140 return buildUniqueFileWithExtension(parent, name, ext);
Daichi Hironofc7fb752016-03-15 19:19:31 +09001141 }
1142
1143 /**
1144 * Splits file name into base name and extension.
1145 * If the display name doesn't have an extension that matches the requested MIME type, the
1146 * extension is regarded as a part of filename and default extension for that MIME type is
1147 * appended.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001148 *
1149 * @hide
Daichi Hironofc7fb752016-03-15 19:19:31 +09001150 */
1151 public static String[] splitFileName(String mimeType, String displayName) {
Ben Kwa62539a22015-04-22 15:43:17 -07001152 String name;
1153 String ext;
1154
1155 if (Document.MIME_TYPE_DIR.equals(mimeType)) {
1156 name = displayName;
1157 ext = null;
1158 } else {
1159 String mimeTypeFromExt;
1160
1161 // Extract requested extension from display name
1162 final int lastDot = displayName.lastIndexOf('.');
1163 if (lastDot >= 0) {
1164 name = displayName.substring(0, lastDot);
1165 ext = displayName.substring(lastDot + 1);
1166 mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
1167 ext.toLowerCase());
1168 } else {
1169 name = displayName;
1170 ext = null;
1171 mimeTypeFromExt = null;
1172 }
1173
1174 if (mimeTypeFromExt == null) {
Jeff Sharkey91e3cd42018-08-27 18:03:33 -06001175 mimeTypeFromExt = ContentResolver.MIME_TYPE_DEFAULT;
Ben Kwa62539a22015-04-22 15:43:17 -07001176 }
1177
Jeff Sharkey63280e02018-09-12 11:47:07 -06001178 final String extFromMimeType;
1179 if (ContentResolver.MIME_TYPE_DEFAULT.equals(mimeType)) {
1180 extFromMimeType = null;
1181 } else {
1182 extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
1183 }
1184
Ben Kwa62539a22015-04-22 15:43:17 -07001185 if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) {
1186 // Extension maps back to requested MIME type; allow it
1187 } else {
1188 // No match; insist that create file matches requested MIME
1189 name = displayName;
1190 ext = extFromMimeType;
1191 }
1192 }
1193
Daichi Hironofc7fb752016-03-15 19:19:31 +09001194 if (ext == null) {
1195 ext = "";
Ben Kwa62539a22015-04-22 15:43:17 -07001196 }
1197
Daichi Hironofc7fb752016-03-15 19:19:31 +09001198 return new String[] { name, ext };
Ben Kwa62539a22015-04-22 15:43:17 -07001199 }
1200
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001201 /** {@hide} */
Ben Kwa62539a22015-04-22 15:43:17 -07001202 private static File buildFile(File parent, String name, String ext) {
1203 if (TextUtils.isEmpty(ext)) {
1204 return new File(parent, name);
1205 } else {
1206 return new File(parent, name + "." + ext);
1207 }
1208 }
Jeff Sharkey85ced632015-07-22 11:11:46 -07001209
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001210 /** {@hide} */
Jeff Sharkeyc4bab982016-02-01 10:16:01 -07001211 public static @NonNull String[] listOrEmpty(@Nullable File dir) {
Jeff Sharkeyb0c363b22018-12-15 11:53:03 -07001212 return (dir != null) ? ArrayUtils.defeatNullable(dir.list())
1213 : EmptyArray.STRING;
Jeff Sharkeyc4bab982016-02-01 10:16:01 -07001214 }
1215
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001216 /** {@hide} */
Jeff Sharkeyc4bab982016-02-01 10:16:01 -07001217 public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
Jeff Sharkeyb0c363b22018-12-15 11:53:03 -07001218 return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
1219 : ArrayUtils.EMPTY_FILE;
Jeff Sharkey85ced632015-07-22 11:11:46 -07001220 }
Jeff Sharkey15447792015-11-05 16:18:51 -08001221
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001222 /** {@hide} */
Jeff Sharkeyc4bab982016-02-01 10:16:01 -07001223 public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
Jeff Sharkeyb0c363b22018-12-15 11:53:03 -07001224 return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter))
1225 : ArrayUtils.EMPTY_FILE;
Jeff Sharkey35871f22016-01-29 17:13:29 -07001226 }
1227
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001228 /** {@hide} */
Jeff Sharkey15447792015-11-05 16:18:51 -08001229 public static @Nullable File newFileOrNull(@Nullable String path) {
1230 return (path != null) ? new File(path) : null;
1231 }
Narayan Kamath5c50e862016-11-24 13:22:40 +00001232
1233 /**
1234 * Creates a directory with name {@code name} under an existing directory {@code baseDir}.
1235 * Returns a {@code File} object representing the directory on success, {@code null} on
1236 * failure.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001237 *
1238 * @hide
Narayan Kamath5c50e862016-11-24 13:22:40 +00001239 */
1240 public static @Nullable File createDir(File baseDir, String name) {
1241 final File dir = new File(baseDir, name);
1242
Sudheer Shankabe0febe2018-11-07 18:24:37 -08001243 return createDir(dir) ? dir : null;
1244 }
1245
1246 /** @hide */
1247 public static boolean createDir(File dir) {
Narayan Kamath5c50e862016-11-24 13:22:40 +00001248 if (dir.exists()) {
Sudheer Shankabe0febe2018-11-07 18:24:37 -08001249 return dir.isDirectory();
Narayan Kamath5c50e862016-11-24 13:22:40 +00001250 }
1251
Sudheer Shankabe0febe2018-11-07 18:24:37 -08001252 return dir.mkdir();
Narayan Kamath5c50e862016-11-24 13:22:40 +00001253 }
Jeff Sharkey373d0172017-02-22 15:47:27 -07001254
1255 /**
1256 * Round the given size of a storage device to a nice round power-of-two
1257 * value, such as 256MB or 32GB. This avoids showing weird values like
1258 * "29.5GB" in UI.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001259 *
1260 * @hide
Jeff Sharkey373d0172017-02-22 15:47:27 -07001261 */
1262 public static long roundStorageSize(long size) {
Jeff Sharkey09734df2017-03-07 20:50:27 -07001263 long val = 1;
1264 long pow = 1;
1265 while ((val * pow) < size) {
1266 val <<= 1;
1267 if (val > 512) {
1268 val = 1;
1269 pow *= 1000;
1270 }
Jeff Sharkey373d0172017-02-22 15:47:27 -07001271 }
Jeff Sharkey09734df2017-03-07 20:50:27 -07001272 return val * pow;
Jeff Sharkey373d0172017-02-22 15:47:27 -07001273 }
Jeff Sharkeyb18f8992018-01-31 21:47:09 -07001274
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001275 /**
1276 * Closes the given object quietly, ignoring any checked exceptions. Does
1277 * nothing if the given object is {@code null}.
Charles Munger219e986e2019-11-15 12:55:55 -08001278 *
1279 * @deprecated This method may suppress potentially significant exceptions, particularly when
1280 * closing writable resources. With a writable resource, a failure thrown from {@code close()}
1281 * should be considered as significant as a failure thrown from a write method because it may
1282 * indicate a failure to flush bytes to the underlying resource.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001283 */
Charles Munger219e986e2019-11-15 12:55:55 -08001284 @Deprecated
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001285 public static void closeQuietly(@Nullable AutoCloseable closeable) {
1286 IoUtils.closeQuietly(closeable);
1287 }
1288
1289 /**
1290 * Closes the given object quietly, ignoring any checked exceptions. Does
1291 * nothing if the given object is {@code null}.
Charles Munger219e986e2019-11-15 12:55:55 -08001292 *
1293 * @deprecated This method may suppress potentially significant exceptions, particularly when
1294 * closing writable resources. With a writable resource, a failure thrown from {@code close()}
1295 * should be considered as significant as a failure thrown from a write method because it may
1296 * indicate a failure to flush bytes to the underlying resource.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001297 */
Charles Munger219e986e2019-11-15 12:55:55 -08001298 @Deprecated
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001299 public static void closeQuietly(@Nullable FileDescriptor fd) {
1300 IoUtils.closeQuietly(fd);
1301 }
1302
1303 /** {@hide} */
Jeff Sharkey63280e02018-09-12 11:47:07 -06001304 public static int translateModeStringToPosix(String mode) {
Jeff Sharkey0c914512018-10-17 18:32:25 -06001305 // Sanity check for invalid chars
1306 for (int i = 0; i < mode.length(); i++) {
1307 switch (mode.charAt(i)) {
1308 case 'r':
1309 case 'w':
1310 case 't':
1311 case 'a':
1312 break;
1313 default:
1314 throw new IllegalArgumentException("Bad mode: " + mode);
1315 }
1316 }
1317
Jeff Sharkey63280e02018-09-12 11:47:07 -06001318 int res = 0;
1319 if (mode.startsWith("rw")) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001320 res = O_RDWR | O_CREAT;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001321 } else if (mode.startsWith("w")) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001322 res = O_WRONLY | O_CREAT;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001323 } else if (mode.startsWith("r")) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001324 res = O_RDONLY;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001325 } else {
1326 throw new IllegalArgumentException("Bad mode: " + mode);
1327 }
1328 if (mode.indexOf('t') != -1) {
1329 res |= O_TRUNC;
1330 }
1331 if (mode.indexOf('a') != -1) {
1332 res |= O_APPEND;
1333 }
1334 return res;
1335 }
1336
1337 /** {@hide} */
1338 public static String translateModePosixToString(int mode) {
1339 String res = "";
Nick Kralevich52671a72018-12-17 12:46:08 -08001340 if ((mode & O_ACCMODE) == O_RDWR) {
1341 res = "rw";
1342 } else if ((mode & O_ACCMODE) == O_WRONLY) {
1343 res = "w";
1344 } else if ((mode & O_ACCMODE) == O_RDONLY) {
1345 res = "r";
Jeff Sharkey63280e02018-09-12 11:47:07 -06001346 } else {
1347 throw new IllegalArgumentException("Bad mode: " + mode);
1348 }
1349 if ((mode & O_TRUNC) == O_TRUNC) {
1350 res += "t";
1351 }
1352 if ((mode & O_APPEND) == O_APPEND) {
1353 res += "a";
1354 }
1355 return res;
1356 }
1357
1358 /** {@hide} */
1359 public static int translateModePosixToPfd(int mode) {
1360 int res = 0;
Nick Kralevich52671a72018-12-17 12:46:08 -08001361 if ((mode & O_ACCMODE) == O_RDWR) {
1362 res = MODE_READ_WRITE;
1363 } else if ((mode & O_ACCMODE) == O_WRONLY) {
1364 res = MODE_WRITE_ONLY;
1365 } else if ((mode & O_ACCMODE) == O_RDONLY) {
1366 res = MODE_READ_ONLY;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001367 } else {
1368 throw new IllegalArgumentException("Bad mode: " + mode);
1369 }
1370 if ((mode & O_CREAT) == O_CREAT) {
1371 res |= MODE_CREATE;
1372 }
1373 if ((mode & O_TRUNC) == O_TRUNC) {
1374 res |= MODE_TRUNCATE;
1375 }
1376 if ((mode & O_APPEND) == O_APPEND) {
1377 res |= MODE_APPEND;
1378 }
1379 return res;
1380 }
1381
1382 /** {@hide} */
1383 public static int translateModePfdToPosix(int mode) {
1384 int res = 0;
1385 if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001386 res = O_RDWR;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001387 } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001388 res = O_WRONLY;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001389 } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001390 res = O_RDONLY;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001391 } else {
1392 throw new IllegalArgumentException("Bad mode: " + mode);
1393 }
1394 if ((mode & MODE_CREATE) == MODE_CREATE) {
1395 res |= O_CREAT;
1396 }
1397 if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) {
1398 res |= O_TRUNC;
1399 }
1400 if ((mode & MODE_APPEND) == MODE_APPEND) {
1401 res |= O_APPEND;
1402 }
1403 return res;
1404 }
1405
1406 /** {@hide} */
Jeff Sharkeybc2ae002018-07-31 10:45:37 -06001407 public static int translateModeAccessToPosix(int mode) {
1408 if (mode == F_OK) {
1409 // There's not an exact mapping, so we attempt a read-only open to
1410 // determine if a file exists
1411 return O_RDONLY;
1412 } else if ((mode & (R_OK | W_OK)) == (R_OK | W_OK)) {
1413 return O_RDWR;
1414 } else if ((mode & R_OK) == R_OK) {
1415 return O_RDONLY;
1416 } else if ((mode & W_OK) == W_OK) {
1417 return O_WRONLY;
1418 } else {
1419 throw new IllegalArgumentException("Bad mode: " + mode);
1420 }
1421 }
1422
1423 /** {@hide} */
Jeff Sharkeyb18f8992018-01-31 21:47:09 -07001424 @VisibleForTesting
1425 public static class MemoryPipe extends Thread implements AutoCloseable {
1426 private final FileDescriptor[] pipe;
1427 private final byte[] data;
1428 private final boolean sink;
1429
1430 private MemoryPipe(byte[] data, boolean sink) throws IOException {
1431 try {
1432 this.pipe = Os.pipe();
1433 } catch (ErrnoException e) {
1434 throw e.rethrowAsIOException();
1435 }
1436 this.data = data;
1437 this.sink = sink;
1438 }
1439
1440 private MemoryPipe startInternal() {
1441 super.start();
1442 return this;
1443 }
1444
1445 public static MemoryPipe createSource(byte[] data) throws IOException {
1446 return new MemoryPipe(data, false).startInternal();
1447 }
1448
1449 public static MemoryPipe createSink(byte[] data) throws IOException {
1450 return new MemoryPipe(data, true).startInternal();
1451 }
1452
1453 public FileDescriptor getFD() {
1454 return sink ? pipe[1] : pipe[0];
1455 }
1456
1457 public FileDescriptor getInternalFD() {
1458 return sink ? pipe[0] : pipe[1];
1459 }
1460
1461 @Override
1462 public void run() {
1463 final FileDescriptor fd = getInternalFD();
1464 try {
1465 int i = 0;
1466 while (i < data.length) {
1467 if (sink) {
1468 i += Os.read(fd, data, i, data.length - i);
1469 } else {
1470 i += Os.write(fd, data, i, data.length - i);
1471 }
1472 }
1473 } catch (IOException | ErrnoException e) {
Jeff Sharkey45c97df2018-02-01 16:01:52 -07001474 // Ignored
Jeff Sharkeyb18f8992018-01-31 21:47:09 -07001475 } finally {
1476 if (sink) {
1477 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
1478 }
1479 IoUtils.closeQuietly(fd);
1480 }
1481 }
1482
1483 @Override
1484 public void close() throws Exception {
1485 IoUtils.closeQuietly(getFD());
1486 }
1487 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001488}