diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | api/test-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/os/storage/StorageManager.java | 79 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/FuseAppLoop.java | 303 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/FuseUnavailableMountException.java | 26 | ||||
| -rw-r--r-- | core/jni/com_android_internal_os_FuseAppLoop.cpp | 224 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java | 4 | ||||
| -rw-r--r-- | services/core/java/com/android/server/StorageManagerService.java | 59 | ||||
| -rw-r--r-- | services/core/java/com/android/server/storage/AppFuseBridge.java | 44 |
10 files changed, 488 insertions, 254 deletions
diff --git a/api/current.txt b/api/current.txt index 788c1d431de2..307d262ea549 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 c07b9284cd75..47f227e7daa9 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 00bc3c2d28aa..c07e91e5a217 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 7e1b5abf8112..e5d73e04bda6 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 3603b6df11da..8edd637ea814 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<CallbackEntry> 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; } - // 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 CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException { + final CallbackEntry entry = mCallbackMap.get(checkInode(inode)); + if (entry == null) { + throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT); } + return entry; } - // 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); - } + private void replySimple(long unique, int result) { + synchronized (mLock) { + replySimpleLocked(unique, result); } } - // 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 void replySimpleLocked(long unique, int result) { + if (mInstance != 0) { + native_replySimple(mInstance, unique, result); } } - 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 long native_new(int fd); + native void native_delete(long ptr); + native void native_start(long ptr); - 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<Long, BytesMapEntry> 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 000000000000..ca3cfb90ae11 --- /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 dd003eb364c5..e125150e8329 100644 --- a/core/jni/com_android_internal_os_FuseAppLoop.cpp +++ b/core/jni/com_android_internal_os_FuseAppLoop.cpp @@ -20,140 +20,214 @@ #include <stdlib.h> #include <sys/stat.h> +#include <map> +#include <memory> + #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 <nativehelper/ScopedPrimitiveArray.h> #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<jbyteArray> LocalBytes; JNIEnv* const mEnv; jobject const mSelf; - ScopedLocalRef<jbyteArray> mJniBuffer; - - template <typename T> - T checkException(T result) const { - if (mEnv->ExceptionCheck()) { - LOGE_EX(mEnv, nullptr); - mEnv->ExceptionClear(); - return -EIO; - } - return result; - } + std::map<uint64_t, std::unique_ptr<LocalBytes>> 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)); - } + void OnOpen(uint64_t unique, uint64_t inode) override { + const jbyteArray buffer = static_cast<jbyteArray>(mEnv->CallObjectMethod( + mSelf, gOnOpenMethod, unique, inode)); + CHECK(!mEnv->ExceptionCheck()); + if (buffer == nullptr) { + return; + } - int32_t OnOpen(uint64_t inode) override { - return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode)); + mBuffers.insert(std::make_pair(inode, std::unique_ptr<LocalBytes>( + new LocalBytes(mEnv, buffer)))); } - int32_t OnFsync(uint64_t inode) override { - return checkException(mEnv->CallIntMethod(mSelf, gOnFsyncMethod, inode)); + void OnFsync(uint64_t unique, uint64_t inode) override { + mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_FSYNC, unique, inode, 0, 0, nullptr); + CHECK(!mEnv->ExceptionCheck()); } - int32_t OnRelease(uint64_t inode) override { - return checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)); + 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()); } - 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; - } + void OnRead(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size) override { + CHECK_LE(size, static_cast<uint32_t>(fuse::kFuseMaxRead)); - mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer)); - CHECK(!mEnv->ExceptionCheck()); + auto it = mBuffers.find(inode); + CHECK(it != mBuffers.end()); - return checkException(result); + mEnv->CallVoidMethod( + mSelf, gOnCommandMethod, FUSE_READ, unique, inode, offset, size, + it->second->get()); + CHECK(!mEnv->ExceptionCheck()); } - int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override { - CHECK_LE(size, static_cast<uint32_t>(kBufferSize)); + void OnWrite(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size, + const void* buffer) override { + CHECK_LE(size, static_cast<uint32_t>(fuse::kFuseMaxWrite)); + + auto it = mBuffers.find(inode); + CHECK(it != mBuffers.end()); + + jbyteArray const javaBuffer = it->second->get(); - mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast<const jbyte*>(buffer)); + mEnv->SetByteArrayRegion(javaBuffer, 0, size, static_cast<const jbyte*>(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<jlong>(new fuse::FuseAppLoop(base::unique_fd(jfd))); +} + +void com_android_internal_os_FuseAppLoop_delete(JNIEnv* env, jobject self, jlong ptr) { + delete reinterpret_cast<fuse::FuseAppLoop*>(ptr); +} + +void com_android_internal_os_FuseAppLoop_start(JNIEnv* env, jobject self, jlong ptr) { Callback callback(env, self); + reinterpret_cast<fuse::FuseAppLoop*>(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<fuse::FuseAppLoop*>(ptr)->ReplySimple(unique, result)) { + reinterpret_cast<fuse::FuseAppLoop*>(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<fuse::FuseAppLoop*>(ptr)->ReplyOpen(unique, fh)) { + reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break(); + } +} + +void com_android_internal_os_FuseAppLoop_replyLookup( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong inode, jint size) { + if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyLookup(unique, inode, size)) { + reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break(); + } +} + +void com_android_internal_os_FuseAppLoop_replyGetAttr( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong inode, jint size) { + if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyGetAttr( + unique, inode, size, S_IFREG | 0777)) { + reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break(); + } +} + +void com_android_internal_os_FuseAppLoop_replyWrite( + JNIEnv* env, jobject self, jlong ptr, jlong unique, jint size) { + if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyWrite(unique, size)) { + reinterpret_cast<fuse::FuseAppLoop*>(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_t>(size) < array.size()); + if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyRead(unique, size, array.get())) { + reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break(); + } } const JNINativeMethod methods[] = { { - "native_start_loop", - "(I)Z", - (void *) com_android_internal_os_FuseAppLoop_start_loop - } + "native_new", + "(I)J", + reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_new) + }, + { + "native_delete", + "(J)V", + reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_delete) + }, + { + "native_start", + "(J)V", + reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_start) + }, + { + "native_replySimple", + "(JJI)V", + reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replySimple) + }, + { + "native_replyOpen", + "(JJJ)V", + reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyOpen) + }, + { + "native_replyLookup", + "(JJJJ)V", + reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyLookup) + }, + { + "native_replyGetAttr", + "(JJJJ)V", + reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyGetAttr) + }, + { + "native_replyRead", + "(JJI[B)V", + reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyRead) + }, + { + "native_replyWrite", + "(JJI)V", + reinterpret_cast<void*>(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 ff98eb763f76..fbba6fffcd8e 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 457c5f8e1f65..c68000ab00d4 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"); + class AppFuseMountScope extends AppFuseBridge.MountScope { + boolean opened = false; + + public AppFuseMountScope(int uid, int pid, int mountId) { + super(uid, pid, mountId); } - 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)); + @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 904d9159e578..6a0b6489f470 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(); |