Support Parcel.write/readFileDescriptor

Flag: EXEMPT host side test change only
Bug: 292141694

Test: atest CtsOsTestCasesRavenwood
Change-Id: I848dc1ebc89c2beebce75ab8698066fc3b0bf72c
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 136c45d..47096db 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -434,7 +434,6 @@
     @RavenwoodThrow
     private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
     @FastNative
-    @RavenwoodThrow
     private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
 
     private static native byte[] nativeCreateByteArray(long nativePtr);
@@ -456,7 +455,6 @@
     @RavenwoodThrow
     private static native IBinder nativeReadStrongBinder(long nativePtr);
     @FastNative
-    @RavenwoodThrow
     private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
 
     private static native long nativeCreate();
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 71957ee..464df23 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -381,6 +381,8 @@
     }
 
     private static void closeInternal$ravenwood(FileDescriptor fd) {
+        // Desktop JVM doesn't have FileDescriptor.close(), so we'll need to go to the ravenwood
+        // side to close it.
         native_close$ravenwood(fd);
     }
 
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
index 8fe6853..1a15d7a 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
@@ -25,19 +25,24 @@
 import static android.os.ParcelFileDescriptor.MODE_WORLD_WRITEABLE;
 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
 
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.ravenwood.common.JvmWorkaround;
 
 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 {
+    private static final String TAG = "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
@@ -98,16 +103,18 @@
         synchronized (sActive) {
             raf = sActive.remove(fd);
         }
+        int fdInt = JvmWorkaround.getInstance().getFdInt(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();
+                // This FD wasn't created by native_open$ravenwood().
+                // The FD was passed to the PFD ctor. Just close it.
+                Os.close(fd);
             }
-        } catch (IOException ignored) {
+        } catch (IOException | ErrnoException e) {
+            Log.w(TAG, "Exception thrown while closing fd " + fdInt, e);
         }
     }
 }
+;
\ No newline at end of file
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
index 22e11e1..2df93cd 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
@@ -15,6 +15,11 @@
  */
 package com.android.platform.test.ravenwood.nativesubstitution;
 
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
@@ -31,6 +36,8 @@
  * {@link ByteBuffer} wouldn't allow...)
  */
 public class Parcel_host {
+    private static final String TAG = "Parcel";
+
     private Parcel_host() {
     }
 
@@ -50,6 +57,11 @@
     // TODO Use the actual value from Parcel.java.
     private static final int OK = 0;
 
+    private final Map<Integer, FileDescriptor> mFdMap = new ConcurrentHashMap<>();
+
+    private static final int FD_PLACEHOLDER = 0xDEADBEEF;
+    private static final int FD_PAYLOAD_SIZE = 8;
+
     private void validate() {
         if (mDeleted) {
             // TODO: Put more info
@@ -67,6 +79,7 @@
         return p;
     }
 
+    /** Native method substitution */
     public static long nativeCreate() {
         final long id = sNextId.getAndIncrement();
         final Parcel_host p = new Parcel_host();
@@ -80,7 +93,8 @@
         mSize = 0;
         mPos = 0;
         mSensitive = false;
-        mAllowFds = false;
+        mAllowFds = true;
+        mFdMap.clear();
     }
 
     private void updateSize() {
@@ -89,16 +103,19 @@
         }
     }
 
+    /** Native method substitution */
     public static void nativeDestroy(long nativePtr) {
         getInstance(nativePtr).mDeleted = true;
         sInstances.remove(nativePtr);
     }
 
+    /** Native method substitution */
     public static void nativeFreeBuffer(long nativePtr) {
         getInstance(nativePtr).freeBuffer();
     }
 
-    public void freeBuffer() {
+    /** Native method substitution */
+    private void freeBuffer() {
         init();
     }
 
@@ -137,32 +154,47 @@
         }
     }
 
+    /** Native method substitution */
     public static void nativeMarkSensitive(long nativePtr) {
         getInstance(nativePtr).mSensitive = true;
     }
+
+    /** Native method substitution */
     public static int nativeDataSize(long nativePtr) {
         return getInstance(nativePtr).mSize;
     }
+
+    /** Native method substitution */
     public static int nativeDataAvail(long nativePtr) {
         var p = getInstance(nativePtr);
         return p.mSize - p.mPos;
     }
+
+    /** Native method substitution */
     public static int nativeDataPosition(long nativePtr) {
         return getInstance(nativePtr).mPos;
     }
+
+    /** Native method substitution */
     public static int nativeDataCapacity(long nativePtr) {
         return getInstance(nativePtr).mBuffer.length;
     }
+
+    /** Native method substitution */
     public static void nativeSetDataSize(long nativePtr, int size) {
         var p = getInstance(nativePtr);
         p.ensureCapacity(size);
         getInstance(nativePtr).mSize = size;
     }
+
+    /** Native method substitution */
     public static void nativeSetDataPosition(long nativePtr, int pos) {
         var p = getInstance(nativePtr);
         // TODO: Should this change the size or the capacity??
         p.mPos = pos;
     }
+
+    /** Native method substitution */
     public static void nativeSetDataCapacity(long nativePtr, int size) {
         if (size < 0) {
             throw new IllegalArgumentException("size < 0: size=" + size);
@@ -173,20 +205,25 @@
         }
     }
 
+    /** Native method substitution */
     public static boolean nativePushAllowFds(long nativePtr, boolean allowFds) {
         var p = getInstance(nativePtr);
         var prev = p.mAllowFds;
         p.mAllowFds = allowFds;
         return prev;
     }
+
+    /** Native method substitution */
     public static void nativeRestoreAllowFds(long nativePtr, boolean lastValue) {
         getInstance(nativePtr).mAllowFds = lastValue;
     }
 
+    /** Native method substitution */
     public static void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len) {
         nativeWriteBlob(nativePtr, b, offset, len);
     }
 
+    /** Native method substitution */
     public static void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len) {
         var p = getInstance(nativePtr);
 
@@ -205,6 +242,7 @@
         }
     }
 
+    /** Native method substitution */
     public static int nativeWriteInt(long nativePtr, int value) {
         var p = getInstance(nativePtr);
         p.ensureMoreCapacity(Integer.BYTES);
@@ -219,14 +257,19 @@
         return OK;
     }
 
+    /** Native method substitution */
     public static int nativeWriteLong(long nativePtr, long value) {
         nativeWriteInt(nativePtr, (int) (value >>> 32));
         nativeWriteInt(nativePtr, (int) (value));
         return OK;
     }
+
+    /** Native method substitution */
     public static int nativeWriteFloat(long nativePtr, float val) {
         return nativeWriteInt(nativePtr, Float.floatToIntBits(val));
     }
+
+    /** Native method substitution */
     public static int nativeWriteDouble(long nativePtr, double val) {
         return nativeWriteLong(nativePtr, Double.doubleToLongBits(val));
     }
@@ -235,6 +278,7 @@
         return ((val + 3) / 4) * 4;
     }
 
+    /** Native method substitution */
     public static void nativeWriteString8(long nativePtr, String val) {
         if (val == null) {
             nativeWriteBlob(nativePtr, null, 0, 0);
@@ -243,15 +287,19 @@
             nativeWriteBlob(nativePtr, bytes, 0, bytes.length);
         }
     }
+
+    /** Native method substitution */
     public static void nativeWriteString16(long nativePtr, String val) {
         // Just reuse String8
         nativeWriteString8(nativePtr, val);
     }
 
+    /** Native method substitution */
     public static byte[] nativeCreateByteArray(long nativePtr) {
         return nativeReadBlob(nativePtr);
     }
 
+    /** Native method substitution */
     public static boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen) {
         if (dest == null) {
             return false;
@@ -271,6 +319,7 @@
         return true;
     }
 
+    /** Native method substitution */
     public static byte[] nativeReadBlob(long nativePtr) {
         var p = getInstance(nativePtr);
         if (p.mSize - p.mPos < 4) {
@@ -295,6 +344,8 @@
 
         return bytes;
     }
+
+    /** Native method substitution */
     public static int nativeReadInt(long nativePtr) {
         var p = getInstance(nativePtr);
 
@@ -310,19 +361,24 @@
 
         return ret;
     }
+
+    /** Native method substitution */
     public static long nativeReadLong(long nativePtr) {
         return (((long) nativeReadInt(nativePtr)) << 32)
                 | (((long) nativeReadInt(nativePtr)) & 0xffff_ffffL);
     }
 
+    /** Native method substitution */
     public static float nativeReadFloat(long nativePtr) {
         return Float.intBitsToFloat(nativeReadInt(nativePtr));
     }
 
+    /** Native method substitution */
     public static double nativeReadDouble(long nativePtr) {
         return Double.longBitsToDouble(nativeReadLong(nativePtr));
     }
 
+    /** Native method substitution */
     public static String nativeReadString8(long nativePtr) {
         final var bytes = nativeReadBlob(nativePtr);
         if (bytes == null) {
@@ -334,10 +390,13 @@
         return nativeReadString8(nativePtr);
     }
 
+    /** Native method substitution */
     public static byte[] nativeMarshall(long nativePtr) {
         var p = getInstance(nativePtr);
         return Arrays.copyOf(p.mBuffer, p.mSize);
     }
+
+    /** Native method substitution */
     public static void nativeUnmarshall(
             long nativePtr, byte[] data, int offset, int length) {
         var p = getInstance(nativePtr);
@@ -346,6 +405,8 @@
         p.mPos += length;
         p.updateSize();
     }
+
+    /** Native method substitution */
     public static int nativeCompareData(long thisNativePtr, long otherNativePtr) {
         var a = getInstance(thisNativePtr);
         var b = getInstance(otherNativePtr);
@@ -355,6 +416,8 @@
             return -1;
         }
     }
+
+    /** Native method substitution */
     public static boolean nativeCompareDataInRange(
             long ptrA, int offsetA, long ptrB, int offsetB, int length) {
         var a = getInstance(ptrA);
@@ -368,6 +431,8 @@
         return Arrays.equals(Arrays.copyOfRange(a.mBuffer, offsetA, offsetA + length),
                 Arrays.copyOfRange(b.mBuffer, offsetB, offsetB + length));
     }
+
+    /** Native method substitution */
     public static void nativeAppendFrom(
             long thisNativePtr, long otherNativePtr, int srcOffset, int length) {
         var dst = getInstance(thisNativePtr);
@@ -382,25 +447,83 @@
         // TODO: Update the other's position?
     }
 
-    public static boolean nativeHasFileDescriptors(long nativePtr) {
-        // Assume false for now, because we don't support writing FDs yet.
-        return false;
-    }
-
-    public static boolean nativeHasFileDescriptorsInRange(
-            long nativePtr, int offset, int length) {
-        // Assume false for now, because we don't support writing FDs yet.
-        return false;
-    }
-
+    /** Native method substitution */
     public static boolean nativeHasBinders(long nativePtr) {
         // Assume false for now, because we don't support adding binders.
         return false;
     }
 
+    /** Native method substitution */
     public static boolean nativeHasBindersInRange(
             long nativePtr, int offset, int length) {
         // Assume false for now, because we don't support writing FDs yet.
         return false;
     }
-}
+
+    /** Native method substitution */
+    public static void nativeWriteFileDescriptor(long nativePtr, java.io.FileDescriptor val) {
+        var p = getInstance(nativePtr);
+
+        if (!p.mAllowFds) {
+            // Simulate the FDS_NOT_ALLOWED case in frameworks/base/core/jni/android_util_Binder.cpp
+            throw new RuntimeException("Not allowed to write file descriptors here");
+        }
+
+        FileDescriptor dup = null;
+        try {
+            dup = Os.dup(val);
+        } catch (ErrnoException e) {
+            throw new RuntimeException(e);
+        }
+        p.mFdMap.put(p.mPos, dup);
+
+        // Parcel.cpp writes two int32s for a FD.
+        // Make sure FD_PAYLOAD_SIZE is in sync with this code.
+        nativeWriteInt(nativePtr, FD_PLACEHOLDER);
+        nativeWriteInt(nativePtr, FD_PLACEHOLDER);
+    }
+
+    /** Native method substitution */
+    public static java.io.FileDescriptor nativeReadFileDescriptor(long nativePtr) {
+        var p = getInstance(nativePtr);
+
+        var pos = p.mPos;
+        var fd = p.mFdMap.get(pos);
+
+        if (fd == null) {
+            Log.w(TAG, "nativeReadFileDescriptor: Not a FD at pos #" + pos);
+            return null;
+        }
+        nativeReadInt(nativePtr);
+        return fd;
+    }
+
+    /** Native method substitution */
+    public static boolean nativeHasFileDescriptors(long nativePtr) {
+        var p = getInstance(nativePtr);
+        return p.mFdMap.size() > 0;
+    }
+
+    /** Native method substitution */
+    public static boolean nativeHasFileDescriptorsInRange(long nativePtr, int offset, int length) {
+        var p = getInstance(nativePtr);
+
+        // Original code: hasFileDescriptorsInRange() in frameworks/native/libs/binder/Parcel.cpp
+        if (offset < 0 || length < 0) {
+            throw new IllegalArgumentException("Negative value not allowed: offset=" + offset
+                    + " length=" + length);
+        }
+        long limit = (long) offset + (long) length;
+        if (limit > p.mSize) {
+            throw new IllegalArgumentException("Out of range: offset=" + offset
+                    + " length=" + length + " dataSize=" + p.mSize);
+        }
+
+        for (var pos : p.mFdMap.keySet()) {
+            if (offset <= pos && (pos + FD_PAYLOAD_SIZE - 1) < (offset + length)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index 8a1fe62..825ab72 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -53,4 +53,9 @@
     public static StructStat stat(String path) throws ErrnoException {
         return RavenwoodRuntimeNative.stat(path);
     }
+
+    /** Ravenwood version of the OS API. */
+    public static void close(FileDescriptor fd) throws ErrnoException {
+        RavenwoodRuntimeNative.close(fd);
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
index e9b305e..2bc8e71 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
@@ -48,6 +48,8 @@
 
     public static native StructStat stat(String path) throws ErrnoException;
 
+    private static native void nClose(int fd) throws ErrnoException;
+
     public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
         return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
     }
@@ -83,4 +85,11 @@
 
         return nFstat(fdInt);
     }
+
+    /** See close(2) */
+    public static void close(FileDescriptor fd) throws ErrnoException {
+        var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
+
+        nClose(fdInt);
+    }
 }
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index e0a3e1c..ee84954 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -167,6 +167,11 @@
     return doStat(env, javaPath, false);
 }
 
+static void nClose(JNIEnv* env, jclass, jint fd) {
+    // Don't use TEMP_FAILURE_RETRY() on close(): https://lkml.org/lkml/2005/9/10/129
+    throwIfMinusOne(env, "close", close(fd));
+}
+
 // ---- Registration ----
 
 static const JNINativeMethod sMethods[] =
@@ -179,6 +184,7 @@
     { "nFstat", "(I)Landroid/system/StructStat;", (void*)nFstat },
     { "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat },
     { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
+    { "nClose", "(I)V", (void*)nClose },
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)