summaryrefslogtreecommitdiff
path: root/core/java/android/os/FileUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/os/FileUtils.java')
-rw-r--r--core/java/android/os/FileUtils.java248
1 files changed, 231 insertions, 17 deletions
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 7c53ec198e7d..21d245d40c64 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -16,6 +16,11 @@
package android.os;
+import static android.system.OsConstants.SPLICE_F_MORE;
+import static android.system.OsConstants.SPLICE_F_MOVE;
+import static android.system.OsConstants.S_ISFIFO;
+import static android.system.OsConstants.S_ISREG;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.provider.DocumentsContract.Document;
@@ -29,6 +34,7 @@ import android.webkit.MimeTypeMap;
import com.android.internal.annotations.VisibleForTesting;
+import libcore.io.IoUtils;
import libcore.util.EmptyArray;
import java.io.BufferedInputStream;
@@ -41,10 +47,12 @@ import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
@@ -81,6 +89,14 @@ public class FileUtils {
private static final File[] EMPTY = new File[0];
+ private static final boolean ENABLE_COPY_OPTIMIZATIONS = true;
+
+ private static final long COPY_CHECKPOINT_BYTES = 524288;
+
+ public interface CopyListener {
+ public void onProgress(long progress);
+ }
+
/**
* Set owner and mode of of given {@link File}.
*
@@ -185,6 +201,9 @@ public class FileUtils {
return false;
}
+ /**
+ * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+ */
@Deprecated
public static boolean copyFile(File srcFile, File destFile) {
try {
@@ -195,14 +214,19 @@ public class FileUtils {
}
}
- // copy a file from srcFile to destFile, return true if succeed, return
- // false if fail
+ /**
+ * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+ */
+ @Deprecated
public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
try (InputStream in = new FileInputStream(srcFile)) {
copyToFileOrThrow(in, destFile);
}
}
+ /**
+ * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+ */
@Deprecated
public static boolean copyToFile(InputStream inputStream, File destFile) {
try {
@@ -214,28 +238,153 @@ public class FileUtils {
}
/**
- * Copy data from a source stream to destFile.
- * Return true if succeed, return false if failed.
+ * @deprecated use {@link #copy(InputStream, OutputStream)} instead.
*/
- public static void copyToFileOrThrow(InputStream inputStream, File destFile)
- throws IOException {
+ @Deprecated
+ public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException {
if (destFile.exists()) {
destFile.delete();
}
- FileOutputStream out = new FileOutputStream(destFile);
- try {
- byte[] buffer = new byte[4096];
- int bytesRead;
- while ((bytesRead = inputStream.read(buffer)) >= 0) {
- out.write(buffer, 0, bytesRead);
+ try (FileOutputStream out = new FileOutputStream(destFile)) {
+ copy(in, out);
+ try {
+ Os.fsync(out.getFD());
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
}
- } finally {
- out.flush();
+ }
+ }
+
+ public static void copy(File from, File to) throws IOException {
+ try (FileInputStream in = new FileInputStream(from);
+ FileOutputStream out = new FileOutputStream(to)) {
+ copy(in, out);
+ }
+ }
+
+ public static void copy(InputStream in, OutputStream out) throws IOException {
+ copy(in, out, null, null);
+ }
+
+ public static void copy(InputStream in, OutputStream out, CopyListener listener,
+ CancellationSignal signal) throws IOException {
+ if (ENABLE_COPY_OPTIMIZATIONS) {
+ if (in instanceof FileInputStream && out instanceof FileOutputStream) {
+ copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(),
+ listener, signal);
+ }
+ }
+
+ // Worse case fallback to userspace
+ copyInternalUserspace(in, out, listener, signal);
+ }
+
+ public static void copy(FileDescriptor in, FileDescriptor out) throws IOException {
+ copy(in, out, null, null);
+ }
+
+ public static void copy(FileDescriptor in, FileDescriptor out, CopyListener listener,
+ CancellationSignal signal) throws IOException {
+ if (ENABLE_COPY_OPTIMIZATIONS) {
try {
- out.getFD().sync();
- } catch (IOException e) {
+ 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)) {
+ copyInternalSendfile(in, out, listener, signal);
+ } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
+ copyInternalSplice(in, out, listener, signal);
+ }
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ // Worse case fallback to userspace
+ copyInternalUserspace(in, out, listener, signal);
+ }
+
+ /**
+ * Requires one of input or output to be a pipe.
+ */
+ @VisibleForTesting
+ public static void copyInternalSplice(FileDescriptor in, FileDescriptor out,
+ CopyListener listener, CancellationSignal signal) throws ErrnoException {
+ long progress = 0;
+ long checkpoint = 0;
+
+ long t;
+ while ((t = Os.splice(in, null, out, null, COPY_CHECKPOINT_BYTES,
+ SPLICE_F_MOVE | SPLICE_F_MORE)) != 0) {
+ progress += t;
+ checkpoint += t;
+
+ if (checkpoint >= COPY_CHECKPOINT_BYTES) {
+ if (signal != null) {
+ signal.throwIfCanceled();
+ }
+ if (listener != null) {
+ listener.onProgress(progress);
+ }
+ checkpoint = 0;
+ }
+ }
+ }
+
+ /**
+ * Requires both input and output to be a regular file.
+ */
+ @VisibleForTesting
+ public static void copyInternalSendfile(FileDescriptor in, FileDescriptor out,
+ CopyListener listener, CancellationSignal signal) throws ErrnoException {
+ long progress = 0;
+ long checkpoint = 0;
+
+ long t;
+ while ((t = Os.sendfile(out, in, null, COPY_CHECKPOINT_BYTES)) != 0) {
+ progress += t;
+ checkpoint += t;
+
+ if (checkpoint >= COPY_CHECKPOINT_BYTES) {
+ if (signal != null) {
+ signal.throwIfCanceled();
+ }
+ if (listener != null) {
+ listener.onProgress(progress);
+ }
+ checkpoint = 0;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public static void copyInternalUserspace(FileDescriptor in, FileDescriptor out,
+ CopyListener listener, CancellationSignal signal) throws IOException {
+ copyInternalUserspace(new FileInputStream(in), new FileOutputStream(out), listener, signal);
+ }
+
+ @VisibleForTesting
+ public static void copyInternalUserspace(InputStream in, OutputStream out,
+ CopyListener listener, CancellationSignal signal) throws IOException {
+ long progress = 0;
+ long checkpoint = 0;
+ byte[] buffer = new byte[8192];
+
+ int t;
+ while ((t = in.read(buffer)) != -1) {
+ out.write(buffer, 0, t);
+
+ progress += t;
+ checkpoint += t;
+
+ if (checkpoint >= COPY_CHECKPOINT_BYTES) {
+ if (signal != null) {
+ signal.throwIfCanceled();
+ }
+ if (listener != null) {
+ listener.onProgress(progress);
+ }
+ checkpoint = 0;
}
- out.close();
}
}
@@ -797,4 +946,69 @@ public class FileUtils {
}
return val * pow;
}
+
+ @VisibleForTesting
+ public static class MemoryPipe extends Thread implements AutoCloseable {
+ private final FileDescriptor[] pipe;
+ private final byte[] data;
+ private final boolean sink;
+
+ private MemoryPipe(byte[] data, boolean sink) throws IOException {
+ try {
+ this.pipe = Os.pipe();
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ this.data = data;
+ this.sink = sink;
+ }
+
+ private MemoryPipe startInternal() {
+ super.start();
+ return this;
+ }
+
+ public static MemoryPipe createSource(byte[] data) throws IOException {
+ return new MemoryPipe(data, false).startInternal();
+ }
+
+ public static MemoryPipe createSink(byte[] data) throws IOException {
+ return new MemoryPipe(data, true).startInternal();
+ }
+
+ public FileDescriptor getFD() {
+ return sink ? pipe[1] : pipe[0];
+ }
+
+ public FileDescriptor getInternalFD() {
+ return sink ? pipe[0] : pipe[1];
+ }
+
+ @Override
+ public void run() {
+ final FileDescriptor fd = getInternalFD();
+ try {
+ int i = 0;
+ while (i < data.length) {
+ if (sink) {
+ i += Os.read(fd, data, i, data.length - i);
+ } else {
+ i += Os.write(fd, data, i, data.length - i);
+ }
+ }
+ } catch (IOException | ErrnoException e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (sink) {
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
+ }
+ IoUtils.closeQuietly(fd);
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ IoUtils.closeQuietly(getFD());
+ }
+ }
}