Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb8f3fb6 authored by Steven Moreland's avatar Steven Moreland Committed by Gerrit Code Review
Browse files

Merge "Add slice copy support for socket file type." into main

parents b87d24f3 744796fb
Loading
Loading
Loading
Loading
+84 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENOSYS;
import static android.system.OsConstants.F_OK;
import static android.system.OsConstants.EIO;
import static android.system.OsConstants.O_ACCMODE;
import static android.system.OsConstants.O_APPEND;
import static android.system.OsConstants.O_CREAT;
@@ -37,6 +38,7 @@ 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 static android.system.OsConstants.S_ISSOCK;
import static android.system.OsConstants.W_OK;

import android.annotation.NonNull;
@@ -459,6 +461,8 @@ public final class FileUtils {
                    }
                } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
                    return copyInternalSplice(in, out, count, signal, executor, listener);
                } else if (S_ISSOCK(st_in.st_mode) || S_ISSOCK(st_out.st_mode)) {
                    return copyInternalSpliceSocket(in, out, count, signal, executor, listener);
                }
            } catch (ErrnoException e) {
                throw e.rethrowAsIOException();
@@ -509,6 +513,86 @@ public final class FileUtils {
        }
        return progress;
    }
    /**
     * Requires one of input or output to be a socket file.
     *
     * @hide
     */
    @VisibleForTesting
    public static long copyInternalSpliceSocket(FileDescriptor in, FileDescriptor out, long count,
            CancellationSignal signal, Executor executor, ProgressListener listener)
            throws ErrnoException {
        long progress = 0;
        long checkpoint = 0;
        long countToRead = count;
        long countInPipe = 0;
        long t;

        FileDescriptor[] pipes = Os.pipe();

        while (countToRead > 0 || countInPipe > 0) {
            if (countToRead > 0) {
                t = Os.splice(in, null, pipes[1], null, Math.min(countToRead, COPY_CHECKPOINT_BYTES),
                              SPLICE_F_MOVE | SPLICE_F_MORE);
                if (t < 0) {
                    // splice error
                    Slog.e(TAG, "splice error, fdIn --> pipe, copy size:" + count +
                           ", copied:" + progress +
                           ", read:" + (count - countToRead) +
                           ", in pipe:" + countInPipe);
                    break;
                } else if (t == 0) {
                    // end of input, input count larger than real size
                    Slog.w(TAG, "Reached the end of the input file. The size to be copied exceeds the actual size, copy size:" + count +
                           ", copied:" + progress +
                           ", read:" + (count - countToRead) +
                           ", in pipe:" + countInPipe);
                    countToRead = 0;
                } else {
                    countInPipe += t;
                    countToRead -= t;
                }
            }

            if (countInPipe > 0) {
                t = Os.splice(pipes[0], null, out, null, Math.min(countInPipe, COPY_CHECKPOINT_BYTES),
                              SPLICE_F_MOVE | SPLICE_F_MORE);
                // The data is already in the pipeline, so the return value will not be zero.
                // If it is 0, it means an error has occurred. So here use t<=0.
                if (t <= 0) {
                    Slog.e(TAG, "splice error, pipe --> fdOut, copy size:" + count +
                           ", copied:" + progress +
                           ", read:" + (count - countToRead) +
                           ", in pipe: " + countInPipe);
                    throw new ErrnoException("splice, pipe --> fdOut", EIO);
                } else {
                    progress += t;
                    checkpoint += t;
                    countInPipe -= t;
                }
            }

            if (checkpoint >= COPY_CHECKPOINT_BYTES) {
                if (signal != null) {
                    signal.throwIfCanceled();
                }
                if (executor != null && listener != null) {
                    final long progressSnapshot = progress;
                    executor.execute(() -> {
                        listener.onProgress(progressSnapshot);
                    });
                }
                checkpoint = 0;
            }
        }
        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.
+84 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.F_OK;
import static android.system.OsConstants.O_APPEND;
import static android.system.OsConstants.O_CREAT;
@@ -37,6 +38,7 @@ import static android.system.OsConstants.O_RDWR;
import static android.system.OsConstants.O_TRUNC;
import static android.system.OsConstants.O_WRONLY;
import static android.system.OsConstants.R_OK;
import static android.system.OsConstants.SOCK_STREAM;
import static android.system.OsConstants.W_OK;
import static android.system.OsConstants.X_OK;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -54,6 +56,7 @@ import static org.junit.Assert.fail;
import android.content.Context;
import android.os.FileUtils.MemoryPipe;
import android.provider.DocumentsContract.Document;
import android.system.Os;
import android.util.DataUnit;

import androidx.test.InstrumentationRegistry;
@@ -70,6 +73,8 @@ import org.junit.runner.RunWith;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -77,6 +82,7 @@ import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.net.InetSocketAddress;

@RunWith(AndroidJUnit4.class)
public class FileUtilsTest {
@@ -249,6 +255,84 @@ public class FileUtilsTest {
        assertArrayEquals(expected, actual);
    }

    //TODO(ravenwood) Remove the _$noRavenwood suffix and add @RavenwoodIgnore instead
    @Test
    public void testCopy_SocketToFile_FileToSocket$noRavenwood() throws Exception {
        for (int size : DATA_SIZES ) {
            final File src = new File(mTarget, "src");
            final File dest = new File(mTarget, "dest");
            byte[] expected = new byte[size];
            byte[] actual = new byte[size];
            new Random().nextBytes(expected);

            // write test data in to src file
            writeFile(src, expected);

            // start server, get data from client and save to dest file (socket --> file)
            FileDescriptor srvSocketFd = Os.socket(AF_INET, SOCK_STREAM, 0);
            Os.bind(srvSocketFd, new InetSocketAddress("localhost", 0));
            Os.listen(srvSocketFd, 5);
            InetSocketAddress localSocketAddress = (InetSocketAddress) Os.getsockname(srvSocketFd);

            final Thread srv = new Thread(new Runnable() {
                public void run() {
                    try {
                        InetSocketAddress peerAddress = new InetSocketAddress();
                        FileDescriptor srvConnFd = Os.accept(srvSocketFd, peerAddress);

                        // read file size
                        byte[] rcvFileSizeByteArray = new byte[8];
                        Os.read(srvConnFd, rcvFileSizeByteArray, 0, rcvFileSizeByteArray.length);
                        long rcvFileSize = 0;
                        for (int i = 0; i < 8; i++) {
                            rcvFileSize <<= 8;
                            rcvFileSize |= (rcvFileSizeByteArray[i] & 0xFF);
                        }

                        FileOutputStream fileOutputStream = new FileOutputStream(dest);
                        // copy data from socket to file
                        FileUtils.copy(srvConnFd, fileOutputStream.getFD(), rcvFileSize, null, null, null);

                        fileOutputStream.close();
                        Os.close(srvConnFd);
                        Os.close(srvSocketFd);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });

            srv.start();


            // start client, get data from dest file and send to server (file --> socket)
            FileDescriptor clientFd = Os.socket(AF_INET, SOCK_STREAM, 0);
            Os.connect(clientFd, localSocketAddress.getAddress(), localSocketAddress.getPort());

            FileInputStream fileInputStream = new FileInputStream(src);
            long sndFileSize = src.length();
            // send the file size to server
            byte[] sndFileSizeByteArray = new byte[8];
            for (int i = 7; i >= 0; i--) {
                sndFileSizeByteArray[i] = (byte)(sndFileSize & 0xFF);
                sndFileSize >>= 8;
            }
            Os.write(clientFd, sndFileSizeByteArray, 0, sndFileSizeByteArray.length);

            // copy data from file to socket
            FileUtils.copy(fileInputStream.getFD(), clientFd, src.length(), null, null, null);

            fileInputStream.close();
            Os.close(clientFd);

            srv.join();

            // read test data from dest file
            actual = readFile(dest);
            assertArrayEquals(expected, actual);
        }
    }

    @Test
    public void testIsFilenameSafe() throws Exception {
        assertTrue(FileUtils.isFilenameSafe(new File("foobar")));