Loading core/java/android/os/IProxyFileDescriptorCallback.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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 android.os; import android.system.ErrnoException; /** * Callback that handles file system requests from ProxyFileDescriptor. * @hide */ public interface IProxyFileDescriptorCallback { /** * Returns size of bytes provided by the file descriptor. * @return Size of bytes * @throws ErrnoException */ long onGetSize() throws ErrnoException; /** * Provides bytes read from file descriptor. * It needs to return exact requested size of bytes unless it reaches file end. * @param offset Where to read bytes from. * @param size Size for read bytes. * @param data Byte array to store read bytes. * @return Size of bytes returned by the function. * @throws ErrnoException */ int onRead(long offset, int size, byte[] data) throws ErrnoException; /** * Handles bytes written to file descriptor. * @param offset Where to write bytes to. * @param size Size for write bytes. * @param data Byte array to be written to somewhere. * @return Size of bytes processed by the function. * @throws ErrnoException */ int onWrite(long offset, int size, byte[] data) throws ErrnoException; /** * Processes fsync request. * @throws ErrnoException */ void onFsync() throws ErrnoException; } core/java/com/android/internal/os/FuseAppLoop.java 0 → 100644 +222 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.internal.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IProxyFileDescriptorCallback; import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.OsConstants; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; public class FuseAppLoop { private static final String TAG = "FuseAppLoop"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final int ROOT_INODE = 1; private static final int MIN_INODE = 2; private final Object mLock = new Object(); private final File mParent; @GuardedBy("mLock") private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>(); @GuardedBy("mLock") private boolean mActive = true; /** * Sequential number can be used as file name and inode in AppFuse. * 0 is regarded as an error, 1 is mount point. So we start the number from 2. */ @GuardedBy("mLock") private int mNextInode = MIN_INODE; private FuseAppLoop(@NonNull File parent) { mParent = parent; } public static @NonNull FuseAppLoop open( @NonNull File parent, @NonNull ParcelFileDescriptor fd) { Preconditions.checkNotNull(parent); Preconditions.checkNotNull(fd); final FuseAppLoop bridge = new FuseAppLoop(parent); final int rawFd = fd.detachFd(); new Thread(new Runnable() { @Override public void run() { bridge.native_start_loop(rawFd); } }, TAG).start(); return bridge; } public @NonNull ParcelFileDescriptor openFile(int mode, IProxyFileDescriptorCallback callback) throws UnmountedException, IOException { int id; synchronized (mLock) { if (!mActive) { throw new UnmountedException(); } if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) { throw new IOException("Too many opened files."); } while (true) { id = mNextInode; mNextInode++; if (mNextInode < 0) { mNextInode = MIN_INODE; } if (mCallbackMap.get(id) == null) { break; } } // Register callback after we succeed to create pfd. mCallbackMap.put(id, new CallbackEntry(callback)); } try { return ParcelFileDescriptor.open(new File(mParent, String.valueOf(id)), mode); } catch (FileNotFoundException error) { synchronized (mLock) { mCallbackMap.remove(id); } throw error; } } public @Nullable File getMountPoint() { synchronized (mLock) { return mActive ? mParent : null; } } private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException { final CallbackEntry entry = mCallbackMap.get(checkInode(inode)); if (entry != null) { return entry; } else { throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT); } } // Called by JNI. @SuppressWarnings("unused") private long onGetSize(long inode) { synchronized(mLock) { try { return getCallbackEntryOrThrowLocked(inode).callback.onGetSize(); } catch (ErrnoException exp) { return -exp.errno; } } } // Called by JNI. @SuppressWarnings("unused") private int onOpen(long inode) { synchronized(mLock) { try { final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode); if (entry.opened) { throw new ErrnoException("onOpen", OsConstants.EMFILE); } entry.opened = true; // Use inode as file handle. It's OK because AppFuse does not allow to open the same // file twice. return (int) inode; } catch (ErrnoException exp) { return -exp.errno; } } } // Called by JNI. @SuppressWarnings("unused") private int onFsync(long inode) { synchronized(mLock) { try { getCallbackEntryOrThrowLocked(inode).callback.onFsync(); return 0; } catch (ErrnoException exp) { return -exp.errno; } } } // Called by JNI. @SuppressWarnings("unused") private int onRelease(long inode) { synchronized(mLock) { mCallbackMap.remove(checkInode(inode)); if (mCallbackMap.size() == 0) { mActive = false; return -1; } return 0; } } // Called by JNI. @SuppressWarnings("unused") private int onRead(long inode, long offset, int size, byte[] bytes) { synchronized(mLock) { try { return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes); } catch (ErrnoException exp) { return -exp.errno; } } } // Called by JNI. @SuppressWarnings("unused") private int onWrite(long inode, long offset, int size, byte[] bytes) { synchronized(mLock) { try { return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes); } catch (ErrnoException exp) { return -exp.errno; } } } native boolean native_start_loop(int fd); private static int checkInode(long inode) { Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode"); return (int) inode; } public static class UnmountedException extends Exception {} private static class CallbackEntry { final IProxyFileDescriptorCallback callback; boolean opened; CallbackEntry(IProxyFileDescriptorCallback callback) { Preconditions.checkNotNull(callback); this.callback = callback; } } } core/jni/Android.mk +3 −0 Original line number Diff line number Diff line Loading @@ -183,6 +183,7 @@ LOCAL_SRC_FILES:= \ android_content_res_Configuration.cpp \ android_animation_PropertyValuesHolder.cpp \ com_android_internal_net_NetworkStatsFactory.cpp \ com_android_internal_os_FuseAppLoop.cpp \ com_android_internal_os_PathClassLoaderFactory.cpp \ com_android_internal_os_Zygote.cpp \ com_android_internal_util_VirtualRefBasePtr.cpp \ Loading @@ -201,6 +202,7 @@ LOCAL_C_INCLUDES += \ $(TOP)/frameworks/base/media/jni \ $(TOP)/system/core/base/include \ $(TOP)/system/core/include \ $(TOP)/system/core/libappfuse/include \ $(TOP)/system/media/camera/include \ $(TOP)/system/netd/include \ external/giflib \ Loading Loading @@ -230,6 +232,7 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_SHARED_LIBRARIES := \ libmemtrack \ libandroidfw \ libappfuse \ libbase \ libexpat \ libnativehelper \ Loading core/jni/AndroidRuntime.cpp +2 −1 Original line number Diff line number Diff line Loading @@ -201,6 +201,7 @@ extern int register_android_content_res_Configuration(JNIEnv* env); extern int register_android_animation_PropertyValuesHolder(JNIEnv *env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); extern int register_com_android_internal_os_PathClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_Zygote(JNIEnv *env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); Loading Loading @@ -1419,7 +1420,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_animation_PropertyValuesHolder), REG_JNI(register_com_android_internal_content_NativeLibraryHelper), REG_JNI(register_com_android_internal_net_NetworkStatsFactory), REG_JNI(register_com_android_internal_os_FuseAppLoop), }; /* Loading core/jni/com_android_internal_os_FuseAppLoop.cpp 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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. */ #define LOG_TAG "FuseAppLoopJNI" #define LOG_NDEBUG 0 #include <stdlib.h> #include <sys/stat.h> #include <android_runtime/Log.h> #include <android-base/logging.h> #include <android-base/unique_fd.h> #include <jni.h> #include <libappfuse/FuseAppLoop.h> #include <nativehelper/ScopedLocalRef.h> #include "core_jni_helpers.h" namespace android { namespace { constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop"; jclass gFuseAppLoopClass; jmethodID gOnGetSizeMethod; jmethodID gOnOpenMethod; jmethodID gOnFsyncMethod; jmethodID gOnReleaseMethod; jmethodID gOnReadMethod; jmethodID gOnWriteMethod; class Callback : public fuse::FuseAppLoopCallback { private: static constexpr size_t kBufferSize = std::max(fuse::kFuseMaxWrite, fuse::kFuseMaxRead); static_assert(kBufferSize <= INT32_MAX, "kBufferSize should be fit in int32_t."); JNIEnv* const mEnv; jobject const mSelf; ScopedLocalRef<jbyteArray> mJniBuffer; bool mActive; template <typename T> T checkException(T result) const { if (mEnv->ExceptionCheck()) { LOGE_EX(mEnv, nullptr); mEnv->ExceptionClear(); return -EIO; } return result; } public: Callback(JNIEnv* env, jobject self) : mEnv(env), mSelf(self), mJniBuffer(env, nullptr), mActive(true) {} bool Init() { mJniBuffer.reset(mEnv->NewByteArray(kBufferSize)); return mJniBuffer.get(); } bool IsActive() override { return mActive; } int64_t OnGetSize(uint64_t inode) override { return checkException(mEnv->CallLongMethod(mSelf, gOnGetSizeMethod, inode)); } int32_t OnOpen(uint64_t inode) override { return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode)); } int32_t OnFsync(uint64_t inode) override { return checkException(mEnv->CallIntMethod(mSelf, gOnFsyncMethod, inode)); } int32_t OnRelease(uint64_t inode) override { if (checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)) == -1) { mActive = false; } return fuse::kFuseSuccess; } int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override { CHECK_LE(size, static_cast<uint32_t>(kBufferSize)); const int32_t result = checkException(mEnv->CallIntMethod( mSelf, gOnReadMethod, inode, offset, size, mJniBuffer.get())); if (result <= 0) { return result; } if (result > static_cast<int32_t>(size)) { LOG(ERROR) << "Returned size is too large."; return -EIO; } mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer)); CHECK(!mEnv->ExceptionCheck()); return checkException(result); } int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override { CHECK_LE(size, static_cast<uint32_t>(kBufferSize)); mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast<const jbyte*>(buffer)); CHECK(!mEnv->ExceptionCheck()); return checkException(mEnv->CallIntMethod( mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get())); } }; jboolean com_android_internal_os_FuseAppLoop_start_loop(JNIEnv* env, jobject self, jint jfd) { base::unique_fd fd(jfd); Callback callback(env, self); if (!callback.Init()) { LOG(ERROR) << "Failed to init callback"; return JNI_FALSE; } return fuse::StartFuseAppLoop(fd.release(), &callback); } const JNINativeMethod methods[] = { { "native_start_loop", "(I)Z", (void *) com_android_internal_os_FuseAppLoop_start_loop } }; } // namespace int register_com_android_internal_os_FuseAppLoop(JNIEnv* env) { gFuseAppLoopClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME)); gOnGetSizeMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onGetSize", "(J)J"); gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(J)I"); gOnFsyncMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onFsync", "(J)I"); gOnReleaseMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRelease", "(J)I"); gOnReadMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRead", "(JJI[B)I"); gOnWriteMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onWrite", "(JJI[B)I"); RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods)); return 0; } } // namespace android Loading
core/java/android/os/IProxyFileDescriptorCallback.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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 android.os; import android.system.ErrnoException; /** * Callback that handles file system requests from ProxyFileDescriptor. * @hide */ public interface IProxyFileDescriptorCallback { /** * Returns size of bytes provided by the file descriptor. * @return Size of bytes * @throws ErrnoException */ long onGetSize() throws ErrnoException; /** * Provides bytes read from file descriptor. * It needs to return exact requested size of bytes unless it reaches file end. * @param offset Where to read bytes from. * @param size Size for read bytes. * @param data Byte array to store read bytes. * @return Size of bytes returned by the function. * @throws ErrnoException */ int onRead(long offset, int size, byte[] data) throws ErrnoException; /** * Handles bytes written to file descriptor. * @param offset Where to write bytes to. * @param size Size for write bytes. * @param data Byte array to be written to somewhere. * @return Size of bytes processed by the function. * @throws ErrnoException */ int onWrite(long offset, int size, byte[] data) throws ErrnoException; /** * Processes fsync request. * @throws ErrnoException */ void onFsync() throws ErrnoException; }
core/java/com/android/internal/os/FuseAppLoop.java 0 → 100644 +222 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.internal.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IProxyFileDescriptorCallback; import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.OsConstants; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; public class FuseAppLoop { private static final String TAG = "FuseAppLoop"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final int ROOT_INODE = 1; private static final int MIN_INODE = 2; private final Object mLock = new Object(); private final File mParent; @GuardedBy("mLock") private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>(); @GuardedBy("mLock") private boolean mActive = true; /** * Sequential number can be used as file name and inode in AppFuse. * 0 is regarded as an error, 1 is mount point. So we start the number from 2. */ @GuardedBy("mLock") private int mNextInode = MIN_INODE; private FuseAppLoop(@NonNull File parent) { mParent = parent; } public static @NonNull FuseAppLoop open( @NonNull File parent, @NonNull ParcelFileDescriptor fd) { Preconditions.checkNotNull(parent); Preconditions.checkNotNull(fd); final FuseAppLoop bridge = new FuseAppLoop(parent); final int rawFd = fd.detachFd(); new Thread(new Runnable() { @Override public void run() { bridge.native_start_loop(rawFd); } }, TAG).start(); return bridge; } public @NonNull ParcelFileDescriptor openFile(int mode, IProxyFileDescriptorCallback callback) throws UnmountedException, IOException { int id; synchronized (mLock) { if (!mActive) { throw new UnmountedException(); } if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) { throw new IOException("Too many opened files."); } while (true) { id = mNextInode; mNextInode++; if (mNextInode < 0) { mNextInode = MIN_INODE; } if (mCallbackMap.get(id) == null) { break; } } // Register callback after we succeed to create pfd. mCallbackMap.put(id, new CallbackEntry(callback)); } try { return ParcelFileDescriptor.open(new File(mParent, String.valueOf(id)), mode); } catch (FileNotFoundException error) { synchronized (mLock) { mCallbackMap.remove(id); } throw error; } } public @Nullable File getMountPoint() { synchronized (mLock) { return mActive ? mParent : null; } } private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException { final CallbackEntry entry = mCallbackMap.get(checkInode(inode)); if (entry != null) { return entry; } else { throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT); } } // Called by JNI. @SuppressWarnings("unused") private long onGetSize(long inode) { synchronized(mLock) { try { return getCallbackEntryOrThrowLocked(inode).callback.onGetSize(); } catch (ErrnoException exp) { return -exp.errno; } } } // Called by JNI. @SuppressWarnings("unused") private int onOpen(long inode) { synchronized(mLock) { try { final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode); if (entry.opened) { throw new ErrnoException("onOpen", OsConstants.EMFILE); } entry.opened = true; // Use inode as file handle. It's OK because AppFuse does not allow to open the same // file twice. return (int) inode; } catch (ErrnoException exp) { return -exp.errno; } } } // Called by JNI. @SuppressWarnings("unused") private int onFsync(long inode) { synchronized(mLock) { try { getCallbackEntryOrThrowLocked(inode).callback.onFsync(); return 0; } catch (ErrnoException exp) { return -exp.errno; } } } // Called by JNI. @SuppressWarnings("unused") private int onRelease(long inode) { synchronized(mLock) { mCallbackMap.remove(checkInode(inode)); if (mCallbackMap.size() == 0) { mActive = false; return -1; } return 0; } } // Called by JNI. @SuppressWarnings("unused") private int onRead(long inode, long offset, int size, byte[] bytes) { synchronized(mLock) { try { return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes); } catch (ErrnoException exp) { return -exp.errno; } } } // Called by JNI. @SuppressWarnings("unused") private int onWrite(long inode, long offset, int size, byte[] bytes) { synchronized(mLock) { try { return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes); } catch (ErrnoException exp) { return -exp.errno; } } } native boolean native_start_loop(int fd); private static int checkInode(long inode) { Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode"); return (int) inode; } public static class UnmountedException extends Exception {} private static class CallbackEntry { final IProxyFileDescriptorCallback callback; boolean opened; CallbackEntry(IProxyFileDescriptorCallback callback) { Preconditions.checkNotNull(callback); this.callback = callback; } } }
core/jni/Android.mk +3 −0 Original line number Diff line number Diff line Loading @@ -183,6 +183,7 @@ LOCAL_SRC_FILES:= \ android_content_res_Configuration.cpp \ android_animation_PropertyValuesHolder.cpp \ com_android_internal_net_NetworkStatsFactory.cpp \ com_android_internal_os_FuseAppLoop.cpp \ com_android_internal_os_PathClassLoaderFactory.cpp \ com_android_internal_os_Zygote.cpp \ com_android_internal_util_VirtualRefBasePtr.cpp \ Loading @@ -201,6 +202,7 @@ LOCAL_C_INCLUDES += \ $(TOP)/frameworks/base/media/jni \ $(TOP)/system/core/base/include \ $(TOP)/system/core/include \ $(TOP)/system/core/libappfuse/include \ $(TOP)/system/media/camera/include \ $(TOP)/system/netd/include \ external/giflib \ Loading Loading @@ -230,6 +232,7 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_SHARED_LIBRARIES := \ libmemtrack \ libandroidfw \ libappfuse \ libbase \ libexpat \ libnativehelper \ Loading
core/jni/AndroidRuntime.cpp +2 −1 Original line number Diff line number Diff line Loading @@ -201,6 +201,7 @@ extern int register_android_content_res_Configuration(JNIEnv* env); extern int register_android_animation_PropertyValuesHolder(JNIEnv *env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); extern int register_com_android_internal_os_PathClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_Zygote(JNIEnv *env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); Loading Loading @@ -1419,7 +1420,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_animation_PropertyValuesHolder), REG_JNI(register_com_android_internal_content_NativeLibraryHelper), REG_JNI(register_com_android_internal_net_NetworkStatsFactory), REG_JNI(register_com_android_internal_os_FuseAppLoop), }; /* Loading
core/jni/com_android_internal_os_FuseAppLoop.cpp 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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. */ #define LOG_TAG "FuseAppLoopJNI" #define LOG_NDEBUG 0 #include <stdlib.h> #include <sys/stat.h> #include <android_runtime/Log.h> #include <android-base/logging.h> #include <android-base/unique_fd.h> #include <jni.h> #include <libappfuse/FuseAppLoop.h> #include <nativehelper/ScopedLocalRef.h> #include "core_jni_helpers.h" namespace android { namespace { constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop"; jclass gFuseAppLoopClass; jmethodID gOnGetSizeMethod; jmethodID gOnOpenMethod; jmethodID gOnFsyncMethod; jmethodID gOnReleaseMethod; jmethodID gOnReadMethod; jmethodID gOnWriteMethod; class Callback : public fuse::FuseAppLoopCallback { private: static constexpr size_t kBufferSize = std::max(fuse::kFuseMaxWrite, fuse::kFuseMaxRead); static_assert(kBufferSize <= INT32_MAX, "kBufferSize should be fit in int32_t."); JNIEnv* const mEnv; jobject const mSelf; ScopedLocalRef<jbyteArray> mJniBuffer; bool mActive; template <typename T> T checkException(T result) const { if (mEnv->ExceptionCheck()) { LOGE_EX(mEnv, nullptr); mEnv->ExceptionClear(); return -EIO; } return result; } public: Callback(JNIEnv* env, jobject self) : mEnv(env), mSelf(self), mJniBuffer(env, nullptr), mActive(true) {} bool Init() { mJniBuffer.reset(mEnv->NewByteArray(kBufferSize)); return mJniBuffer.get(); } bool IsActive() override { return mActive; } int64_t OnGetSize(uint64_t inode) override { return checkException(mEnv->CallLongMethod(mSelf, gOnGetSizeMethod, inode)); } int32_t OnOpen(uint64_t inode) override { return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode)); } int32_t OnFsync(uint64_t inode) override { return checkException(mEnv->CallIntMethod(mSelf, gOnFsyncMethod, inode)); } int32_t OnRelease(uint64_t inode) override { if (checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)) == -1) { mActive = false; } return fuse::kFuseSuccess; } int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override { CHECK_LE(size, static_cast<uint32_t>(kBufferSize)); const int32_t result = checkException(mEnv->CallIntMethod( mSelf, gOnReadMethod, inode, offset, size, mJniBuffer.get())); if (result <= 0) { return result; } if (result > static_cast<int32_t>(size)) { LOG(ERROR) << "Returned size is too large."; return -EIO; } mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer)); CHECK(!mEnv->ExceptionCheck()); return checkException(result); } int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override { CHECK_LE(size, static_cast<uint32_t>(kBufferSize)); mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast<const jbyte*>(buffer)); CHECK(!mEnv->ExceptionCheck()); return checkException(mEnv->CallIntMethod( mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get())); } }; jboolean com_android_internal_os_FuseAppLoop_start_loop(JNIEnv* env, jobject self, jint jfd) { base::unique_fd fd(jfd); Callback callback(env, self); if (!callback.Init()) { LOG(ERROR) << "Failed to init callback"; return JNI_FALSE; } return fuse::StartFuseAppLoop(fd.release(), &callback); } const JNINativeMethod methods[] = { { "native_start_loop", "(I)Z", (void *) com_android_internal_os_FuseAppLoop_start_loop } }; } // namespace int register_com_android_internal_os_FuseAppLoop(JNIEnv* env) { gFuseAppLoopClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME)); gOnGetSizeMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onGetSize", "(J)J"); gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(J)I"); gOnFsyncMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onFsync", "(J)I"); gOnReleaseMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRelease", "(J)I"); gOnReadMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRead", "(JJI[B)I"); gOnWriteMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onWrite", "(JJI[B)I"); RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods)); return 0; } } // namespace android