diff options
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/os/FileUtils.java | 248 |
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()); + } + } } |
