From 812c95d37dccf8a1fcef55c6999c6d69ecbac400 Mon Sep 17 00:00:00 2001 From: Daichi Hirono Date: Wed, 8 Feb 2017 16:20:20 +0900 Subject: [PATCH] Allow apps to process ProxyFDCallback asynchrnously. Previously callback methods of ProxyFileDescriptorCallback were invoked on a background thread prepared in the framework. So all methods were invoked and processed synchronously. This was problem because if it took time to fetch bytes of one file, operations for other files were also blocked. The CL changes ProxyFileDescriptorCallback methods to be invoked on Handler passed by apps. Now application can prepare a Handler per file so that one file does not block others. Bug: 35229514 Test: cts-tradefed run cts -m CtsOsTestCases -t android.os.storage.cts.StorageManagerTest#testOpenProxyFileDescriptor_async Change-Id: Ibadc4aad4c0373a3da586459a8f775e40288c895 (cherry picked from commit 4f156065c860d916b649e2b464e9405cafc732e9) --- api/current.txt | 1 + api/system-current.txt | 1 + api/test-current.txt | 1 + .../android/os/storage/StorageManager.java | 79 +++-- .../com/android/internal/os/FuseAppLoop.java | 315 ++++++++++++------ .../os/FuseUnavailableMountException.java | 26 ++ .../com_android_internal_os_FuseAppLoop.cpp | 238 ++++++++----- .../StorageManagerIntegrationTest.java | 4 +- .../android/server/StorageManagerService.java | 61 ++-- .../android/server/storage/AppFuseBridge.java | 44 +-- 10 files changed, 502 insertions(+), 268 deletions(-) create mode 100644 core/java/com/android/internal/os/FuseUnavailableMountException.java diff --git a/api/current.txt b/api/current.txt index 788c1d431de21..307d262ea549f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -31898,6 +31898,7 @@ package android.os.storage { method public boolean isObbMounted(java.lang.String); method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException; + method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException; method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException; method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException; method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener); diff --git a/api/system-current.txt b/api/system-current.txt index c07b9284cd754..47f227e7daa92 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -34733,6 +34733,7 @@ package android.os.storage { method public boolean isObbMounted(java.lang.String); method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException; + method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException; method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException; method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException; method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener); diff --git a/api/test-current.txt b/api/test-current.txt index 00bc3c2d28aa2..c07e91e5a2170 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -32035,6 +32035,7 @@ package android.os.storage { method public boolean isObbMounted(java.lang.String); method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException; + method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException; method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException; method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException; method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener); diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 7e1b5abf8112f..e5d73e04bda6b 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -59,6 +59,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.AppFuseMount; import com.android.internal.os.FuseAppLoop; +import com.android.internal.os.FuseAppLoop.UnmountedException; +import com.android.internal.os.FuseUnavailableMountException; import com.android.internal.os.RoSystemProperties; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; @@ -82,6 +84,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import libcore.io.IoUtils; /** * StorageManager is the interface to the systems storage service. The storage @@ -1390,53 +1393,52 @@ public class StorageManager { /** {@hide} */ @VisibleForTesting public @NonNull ParcelFileDescriptor openProxyFileDescriptor( - int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory) + int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory) throws IOException { MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1); // Retry is needed because the mount point mFuseAppLoop is using may be unmounted before // invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount // the bridge by calling mountProxyFileDescriptorBridge. - int retry = 3; - while (retry-- > 0) { + while (true) { try { synchronized (mFuseAppLoopLock) { + boolean newlyCreated = false; if (mFuseAppLoop == null) { final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge(); if (mount == null) { - Log.e(TAG, "Failed to open proxy file bridge."); - throw new IOException("Failed to open proxy file bridge."); + throw new IOException("Failed to mount proxy bridge"); } - mFuseAppLoop = FuseAppLoop.open(mount.mountPointId, mount.fd, factory); + mFuseAppLoop = new FuseAppLoop(mount.mountPointId, mount.fd, factory); + newlyCreated = true; + } + if (handler == null) { + handler = new Handler(Looper.getMainLooper()); } - try { - final int fileId = mFuseAppLoop.registerCallback(callback); - final ParcelFileDescriptor pfd = - mStorageManager.openProxyFileDescriptor( - mFuseAppLoop.getMountPointId(), fileId, mode); - if (pfd != null) { - return pfd; + final int fileId = mFuseAppLoop.registerCallback(callback, handler); + final ParcelFileDescriptor pfd = mStorageManager.openProxyFileDescriptor( + mFuseAppLoop.getMountPointId(), fileId, mode); + if (pfd == null) { + mFuseAppLoop.unregisterCallback(fileId); + throw new FuseUnavailableMountException( + mFuseAppLoop.getMountPointId()); + } + return pfd; + } catch (FuseUnavailableMountException exception) { + // The bridge is being unmounted. Tried to recreate it unless the bridge was + // just created. + if (newlyCreated) { + throw new IOException(exception); } - // Probably the bridge is being unmounted but mFuseAppLoop has not been - // noticed it yet. - mFuseAppLoop.unregisterCallback(fileId); - } catch (FuseAppLoop.UnmountedException error) { - Log.d(TAG, "mFuseAppLoop has been already unmounted."); mFuseAppLoop = null; continue; } } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - break; - } } catch (RemoteException e) { - e.rethrowFromSystemServer(); + // Cannot recover from remote exception. + throw new IOException(e); } } - - throw new IOException("Failed to mount bridge."); } /** @@ -1448,16 +1450,37 @@ public class StorageManager { * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or * {@link ParcelFileDescriptor#MODE_READ_WRITE} * @param callback Callback to process file operation requests issued on returned file - * descriptor. The callback is invoked on a thread managed by the framework. + * descriptor. * @return Seekable ParcelFileDescriptor. * @throws IOException */ public @NonNull ParcelFileDescriptor openProxyFileDescriptor( int mode, ProxyFileDescriptorCallback callback) throws IOException { - return openProxyFileDescriptor(mode, callback, null); + return openProxyFileDescriptor(mode, callback, null, null); } + /** + * Opens seekable ParcelFileDescriptor that routes file operation requests to + * ProxyFileDescriptorCallback. + * + * @param mode The desired access mode, must be one of + * {@link ParcelFileDescriptor#MODE_READ_ONLY}, + * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or + * {@link ParcelFileDescriptor#MODE_READ_WRITE} + * @param callback Callback to process file operation requests issued on returned file + * descriptor. + * @param handler Handler that invokes callback methods. + * @return Seekable ParcelFileDescriptor. + * @throws IOException + */ + public @NonNull ParcelFileDescriptor openProxyFileDescriptor( + int mode, ProxyFileDescriptorCallback callback, Handler handler) + throws IOException { + return openProxyFileDescriptor(mode, callback, handler, null); + } + + /** {@hide} */ @VisibleForTesting public int getProxyFileDescriptorMountPointId() { diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java index 3603b6df11dad..8edd637ea8148 100644 --- a/core/java/com/android/internal/os/FuseAppLoop.java +++ b/core/java/com/android/internal/os/FuseAppLoop.java @@ -19,16 +19,16 @@ package com.android.internal.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.ProxyFileDescriptorCallback; +import android.os.Handler; 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.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; - -import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ThreadFactory; public class FuseAppLoop { @@ -42,14 +42,21 @@ public class FuseAppLoop { return new Thread(r, TAG); } }; + private static final int FUSE_OK = 0; private final Object mLock = new Object(); private final int mMountPointId; private final Thread mThread; + private final Handler mDefaultHandler; + + private static final int CMD_FSYNC = 1; @GuardedBy("mLock") private final SparseArray mCallbackMap = new SparseArray<>(); + @GuardedBy("mLock") + private final BytesMap mBytesMap = new BytesMap(); + /** * 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. @@ -57,38 +64,40 @@ public class FuseAppLoop { @GuardedBy("mLock") private int mNextInode = MIN_INODE; - private FuseAppLoop( + @GuardedBy("mLock") + private long mInstance; + + public FuseAppLoop( int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) { mMountPointId = mountPointId; - final int rawFd = fd.detachFd(); if (factory == null) { factory = sDefaultThreadFactory; } - mThread = factory.newThread(new Runnable() { - @Override - public void run() { - // rawFd is closed by native_start_loop. Java code does not need to close it. - native_start_loop(rawFd); + mInstance = native_new(fd.detachFd()); + mThread = factory.newThread(() -> { + native_start(mInstance); + synchronized (mLock) { + native_delete(mInstance); + mInstance = 0; + mBytesMap.clear(); } }); + mThread.start(); + mDefaultHandler = null; } - public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd, - @Nullable ThreadFactory factory) { - Preconditions.checkNotNull(fd); - final FuseAppLoop loop = new FuseAppLoop(mountPointId, fd, factory); - loop.mThread.start(); - return loop; - } - - public int registerCallback(@NonNull ProxyFileDescriptorCallback callback) - throws UnmountedException, IOException { - if (mThread.getState() == Thread.State.TERMINATED) { - throw new UnmountedException(); - } + public int registerCallback(@NonNull ProxyFileDescriptorCallback callback, + @NonNull Handler handler) throws FuseUnavailableMountException { synchronized (mLock) { - if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) { - throw new IOException("Too many opened files."); + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(handler); + Preconditions.checkState( + mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files."); + Preconditions.checkArgument( + Thread.currentThread().getId() != handler.getLooper().getThread().getId(), + "Handler must be different from the current thread"); + if (mInstance == 0) { + throw new FuseUnavailableMountException(mMountPointId); } int id; while (true) { @@ -101,118 +110,171 @@ public class FuseAppLoop { break; } } - mCallbackMap.put(id, new CallbackEntry(callback)); + mCallbackMap.put(id, new CallbackEntry(callback, handler)); return id; } } public void unregisterCallback(int id) { - mCallbackMap.remove(id); + synchronized (mLock) { + mCallbackMap.remove(id); + } } public int getMountPointId() { return mMountPointId; } - 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); - } - } + // Defined in fuse.h + private static final int FUSE_LOOKUP = 1; + private static final int FUSE_GETATTR = 3; + private static final int FUSE_OPEN = 14; + private static final int FUSE_READ = 15; + private static final int FUSE_WRITE = 16; + private static final int FUSE_RELEASE = 18; + private static final int FUSE_FSYNC = 20; + + // Defined in FuseBuffer.h + private static final int FUSE_MAX_WRITE = 256 * 1024; // Called by JNI. @SuppressWarnings("unused") - private long onGetSize(long inode) { + private void onCommand(int command, long unique, long inode, long offset, int size, + byte[] data) { synchronized(mLock) { try { - return getCallbackEntryOrThrowLocked(inode).callback.onGetSize(); - } catch (ErrnoException exp) { - return getError(exp); + final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode); + entry.postRunnable(() -> { + try { + switch (command) { + case FUSE_LOOKUP: { + final long fileSize = entry.callback.onGetSize(); + synchronized (mLock) { + if (mInstance != 0) { + native_replyLookup(mInstance, unique, inode, fileSize); + } + } + break; + } + case FUSE_GETATTR: { + final long fileSize = entry.callback.onGetSize(); + synchronized (mLock) { + if (mInstance != 0) { + native_replyGetAttr(mInstance, unique, inode, fileSize); + } + } + break; + } + case FUSE_READ: + final int readSize = entry.callback.onRead(offset, size, data); + synchronized (mLock) { + if (mInstance != 0) { + native_replyRead(mInstance, unique, readSize, data); + } + } + break; + case FUSE_WRITE: + final int writeSize = entry.callback.onWrite(offset, size, data); + synchronized (mLock) { + if (mInstance != 0) { + native_replyWrite(mInstance, unique, writeSize); + } + } + break; + case FUSE_FSYNC: + entry.callback.onFsync(); + synchronized (mLock) { + if (mInstance != 0) { + native_replySimple(mInstance, unique, FUSE_OK); + } + } + break; + case FUSE_RELEASE: + entry.callback.onRelease(); + synchronized (mLock) { + if (mInstance != 0) { + native_replySimple(mInstance, unique, FUSE_OK); + } + mBytesMap.stopUsing(entry.getThreadId()); + } + break; + default: + throw new IllegalArgumentException( + "Unknown FUSE command: " + command); + } + } catch (Exception error) { + Log.e(TAG, "", error); + replySimple(unique, getError(error)); + } + }); + } catch (ErrnoException error) { + Log.e(TAG, "", error); + replySimpleLocked(unique, getError(error)); } } } // Called by JNI. @SuppressWarnings("unused") - private int onOpen(long inode) { - synchronized(mLock) { + private byte[] onOpen(long unique, 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 getError(exp); + if (mInstance != 0) { + native_replyOpen(mInstance, unique, /* fh */ inode); + entry.opened = true; + return mBytesMap.startUsing(entry.getThreadId()); + } + } catch (ErrnoException error) { + replySimpleLocked(unique, getError(error)); } + return null; } } - // Called by JNI. - @SuppressWarnings("unused") - private int onFsync(long inode) { - synchronized(mLock) { - try { - getCallbackEntryOrThrowLocked(inode).callback.onFsync(); - return 0; - } catch (ErrnoException exp) { - return getError(exp); + private static int getError(@NonNull Exception error) { + if (error instanceof ErrnoException) { + final int errno = ((ErrnoException) error).errno; + if (errno != OsConstants.ENOSYS) { + return -errno; } } + return -OsConstants.EBADF; + } + + private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException { + final CallbackEntry entry = mCallbackMap.get(checkInode(inode)); + if (entry == null) { + throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT); + } + return entry; + } + + private void replySimple(long unique, int result) { + synchronized (mLock) { + replySimpleLocked(unique, result); + } } - // Called by JNI. - @SuppressWarnings("unused") - private int onRelease(long inode) { - synchronized(mLock) { - try { - getCallbackEntryOrThrowLocked(inode).callback.onRelease(); - return 0; - } catch (ErrnoException exp) { - return getError(exp); - } finally { - mCallbackMap.remove(checkInode(inode)); - } + private void replySimpleLocked(long unique, int result) { + if (mInstance != 0) { + native_replySimple(mInstance, unique, result); } } - // 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 getError(exp); - } - } - } + native long native_new(int fd); + native void native_delete(long ptr); + native void native_start(long ptr); - // 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 getError(exp); - } - } - } - - private static int getError(@NonNull ErrnoException exp) { - // Should not return ENOSYS because the kernel stops - // dispatching the FUSE action once FUSE implementation returns ENOSYS for the action. - return exp.errno != OsConstants.ENOSYS ? -exp.errno : -OsConstants.EIO; - } - - native boolean native_start_loop(int fd); + native void native_replySimple(long ptr, long unique, int result); + native void native_replyOpen(long ptr, long unique, long fh); + native void native_replyLookup(long ptr, long unique, long inode, long size); + native void native_replyGetAttr(long ptr, long unique, long inode, long size); + native void native_replyWrite(long ptr, long unique, int size); + native void native_replyRead(long ptr, long unique, int size, byte[] bytes); private static int checkInode(long inode) { Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode"); @@ -223,10 +285,61 @@ public class FuseAppLoop { private static class CallbackEntry { final ProxyFileDescriptorCallback callback; + final Handler handler; boolean opened; - CallbackEntry(ProxyFileDescriptorCallback callback) { - Preconditions.checkNotNull(callback); - this.callback = callback; + + CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) { + this.callback = Preconditions.checkNotNull(callback); + this.handler = Preconditions.checkNotNull(handler); + } + + long getThreadId() { + return handler.getLooper().getThread().getId(); + } + + void postRunnable(Runnable runnable) throws ErrnoException { + final boolean result = handler.post(runnable); + if (!result) { + throw new ErrnoException("postRunnable", OsConstants.EBADF); + } + } + } + + /** + * Entry for bytes map. + */ + private static class BytesMapEntry { + int counter = 0; + byte[] bytes = new byte[FUSE_MAX_WRITE]; + } + + /** + * Map between Thread ID and byte buffer. + */ + private static class BytesMap { + final Map mEntries = new HashMap<>(); + + byte[] startUsing(long threadId) { + BytesMapEntry entry = mEntries.get(threadId); + if (entry == null) { + entry = new BytesMapEntry(); + mEntries.put(threadId, entry); + } + entry.counter++; + return entry.bytes; + } + + void stopUsing(long threadId) { + final BytesMapEntry entry = mEntries.get(threadId); + Preconditions.checkNotNull(entry); + entry.counter--; + if (entry.counter <= 0) { + mEntries.remove(threadId); + } + } + + void clear() { + mEntries.clear(); } } } diff --git a/core/java/com/android/internal/os/FuseUnavailableMountException.java b/core/java/com/android/internal/os/FuseUnavailableMountException.java new file mode 100644 index 0000000000000..ca3cfb90ae11c --- /dev/null +++ b/core/java/com/android/internal/os/FuseUnavailableMountException.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 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; + +/** + * Exception occurred when the mount point has already been unavailable. + */ +public class FuseUnavailableMountException extends Exception { + public FuseUnavailableMountException(int mountId) { + super("AppFuse mount point " + mountId + " is unavailable"); + } +} diff --git a/core/jni/com_android_internal_os_FuseAppLoop.cpp b/core/jni/com_android_internal_os_FuseAppLoop.cpp index dd003eb364c5e..e125150e83297 100644 --- a/core/jni/com_android_internal_os_FuseAppLoop.cpp +++ b/core/jni/com_android_internal_os_FuseAppLoop.cpp @@ -20,140 +20,214 @@ #include #include +#include +#include + #include #include #include #include #include #include +#include #include "core_jni_helpers.h" namespace android { - namespace { - constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop"; jclass gFuseAppLoopClass; -jmethodID gOnGetSizeMethod; +jmethodID gOnCommandMethod; 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."); - + typedef ScopedLocalRef LocalBytes; JNIEnv* const mEnv; jobject const mSelf; - ScopedLocalRef mJniBuffer; - - template - T checkException(T result) const { - if (mEnv->ExceptionCheck()) { - LOGE_EX(mEnv, nullptr); - mEnv->ExceptionClear(); - return -EIO; - } - return result; - } + std::map> mBuffers; public: Callback(JNIEnv* env, jobject self) : - mEnv(env), - mSelf(self), - mJniBuffer(env, nullptr) {} + mEnv(env), mSelf(self) {} - bool Init() { - mJniBuffer.reset(mEnv->NewByteArray(kBufferSize)); - return mJniBuffer.get(); + void OnLookup(uint64_t unique, uint64_t inode) override { + mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_LOOKUP, unique, inode, 0, 0, nullptr); + CHECK(!mEnv->ExceptionCheck()); } - bool IsActive() override { - return true; + void OnGetAttr(uint64_t unique, uint64_t inode) override { + mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_GETATTR, unique, inode, 0, 0, nullptr); + CHECK(!mEnv->ExceptionCheck()); } - 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 { - return checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)); - } - - int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override { - CHECK_LE(size, static_cast(kBufferSize)); - const int32_t result = checkException(mEnv->CallIntMethod( - mSelf, gOnReadMethod, inode, offset, size, mJniBuffer.get())); - if (result <= 0) { - return result; - } - if (result > static_cast(size)) { - LOG(ERROR) << "Returned size is too large."; - return -EIO; + void OnOpen(uint64_t unique, uint64_t inode) override { + const jbyteArray buffer = static_cast(mEnv->CallObjectMethod( + mSelf, gOnOpenMethod, unique, inode)); + CHECK(!mEnv->ExceptionCheck()); + if (buffer == nullptr) { + return; } - mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast(buffer)); - CHECK(!mEnv->ExceptionCheck()); - - return checkException(result); + mBuffers.insert(std::make_pair(inode, std::unique_ptr( + new LocalBytes(mEnv, buffer)))); } - int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override { - CHECK_LE(size, static_cast(kBufferSize)); + void OnFsync(uint64_t unique, uint64_t inode) override { + mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_FSYNC, unique, inode, 0, 0, nullptr); + CHECK(!mEnv->ExceptionCheck()); + } - mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast(buffer)); + void OnRelease(uint64_t unique, uint64_t inode) override { + mBuffers.erase(inode); + mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_RELEASE, unique, inode, 0, 0, nullptr); + CHECK(!mEnv->ExceptionCheck()); + } + + void OnRead(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size) override { + CHECK_LE(size, static_cast(fuse::kFuseMaxRead)); + + auto it = mBuffers.find(inode); + CHECK(it != mBuffers.end()); + + mEnv->CallVoidMethod( + mSelf, gOnCommandMethod, FUSE_READ, unique, inode, offset, size, + it->second->get()); + CHECK(!mEnv->ExceptionCheck()); + } + + void OnWrite(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size, + const void* buffer) override { + CHECK_LE(size, static_cast(fuse::kFuseMaxWrite)); + + auto it = mBuffers.find(inode); + CHECK(it != mBuffers.end()); + + jbyteArray const javaBuffer = it->second->get(); + + mEnv->SetByteArrayRegion(javaBuffer, 0, size, static_cast(buffer)); CHECK(!mEnv->ExceptionCheck()); - return checkException(mEnv->CallIntMethod( - mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get())); + mEnv->CallVoidMethod( + mSelf, gOnCommandMethod, FUSE_WRITE, unique, inode, offset, size, javaBuffer); + CHECK(!mEnv->ExceptionCheck()); } }; -jboolean com_android_internal_os_FuseAppLoop_start_loop(JNIEnv* env, jobject self, jint jfd) { - base::unique_fd fd(jfd); +jlong com_android_internal_os_FuseAppLoop_new(JNIEnv* env, jobject self, jint jfd) { + return reinterpret_cast(new fuse::FuseAppLoop(base::unique_fd(jfd))); +} + +void com_android_internal_os_FuseAppLoop_delete(JNIEnv* env, jobject self, jlong ptr) { + delete reinterpret_cast(ptr); +} + +void com_android_internal_os_FuseAppLoop_start(JNIEnv* env, jobject self, jlong ptr) { Callback callback(env, self); + reinterpret_cast(ptr)->Start(&callback); +} - if (!callback.Init()) { - LOG(ERROR) << "Failed to init callback"; - return JNI_FALSE; +void com_android_internal_os_FuseAppLoop_replySimple( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jint result) { + if (!reinterpret_cast(ptr)->ReplySimple(unique, result)) { + reinterpret_cast(ptr)->Break(); } +} - return fuse::StartFuseAppLoop(fd.release(), &callback); +void com_android_internal_os_FuseAppLoop_replyOpen( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong fh) { + if (!reinterpret_cast(ptr)->ReplyOpen(unique, fh)) { + reinterpret_cast(ptr)->Break(); + } +} + +void com_android_internal_os_FuseAppLoop_replyLookup( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong inode, jint size) { + if (!reinterpret_cast(ptr)->ReplyLookup(unique, inode, size)) { + reinterpret_cast(ptr)->Break(); + } +} + +void com_android_internal_os_FuseAppLoop_replyGetAttr( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong inode, jint size) { + if (!reinterpret_cast(ptr)->ReplyGetAttr( + unique, inode, size, S_IFREG | 0777)) { + reinterpret_cast(ptr)->Break(); + } +} + +void com_android_internal_os_FuseAppLoop_replyWrite( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jint size) { + if (!reinterpret_cast(ptr)->ReplyWrite(unique, size)) { + reinterpret_cast(ptr)->Break(); + } +} + +void com_android_internal_os_FuseAppLoop_replyRead( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jint size, jbyteArray data) { + ScopedByteArrayRO array(env, data); + CHECK(size >= 0); + CHECK(static_cast(size) < array.size()); + if (!reinterpret_cast(ptr)->ReplyRead(unique, size, array.get())) { + reinterpret_cast(ptr)->Break(); + } } const JNINativeMethod methods[] = { { - "native_start_loop", - "(I)Z", - (void *) com_android_internal_os_FuseAppLoop_start_loop - } + "native_new", + "(I)J", + reinterpret_cast(com_android_internal_os_FuseAppLoop_new) + }, + { + "native_delete", + "(J)V", + reinterpret_cast(com_android_internal_os_FuseAppLoop_delete) + }, + { + "native_start", + "(J)V", + reinterpret_cast(com_android_internal_os_FuseAppLoop_start) + }, + { + "native_replySimple", + "(JJI)V", + reinterpret_cast(com_android_internal_os_FuseAppLoop_replySimple) + }, + { + "native_replyOpen", + "(JJJ)V", + reinterpret_cast(com_android_internal_os_FuseAppLoop_replyOpen) + }, + { + "native_replyLookup", + "(JJJJ)V", + reinterpret_cast(com_android_internal_os_FuseAppLoop_replyLookup) + }, + { + "native_replyGetAttr", + "(JJJJ)V", + reinterpret_cast(com_android_internal_os_FuseAppLoop_replyGetAttr) + }, + { + "native_replyRead", + "(JJI[B)V", + reinterpret_cast(com_android_internal_os_FuseAppLoop_replyRead) + }, + { + "native_replyWrite", + "(JJI)V", + reinterpret_cast(com_android_internal_os_FuseAppLoop_replyWrite) + }, }; - } // 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"); + gOnCommandMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onCommand", "(IJJJI[B)V"); + gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(JJ)[B"); RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods)); return 0; } - } // namespace android diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java index ff98eb763f76d..fbba6fffcd8ef 100644 --- a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java +++ b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java @@ -264,7 +264,7 @@ public class StorageManagerIntegrationTest extends StorageManagerBaseTest { final MyThreadFactory factory = new MyThreadFactory(); int firstMountId; try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor( - ParcelFileDescriptor.MODE_READ_ONLY, callback, factory)) { + ParcelFileDescriptor.MODE_READ_ONLY, callback, null, factory)) { assertNotSame(Thread.State.TERMINATED, factory.thread.getState()); firstMountId = mSm.getProxyFileDescriptorMountPointId(); assertNotSame(-1, firstMountId); @@ -276,7 +276,7 @@ public class StorageManagerIntegrationTest extends StorageManagerBaseTest { // StorageManager should mount another bridge on the next open request. try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor( - ParcelFileDescriptor.MODE_WRITE_ONLY, callback, factory)) { + ParcelFileDescriptor.MODE_WRITE_ONLY, callback, null, factory)) { assertNotSame(Thread.State.TERMINATED, factory.thread.getState()); assertNotSame(firstMountId, mSm.getProxyFileDescriptorMountPointId()); } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 457c5f8e1f655..c68000ab00d4c 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -98,6 +98,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IMediaContainerService; import com.android.internal.os.AppFuseMount; import com.android.internal.os.FuseAppLoop; +import com.android.internal.os.FuseUnavailableMountException; import com.android.internal.os.SomeArgs; import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; @@ -3007,32 +3008,36 @@ class StorageManagerService extends IStorageManager.Stub } } - private ParcelFileDescriptor mountAppFuse(int uid, int mountId) - throws NativeDaemonConnectorException { - final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute( - "appfuse", "mount", uid, Process.myPid(), mountId); - if (event.getFileDescriptors() == null || - event.getFileDescriptors().length == 0) { - throw new NativeDaemonConnectorException("Cannot obtain device FD"); - } - return new ParcelFileDescriptor(event.getFileDescriptors()[0]); - } - class AppFuseMountScope extends AppFuseBridge.MountScope { - public AppFuseMountScope(int uid, int pid, int mountId) - throws NativeDaemonConnectorException { - super(uid, pid, mountId, mountAppFuse(uid, mountId)); + boolean opened = false; + + public AppFuseMountScope(int uid, int pid, int mountId) { + super(uid, pid, mountId); + } + + @Override + public ParcelFileDescriptor open() throws NativeDaemonConnectorException { + final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute( + "appfuse", "mount", uid, Process.myPid(), mountId); + opened = true; + if (event.getFileDescriptors() == null || + event.getFileDescriptors().length == 0) { + throw new NativeDaemonConnectorException("Cannot obtain device FD"); + } + return new ParcelFileDescriptor(event.getFileDescriptors()[0]); } @Override public void close() throws Exception { - super.close(); - mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId); + if (opened) { + mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId); + opened = false; + } } } @Override - public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException { + public @Nullable AppFuseMount mountProxyFileDescriptorBridge() { Slog.v(TAG, "mountProxyFileDescriptorBridge"); final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); @@ -3049,12 +3054,12 @@ class StorageManagerService extends IStorageManager.Stub final int name = mNextAppFuseName++; try { return new AppFuseMount( - name, - mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name))); - } catch (AppFuseBridge.BridgeException e) { + name, mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name))); + } catch (FuseUnavailableMountException e) { if (newlyCreated) { // If newly created bridge fails, it's a real error. - throw new RemoteException(e.getMessage()); + Slog.e(TAG, "", e); + return null; } // It seems the thread of mAppFuseBridge has already been terminated. mAppFuseBridge = null; @@ -3067,19 +3072,21 @@ class StorageManagerService extends IStorageManager.Stub } @Override - public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode) - throws RemoteException { - Slog.v(TAG, "mountProxyFileDescriptorBridge"); + public @Nullable ParcelFileDescriptor openProxyFileDescriptor( + int mountId, int fileId, int mode) { + Slog.v(TAG, "mountProxyFileDescriptor"); final int pid = Binder.getCallingPid(); try { synchronized (mAppFuseLock) { if (mAppFuseBridge == null) { - throw new RemoteException("Cannot find mount point"); + Slog.e(TAG, "FuseBridge has not been created"); + return null; } return mAppFuseBridge.openFile(pid, mountId, fileId, mode); } - } catch (FileNotFoundException | SecurityException | InterruptedException error) { - throw new RemoteException(error.getMessage()); + } catch (FuseUnavailableMountException | InterruptedException error) { + Slog.v(TAG, "The mount point has already been invalid", error); + return null; } } diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java index 904d9159e5786..6a0b6489f470d 100644 --- a/services/core/java/com/android/server/storage/AppFuseBridge.java +++ b/services/core/java/com/android/server/storage/AppFuseBridge.java @@ -21,7 +21,9 @@ import android.system.ErrnoException; import android.system.Os; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.FuseUnavailableMountException; import com.android.internal.util.Preconditions; +import com.android.server.NativeDaemonConnectorException; import libcore.io.IoUtils; import java.io.File; import java.io.FileNotFoundException; @@ -54,17 +56,17 @@ public class AppFuseBridge implements Runnable { } public ParcelFileDescriptor addBridge(MountScope mountScope) - throws BridgeException { + throws FuseUnavailableMountException, NativeDaemonConnectorException { try { synchronized (this) { Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0); if (mNativeLoop == 0) { - throw new BridgeException("The thread has already been terminated"); + throw new FuseUnavailableMountException(mountScope.mountId); } final int fd = native_add_bridge( - mNativeLoop, mountScope.mountId, mountScope.deviceFd.detachFd()); + mNativeLoop, mountScope.mountId, mountScope.open().detachFd()); if (fd == -1) { - throw new BridgeException("Failed to invoke native_add_bridge"); + throw new FuseUnavailableMountException(mountScope.mountId); } final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd); mScopes.put(mountScope.mountId, mountScope); @@ -86,12 +88,12 @@ public class AppFuseBridge implements Runnable { } public ParcelFileDescriptor openFile(int pid, int mountId, int fileId, int mode) - throws FileNotFoundException, SecurityException, InterruptedException { + throws FuseUnavailableMountException, InterruptedException { final MountScope scope; synchronized (this) { scope = mScopes.get(mountId); if (scope == null) { - throw new FileNotFoundException("Cannot find mount point"); + throw new FuseUnavailableMountException(mountId); } } if (scope.pid != pid) { @@ -99,17 +101,14 @@ public class AppFuseBridge implements Runnable { } final boolean result = scope.waitForMount(); if (result == false) { - throw new FileNotFoundException("Mount failed"); + throw new FuseUnavailableMountException(mountId); } try { - if (Os.stat(scope.mountPoint.getPath()).st_ino != 1) { - throw new FileNotFoundException("Could not find bridge mount point."); - } - } catch (ErrnoException e) { - throw new FileNotFoundException( - "Failed to stat mount point: " + scope.mountPoint.getParent()); + return ParcelFileDescriptor.open( + new File(scope.mountPoint, String.valueOf(fileId)), mode); + } catch (FileNotFoundException error) { + throw new FuseUnavailableMountException(mountId); } - return ParcelFileDescriptor.open(new File(scope.mountPoint, String.valueOf(fileId)), mode); } // Used by com_android_server_storage_AppFuse.cpp. @@ -130,20 +129,18 @@ public class AppFuseBridge implements Runnable { } } - public static class MountScope implements AutoCloseable { + public static abstract class MountScope implements AutoCloseable { public final int uid; public final int pid; public final int mountId; - public final ParcelFileDescriptor deviceFd; public final File mountPoint; private final CountDownLatch mMounted = new CountDownLatch(1); private boolean mMountResult = false; - public MountScope(int uid, int pid, int mountId, ParcelFileDescriptor deviceFd) { + public MountScope(int uid, int pid, int mountId) { this.uid = uid; this.pid = pid; this.mountId = mountId; - this.deviceFd = deviceFd; this.mountPoint = new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId)); } @@ -161,16 +158,7 @@ public class AppFuseBridge implements Runnable { return mMountResult; } - @Override - public void close() throws Exception { - deviceFd.close(); - } - } - - public static class BridgeException extends Exception { - public BridgeException(String message) { - super(message); - } + public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException; } private native long native_new();