blob: 0384faa88be570d95e150bdc9bb2252a30862897 [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;
Jeff Sharkey91e3cd42018-08-27 18:03:33 -060043import android.content.ContentResolver;
Ben Kwa62539a22015-04-22 15:43:17 -070044import android.provider.DocumentsContract.Document;
Elliott Hughes34385d32014-04-28 11:11:32 -070045import android.system.ErrnoException;
46import android.system.Os;
Jeff Sharkey35871f22016-01-29 17:13:29 -070047import android.system.StructStat;
Jeff Sharkey0cce5352014-11-26 13:38:26 -080048import android.text.TextUtils;
Jeff Sharkeyd9526902013-03-14 14:11:57 -070049import android.util.Log;
Jeff Sharkey184a0102013-07-10 16:19:52 -070050import android.util.Slog;
Ben Kwa62539a22015-04-22 15:43:17 -070051import android.webkit.MimeTypeMap;
Jeff Sharkey184a0102013-07-10 16:19:52 -070052
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -070053import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeyb0c363b22018-12-15 11:53:03 -070054import com.android.internal.util.ArrayUtils;
Jeff Sharkey45c97df2018-02-01 16:01:52 -070055import com.android.internal.util.SizedInputStream;
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -070056
Jeff Sharkeyb18f8992018-01-31 21:47:09 -070057import libcore.io.IoUtils;
Jeff Sharkeyc4bab982016-02-01 10:16:01 -070058import libcore.util.EmptyArray;
59
Guang Zhu90619812012-10-12 15:50:44 -070060import java.io.BufferedInputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061import java.io.ByteArrayOutputStream;
62import java.io.File;
Jeff Sharkey184a0102013-07-10 16:19:52 -070063import java.io.FileDescriptor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064import java.io.FileInputStream;
Wink Saville6d25a992011-06-03 17:03:51 -070065import java.io.FileNotFoundException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066import java.io.FileOutputStream;
Jeff Sharkey35871f22016-01-29 17:13:29 -070067import java.io.FilenameFilter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068import java.io.IOException;
69import java.io.InputStream;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -070070import java.io.OutputStream;
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -070071import java.nio.charset.StandardCharsets;
Jeff Sharkey47ad1862018-12-11 12:24:16 -070072import java.security.DigestInputStream;
73import java.security.MessageDigest;
74import java.security.NoSuchAlgorithmException;
Jeff Sharkeyd9526902013-03-14 14:11:57 -070075import java.util.Arrays;
Jeff Sharkey3f64ec52019-01-22 13:19:40 -070076import java.util.Collection;
Jeff Sharkeyd9526902013-03-14 14:11:57 -070077import java.util.Comparator;
Ben Kwa62539a22015-04-22 15:43:17 -070078import java.util.Objects;
Jeff Sharkey5aae0c92018-07-09 16:38:20 -060079import java.util.concurrent.Executor;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -070080import java.util.concurrent.TimeUnit;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081import java.util.regex.Pattern;
Wink Saville6d25a992011-06-03 17:03:51 -070082import java.util.zip.CRC32;
83import java.util.zip.CheckedInputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085/**
Jeff Sharkey5aae0c92018-07-09 16:38:20 -060086 * Utility methods useful for working with files.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 */
Wink Saville6d25a992011-06-03 17:03:51 -070088public class FileUtils {
Jeff Sharkeyd9526902013-03-14 14:11:57 -070089 private static final String TAG = "FileUtils";
90
Jeff Sharkey5aae0c92018-07-09 16:38:20 -060091 /** {@hide} */ public static final int S_IRWXU = 00700;
92 /** {@hide} */ public static final int S_IRUSR = 00400;
93 /** {@hide} */ public static final int S_IWUSR = 00200;
94 /** {@hide} */ public static final int S_IXUSR = 00100;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095
Jeff Sharkey5aae0c92018-07-09 16:38:20 -060096 /** {@hide} */ public static final int S_IRWXG = 00070;
97 /** {@hide} */ public static final int S_IRGRP = 00040;
98 /** {@hide} */ public static final int S_IWGRP = 00020;
99 /** {@hide} */ public static final int S_IXGRP = 00010;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600101 /** {@hide} */ public static final int S_IRWXO = 00007;
102 /** {@hide} */ public static final int S_IROTH = 00004;
103 /** {@hide} */ public static final int S_IWOTH = 00002;
104 /** {@hide} */ public static final int S_IXOTH = 00001;
105
106 private FileUtils() {
107 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108
Andreas Gampe0693fd82016-04-28 19:33:05 -0700109 /** Regular expression for safe filenames: no spaces or metacharacters.
110 *
111 * Use a preload holder so that FileUtils can be compile-time initialized.
112 */
113 private static class NoImagePreloadHolder {
114 public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
115 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116
Brett Chabot968a5322018-07-11 16:26:55 -0700117 // non-final so it can be toggled by Robolectric's ShadowFileUtils
118 private static boolean sEnableCopyOptimizations = true;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700119
120 private static final long COPY_CHECKPOINT_BYTES = 524288;
121
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600122 /**
123 * Listener that is called periodically as progress is made.
124 */
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700125 public interface ProgressListener {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700126 public void onProgress(long progress);
127 }
128
Jeff Sharkey184a0102013-07-10 16:19:52 -0700129 /**
130 * Set owner and mode of of given {@link File}.
131 *
132 * @param mode to apply through {@code chmod}
133 * @param uid to apply through {@code chown}, or -1 to leave unchanged
134 * @param gid to apply through {@code chown}, or -1 to leave unchanged
135 * @return 0 on success, otherwise errno.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600136 * @hide
Jeff Sharkey184a0102013-07-10 16:19:52 -0700137 */
138 public static int setPermissions(File path, int mode, int uid, int gid) {
139 return setPermissions(path.getAbsolutePath(), mode, uid, gid);
140 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141
Jeff Sharkey184a0102013-07-10 16:19:52 -0700142 /**
143 * Set owner and mode of of given path.
144 *
145 * @param mode to apply through {@code chmod}
146 * @param uid to apply through {@code chown}, or -1 to leave unchanged
147 * @param gid to apply through {@code chown}, or -1 to leave unchanged
148 * @return 0 on success, otherwise errno.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600149 * @hide
Jeff Sharkey184a0102013-07-10 16:19:52 -0700150 */
151 public static int setPermissions(String path, int mode, int uid, int gid) {
152 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700153 Os.chmod(path, mode);
Jeff Sharkey184a0102013-07-10 16:19:52 -0700154 } catch (ErrnoException e) {
155 Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
156 return e.errno;
157 }
158
159 if (uid >= 0 || gid >= 0) {
160 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700161 Os.chown(path, uid, gid);
Jeff Sharkey184a0102013-07-10 16:19:52 -0700162 } catch (ErrnoException e) {
163 Slog.w(TAG, "Failed to chown(" + path + "): " + e);
164 return e.errno;
165 }
166 }
167
168 return 0;
169 }
170
171 /**
172 * Set owner and mode of of given {@link FileDescriptor}.
173 *
174 * @param mode to apply through {@code chmod}
175 * @param uid to apply through {@code chown}, or -1 to leave unchanged
176 * @param gid to apply through {@code chown}, or -1 to leave unchanged
177 * @return 0 on success, otherwise errno.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600178 * @hide
Jeff Sharkey184a0102013-07-10 16:19:52 -0700179 */
180 public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
181 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700182 Os.fchmod(fd, mode);
Jeff Sharkey184a0102013-07-10 16:19:52 -0700183 } catch (ErrnoException e) {
184 Slog.w(TAG, "Failed to fchmod(): " + e);
185 return e.errno;
186 }
187
188 if (uid >= 0 || gid >= 0) {
189 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700190 Os.fchown(fd, uid, gid);
Jeff Sharkey184a0102013-07-10 16:19:52 -0700191 } catch (ErrnoException e) {
192 Slog.w(TAG, "Failed to fchown(): " + e);
193 return e.errno;
194 }
195 }
196
197 return 0;
198 }
199
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600200 /**
201 * Copy the owner UID, owner GID, and mode bits from one file to another.
202 *
203 * @param from File where attributes should be copied from.
204 * @param to File where attributes should be copied to.
205 * @hide
206 */
207 public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
Jeff Sharkey35871f22016-01-29 17:13:29 -0700208 try {
209 final StructStat stat = Os.stat(from.getAbsolutePath());
210 Os.chmod(to.getAbsolutePath(), stat.st_mode);
211 Os.chown(to.getAbsolutePath(), stat.st_uid, stat.st_gid);
212 } catch (ErrnoException e) {
213 throw e.rethrowAsIOException();
214 }
215 }
216
Jeff Sharkey184a0102013-07-10 16:19:52 -0700217 /**
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600218 * @deprecated use {@link Os#stat(String)} instead.
219 * @hide
Jeff Sharkey184a0102013-07-10 16:19:52 -0700220 */
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600221 @Deprecated
Jeff Sharkey184a0102013-07-10 16:19:52 -0700222 public static int getUid(String path) {
223 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700224 return Os.stat(path).st_uid;
Jeff Sharkey184a0102013-07-10 16:19:52 -0700225 } catch (ErrnoException e) {
226 return -1;
227 }
228 }
Dianne Hackborn053f61d2013-06-26 18:07:43 -0700229
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700230 /**
231 * Perform an fsync on the given FileOutputStream. The stream at this
232 * point must be flushed but not yet closed.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600233 *
234 * @hide
Dianne Hackborn8bdf5932010-10-15 12:54:40 -0700235 */
236 public static boolean sync(FileOutputStream stream) {
237 try {
238 if (stream != null) {
239 stream.getFD().sync();
240 }
241 return true;
242 } catch (IOException e) {
243 }
244 return false;
245 }
246
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700247 /**
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700248 * @deprecated use {@link #copy(File, File)} instead.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600249 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700250 */
Jeff Sharkey35871f22016-01-29 17:13:29 -0700251 @Deprecated
252 public static boolean copyFile(File srcFile, File destFile) {
253 try {
254 copyFileOrThrow(srcFile, destFile);
255 return true;
256 } catch (IOException e) {
257 return false;
258 }
259 }
260
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700261 /**
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700262 * @deprecated use {@link #copy(File, File)} instead.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600263 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700264 */
265 @Deprecated
Jeff Sharkey35871f22016-01-29 17:13:29 -0700266 public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
267 try (InputStream in = new FileInputStream(srcFile)) {
268 copyToFileOrThrow(in, destFile);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 }
Jeff Sharkey35871f22016-01-29 17:13:29 -0700270 }
271
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700272 /**
273 * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600274 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700275 */
Jeff Sharkey35871f22016-01-29 17:13:29 -0700276 @Deprecated
277 public static boolean copyToFile(InputStream inputStream, File destFile) {
278 try {
279 copyToFileOrThrow(inputStream, destFile);
280 return true;
281 } catch (IOException e) {
282 return false;
283 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 }
Guang Zhu90619812012-10-12 15:50:44 -0700285
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 /**
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700287 * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600288 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 */
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700290 @Deprecated
291 public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException {
Jeff Sharkey35871f22016-01-29 17:13:29 -0700292 if (destFile.exists()) {
293 destFile.delete();
294 }
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700295 try (FileOutputStream out = new FileOutputStream(destFile)) {
296 copy(in, out);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 try {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700298 Os.fsync(out.getFD());
299 } catch (ErrnoException e) {
300 throw e.rethrowAsIOException();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 }
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700302 }
303 }
304
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700305 /**
306 * Copy the contents of one file to another, replacing any existing content.
307 * <p>
308 * Attempts to use several optimization strategies to copy the data in the
309 * kernel before falling back to a userspace copy as a last resort.
310 *
311 * @return number of bytes copied.
312 */
313 public static long copy(@NonNull File from, @NonNull File to) throws IOException {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600314 return copy(from, to, null, null, null);
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700315 }
316
317 /**
318 * Copy the contents of one file to another, replacing any existing content.
319 * <p>
320 * Attempts to use several optimization strategies to copy the data in the
321 * kernel before falling back to a userspace copy as a last resort.
322 *
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700323 * @param signal to signal if the copy should be cancelled early.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600324 * @param executor that listener events should be delivered via.
325 * @param listener to be periodically notified as the copy progresses.
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700326 * @return number of bytes copied.
327 */
328 public static long copy(@NonNull File from, @NonNull File to,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600329 @Nullable CancellationSignal signal, @Nullable Executor executor,
330 @Nullable ProgressListener listener) throws IOException {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700331 try (FileInputStream in = new FileInputStream(from);
332 FileOutputStream out = new FileOutputStream(to)) {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600333 return copy(in, out, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700334 }
335 }
336
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700337 /**
338 * Copy the contents of one stream to another.
339 * <p>
340 * Attempts to use several optimization strategies to copy the data in the
341 * kernel before falling back to a userspace copy as a last resort.
342 *
343 * @return number of bytes copied.
344 */
345 public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600346 return copy(in, out, null, null, null);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700347 }
348
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700349 /**
350 * Copy the contents of one stream to another.
351 * <p>
352 * Attempts to use several optimization strategies to copy the data in the
353 * kernel before falling back to a userspace copy as a last resort.
354 *
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700355 * @param signal to signal if the copy should be cancelled early.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600356 * @param executor that listener events should be delivered via.
357 * @param listener to be periodically notified as the copy progresses.
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700358 * @return number of bytes copied.
359 */
360 public static long copy(@NonNull InputStream in, @NonNull OutputStream out,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600361 @Nullable CancellationSignal signal, @Nullable Executor executor,
362 @Nullable ProgressListener listener) throws IOException {
Brett Chabot968a5322018-07-11 16:26:55 -0700363 if (sEnableCopyOptimizations) {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700364 if (in instanceof FileInputStream && out instanceof FileOutputStream) {
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700365 return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(),
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600366 signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700367 }
368 }
369
370 // Worse case fallback to userspace
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600371 return copyInternalUserspace(in, out, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700372 }
373
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700374 /**
375 * Copy the contents of one FD to another.
376 * <p>
377 * Attempts to use several optimization strategies to copy the data in the
378 * kernel before falling back to a userspace copy as a last resort.
379 *
380 * @return number of bytes copied.
381 */
382 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out)
383 throws IOException {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600384 return copy(in, out, null, null, null);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700385 }
386
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700387 /**
388 * Copy the contents of one FD to another.
389 * <p>
390 * Attempts to use several optimization strategies to copy the data in the
391 * kernel before falling back to a userspace copy as a last resort.
392 *
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700393 * @param signal to signal if the copy should be cancelled early.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600394 * @param executor that listener events should be delivered via.
395 * @param listener to be periodically notified as the copy progresses.
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700396 * @return number of bytes copied.
397 */
398 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600399 @Nullable CancellationSignal signal, @Nullable Executor executor,
400 @Nullable ProgressListener listener) throws IOException {
401 return copy(in, out, Long.MAX_VALUE, signal, executor, listener);
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700402 }
403
404 /**
405 * Copy the contents of one FD to another.
406 * <p>
407 * Attempts to use several optimization strategies to copy the data in the
408 * kernel before falling back to a userspace copy as a last resort.
409 *
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700410 * @param count the number of bytes to copy.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600411 * @param signal to signal if the copy should be cancelled early.
412 * @param executor that listener events should be delivered via.
413 * @param listener to be periodically notified as the copy progresses.
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700414 * @return number of bytes copied.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600415 * @hide
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700416 */
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600417 public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count,
418 @Nullable CancellationSignal signal, @Nullable Executor executor,
419 @Nullable ProgressListener listener) throws IOException {
Brett Chabot968a5322018-07-11 16:26:55 -0700420 if (sEnableCopyOptimizations) {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700421 try {
422 final StructStat st_in = Os.fstat(in);
423 final StructStat st_out = Os.fstat(out);
424 if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600425 return copyInternalSendfile(in, out, count, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700426 } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600427 return copyInternalSplice(in, out, count, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700428 }
429 } catch (ErrnoException e) {
430 throw e.rethrowAsIOException();
431 }
432 }
433
434 // Worse case fallback to userspace
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600435 return copyInternalUserspace(in, out, count, signal, executor, listener);
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700436 }
437
438 /**
439 * Requires one of input or output to be a pipe.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600440 *
441 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700442 */
443 @VisibleForTesting
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600444 public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
445 CancellationSignal signal, Executor executor, ProgressListener listener)
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700446 throws ErrnoException {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700447 long progress = 0;
448 long checkpoint = 0;
449
450 long t;
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700451 while ((t = Os.splice(in, null, out, null, Math.min(count, COPY_CHECKPOINT_BYTES),
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700452 SPLICE_F_MOVE | SPLICE_F_MORE)) != 0) {
453 progress += t;
454 checkpoint += t;
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700455 count -= t;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700456
457 if (checkpoint >= COPY_CHECKPOINT_BYTES) {
458 if (signal != null) {
459 signal.throwIfCanceled();
460 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600461 if (executor != null && listener != null) {
462 final long progressSnapshot = progress;
463 executor.execute(() -> {
464 listener.onProgress(progressSnapshot);
465 });
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700466 }
467 checkpoint = 0;
468 }
469 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600470 if (executor != null && listener != null) {
471 final long progressSnapshot = progress;
472 executor.execute(() -> {
473 listener.onProgress(progressSnapshot);
474 });
Jeff Sharkeyacbde7d2018-03-26 16:54:36 -0600475 }
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700476 return progress;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700477 }
478
479 /**
480 * Requires both input and output to be a regular file.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600481 *
482 * @hide
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700483 */
484 @VisibleForTesting
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600485 public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
486 CancellationSignal signal, Executor executor, ProgressListener listener)
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700487 throws ErrnoException {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700488 long progress = 0;
489 long checkpoint = 0;
490
491 long t;
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700492 while ((t = Os.sendfile(out, in, null, Math.min(count, COPY_CHECKPOINT_BYTES))) != 0) {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700493 progress += t;
494 checkpoint += t;
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700495 count -= t;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700496
497 if (checkpoint >= COPY_CHECKPOINT_BYTES) {
498 if (signal != null) {
499 signal.throwIfCanceled();
500 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600501 if (executor != null && listener != null) {
502 final long progressSnapshot = progress;
503 executor.execute(() -> {
504 listener.onProgress(progressSnapshot);
505 });
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700506 }
507 checkpoint = 0;
508 }
509 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600510 if (executor != null && listener != null) {
511 final long progressSnapshot = progress;
512 executor.execute(() -> {
513 listener.onProgress(progressSnapshot);
514 });
Jeff Sharkeyacbde7d2018-03-26 16:54:36 -0600515 }
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700516 return progress;
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700517 }
518
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600519 /** {@hide} */
520 @Deprecated
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700521 @VisibleForTesting
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700522 public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600523 ProgressListener listener, CancellationSignal signal, long count)
524 throws IOException {
525 return copyInternalUserspace(in, out, count, signal, Runnable::run, listener);
526 }
527
528 /** {@hide} */
529 @VisibleForTesting
530 public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count,
531 CancellationSignal signal, Executor executor, ProgressListener listener)
532 throws IOException {
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700533 if (count != Long.MAX_VALUE) {
534 return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count),
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600535 new FileOutputStream(out), signal, executor, listener);
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700536 } else {
537 return copyInternalUserspace(new FileInputStream(in),
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600538 new FileOutputStream(out), signal, executor, listener);
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700539 }
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700540 }
541
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600542 /** {@hide} */
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700543 @VisibleForTesting
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700544 public static long copyInternalUserspace(InputStream in, OutputStream out,
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600545 CancellationSignal signal, Executor executor, ProgressListener listener)
546 throws IOException {
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700547 long progress = 0;
548 long checkpoint = 0;
549 byte[] buffer = new byte[8192];
550
551 int t;
552 while ((t = in.read(buffer)) != -1) {
553 out.write(buffer, 0, t);
554
555 progress += t;
556 checkpoint += t;
557
558 if (checkpoint >= COPY_CHECKPOINT_BYTES) {
559 if (signal != null) {
560 signal.throwIfCanceled();
561 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600562 if (executor != null && listener != null) {
563 final long progressSnapshot = progress;
564 executor.execute(() -> {
565 listener.onProgress(progressSnapshot);
566 });
Jeff Sharkeyb18f8992018-01-31 21:47:09 -0700567 }
568 checkpoint = 0;
569 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570 }
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600571 if (executor != null && listener != null) {
572 final long progressSnapshot = progress;
573 executor.execute(() -> {
574 listener.onProgress(progressSnapshot);
575 });
Jeff Sharkeyacbde7d2018-03-26 16:54:36 -0600576 }
Jeff Sharkey45c97df2018-02-01 16:01:52 -0700577 return progress;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 }
579
580 /**
581 * Check if a filename is "safe" (no metacharacters or spaces).
582 * @param file The file to check
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600583 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 */
585 public static boolean isFilenameSafe(File file) {
586 // Note, we check whether it matches what's known to be safe,
587 // rather than what's known to be unsafe. Non-ASCII, control
588 // characters, etc. are all unsafe by default.
Andreas Gampe0693fd82016-04-28 19:33:05 -0700589 return NoImagePreloadHolder.SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 }
591
592 /**
593 * Read a text file into a String, optionally limiting the length.
594 * @param file to read (will not seek, so things like /proc files are OK)
595 * @param max length (positive for head, negative of tail, 0 for no limit)
596 * @param ellipsis to add of the file was truncated (can be null)
597 * @return the contents of the file, possibly truncated
598 * @throws IOException if something goes wrong reading the file
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600599 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 */
601 public static String readTextFile(File file, int max, String ellipsis) throws IOException {
602 InputStream input = new FileInputStream(file);
Guang Zhu90619812012-10-12 15:50:44 -0700603 // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
604 // input stream, bytes read not equal to buffer size is not necessarily the correct
605 // indication for EOF; but it is true for BufferedInputStream due to its implementation.
606 BufferedInputStream bis = new BufferedInputStream(input);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607 try {
Dan Egnor42471dd2010-01-07 17:25:22 -0800608 long size = file.length();
609 if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
610 if (size > 0 && (max == 0 || size < max)) max = (int) size;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 byte[] data = new byte[max + 1];
Guang Zhu90619812012-10-12 15:50:44 -0700612 int length = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 if (length <= 0) return "";
614 if (length <= max) return new String(data, 0, length);
615 if (ellipsis == null) return new String(data, 0, max);
616 return new String(data, 0, max) + ellipsis;
Dan Egnor42471dd2010-01-07 17:25:22 -0800617 } else if (max < 0) { // "tail" mode: keep the last N
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 int len;
619 boolean rolled = false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700620 byte[] last = null;
621 byte[] data = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800622 do {
623 if (last != null) rolled = true;
624 byte[] tmp = last; last = data; data = tmp;
625 if (data == null) data = new byte[-max];
Guang Zhu90619812012-10-12 15:50:44 -0700626 len = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 } while (len == data.length);
628
629 if (last == null && len <= 0) return "";
630 if (last == null) return new String(data, 0, len);
631 if (len > 0) {
632 rolled = true;
633 System.arraycopy(last, len, last, 0, last.length - len);
634 System.arraycopy(data, 0, last, last.length - len, len);
635 }
636 if (ellipsis == null || !rolled) return new String(last);
637 return ellipsis + new String(last);
Dan Egnor42471dd2010-01-07 17:25:22 -0800638 } else { // "cat" mode: size unknown, read it all in streaming fashion
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 ByteArrayOutputStream contents = new ByteArrayOutputStream();
640 int len;
641 byte[] data = new byte[1024];
642 do {
Guang Zhu90619812012-10-12 15:50:44 -0700643 len = bis.read(data);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800644 if (len > 0) contents.write(data, 0, len);
645 } while (len == data.length);
646 return contents.toString();
647 }
648 } finally {
Guang Zhu90619812012-10-12 15:50:44 -0700649 bis.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 input.close();
651 }
652 }
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400653
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600654 /** {@hide} */
Jeff Sharkey2271ba32016-02-01 17:57:08 -0700655 public static void stringToFile(File file, String string) throws IOException {
656 stringToFile(file.getAbsolutePath(), string);
657 }
658
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600659 /**
Narayan Kamath6d051fc2016-11-22 20:20:00 +0000660 * Writes the bytes given in {@code content} to the file whose absolute path
661 * is {@code filename}.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600662 *
663 * @hide
Narayan Kamath6d051fc2016-11-22 20:20:00 +0000664 */
665 public static void bytesToFile(String filename, byte[] content) throws IOException {
Jeff Sharkey89182982017-11-01 19:02:56 -0600666 if (filename.startsWith("/proc/")) {
667 final int oldMask = StrictMode.allowThreadDiskWritesMask();
668 try (FileOutputStream fos = new FileOutputStream(filename)) {
669 fos.write(content);
670 } finally {
671 StrictMode.setThreadPolicyMask(oldMask);
672 }
673 } else {
674 try (FileOutputStream fos = new FileOutputStream(filename)) {
675 fos.write(content);
676 }
Narayan Kamath6d051fc2016-11-22 20:20:00 +0000677 }
678 }
679
Jeff Sharkey2271ba32016-02-01 17:57:08 -0700680 /**
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400681 * Writes string to file. Basically same as "echo -n $string > $filename"
682 *
683 * @param filename
684 * @param string
685 * @throws IOException
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600686 * @hide
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400687 */
688 public static void stringToFile(String filename, String string) throws IOException {
Jeff Sharkey032c08a2017-01-19 17:05:10 -0700689 bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
Mike Lockwoodda8bb742011-05-28 13:24:04 -0400690 }
Wink Saville1b9a6a62011-06-04 07:31:35 -0700691
Wink Saville6d25a992011-06-03 17:03:51 -0700692 /**
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600693 * Computes the checksum of a file using the CRC32 checksum routine. The
694 * value of the checksum is returned.
Wink Saville6d25a992011-06-03 17:03:51 -0700695 *
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600696 * @param file the file to checksum, must not be null
Wink Saville6d25a992011-06-03 17:03:51 -0700697 * @return the checksum value or an exception is thrown.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600698 * @deprecated this is a weak hashing algorithm, and should not be used due
699 * to its potential for collision.
700 * @hide
Wink Saville6d25a992011-06-03 17:03:51 -0700701 */
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600702 @Deprecated
Wink Saville6d25a992011-06-03 17:03:51 -0700703 public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
704 CRC32 checkSummer = new CRC32();
705 CheckedInputStream cis = null;
706
707 try {
708 cis = new CheckedInputStream( new FileInputStream(file), checkSummer);
709 byte[] buf = new byte[128];
710 while(cis.read(buf) >= 0) {
711 // Just read for checksum to get calculated.
712 }
713 return checkSummer.getValue();
714 } finally {
715 if (cis != null) {
716 try {
717 cis.close();
718 } catch (IOException e) {
719 }
720 }
721 }
722 }
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700723
724 /**
Jeff Sharkey47ad1862018-12-11 12:24:16 -0700725 * Compute the digest of the given file using the requested algorithm.
726 *
727 * @param algorithm Any valid algorithm accepted by
728 * {@link MessageDigest#getInstance(String)}.
729 * @hide
730 */
731 public static byte[] digest(@NonNull File file, @NonNull String algorithm)
732 throws IOException, NoSuchAlgorithmException {
733 try (FileInputStream in = new FileInputStream(file)) {
734 return digest(in, algorithm);
735 }
736 }
737
738 /**
739 * Compute the digest of the given file using the requested algorithm.
740 *
741 * @param algorithm Any valid algorithm accepted by
742 * {@link MessageDigest#getInstance(String)}.
743 * @hide
744 */
745 public static byte[] digest(@NonNull InputStream in, @NonNull String algorithm)
746 throws IOException, NoSuchAlgorithmException {
747 // TODO: implement kernel optimizations
748 return digestInternalUserspace(in, algorithm);
749 }
750
751 /**
752 * Compute the digest of the given file using the requested algorithm.
753 *
754 * @param algorithm Any valid algorithm accepted by
755 * {@link MessageDigest#getInstance(String)}.
756 * @hide
757 */
758 public static byte[] digest(FileDescriptor fd, String algorithm)
759 throws IOException, NoSuchAlgorithmException {
760 // TODO: implement kernel optimizations
761 return digestInternalUserspace(new FileInputStream(fd), algorithm);
762 }
763
764 private static byte[] digestInternalUserspace(InputStream in, String algorithm)
765 throws IOException, NoSuchAlgorithmException {
766 final MessageDigest digest = MessageDigest.getInstance(algorithm);
767 try (DigestInputStream digestStream = new DigestInputStream(in, digest)) {
768 final byte[] buffer = new byte[8192];
769 while (digestStream.read(buffer) != -1) {
770 }
771 }
772 return digest.digest();
773 }
774
775 /**
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700776 * Delete older files in a directory until only those matching the given
777 * constraints remain.
778 *
779 * @param minCount Always keep at least this many files.
Makoto Onukia9dca242017-06-21 17:06:49 -0700780 * @param minAgeMs Always keep files younger than this age, in milliseconds.
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800781 * @return if any files were deleted.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600782 * @hide
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700783 */
Makoto Onukia9dca242017-06-21 17:06:49 -0700784 public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) {
785 if (minCount < 0 || minAgeMs < 0) {
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700786 throw new IllegalArgumentException("Constraints must be positive or 0");
787 }
788
789 final File[] files = dir.listFiles();
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800790 if (files == null) return false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700791
792 // Sort with newest files first
793 Arrays.sort(files, new Comparator<File>() {
794 @Override
795 public int compare(File lhs, File rhs) {
Ian Rogers660e6de2016-05-17 11:50:54 -0700796 return Long.compare(rhs.lastModified(), lhs.lastModified());
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700797 }
798 });
799
800 // Keep at least minCount files
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800801 boolean deleted = false;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700802 for (int i = minCount; i < files.length; i++) {
803 final File file = files[i];
804
Makoto Onukia9dca242017-06-21 17:06:49 -0700805 // Keep files newer than minAgeMs
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700806 final long age = System.currentTimeMillis() - file.lastModified();
Makoto Onukia9dca242017-06-21 17:06:49 -0700807 if (age > minAgeMs) {
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800808 if (file.delete()) {
809 Log.d(TAG, "Deleted old file " + file);
810 deleted = true;
811 }
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700812 }
813 }
Jeff Sharkeyebf8ad52014-01-30 15:01:22 -0800814 return deleted;
Jeff Sharkeyd9526902013-03-14 14:11:57 -0700815 }
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800816
817 /**
818 * Test if a file lives under the given directory, either as a direct child
819 * or a distant grandchild.
820 * <p>
821 * Both files <em>must</em> have been resolved using
822 * {@link File#getCanonicalFile()} to avoid symlink or path traversal
823 * attacks.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600824 *
825 * @hide
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800826 */
Jeff Sharkey48877892015-03-18 11:27:19 -0700827 public static boolean contains(File[] dirs, File file) {
828 for (File dir : dirs) {
829 if (contains(dir, file)) {
830 return true;
831 }
832 }
833 return false;
834 }
835
Jeff Sharkey3f64ec52019-01-22 13:19:40 -0700836 /** {@hide} */
837 public static boolean contains(Collection<File> dirs, File file) {
838 for (File dir : dirs) {
839 if (contains(dir, file)) {
840 return true;
841 }
842 }
843 return false;
844 }
845
Jeff Sharkey48877892015-03-18 11:27:19 -0700846 /**
847 * Test if a file lives under the given directory, either as a direct child
848 * or a distant grandchild.
849 * <p>
850 * Both files <em>must</em> have been resolved using
851 * {@link File#getCanonicalFile()} to avoid symlink or path traversal
852 * attacks.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600853 *
854 * @hide
Jeff Sharkey48877892015-03-18 11:27:19 -0700855 */
Jeff Sharkeycb269aac2019-01-25 11:15:38 -0700856 @TestApi
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800857 public static boolean contains(File dir, File file) {
Jeff Sharkey50a05452015-04-29 11:24:52 -0700858 if (dir == null || file == null) return false;
Jeff Sharkeyd5d5e922017-02-21 10:51:23 -0700859 return contains(dir.getAbsolutePath(), file.getAbsolutePath());
860 }
Jeff Sharkeyd7460572014-07-06 20:44:55 -0700861
Alan Stokes2a7f8e62019-01-18 17:07:07 +0000862 /**
863 * Test if a file lives under the given directory, either as a direct child
864 * or a distant grandchild.
865 * <p>
866 * Both files <em>must</em> have been resolved using
867 * {@link File#getCanonicalFile()} to avoid symlink or path traversal
868 * attacks.
869 *
870 * @hide
871 */
Jeff Sharkeyd5d5e922017-02-21 10:51:23 -0700872 public static boolean contains(String dirPath, String filePath) {
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800873 if (dirPath.equals(filePath)) {
874 return true;
875 }
Jeff Sharkey4ca728c2014-01-10 16:27:19 -0800876 if (!dirPath.endsWith("/")) {
877 dirPath += "/";
878 }
879 return filePath.startsWith(dirPath);
880 }
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700881
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600882 /** {@hide} */
Jeff Sharkeyfcf1e552016-04-14 20:44:58 -0600883 public static boolean deleteContentsAndDir(File dir) {
884 if (deleteContents(dir)) {
885 return dir.delete();
886 } else {
887 return false;
888 }
889 }
890
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600891 /** {@hide} */
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700892 public static boolean deleteContents(File dir) {
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700893 File[] files = dir.listFiles();
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700894 boolean success = true;
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700895 if (files != null) {
896 for (File file : files) {
897 if (file.isDirectory()) {
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700898 success &= deleteContents(file);
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700899 }
Jeff Sharkey73767b92014-07-04 20:18:13 -0700900 if (!file.delete()) {
901 Log.w(TAG, "Failed to delete " + file);
902 success = false;
903 }
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700904 }
905 }
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -0700906 return success;
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700907 }
908
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800909 private static boolean isValidExtFilenameChar(char c) {
910 switch (c) {
911 case '\0':
912 case '/':
913 return false;
914 default:
915 return true;
916 }
917 }
918
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700919 /**
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800920 * Check if given filename is valid for an ext4 filesystem.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600921 *
922 * @hide
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700923 */
924 public static boolean isValidExtFilename(String name) {
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800925 return (name != null) && name.equals(buildValidExtFilename(name));
926 }
927
928 /**
929 * Mutate the given filename to make it valid for an ext4 filesystem,
930 * replacing any invalid characters with "_".
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600931 *
932 * @hide
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800933 */
934 public static String buildValidExtFilename(String name) {
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700935 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800936 return "(invalid)";
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700937 }
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800938 final StringBuilder res = new StringBuilder(name.length());
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700939 for (int i = 0; i < name.length(); i++) {
940 final char c = name.charAt(i);
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800941 if (isValidExtFilenameChar(c)) {
942 res.append(c);
943 } else {
944 res.append('_');
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -0700945 }
946 }
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -0700947 trimFilename(res, 255);
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800948 return res.toString();
949 }
950
951 private static boolean isValidFatFilenameChar(char c) {
952 if ((0x00 <= c && c <= 0x1f)) {
953 return false;
954 }
955 switch (c) {
956 case '"':
957 case '*':
958 case '/':
959 case ':':
960 case '<':
961 case '>':
962 case '?':
963 case '\\':
964 case '|':
965 case 0x7F:
966 return false;
967 default:
968 return true;
969 }
970 }
971
972 /**
973 * Check if given filename is valid for a FAT filesystem.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600974 *
975 * @hide
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800976 */
977 public static boolean isValidFatFilename(String name) {
978 return (name != null) && name.equals(buildValidFatFilename(name));
979 }
980
981 /**
982 * Mutate the given filename to make it valid for a FAT filesystem,
983 * replacing any invalid characters with "_".
Jeff Sharkey5aae0c92018-07-09 16:38:20 -0600984 *
985 * @hide
Jeff Sharkey0cce5352014-11-26 13:38:26 -0800986 */
987 public static String buildValidFatFilename(String name) {
988 if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
989 return "(invalid)";
990 }
991 final StringBuilder res = new StringBuilder(name.length());
992 for (int i = 0; i < name.length(); i++) {
993 final char c = name.charAt(i);
994 if (isValidFatFilenameChar(c)) {
995 res.append(c);
996 } else {
997 res.append('_');
998 }
999 }
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -07001000 // Even though vfat allows 255 UCS-2 chars, we might eventually write to
1001 // ext4 through a FUSE layer, so use that limit.
1002 trimFilename(res, 255);
Jeff Sharkey0cce5352014-11-26 13:38:26 -08001003 return res.toString();
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -07001004 }
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001005
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001006 /** {@hide} */
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -07001007 @VisibleForTesting
1008 public static String trimFilename(String str, int maxBytes) {
1009 final StringBuilder res = new StringBuilder(str);
1010 trimFilename(res, maxBytes);
1011 return res.toString();
1012 }
1013
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001014 /** {@hide} */
Jeff Sharkey4f5e8b32015-06-11 19:13:37 -07001015 private static void trimFilename(StringBuilder res, int maxBytes) {
1016 byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
1017 if (raw.length > maxBytes) {
1018 maxBytes -= 3;
1019 while (raw.length > maxBytes) {
1020 res.deleteCharAt(res.length() / 2);
1021 raw = res.toString().getBytes(StandardCharsets.UTF_8);
1022 }
1023 res.insert(res.length() / 2, "...");
1024 }
1025 }
1026
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001027 /** {@hide} */
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001028 public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
Jeff Sharkeyd7460572014-07-06 20:44:55 -07001029 if (path == null) return null;
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001030 final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
1031 return (result != null) ? result.getAbsolutePath() : null;
1032 }
1033
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001034 /** {@hide} */
Jeff Sharkeyd7460572014-07-06 20:44:55 -07001035 public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
1036 if (paths == null) return null;
1037 final String[] result = new String[paths.length];
1038 for (int i = 0; i < paths.length; i++) {
1039 result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]);
1040 }
1041 return result;
1042 }
1043
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001044 /**
1045 * Given a path under the "before" directory, rewrite it to live under the
1046 * "after" directory. For example, {@code /before/foo/bar.txt} would become
1047 * {@code /after/foo/bar.txt}.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001048 *
1049 * @hide
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001050 */
1051 public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
Jeff Sharkey41be35d2015-05-13 12:38:16 -07001052 if (file == null || beforeDir == null || afterDir == null) return null;
Jeff Sharkey57dcf5b2014-06-18 17:46:05 -07001053 if (contains(beforeDir, file)) {
1054 final String splice = file.getAbsolutePath().substring(
1055 beforeDir.getAbsolutePath().length());
1056 return new File(afterDir, splice);
1057 }
1058 return null;
1059 }
Ben Kwa62539a22015-04-22 15:43:17 -07001060
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001061 /** {@hide} */
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001062 private static File buildUniqueFileWithExtension(File parent, String name, String ext)
1063 throws FileNotFoundException {
1064 File file = buildFile(parent, name, ext);
1065
1066 // If conflicting file, try adding counter suffix
1067 int n = 0;
1068 while (file.exists()) {
1069 if (n++ >= 32) {
1070 throw new FileNotFoundException("Failed to create unique file");
1071 }
1072 file = buildFile(parent, name + " (" + n + ")", ext);
1073 }
1074
1075 return file;
1076 }
1077
Ben Kwa62539a22015-04-22 15:43:17 -07001078 /**
1079 * Generates a unique file name under the given parent directory. If the display name doesn't
1080 * have an extension that matches the requested MIME type, the default extension for that MIME
1081 * type is appended. If a file already exists, the name is appended with a numerical value to
1082 * make it unique.
1083 *
1084 * For example, the display name 'example' with 'text/plain' MIME might produce
1085 * 'example.txt' or 'example (1).txt', etc.
1086 *
1087 * @throws FileNotFoundException
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001088 * @hide
Ben Kwa62539a22015-04-22 15:43:17 -07001089 */
1090 public static File buildUniqueFile(File parent, String mimeType, String displayName)
1091 throws FileNotFoundException {
Daichi Hironofc7fb752016-03-15 19:19:31 +09001092 final String[] parts = splitFileName(mimeType, displayName);
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001093 return buildUniqueFileWithExtension(parent, parts[0], parts[1]);
1094 }
Daichi Hironofc7fb752016-03-15 19:19:31 +09001095
Jeff Sharkey88050332019-02-11 11:07:09 -07001096 /** {@hide} */
1097 public static File buildNonUniqueFile(File parent, String mimeType, String displayName) {
1098 final String[] parts = splitFileName(mimeType, displayName);
1099 return buildFile(parent, parts[0], parts[1]);
1100 }
1101
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001102 /**
1103 * Generates a unique file name under the given parent directory, keeping
1104 * any extension intact.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001105 *
1106 * @hide
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001107 */
1108 public static File buildUniqueFile(File parent, String displayName)
1109 throws FileNotFoundException {
1110 final String name;
1111 final String ext;
1112
1113 // Extract requested extension from display name
1114 final int lastDot = displayName.lastIndexOf('.');
1115 if (lastDot >= 0) {
1116 name = displayName.substring(0, lastDot);
1117 ext = displayName.substring(lastDot + 1);
1118 } else {
1119 name = displayName;
1120 ext = null;
Daichi Hironofc7fb752016-03-15 19:19:31 +09001121 }
1122
Jeff Sharkeyaa444762016-09-20 18:54:46 -06001123 return buildUniqueFileWithExtension(parent, name, ext);
Daichi Hironofc7fb752016-03-15 19:19:31 +09001124 }
1125
1126 /**
1127 * Splits file name into base name and extension.
1128 * If the display name doesn't have an extension that matches the requested MIME type, the
1129 * extension is regarded as a part of filename and default extension for that MIME type is
1130 * appended.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001131 *
1132 * @hide
Daichi Hironofc7fb752016-03-15 19:19:31 +09001133 */
1134 public static String[] splitFileName(String mimeType, String displayName) {
Ben Kwa62539a22015-04-22 15:43:17 -07001135 String name;
1136 String ext;
1137
1138 if (Document.MIME_TYPE_DIR.equals(mimeType)) {
1139 name = displayName;
1140 ext = null;
1141 } else {
1142 String mimeTypeFromExt;
1143
1144 // Extract requested extension from display name
1145 final int lastDot = displayName.lastIndexOf('.');
1146 if (lastDot >= 0) {
1147 name = displayName.substring(0, lastDot);
1148 ext = displayName.substring(lastDot + 1);
1149 mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
1150 ext.toLowerCase());
1151 } else {
1152 name = displayName;
1153 ext = null;
1154 mimeTypeFromExt = null;
1155 }
1156
1157 if (mimeTypeFromExt == null) {
Jeff Sharkey91e3cd42018-08-27 18:03:33 -06001158 mimeTypeFromExt = ContentResolver.MIME_TYPE_DEFAULT;
Ben Kwa62539a22015-04-22 15:43:17 -07001159 }
1160
Jeff Sharkey63280e02018-09-12 11:47:07 -06001161 final String extFromMimeType;
1162 if (ContentResolver.MIME_TYPE_DEFAULT.equals(mimeType)) {
1163 extFromMimeType = null;
1164 } else {
1165 extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
1166 }
1167
Ben Kwa62539a22015-04-22 15:43:17 -07001168 if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) {
1169 // Extension maps back to requested MIME type; allow it
1170 } else {
1171 // No match; insist that create file matches requested MIME
1172 name = displayName;
1173 ext = extFromMimeType;
1174 }
1175 }
1176
Daichi Hironofc7fb752016-03-15 19:19:31 +09001177 if (ext == null) {
1178 ext = "";
Ben Kwa62539a22015-04-22 15:43:17 -07001179 }
1180
Daichi Hironofc7fb752016-03-15 19:19:31 +09001181 return new String[] { name, ext };
Ben Kwa62539a22015-04-22 15:43:17 -07001182 }
1183
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001184 /** {@hide} */
Ben Kwa62539a22015-04-22 15:43:17 -07001185 private static File buildFile(File parent, String name, String ext) {
1186 if (TextUtils.isEmpty(ext)) {
1187 return new File(parent, name);
1188 } else {
1189 return new File(parent, name + "." + ext);
1190 }
1191 }
Jeff Sharkey85ced632015-07-22 11:11:46 -07001192
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001193 /** {@hide} */
Jeff Sharkeyc4bab982016-02-01 10:16:01 -07001194 public static @NonNull String[] listOrEmpty(@Nullable File dir) {
Jeff Sharkeyb0c363b22018-12-15 11:53:03 -07001195 return (dir != null) ? ArrayUtils.defeatNullable(dir.list())
1196 : EmptyArray.STRING;
Jeff Sharkeyc4bab982016-02-01 10:16:01 -07001197 }
1198
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001199 /** {@hide} */
Jeff Sharkeyc4bab982016-02-01 10:16:01 -07001200 public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
Jeff Sharkeyb0c363b22018-12-15 11:53:03 -07001201 return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
1202 : ArrayUtils.EMPTY_FILE;
Jeff Sharkey85ced632015-07-22 11:11:46 -07001203 }
Jeff Sharkey15447792015-11-05 16:18:51 -08001204
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001205 /** {@hide} */
Jeff Sharkeyc4bab982016-02-01 10:16:01 -07001206 public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
Jeff Sharkeyb0c363b22018-12-15 11:53:03 -07001207 return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter))
1208 : ArrayUtils.EMPTY_FILE;
Jeff Sharkey35871f22016-01-29 17:13:29 -07001209 }
1210
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001211 /** {@hide} */
Jeff Sharkey15447792015-11-05 16:18:51 -08001212 public static @Nullable File newFileOrNull(@Nullable String path) {
1213 return (path != null) ? new File(path) : null;
1214 }
Narayan Kamath5c50e862016-11-24 13:22:40 +00001215
1216 /**
1217 * Creates a directory with name {@code name} under an existing directory {@code baseDir}.
1218 * Returns a {@code File} object representing the directory on success, {@code null} on
1219 * failure.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001220 *
1221 * @hide
Narayan Kamath5c50e862016-11-24 13:22:40 +00001222 */
1223 public static @Nullable File createDir(File baseDir, String name) {
1224 final File dir = new File(baseDir, name);
1225
Sudheer Shankabe0febe2018-11-07 18:24:37 -08001226 return createDir(dir) ? dir : null;
1227 }
1228
1229 /** @hide */
1230 public static boolean createDir(File dir) {
Narayan Kamath5c50e862016-11-24 13:22:40 +00001231 if (dir.exists()) {
Sudheer Shankabe0febe2018-11-07 18:24:37 -08001232 return dir.isDirectory();
Narayan Kamath5c50e862016-11-24 13:22:40 +00001233 }
1234
Sudheer Shankabe0febe2018-11-07 18:24:37 -08001235 return dir.mkdir();
Narayan Kamath5c50e862016-11-24 13:22:40 +00001236 }
Jeff Sharkey373d0172017-02-22 15:47:27 -07001237
1238 /**
1239 * Round the given size of a storage device to a nice round power-of-two
1240 * value, such as 256MB or 32GB. This avoids showing weird values like
1241 * "29.5GB" in UI.
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001242 *
1243 * @hide
Jeff Sharkey373d0172017-02-22 15:47:27 -07001244 */
1245 public static long roundStorageSize(long size) {
Jeff Sharkey09734df2017-03-07 20:50:27 -07001246 long val = 1;
1247 long pow = 1;
1248 while ((val * pow) < size) {
1249 val <<= 1;
1250 if (val > 512) {
1251 val = 1;
1252 pow *= 1000;
1253 }
Jeff Sharkey373d0172017-02-22 15:47:27 -07001254 }
Jeff Sharkey09734df2017-03-07 20:50:27 -07001255 return val * pow;
Jeff Sharkey373d0172017-02-22 15:47:27 -07001256 }
Jeff Sharkeyb18f8992018-01-31 21:47:09 -07001257
Jeff Sharkey5aae0c92018-07-09 16:38:20 -06001258 /**
1259 * Closes the given object quietly, ignoring any checked exceptions. Does
1260 * nothing if the given object is {@code null}.
1261 */
1262 public static void closeQuietly(@Nullable AutoCloseable closeable) {
1263 IoUtils.closeQuietly(closeable);
1264 }
1265
1266 /**
1267 * Closes the given object quietly, ignoring any checked exceptions. Does
1268 * nothing if the given object is {@code null}.
1269 */
1270 public static void closeQuietly(@Nullable FileDescriptor fd) {
1271 IoUtils.closeQuietly(fd);
1272 }
1273
1274 /** {@hide} */
Jeff Sharkey63280e02018-09-12 11:47:07 -06001275 public static int translateModeStringToPosix(String mode) {
Jeff Sharkey0c914512018-10-17 18:32:25 -06001276 // Sanity check for invalid chars
1277 for (int i = 0; i < mode.length(); i++) {
1278 switch (mode.charAt(i)) {
1279 case 'r':
1280 case 'w':
1281 case 't':
1282 case 'a':
1283 break;
1284 default:
1285 throw new IllegalArgumentException("Bad mode: " + mode);
1286 }
1287 }
1288
Jeff Sharkey63280e02018-09-12 11:47:07 -06001289 int res = 0;
1290 if (mode.startsWith("rw")) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001291 res = O_RDWR | O_CREAT;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001292 } else if (mode.startsWith("w")) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001293 res = O_WRONLY | O_CREAT;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001294 } else if (mode.startsWith("r")) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001295 res = O_RDONLY;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001296 } else {
1297 throw new IllegalArgumentException("Bad mode: " + mode);
1298 }
1299 if (mode.indexOf('t') != -1) {
1300 res |= O_TRUNC;
1301 }
1302 if (mode.indexOf('a') != -1) {
1303 res |= O_APPEND;
1304 }
1305 return res;
1306 }
1307
1308 /** {@hide} */
1309 public static String translateModePosixToString(int mode) {
1310 String res = "";
Nick Kralevich52671a72018-12-17 12:46:08 -08001311 if ((mode & O_ACCMODE) == O_RDWR) {
1312 res = "rw";
1313 } else if ((mode & O_ACCMODE) == O_WRONLY) {
1314 res = "w";
1315 } else if ((mode & O_ACCMODE) == O_RDONLY) {
1316 res = "r";
Jeff Sharkey63280e02018-09-12 11:47:07 -06001317 } else {
1318 throw new IllegalArgumentException("Bad mode: " + mode);
1319 }
1320 if ((mode & O_TRUNC) == O_TRUNC) {
1321 res += "t";
1322 }
1323 if ((mode & O_APPEND) == O_APPEND) {
1324 res += "a";
1325 }
1326 return res;
1327 }
1328
1329 /** {@hide} */
1330 public static int translateModePosixToPfd(int mode) {
1331 int res = 0;
Nick Kralevich52671a72018-12-17 12:46:08 -08001332 if ((mode & O_ACCMODE) == O_RDWR) {
1333 res = MODE_READ_WRITE;
1334 } else if ((mode & O_ACCMODE) == O_WRONLY) {
1335 res = MODE_WRITE_ONLY;
1336 } else if ((mode & O_ACCMODE) == O_RDONLY) {
1337 res = MODE_READ_ONLY;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001338 } else {
1339 throw new IllegalArgumentException("Bad mode: " + mode);
1340 }
1341 if ((mode & O_CREAT) == O_CREAT) {
1342 res |= MODE_CREATE;
1343 }
1344 if ((mode & O_TRUNC) == O_TRUNC) {
1345 res |= MODE_TRUNCATE;
1346 }
1347 if ((mode & O_APPEND) == O_APPEND) {
1348 res |= MODE_APPEND;
1349 }
1350 return res;
1351 }
1352
1353 /** {@hide} */
1354 public static int translateModePfdToPosix(int mode) {
1355 int res = 0;
1356 if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001357 res = O_RDWR;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001358 } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001359 res = O_WRONLY;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001360 } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) {
Nick Kralevich52671a72018-12-17 12:46:08 -08001361 res = O_RDONLY;
Jeff Sharkey63280e02018-09-12 11:47:07 -06001362 } else {
1363 throw new IllegalArgumentException("Bad mode: " + mode);
1364 }
1365 if ((mode & MODE_CREATE) == MODE_CREATE) {
1366 res |= O_CREAT;
1367 }
1368 if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) {
1369 res |= O_TRUNC;
1370 }
1371 if ((mode & MODE_APPEND) == MODE_APPEND) {
1372 res |= O_APPEND;
1373 }
1374 return res;
1375 }
1376
1377 /** {@hide} */
Jeff Sharkeybc2ae002018-07-31 10:45:37 -06001378 public static int translateModeAccessToPosix(int mode) {
1379 if (mode == F_OK) {
1380 // There's not an exact mapping, so we attempt a read-only open to
1381 // determine if a file exists
1382 return O_RDONLY;
1383 } else if ((mode & (R_OK | W_OK)) == (R_OK | W_OK)) {
1384 return O_RDWR;
1385 } else if ((mode & R_OK) == R_OK) {
1386 return O_RDONLY;
1387 } else if ((mode & W_OK) == W_OK) {
1388 return O_WRONLY;
1389 } else {
1390 throw new IllegalArgumentException("Bad mode: " + mode);
1391 }
1392 }
1393
1394 /** {@hide} */
Jeff Sharkeyb18f8992018-01-31 21:47:09 -07001395 @VisibleForTesting
1396 public static class MemoryPipe extends Thread implements AutoCloseable {
1397 private final FileDescriptor[] pipe;
1398 private final byte[] data;
1399 private final boolean sink;
1400
1401 private MemoryPipe(byte[] data, boolean sink) throws IOException {
1402 try {
1403 this.pipe = Os.pipe();
1404 } catch (ErrnoException e) {
1405 throw e.rethrowAsIOException();
1406 }
1407 this.data = data;
1408 this.sink = sink;
1409 }
1410
1411 private MemoryPipe startInternal() {
1412 super.start();
1413 return this;
1414 }
1415
1416 public static MemoryPipe createSource(byte[] data) throws IOException {
1417 return new MemoryPipe(data, false).startInternal();
1418 }
1419
1420 public static MemoryPipe createSink(byte[] data) throws IOException {
1421 return new MemoryPipe(data, true).startInternal();
1422 }
1423
1424 public FileDescriptor getFD() {
1425 return sink ? pipe[1] : pipe[0];
1426 }
1427
1428 public FileDescriptor getInternalFD() {
1429 return sink ? pipe[0] : pipe[1];
1430 }
1431
1432 @Override
1433 public void run() {
1434 final FileDescriptor fd = getInternalFD();
1435 try {
1436 int i = 0;
1437 while (i < data.length) {
1438 if (sink) {
1439 i += Os.read(fd, data, i, data.length - i);
1440 } else {
1441 i += Os.write(fd, data, i, data.length - i);
1442 }
1443 }
1444 } catch (IOException | ErrnoException e) {
Jeff Sharkey45c97df2018-02-01 16:01:52 -07001445 // Ignored
Jeff Sharkeyb18f8992018-01-31 21:47:09 -07001446 } finally {
1447 if (sink) {
1448 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
1449 }
1450 IoUtils.closeQuietly(fd);
1451 }
1452 }
1453
1454 @Override
1455 public void close() throws Exception {
1456 IoUtils.closeQuietly(getFD());
1457 }
1458 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001459}