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

Commit 6f218ec3 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Basic `ParcelFileDescriptor` on Ravenwood.

One of the final foundational building blocks, which wraps a Java
`FileDescriptor` object.  Since many of the existing APIs here offer
to work with "raw" int FD values, we need to reach "behind the
curtain" to read/write the internal value.  Thankfully `SharedSecrets`
already offers a way to gain access without requiring us to open
up the entire `java.io` area of the `java.base` module.

Bring along `android.util.CloseGuard` as a no-op wrapper.

Bug: 324417456
Test: atest CtsOsTestCasesRavenwood
Change-Id: I8cffacf96d94aea2d40d75055a2411717e0c1bcf
parent 919032ef
Loading
Loading
Loading
Loading
+109 −14
Original line number Diff line number Diff line
@@ -40,14 +40,18 @@ import android.content.ContentProvider;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
import android.ravenwood.annotation.RavenwoodReplace;
import android.ravenwood.annotation.RavenwoodThrow;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
import android.util.CloseGuard;
import android.util.Log;
import android.util.Slog;

import dalvik.system.CloseGuard;
import dalvik.system.VMRuntime;

import libcore.io.IoUtils;
@@ -70,6 +74,8 @@ import java.nio.ByteOrder;
 * The FileDescriptor returned by {@link Parcel#readFileDescriptor}, allowing
 * you to close it when done with it.
 */
@RavenwoodKeepWholeClass
@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.ParcelFileDescriptor_host")
public class ParcelFileDescriptor implements Parcelable, Closeable {
    private static final String TAG = "ParcelFileDescriptor";

@@ -197,11 +203,11 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
        }
        mWrapped = null;
        mFd = fd;
        IoUtils.setFdOwner(mFd, this);
        setFdOwner(mFd);

        mCommFd = commChannel;
        if (mCommFd != null) {
            IoUtils.setFdOwner(mCommFd, this);
            setFdOwner(mCommFd);
        }

        mGuard.open("close");
@@ -284,15 +290,17 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     */
    // We can't accept a generic Executor here, since we need to use
    // MessageQueue.addOnFileDescriptorEventListener()
    @RavenwoodThrow(blockedBy = MessageQueue.class)
    @SuppressLint("ExecutorRegistration")
    public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd,
            @NonNull Handler handler, @NonNull OnCloseListener listener) throws IOException {
        final FileDescriptor original = new FileDescriptor();
        original.setInt$(pfd.detachFd());
        setFdInt(original, pfd.detachFd());
        return fromFd(original, handler, listener);
    }

    /** {@hide} */
    @RavenwoodThrow(blockedBy = MessageQueue.class)
    public static ParcelFileDescriptor fromFd(FileDescriptor fd, Handler handler,
            final OnCloseListener listener) throws IOException {
        if (handler == null) {
@@ -318,7 +326,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
                }
                if (status != null) {
                    queue.removeOnFileDescriptorEventListener(fd);
                    IoUtils.closeQuietly(fd);
                    closeInternal(fd);
                    listener.onClose(status.asIOException());
                    return 0;
                }
@@ -329,6 +337,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
        return pfd;
    }

    @RavenwoodReplace
    private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException {
        if ((mode & MODE_WRITE_ONLY) != 0 && (mode & MODE_APPEND) == 0
                && (mode & MODE_TRUNCATE) == 0 && ((mode & MODE_READ_ONLY) == 0)
@@ -352,17 +361,38 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
        }
    }

    private static FileDescriptor openInternal$ravenwood(File file, int mode)
            throws FileNotFoundException {
        try {
            return native_open$ravenwood(file, mode);
        } catch (FileNotFoundException e) {
            throw e;
        } catch (IOException e) {
            throw new FileNotFoundException(e.getMessage());
        }
    }

    @RavenwoodReplace
    private static void closeInternal(FileDescriptor fd) {
        IoUtils.closeQuietly(fd);
    }

    private static void closeInternal$ravenwood(FileDescriptor fd) {
        native_close$ravenwood(fd);
    }

    /**
     * Create a new ParcelFileDescriptor that is a dup of an existing
     * FileDescriptor.  This obeys standard POSIX semantics, where the
     * new file descriptor shared state such as file position with the
     * original file descriptor.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException {
        try {
            final FileDescriptor fd = new FileDescriptor();
            int intfd = Os.fcntlInt(orig, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0);
            fd.setInt$(intfd);
            setFdInt(fd, intfd);
            return new ParcelFileDescriptor(fd);
        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
@@ -375,6 +405,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * new file descriptor shared state such as file position with the
     * original file descriptor.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public ParcelFileDescriptor dup() throws IOException {
        if (mWrapped != null) {
            return mWrapped.dup();
@@ -393,14 +424,15 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * @return Returns a new ParcelFileDescriptor holding a FileDescriptor
     * for a dup of the given fd.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor fromFd(int fd) throws IOException {
        final FileDescriptor original = new FileDescriptor();
        original.setInt$(fd);
        setFdInt(original, fd);

        try {
            final FileDescriptor dup = new FileDescriptor();
            int intfd = Os.fcntlInt(original, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0);
            dup.setInt$(intfd);
            setFdInt(dup, intfd);
            return new ParcelFileDescriptor(dup);
        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
@@ -423,7 +455,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     */
    public static ParcelFileDescriptor adoptFd(int fd) {
        final FileDescriptor fdesc = new FileDescriptor();
        fdesc.setInt$(fd);
        setFdInt(fdesc, fd);

        return new ParcelFileDescriptor(fdesc);
    }
@@ -452,6 +484,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     *
     * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor fromSocket(Socket socket) {
        FileDescriptor fd = socket.getFileDescriptor$();
        try {
@@ -485,6 +518,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     *
     * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) {
        FileDescriptor fd = datagramSocket.getFileDescriptor$();
        try {
@@ -499,6 +533,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * ParcelFileDescriptor in the returned array is the read side; the second
     * is the write side.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor[] createPipe() throws IOException {
        try {
            final FileDescriptor[] fds = Os.pipe2(ifAtLeastQ(O_CLOEXEC));
@@ -520,6 +555,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * calling {@link #checkError()}, usually after detecting an EOF.
     * This can also be used to detect remote crashes.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor[] createReliablePipe() throws IOException {
        try {
            final FileDescriptor[] comm = createCommSocketPair();
@@ -536,6 +572,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * Create two ParcelFileDescriptors structured as a pair of sockets
     * connected to each other. The two sockets are indistinguishable.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor[] createSocketPair() throws IOException {
        return createSocketPair(SOCK_STREAM);
    }
@@ -543,6 +580,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
    /**
     * @hide
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor[] createSocketPair(int type) throws IOException {
        try {
            final FileDescriptor fd0 = new FileDescriptor();
@@ -565,6 +603,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * calling {@link #checkError()}, usually after detecting an EOF.
     * This can also be used to detect remote crashes.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor[] createReliableSocketPair() throws IOException {
        return createReliableSocketPair(SOCK_STREAM);
    }
@@ -572,6 +611,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
    /**
     * @hide
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor[] createReliableSocketPair(int type) throws IOException {
        try {
            final FileDescriptor[] comm = createCommSocketPair();
@@ -586,6 +626,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
        }
    }

    @RavenwoodThrow(reason = "Requires JNI support")
    private static FileDescriptor[] createCommSocketPair() throws IOException {
        try {
            // Use SOCK_SEQPACKET so that we have a guarantee that the status
@@ -614,6 +655,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     */
    @UnsupportedAppUsage
    @Deprecated
    @RavenwoodThrow(reason = "Requires JNI support")
    public static ParcelFileDescriptor fromData(byte[] data, String name) throws IOException {
        if (data == null) return null;
        MemoryFile file = new MemoryFile(name, data.length);
@@ -669,9 +711,10 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * @hide
     */
    @TestApi
    @RavenwoodThrow(reason = "Requires kernel support")
    public static File getFile(FileDescriptor fd) throws IOException {
        try {
            final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
            final String path = Os.readlink("/proc/self/fd/" + getFdInt(fd));
            if (OsConstants.S_ISREG(Os.stat(path).st_mode)
                    || OsConstants.S_ISCHR(Os.stat(path).st_mode)) {
                return new File(path);
@@ -700,6 +743,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * Return the total size of the file representing this fd, as determined by
     * {@code stat()}. Returns -1 if the fd is not a file.
     */
    @RavenwoodThrow(reason = "Requires JNI support")
    public long getStatSize() {
        if (mWrapped != null) {
            return mWrapped.getStatSize();
@@ -724,6 +768,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * @hide
     */
    @UnsupportedAppUsage
    @RavenwoodThrow(reason = "Requires JNI support")
    public long seekTo(long pos) throws IOException {
        if (mWrapped != null) {
            return mWrapped.seekTo(pos);
@@ -751,7 +796,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
            if (mClosed) {
                throw new IllegalStateException("Already closed");
            }
            return mFd.getInt$();
            return getFdInt(mFd);
        }
    }

@@ -773,7 +818,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
            if (mClosed) {
                throw new IllegalStateException("Already closed");
            }
            int fd = IoUtils.acquireRawFd(mFd);
            int fd = acquireRawFd(mFd);
            writeCommStatusAndClose(Status.DETACHED, null);
            mClosed = true;
            mGuard.close();
@@ -832,7 +877,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
        }
        // Status MUST be sent before closing actual descriptor
        writeCommStatusAndClose(status, msg);
        IoUtils.closeQuietly(mFd);
        closeInternal(mFd);
        releaseResources();
    }

@@ -899,7 +944,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
            }

        } finally {
            IoUtils.closeQuietly(mCommFd);
            closeInternal(mCommFd);
            mCommFd = null;
        }
    }
@@ -991,6 +1036,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * take care of calling {@link ParcelFileDescriptor#close
     * ParcelFileDescriptor.close()} for you when the stream is closed.
     */
    @RavenwoodKeepWholeClass
    public static class AutoCloseInputStream extends FileInputStream {
        private final ParcelFileDescriptor mPfd;

@@ -1042,6 +1088,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
     * take care of calling {@link ParcelFileDescriptor#close
     * ParcelFileDescriptor.close()} for you when the stream is closed.
     */
    @RavenwoodKeepWholeClass
    public static class AutoCloseOutputStream extends FileOutputStream {
        private final ParcelFileDescriptor mPfd;

@@ -1232,10 +1279,58 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
        }
    }

    // These native methods are currently only implemented by Ravenwood, as it's the only
    // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
    private static native void native_setFdInt$ravenwood(FileDescriptor fd, int fdInt);
    private static native int native_getFdInt$ravenwood(FileDescriptor fd);
    private static native FileDescriptor native_open$ravenwood(File file, int pfdMode)
            throws IOException;
    private static native void native_close$ravenwood(FileDescriptor fd);

    @RavenwoodReplace
    private static void setFdInt(FileDescriptor fd, int fdInt) {
        fd.setInt$(fdInt);
    }

    private static void setFdInt$ravenwood(FileDescriptor fd, int fdInt) {
        native_setFdInt$ravenwood(fd, fdInt);
    }

    @RavenwoodReplace
    private static int getFdInt(FileDescriptor fd) {
        return fd.getInt$();
    }

    private static int getFdInt$ravenwood(FileDescriptor fd) {
        return native_getFdInt$ravenwood(fd);
    }

    @RavenwoodReplace
    private void setFdOwner(FileDescriptor fd) {
        IoUtils.setFdOwner(fd, this);
    }

    private void setFdOwner$ravenwood(FileDescriptor fd) {
        // FD owners currently unsupported under Ravenwood; ignored
    }

    @RavenwoodReplace
    private int acquireRawFd(FileDescriptor fd) {
        return IoUtils.acquireRawFd(fd);
    }

    private int acquireRawFd$ravenwood(FileDescriptor fd) {
        // FD owners currently unsupported under Ravenwood; return FD directly
        return getFdInt(fd);

    }

    @RavenwoodThrow
    private static boolean isAtLeastQ() {
        return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
    }

    @RavenwoodThrow
    private static int ifAtLeastQ(int value) {
        return isAtLeastQ() ? value : 0;
    }
+32 −4
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package android.util;

import android.annotation.NonNull;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodReplace;

/**
 * CloseGuard is a mechanism for flagging implicit finalizer cleanup of
@@ -108,15 +110,35 @@ import android.annotation.NonNull;
 * in a method, the call to {@code open} should occur just after
 * resource acquisition.
 */
@RavenwoodKeepWholeClass
public final class CloseGuard {
    private final dalvik.system.CloseGuard mImpl;

    /**
     * Constructs a new CloseGuard instance.
     * {@link #open(String)} can be used to set up the instance to warn on failure to close.
     *
     * @hide
     */
    public static CloseGuard get() {
        return new CloseGuard();
    }

    /**
     * Constructs a new CloseGuard instance.
     * {@link #open(String)} can be used to set up the instance to warn on failure to close.
     */
    public CloseGuard() {
        mImpl = dalvik.system.CloseGuard.get();
        mImpl = getImpl();
    }

    @RavenwoodReplace
    private dalvik.system.CloseGuard getImpl() {
        return dalvik.system.CloseGuard.get();
    }

    private dalvik.system.CloseGuard getImpl$ravenwood() {
        return null;
    }

    /**
@@ -127,19 +149,25 @@ public final class CloseGuard {
     * @throws NullPointerException if closeMethodName is null.
     */
    public void open(@NonNull String closeMethodName) {
        if (mImpl != null) {
            mImpl.open(closeMethodName);
        }
    }

    /** Marks this CloseGuard instance as closed to avoid warnings on finalization. */
    public void close() {
        if (mImpl != null) {
            mImpl.close();
        }
    }

    /**
     * Logs a warning if the caller did not properly cleanup by calling an explicit close method
     * before finalization.
     */
    public void warnIfOpen() {
        if (mImpl != null) {
            mImpl.warnIfOpen();
        }
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ com.android.internal.os.RuntimeInit
com.android.internal.power.ModemPowerProfile

android.util.AtomicFile
android.util.CloseGuard
android.util.DataUnit
android.util.DayOfMonthCursor
android.util.DumpableContainer
@@ -68,6 +69,9 @@ android.os.Message
android.os.MessageQueue
android.os.PackageTagsList
android.os.Parcel
android.os.ParcelFileDescriptor
android.os.ParcelFileDescriptor$AutoCloseInputStream
android.os.ParcelFileDescriptor$AutoCloseOutputStream
android.os.Parcelable
android.os.PowerComponents
android.os.Process
+128 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.hoststubgen.nativesubstitution;

import static android.os.ParcelFileDescriptor.MODE_APPEND;
import static android.os.ParcelFileDescriptor.MODE_CREATE;
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_WORLD_READABLE;
import static android.os.ParcelFileDescriptor.MODE_WORLD_WRITEABLE;
import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;

import com.android.internal.annotations.GuardedBy;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;

public class ParcelFileDescriptor_host {
    /**
     * Since we don't have a great way to keep an unmanaged {@code FileDescriptor} reference
     * alive, we keep a strong reference to the {@code RandomAccessFile} we used to open it. This
     * gives us a way to look up the original parent object when closing later.
     */
    @GuardedBy("sActive")
    private static final Map<FileDescriptor, RandomAccessFile> sActive = new HashMap<>();

    public static void native_setFdInt$ravenwood(FileDescriptor fd, int fdInt) {
        try {
            final Object obj = Class.forName("jdk.internal.access.SharedSecrets").getMethod(
                    "getJavaIOFileDescriptorAccess").invoke(null);
            Class.forName("jdk.internal.access.JavaIOFileDescriptorAccess").getMethod(
                    "set", FileDescriptor.class, int.class).invoke(obj, fd, fdInt);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException("Failed to interact with raw FileDescriptor internals;"
                    + " perhaps JRE has changed?", e);
        }
    }

    public static int native_getFdInt$ravenwood(FileDescriptor fd) {
        try {
            final Object obj = Class.forName("jdk.internal.access.SharedSecrets").getMethod(
                    "getJavaIOFileDescriptorAccess").invoke(null);
            return (int) Class.forName("jdk.internal.access.JavaIOFileDescriptorAccess").getMethod(
                    "get", FileDescriptor.class).invoke(obj, fd);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException("Failed to interact with raw FileDescriptor internals;"
                    + " perhaps JRE has changed?", e);
        }
    }

    public static FileDescriptor native_open$ravenwood(File file, int pfdMode) throws IOException {
        if ((pfdMode & MODE_CREATE) != 0 && !file.exists()) {
            throw new FileNotFoundException();
        }

        final String modeString;
        if ((pfdMode & MODE_READ_WRITE) == MODE_READ_WRITE) {
            modeString = "rw";
        } else if ((pfdMode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
            modeString = "rw";
        } else if ((pfdMode & MODE_READ_ONLY) == MODE_READ_ONLY) {
            modeString = "r";
        } else {
            throw new IllegalArgumentException();
        }

        final RandomAccessFile raf = new RandomAccessFile(file, modeString);

        // Now that we have a real file on disk, match requested flags
        if ((pfdMode & MODE_TRUNCATE) != 0) {
            raf.setLength(0);
        }
        if ((pfdMode & MODE_APPEND) != 0) {
            raf.seek(raf.length());
        }
        if ((pfdMode & MODE_WORLD_READABLE) != 0) {
            file.setReadable(true, false);
        }
        if ((pfdMode & MODE_WORLD_WRITEABLE) != 0) {
            file.setWritable(true, false);
        }

        final FileDescriptor fd = raf.getFD();
        synchronized (sActive) {
            sActive.put(fd, raf);
        }
        return fd;
    }

    public static void native_close$ravenwood(FileDescriptor fd) {
        final RandomAccessFile raf;
        synchronized (sActive) {
            raf = sActive.remove(fd);
        }
        try {
            if (raf != null) {
                raf.close();
            } else {
                // Odd, we don't remember opening this ourselves, but let's release the
                // underlying resource as requested
                System.err.println("Closing unknown FileDescriptor: " + fd);
                new FileOutputStream(fd).close();
            }
        } catch (IOException ignored) {
        }
    }
}