From 963cd006c45716b034f656bf7e7179e6476f7e4d Mon Sep 17 00:00:00 2001 From: Bjorn Bringert Date: Fri, 29 May 2009 14:05:12 +0100 Subject: [PATCH] Allow creating AssetFileDescriptors for MemoryFiles. This allows content providers to use in-memory data to implement ContentProvider.openAssetFile(), instead of just normal files and sockets as before. To test cross-process use of AssetFileDescriptors for MemoryFiles, a test content provider and a client for it are added to AndroidTests. Fixes http://b/issue?id=1871731 --- .../content/res/AssetFileDescriptor.java | 88 ++++++++ core/java/android/os/MemoryFile.java | 117 +++++++++- core/jni/android_os_MemoryFile.cpp | 30 ++- tests/AndroidTests/AndroidManifest.xml | 6 + .../content/MemoryFileProvider.java | 211 ++++++++++++++++++ .../content/MemoryFileProviderTest.java | 83 +++++++ .../android/unit_tests/os/MemoryFileTest.java | 52 ++++- 7 files changed, 575 insertions(+), 12 deletions(-) create mode 100644 tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProvider.java create mode 100644 tests/AndroidTests/src/com/android/unit_tests/content/MemoryFileProviderTest.java diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java index 231e3e24a27c8..a37e4e8cc3bf3 100644 --- a/core/java/android/content/res/AssetFileDescriptor.java +++ b/core/java/android/content/res/AssetFileDescriptor.java @@ -16,6 +16,7 @@ package android.content.res; +import android.os.MemoryFile; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; @@ -24,6 +25,8 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.FileChannel; /** * File descriptor of an entry in the AssetManager. This provides your own @@ -123,6 +126,13 @@ public class AssetFileDescriptor implements Parcelable { mFd.close(); } + /** + * Checks whether this file descriptor is for a memory file. + */ + private boolean isMemoryFile() throws IOException { + return MemoryFile.isMemoryFile(mFd.getFileDescriptor()); + } + /** * Create and return a new auto-close input stream for this asset. This * will either return a full asset {@link AutoCloseInputStream}, or @@ -132,6 +142,12 @@ public class AssetFileDescriptor implements Parcelable { * should only call this once for a particular asset. */ public FileInputStream createInputStream() throws IOException { + if (isMemoryFile()) { + if (mLength > Integer.MAX_VALUE) { + throw new IOException("File length too large for a memory file: " + mLength); + } + return new AutoCloseMemoryFileInputStream(mFd, (int)mLength); + } if (mLength < 0) { return new ParcelFileDescriptor.AutoCloseInputStream(mFd); } @@ -261,6 +277,66 @@ public class AssetFileDescriptor implements Parcelable { } } + /** + * An input stream that reads from a MemoryFile and closes it when the stream is closed. + * This extends FileInputStream just because {@link #createInputStream} returns + * a FileInputStream. All the FileInputStream methods are + * overridden to use the MemoryFile instead. + */ + private static class AutoCloseMemoryFileInputStream extends FileInputStream { + private ParcelFileDescriptor mParcelFd; + private MemoryFile mFile; + private InputStream mStream; + + public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length) + throws IOException { + super(fd.getFileDescriptor()); + mParcelFd = fd; + mFile = new MemoryFile(fd.getFileDescriptor(), length, "r"); + mStream = mFile.getInputStream(); + } + + @Override + public int available() throws IOException { + return mStream.available(); + } + + @Override + public void close() throws IOException { + mParcelFd.close(); // must close ParcelFileDescriptor, not just the file descriptor, + // since it could be a subclass of ParcelFileDescriptor. + // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases + // a content provider + mFile.close(); // to unmap the memory file from the address space. + mStream.close(); // doesn't actually do anything + } + + @Override + public FileChannel getChannel() { + return null; + } + + @Override + public int read() throws IOException { + return mStream.read(); + } + + @Override + public int read(byte[] buffer, int offset, int count) throws IOException { + return mStream.read(buffer, offset, count); + } + + @Override + public int read(byte[] buffer) throws IOException { + return mStream.read(buffer); + } + + @Override + public long skip(long count) throws IOException { + return mStream.skip(count); + } + } + /** * An OutputStream you can create on a ParcelFileDescriptor, which will * take care of calling {@link ParcelFileDescriptor#close @@ -345,4 +421,16 @@ public class AssetFileDescriptor implements Parcelable { return new AssetFileDescriptor[size]; } }; + + /** + * Creates an AssetFileDescriptor from a memory file. + * + * @hide + */ + public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile) + throws IOException { + ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor(); + return new AssetFileDescriptor(fd, 0, memoryFile.length()); + } + } diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java index 65e83c77957a3..7e4cf8ae3284e 100644 --- a/core/java/android/os/MemoryFile.java +++ b/core/java/android/os/MemoryFile.java @@ -37,9 +37,14 @@ public class MemoryFile { private static String TAG = "MemoryFile"; + // mmap(2) protection flags from + private static final int PROT_READ = 0x1; + private static final int PROT_WRITE = 0x2; + private static native FileDescriptor native_open(String name, int length) throws IOException; // returns memory address for ashmem region - private static native int native_mmap(FileDescriptor fd, int length) throws IOException; + private static native int native_mmap(FileDescriptor fd, int length, int mode) + throws IOException; private static native void native_munmap(int addr, int length) throws IOException; private static native void native_close(FileDescriptor fd); private static native int native_read(FileDescriptor fd, int address, byte[] buffer, @@ -47,14 +52,16 @@ public class MemoryFile private static native void native_write(FileDescriptor fd, int address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; + private static native boolean native_is_ashmem_region(FileDescriptor fd) throws IOException; private FileDescriptor mFD; // ashmem file descriptor private int mAddress; // address of ashmem memory private int mLength; // total length of our ashmem region private boolean mAllowPurging = false; // true if our ashmem region is unpinned + private final boolean mOwnsRegion; // false if this is a ref to an existing ashmem region /** - * MemoryFile constructor. + * Allocates a new ashmem region. The region is initially not purgable. * * @param name optional name for the file (can be null). * @param length of the memory file in bytes. @@ -63,11 +70,43 @@ public class MemoryFile public MemoryFile(String name, int length) throws IOException { mLength = length; mFD = native_open(name, length); - mAddress = native_mmap(mFD, length); + mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); + mOwnsRegion = true; } /** - * Closes and releases all resources for the memory file. + * Creates a reference to an existing memory file. Changes to the original file + * will be available through this reference. + * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail. + * + * @param fd File descriptor for an existing memory file, as returned by + * {@link #getFileDescriptor()}. This file descriptor will be closed + * by {@link #close()}. + * @param length Length of the memory file in bytes. + * @param mode File mode. Currently only "r" for read-only access is supported. + * @throws NullPointerException if fd is null. + * @throws IOException If fd does not refer to an existing memory file, + * or if the file mode of the existing memory file is more restrictive + * than mode. + * + * @hide + */ + public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException { + if (fd == null) { + throw new NullPointerException("File descriptor is null."); + } + if (!isMemoryFile(fd)) { + throw new IllegalArgumentException("Not a memory file."); + } + mLength = length; + mFD = fd; + mAddress = native_mmap(mFD, length, modeToProt(mode)); + mOwnsRegion = false; + } + + /** + * Closes the memory file. If there are no other open references to the memory + * file, it will be deleted. */ public void close() { deactivate(); @@ -76,7 +115,14 @@ public class MemoryFile } } - private void deactivate() { + /** + * Unmaps the memory file from the process's memory space, but does not close it. + * After this method has been called, read and write operations through this object + * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor. + * + * @hide + */ + public void deactivate() { if (!isDeactivated()) { try { native_munmap(mAddress, mLength); @@ -135,6 +181,9 @@ public class MemoryFile * @return previous value of allowPurging */ synchronized public boolean allowPurging(boolean allowPurging) throws IOException { + if (!mOwnsRegion) { + throw new IOException("Only the owner can make ashmem regions purgable."); + } boolean oldValue = mAllowPurging; if (oldValue != allowPurging) { native_pin(mFD, !allowPurging); @@ -210,6 +259,64 @@ public class MemoryFile native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); } + /** + * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()} + * for caveats. This must be here to allow classes outside android.osfd is not a valid file descriptor. + * + * @hide + */ + public static boolean isMemoryFile(FileDescriptor fd) throws IOException { + return native_is_ashmem_region(fd); + } + + /** + * Converts a file mode string to a prot value as expected by + * native_mmap(). + * + * @throws IllegalArgumentException if the file mode is invalid. + */ + private static int modeToProt(String mode) { + if ("r".equals(mode)) { + return PROT_READ; + } else { + throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'"); + } + } + private class MemoryInputStream extends InputStream { private int mMark = 0; diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp index 6c16150a4cc8b..8643393f7e8a4 100644 --- a/core/jni/android_os_MemoryFile.cpp +++ b/core/jni/android_os_MemoryFile.cpp @@ -39,17 +39,17 @@ static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring na if (result < 0) { jniThrowException(env, "java/io/IOException", "ashmem_create_region failed"); - return NULL; + return NULL; } return jniCreateFileDescriptor(env, result); } static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, - jint length) + jint length, jint prot) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - jint result = (jint)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0); if (!result) jniThrowException(env, "java/io/IOException", "mmap failed"); return result; @@ -118,14 +118,36 @@ static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDe } } +static jboolean android_os_MemoryFile_is_ashmem_region(JNIEnv* env, jobject clazz, + jobject fileDescriptor) { + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region. + // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel + // should return ENOTTY for all other valid file descriptors + int result = ashmem_get_size_region(fd); + if (result < 0) { + if (errno == ENOTTY) { + // ENOTTY means that the ioctl does not apply to this object, + // i.e., it is not an ashmem region. + return JNI_FALSE; + } + // Some other error, throw exception + jniThrowIOException(env, errno); + return JNI_FALSE; + } + return JNI_TRUE; +} + static const JNINativeMethod methods[] = { {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, - {"native_mmap", "(Ljava/io/FileDescriptor;I)I", (void*)android_os_MemoryFile_mmap}, + {"native_mmap", "(Ljava/io/FileDescriptor;II)I", (void*)android_os_MemoryFile_mmap}, {"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap}, {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, {"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read}, {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write}, {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, + {"native_is_ashmem_region", "(Ljava/io/FileDescriptor;)Z", + (void*)android_os_MemoryFile_is_ashmem_region} }; static const char* const kClassPathName = "android/os/MemoryFile"; diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml index 843d844379ae4..fd6e6d8ec3d61 100644 --- a/tests/AndroidTests/AndroidManifest.xml +++ b/tests/AndroidTests/AndroidManifest.xml @@ -206,6 +206,12 @@ + + + +