|
|
|
|
@@ -53,32 +53,35 @@ import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.Comparator;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
import java.util.concurrent.Executor;
|
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
import java.util.zip.CRC32;
|
|
|
|
|
import java.util.zip.CheckedInputStream;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tools for managing files. Not for public consumption.
|
|
|
|
|
* @hide
|
|
|
|
|
* Utility methods useful for working with files.
|
|
|
|
|
*/
|
|
|
|
|
public class FileUtils {
|
|
|
|
|
private static final String TAG = "FileUtils";
|
|
|
|
|
|
|
|
|
|
public static final int S_IRWXU = 00700;
|
|
|
|
|
public static final int S_IRUSR = 00400;
|
|
|
|
|
public static final int S_IWUSR = 00200;
|
|
|
|
|
public static final int S_IXUSR = 00100;
|
|
|
|
|
/** {@hide} */ public static final int S_IRWXU = 00700;
|
|
|
|
|
/** {@hide} */ public static final int S_IRUSR = 00400;
|
|
|
|
|
/** {@hide} */ public static final int S_IWUSR = 00200;
|
|
|
|
|
/** {@hide} */ public static final int S_IXUSR = 00100;
|
|
|
|
|
|
|
|
|
|
public static final int S_IRWXG = 00070;
|
|
|
|
|
public static final int S_IRGRP = 00040;
|
|
|
|
|
public static final int S_IWGRP = 00020;
|
|
|
|
|
public static final int S_IXGRP = 00010;
|
|
|
|
|
/** {@hide} */ public static final int S_IRWXG = 00070;
|
|
|
|
|
/** {@hide} */ public static final int S_IRGRP = 00040;
|
|
|
|
|
/** {@hide} */ public static final int S_IWGRP = 00020;
|
|
|
|
|
/** {@hide} */ public static final int S_IXGRP = 00010;
|
|
|
|
|
|
|
|
|
|
public static final int S_IRWXO = 00007;
|
|
|
|
|
public static final int S_IROTH = 00004;
|
|
|
|
|
public static final int S_IWOTH = 00002;
|
|
|
|
|
public static final int S_IXOTH = 00001;
|
|
|
|
|
/** {@hide} */ public static final int S_IRWXO = 00007;
|
|
|
|
|
/** {@hide} */ public static final int S_IROTH = 00004;
|
|
|
|
|
/** {@hide} */ public static final int S_IWOTH = 00002;
|
|
|
|
|
/** {@hide} */ public static final int S_IXOTH = 00001;
|
|
|
|
|
|
|
|
|
|
private FileUtils() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Regular expression for safe filenames: no spaces or metacharacters.
|
|
|
|
|
*
|
|
|
|
|
@@ -94,6 +97,9 @@ public class FileUtils {
|
|
|
|
|
|
|
|
|
|
private static final long COPY_CHECKPOINT_BYTES = 524288;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Listener that is called periodically as progress is made.
|
|
|
|
|
*/
|
|
|
|
|
public interface ProgressListener {
|
|
|
|
|
public void onProgress(long progress);
|
|
|
|
|
}
|
|
|
|
|
@@ -105,6 +111,7 @@ public class FileUtils {
|
|
|
|
|
* @param uid to apply through {@code chown}, or -1 to leave unchanged
|
|
|
|
|
* @param gid to apply through {@code chown}, or -1 to leave unchanged
|
|
|
|
|
* @return 0 on success, otherwise errno.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static int setPermissions(File path, int mode, int uid, int gid) {
|
|
|
|
|
return setPermissions(path.getAbsolutePath(), mode, uid, gid);
|
|
|
|
|
@@ -117,6 +124,7 @@ public class FileUtils {
|
|
|
|
|
* @param uid to apply through {@code chown}, or -1 to leave unchanged
|
|
|
|
|
* @param gid to apply through {@code chown}, or -1 to leave unchanged
|
|
|
|
|
* @return 0 on success, otherwise errno.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static int setPermissions(String path, int mode, int uid, int gid) {
|
|
|
|
|
try {
|
|
|
|
|
@@ -145,6 +153,7 @@ public class FileUtils {
|
|
|
|
|
* @param uid to apply through {@code chown}, or -1 to leave unchanged
|
|
|
|
|
* @param gid to apply through {@code chown}, or -1 to leave unchanged
|
|
|
|
|
* @return 0 on success, otherwise errno.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
|
|
|
|
|
try {
|
|
|
|
|
@@ -166,7 +175,14 @@ public class FileUtils {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void copyPermissions(File from, File to) throws IOException {
|
|
|
|
|
/**
|
|
|
|
|
* Copy the owner UID, owner GID, and mode bits from one file to another.
|
|
|
|
|
*
|
|
|
|
|
* @param from File where attributes should be copied from.
|
|
|
|
|
* @param to File where attributes should be copied to.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
|
|
|
|
|
try {
|
|
|
|
|
final StructStat stat = Os.stat(from.getAbsolutePath());
|
|
|
|
|
Os.chmod(to.getAbsolutePath(), stat.st_mode);
|
|
|
|
|
@@ -177,8 +193,10 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return owning UID of given path, otherwise -1.
|
|
|
|
|
* @deprecated use {@link Os#stat(String)} instead.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public static int getUid(String path) {
|
|
|
|
|
try {
|
|
|
|
|
return Os.stat(path).st_uid;
|
|
|
|
|
@@ -190,6 +208,8 @@ public class FileUtils {
|
|
|
|
|
/**
|
|
|
|
|
* Perform an fsync on the given FileOutputStream. The stream at this
|
|
|
|
|
* point must be flushed but not yet closed.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static boolean sync(FileOutputStream stream) {
|
|
|
|
|
try {
|
|
|
|
|
@@ -204,6 +224,7 @@ public class FileUtils {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @deprecated use {@link #copy(File, File)} instead.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public static boolean copyFile(File srcFile, File destFile) {
|
|
|
|
|
@@ -217,6 +238,7 @@ public class FileUtils {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @deprecated use {@link #copy(File, File)} instead.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
|
|
|
|
|
@@ -227,6 +249,7 @@ public class FileUtils {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @deprecated use {@link #copy(InputStream, OutputStream)} instead.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public static boolean copyToFile(InputStream inputStream, File destFile) {
|
|
|
|
|
@@ -240,6 +263,7 @@ public class FileUtils {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @deprecated use {@link #copy(InputStream, OutputStream)} instead.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException {
|
|
|
|
|
@@ -265,7 +289,7 @@ public class FileUtils {
|
|
|
|
|
* @return number of bytes copied.
|
|
|
|
|
*/
|
|
|
|
|
public static long copy(@NonNull File from, @NonNull File to) throws IOException {
|
|
|
|
|
return copy(from, to, null, null);
|
|
|
|
|
return copy(from, to, null, null, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -274,16 +298,17 @@ public class FileUtils {
|
|
|
|
|
* Attempts to use several optimization strategies to copy the data in the
|
|
|
|
|
* kernel before falling back to a userspace copy as a last resort.
|
|
|
|
|
*
|
|
|
|
|
* @param listener to be periodically notified as the copy progresses.
|
|
|
|
|
* @param signal to signal if the copy should be cancelled early.
|
|
|
|
|
* @param executor that listener events should be delivered via.
|
|
|
|
|
* @param listener to be periodically notified as the copy progresses.
|
|
|
|
|
* @return number of bytes copied.
|
|
|
|
|
*/
|
|
|
|
|
public static long copy(@NonNull File from, @NonNull File to,
|
|
|
|
|
@Nullable ProgressListener listener, @Nullable CancellationSignal signal)
|
|
|
|
|
throws IOException {
|
|
|
|
|
@Nullable CancellationSignal signal, @Nullable Executor executor,
|
|
|
|
|
@Nullable ProgressListener listener) throws IOException {
|
|
|
|
|
try (FileInputStream in = new FileInputStream(from);
|
|
|
|
|
FileOutputStream out = new FileOutputStream(to)) {
|
|
|
|
|
return copy(in, out, listener, signal);
|
|
|
|
|
return copy(in, out, signal, executor, listener);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -296,7 +321,7 @@ public class FileUtils {
|
|
|
|
|
* @return number of bytes copied.
|
|
|
|
|
*/
|
|
|
|
|
public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
|
|
|
|
|
return copy(in, out, null, null);
|
|
|
|
|
return copy(in, out, null, null, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -305,22 +330,23 @@ public class FileUtils {
|
|
|
|
|
* Attempts to use several optimization strategies to copy the data in the
|
|
|
|
|
* kernel before falling back to a userspace copy as a last resort.
|
|
|
|
|
*
|
|
|
|
|
* @param listener to be periodically notified as the copy progresses.
|
|
|
|
|
* @param signal to signal if the copy should be cancelled early.
|
|
|
|
|
* @param executor that listener events should be delivered via.
|
|
|
|
|
* @param listener to be periodically notified as the copy progresses.
|
|
|
|
|
* @return number of bytes copied.
|
|
|
|
|
*/
|
|
|
|
|
public static long copy(@NonNull InputStream in, @NonNull OutputStream out,
|
|
|
|
|
@Nullable ProgressListener listener, @Nullable CancellationSignal signal)
|
|
|
|
|
throws IOException {
|
|
|
|
|
@Nullable CancellationSignal signal, @Nullable Executor executor,
|
|
|
|
|
@Nullable ProgressListener listener) throws IOException {
|
|
|
|
|
if (ENABLE_COPY_OPTIMIZATIONS) {
|
|
|
|
|
if (in instanceof FileInputStream && out instanceof FileOutputStream) {
|
|
|
|
|
return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(),
|
|
|
|
|
listener, signal);
|
|
|
|
|
signal, executor, listener);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Worse case fallback to userspace
|
|
|
|
|
return copyInternalUserspace(in, out, listener, signal);
|
|
|
|
|
return copyInternalUserspace(in, out, signal, executor, listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -333,7 +359,7 @@ public class FileUtils {
|
|
|
|
|
*/
|
|
|
|
|
public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out)
|
|
|
|
|
throws IOException {
|
|
|
|
|
return copy(in, out, null, null);
|
|
|
|
|
return copy(in, out, null, null, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -342,14 +368,15 @@ public class FileUtils {
|
|
|
|
|
* Attempts to use several optimization strategies to copy the data in the
|
|
|
|
|
* kernel before falling back to a userspace copy as a last resort.
|
|
|
|
|
*
|
|
|
|
|
* @param listener to be periodically notified as the copy progresses.
|
|
|
|
|
* @param signal to signal if the copy should be cancelled early.
|
|
|
|
|
* @param executor that listener events should be delivered via.
|
|
|
|
|
* @param listener to be periodically notified as the copy progresses.
|
|
|
|
|
* @return number of bytes copied.
|
|
|
|
|
*/
|
|
|
|
|
public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
|
|
|
|
|
@Nullable ProgressListener listener, @Nullable CancellationSignal signal)
|
|
|
|
|
throws IOException {
|
|
|
|
|
return copy(in, out, listener, signal, Long.MAX_VALUE);
|
|
|
|
|
@Nullable CancellationSignal signal, @Nullable Executor executor,
|
|
|
|
|
@Nullable ProgressListener listener) throws IOException {
|
|
|
|
|
return copy(in, out, Long.MAX_VALUE, signal, executor, listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -358,22 +385,24 @@ public class FileUtils {
|
|
|
|
|
* Attempts to use several optimization strategies to copy the data in the
|
|
|
|
|
* kernel before falling back to a userspace copy as a last resort.
|
|
|
|
|
*
|
|
|
|
|
* @param listener to be periodically notified as the copy progresses.
|
|
|
|
|
* @param signal to signal if the copy should be cancelled early.
|
|
|
|
|
* @param count the number of bytes to copy.
|
|
|
|
|
* @param signal to signal if the copy should be cancelled early.
|
|
|
|
|
* @param executor that listener events should be delivered via.
|
|
|
|
|
* @param listener to be periodically notified as the copy progresses.
|
|
|
|
|
* @return number of bytes copied.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
|
|
|
|
|
@Nullable ProgressListener listener, @Nullable CancellationSignal signal, long count)
|
|
|
|
|
throws IOException {
|
|
|
|
|
public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count,
|
|
|
|
|
@Nullable CancellationSignal signal, @Nullable Executor executor,
|
|
|
|
|
@Nullable ProgressListener listener) throws IOException {
|
|
|
|
|
if (ENABLE_COPY_OPTIMIZATIONS) {
|
|
|
|
|
try {
|
|
|
|
|
final StructStat st_in = Os.fstat(in);
|
|
|
|
|
final StructStat st_out = Os.fstat(out);
|
|
|
|
|
if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) {
|
|
|
|
|
return copyInternalSendfile(in, out, listener, signal, count);
|
|
|
|
|
return copyInternalSendfile(in, out, count, signal, executor, listener);
|
|
|
|
|
} else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
|
|
|
|
|
return copyInternalSplice(in, out, listener, signal, count);
|
|
|
|
|
return copyInternalSplice(in, out, count, signal, executor, listener);
|
|
|
|
|
}
|
|
|
|
|
} catch (ErrnoException e) {
|
|
|
|
|
throw e.rethrowAsIOException();
|
|
|
|
|
@@ -381,15 +410,17 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Worse case fallback to userspace
|
|
|
|
|
return copyInternalUserspace(in, out, listener, signal, count);
|
|
|
|
|
return copyInternalUserspace(in, out, count, signal, executor, listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Requires one of input or output to be a pipe.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
public static long copyInternalSplice(FileDescriptor in, FileDescriptor out,
|
|
|
|
|
ProgressListener listener, CancellationSignal signal, long count)
|
|
|
|
|
public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
|
|
|
|
|
CancellationSignal signal, Executor executor, ProgressListener listener)
|
|
|
|
|
throws ErrnoException {
|
|
|
|
|
long progress = 0;
|
|
|
|
|
long checkpoint = 0;
|
|
|
|
|
@@ -405,24 +436,32 @@ public class FileUtils {
|
|
|
|
|
if (signal != null) {
|
|
|
|
|
signal.throwIfCanceled();
|
|
|
|
|
}
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onProgress(progress);
|
|
|
|
|
if (executor != null && listener != null) {
|
|
|
|
|
final long progressSnapshot = progress;
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
listener.onProgress(progressSnapshot);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
checkpoint = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onProgress(progress);
|
|
|
|
|
if (executor != null && listener != null) {
|
|
|
|
|
final long progressSnapshot = progress;
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
listener.onProgress(progressSnapshot);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return progress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Requires both input and output to be a regular file.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out,
|
|
|
|
|
ProgressListener listener, CancellationSignal signal, long count)
|
|
|
|
|
public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
|
|
|
|
|
CancellationSignal signal, Executor executor, ProgressListener listener)
|
|
|
|
|
throws ErrnoException {
|
|
|
|
|
long progress = 0;
|
|
|
|
|
long checkpoint = 0;
|
|
|
|
|
@@ -437,33 +476,52 @@ public class FileUtils {
|
|
|
|
|
if (signal != null) {
|
|
|
|
|
signal.throwIfCanceled();
|
|
|
|
|
}
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onProgress(progress);
|
|
|
|
|
if (executor != null && listener != null) {
|
|
|
|
|
final long progressSnapshot = progress;
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
listener.onProgress(progressSnapshot);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
checkpoint = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onProgress(progress);
|
|
|
|
|
if (executor != null && listener != null) {
|
|
|
|
|
final long progressSnapshot = progress;
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
listener.onProgress(progressSnapshot);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return progress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
@Deprecated
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out,
|
|
|
|
|
ProgressListener listener, CancellationSignal signal, long count) throws IOException {
|
|
|
|
|
ProgressListener listener, CancellationSignal signal, long count)
|
|
|
|
|
throws IOException {
|
|
|
|
|
return copyInternalUserspace(in, out, count, signal, Runnable::run, listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count,
|
|
|
|
|
CancellationSignal signal, Executor executor, ProgressListener listener)
|
|
|
|
|
throws IOException {
|
|
|
|
|
if (count != Long.MAX_VALUE) {
|
|
|
|
|
return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count),
|
|
|
|
|
new FileOutputStream(out), listener, signal);
|
|
|
|
|
new FileOutputStream(out), signal, executor, listener);
|
|
|
|
|
} else {
|
|
|
|
|
return copyInternalUserspace(new FileInputStream(in),
|
|
|
|
|
new FileOutputStream(out), listener, signal);
|
|
|
|
|
new FileOutputStream(out), signal, executor, listener);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
public static long copyInternalUserspace(InputStream in, OutputStream out,
|
|
|
|
|
ProgressListener listener, CancellationSignal signal) throws IOException {
|
|
|
|
|
CancellationSignal signal, Executor executor, ProgressListener listener)
|
|
|
|
|
throws IOException {
|
|
|
|
|
long progress = 0;
|
|
|
|
|
long checkpoint = 0;
|
|
|
|
|
byte[] buffer = new byte[8192];
|
|
|
|
|
@@ -479,14 +537,20 @@ public class FileUtils {
|
|
|
|
|
if (signal != null) {
|
|
|
|
|
signal.throwIfCanceled();
|
|
|
|
|
}
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onProgress(progress);
|
|
|
|
|
if (executor != null && listener != null) {
|
|
|
|
|
final long progressSnapshot = progress;
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
listener.onProgress(progressSnapshot);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
checkpoint = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (listener != null) {
|
|
|
|
|
listener.onProgress(progress);
|
|
|
|
|
if (executor != null && listener != null) {
|
|
|
|
|
final long progressSnapshot = progress;
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
listener.onProgress(progressSnapshot);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return progress;
|
|
|
|
|
}
|
|
|
|
|
@@ -494,6 +558,7 @@ public class FileUtils {
|
|
|
|
|
/**
|
|
|
|
|
* Check if a filename is "safe" (no metacharacters or spaces).
|
|
|
|
|
* @param file The file to check
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static boolean isFilenameSafe(File file) {
|
|
|
|
|
// Note, we check whether it matches what's known to be safe,
|
|
|
|
|
@@ -509,6 +574,7 @@ public class FileUtils {
|
|
|
|
|
* @param ellipsis to add of the file was truncated (can be null)
|
|
|
|
|
* @return the contents of the file, possibly truncated
|
|
|
|
|
* @throws IOException if something goes wrong reading the file
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static String readTextFile(File file, int max, String ellipsis) throws IOException {
|
|
|
|
|
InputStream input = new FileInputStream(file);
|
|
|
|
|
@@ -563,13 +629,16 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static void stringToFile(File file, String string) throws IOException {
|
|
|
|
|
stringToFile(file.getAbsolutePath(), string);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
/**
|
|
|
|
|
* Writes the bytes given in {@code content} to the file whose absolute path
|
|
|
|
|
* is {@code filename}.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static void bytesToFile(String filename, byte[] content) throws IOException {
|
|
|
|
|
if (filename.startsWith("/proc/")) {
|
|
|
|
|
@@ -592,18 +661,23 @@ public class FileUtils {
|
|
|
|
|
* @param filename
|
|
|
|
|
* @param string
|
|
|
|
|
* @throws IOException
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static void stringToFile(String filename, String string) throws IOException {
|
|
|
|
|
bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Computes the checksum of a file using the CRC32 checksum routine.
|
|
|
|
|
* The value of the checksum is returned.
|
|
|
|
|
* Computes the checksum of a file using the CRC32 checksum routine. The
|
|
|
|
|
* value of the checksum is returned.
|
|
|
|
|
*
|
|
|
|
|
* @param file the file to checksum, must not be null
|
|
|
|
|
* @param file the file to checksum, must not be null
|
|
|
|
|
* @return the checksum value or an exception is thrown.
|
|
|
|
|
* @deprecated this is a weak hashing algorithm, and should not be used due
|
|
|
|
|
* to its potential for collision.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
|
|
|
|
|
CRC32 checkSummer = new CRC32();
|
|
|
|
|
CheckedInputStream cis = null;
|
|
|
|
|
@@ -632,6 +706,7 @@ public class FileUtils {
|
|
|
|
|
* @param minCount Always keep at least this many files.
|
|
|
|
|
* @param minAgeMs Always keep files younger than this age, in milliseconds.
|
|
|
|
|
* @return if any files were deleted.
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) {
|
|
|
|
|
if (minCount < 0 || minAgeMs < 0) {
|
|
|
|
|
@@ -673,6 +748,8 @@ public class FileUtils {
|
|
|
|
|
* Both files <em>must</em> have been resolved using
|
|
|
|
|
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
|
|
|
|
|
* attacks.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static boolean contains(File[] dirs, File file) {
|
|
|
|
|
for (File dir : dirs) {
|
|
|
|
|
@@ -690,12 +767,15 @@ public class FileUtils {
|
|
|
|
|
* Both files <em>must</em> have been resolved using
|
|
|
|
|
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
|
|
|
|
|
* attacks.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static boolean contains(File dir, File file) {
|
|
|
|
|
if (dir == null || file == null) return false;
|
|
|
|
|
return contains(dir.getAbsolutePath(), file.getAbsolutePath());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static boolean contains(String dirPath, String filePath) {
|
|
|
|
|
if (dirPath.equals(filePath)) {
|
|
|
|
|
return true;
|
|
|
|
|
@@ -706,6 +786,7 @@ public class FileUtils {
|
|
|
|
|
return filePath.startsWith(dirPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static boolean deleteContentsAndDir(File dir) {
|
|
|
|
|
if (deleteContents(dir)) {
|
|
|
|
|
return dir.delete();
|
|
|
|
|
@@ -714,6 +795,7 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static boolean deleteContents(File dir) {
|
|
|
|
|
File[] files = dir.listFiles();
|
|
|
|
|
boolean success = true;
|
|
|
|
|
@@ -743,6 +825,8 @@ public class FileUtils {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if given filename is valid for an ext4 filesystem.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static boolean isValidExtFilename(String name) {
|
|
|
|
|
return (name != null) && name.equals(buildValidExtFilename(name));
|
|
|
|
|
@@ -751,6 +835,8 @@ public class FileUtils {
|
|
|
|
|
/**
|
|
|
|
|
* Mutate the given filename to make it valid for an ext4 filesystem,
|
|
|
|
|
* replacing any invalid characters with "_".
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static String buildValidExtFilename(String name) {
|
|
|
|
|
if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
|
|
|
|
|
@@ -792,6 +878,8 @@ public class FileUtils {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if given filename is valid for a FAT filesystem.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static boolean isValidFatFilename(String name) {
|
|
|
|
|
return (name != null) && name.equals(buildValidFatFilename(name));
|
|
|
|
|
@@ -800,6 +888,8 @@ public class FileUtils {
|
|
|
|
|
/**
|
|
|
|
|
* Mutate the given filename to make it valid for a FAT filesystem,
|
|
|
|
|
* replacing any invalid characters with "_".
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static String buildValidFatFilename(String name) {
|
|
|
|
|
if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
|
|
|
|
|
@@ -820,6 +910,7 @@ public class FileUtils {
|
|
|
|
|
return res.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
public static String trimFilename(String str, int maxBytes) {
|
|
|
|
|
final StringBuilder res = new StringBuilder(str);
|
|
|
|
|
@@ -827,6 +918,7 @@ public class FileUtils {
|
|
|
|
|
return res.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
private static void trimFilename(StringBuilder res, int maxBytes) {
|
|
|
|
|
byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
if (raw.length > maxBytes) {
|
|
|
|
|
@@ -839,12 +931,14 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
|
|
|
|
|
if (path == null) return null;
|
|
|
|
|
final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
|
|
|
|
|
return (result != null) ? result.getAbsolutePath() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
|
|
|
|
|
if (paths == null) return null;
|
|
|
|
|
final String[] result = new String[paths.length];
|
|
|
|
|
@@ -858,6 +952,8 @@ public class FileUtils {
|
|
|
|
|
* Given a path under the "before" directory, rewrite it to live under the
|
|
|
|
|
* "after" directory. For example, {@code /before/foo/bar.txt} would become
|
|
|
|
|
* {@code /after/foo/bar.txt}.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
|
|
|
|
|
if (file == null || beforeDir == null || afterDir == null) return null;
|
|
|
|
|
@@ -869,6 +965,7 @@ public class FileUtils {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
private static File buildUniqueFileWithExtension(File parent, String name, String ext)
|
|
|
|
|
throws FileNotFoundException {
|
|
|
|
|
File file = buildFile(parent, name, ext);
|
|
|
|
|
@@ -895,6 +992,7 @@ public class FileUtils {
|
|
|
|
|
* 'example.txt' or 'example (1).txt', etc.
|
|
|
|
|
*
|
|
|
|
|
* @throws FileNotFoundException
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static File buildUniqueFile(File parent, String mimeType, String displayName)
|
|
|
|
|
throws FileNotFoundException {
|
|
|
|
|
@@ -905,6 +1003,8 @@ public class FileUtils {
|
|
|
|
|
/**
|
|
|
|
|
* Generates a unique file name under the given parent directory, keeping
|
|
|
|
|
* any extension intact.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static File buildUniqueFile(File parent, String displayName)
|
|
|
|
|
throws FileNotFoundException {
|
|
|
|
|
@@ -929,6 +1029,8 @@ public class FileUtils {
|
|
|
|
|
* If the display name doesn't have an extension that matches the requested MIME type, the
|
|
|
|
|
* extension is regarded as a part of filename and default extension for that MIME type is
|
|
|
|
|
* appended.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static String[] splitFileName(String mimeType, String displayName) {
|
|
|
|
|
String name;
|
|
|
|
|
@@ -975,6 +1077,7 @@ public class FileUtils {
|
|
|
|
|
return new String[] { name, ext };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
private static File buildFile(File parent, String name, String ext) {
|
|
|
|
|
if (TextUtils.isEmpty(ext)) {
|
|
|
|
|
return new File(parent, name);
|
|
|
|
|
@@ -983,6 +1086,7 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static @NonNull String[] listOrEmpty(@Nullable File dir) {
|
|
|
|
|
if (dir == null) return EmptyArray.STRING;
|
|
|
|
|
final String[] res = dir.list();
|
|
|
|
|
@@ -993,6 +1097,7 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
|
|
|
|
|
if (dir == null) return EMPTY;
|
|
|
|
|
final File[] res = dir.listFiles();
|
|
|
|
|
@@ -1003,6 +1108,7 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
|
|
|
|
|
if (dir == null) return EMPTY;
|
|
|
|
|
final File[] res = dir.listFiles(filter);
|
|
|
|
|
@@ -1013,6 +1119,7 @@ public class FileUtils {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
public static @Nullable File newFileOrNull(@Nullable String path) {
|
|
|
|
|
return (path != null) ? new File(path) : null;
|
|
|
|
|
}
|
|
|
|
|
@@ -1021,6 +1128,8 @@ public class FileUtils {
|
|
|
|
|
* Creates a directory with name {@code name} under an existing directory {@code baseDir}.
|
|
|
|
|
* Returns a {@code File} object representing the directory on success, {@code null} on
|
|
|
|
|
* failure.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static @Nullable File createDir(File baseDir, String name) {
|
|
|
|
|
final File dir = new File(baseDir, name);
|
|
|
|
|
@@ -1036,6 +1145,8 @@ public class FileUtils {
|
|
|
|
|
* Round the given size of a storage device to a nice round power-of-two
|
|
|
|
|
* value, such as 256MB or 32GB. This avoids showing weird values like
|
|
|
|
|
* "29.5GB" in UI.
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static long roundStorageSize(long size) {
|
|
|
|
|
long val = 1;
|
|
|
|
|
@@ -1050,6 +1161,23 @@ public class FileUtils {
|
|
|
|
|
return val * pow;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Closes the given object quietly, ignoring any checked exceptions. Does
|
|
|
|
|
* nothing if the given object is {@code null}.
|
|
|
|
|
*/
|
|
|
|
|
public static void closeQuietly(@Nullable AutoCloseable closeable) {
|
|
|
|
|
IoUtils.closeQuietly(closeable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Closes the given object quietly, ignoring any checked exceptions. Does
|
|
|
|
|
* nothing if the given object is {@code null}.
|
|
|
|
|
*/
|
|
|
|
|
public static void closeQuietly(@Nullable FileDescriptor fd) {
|
|
|
|
|
IoUtils.closeQuietly(fd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@hide} */
|
|
|
|
|
@VisibleForTesting
|
|
|
|
|
public static class MemoryPipe extends Thread implements AutoCloseable {
|
|
|
|
|
private final FileDescriptor[] pipe;
|
|
|
|
|
|