summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt1
-rw-r--r--api/system-current.txt1
-rw-r--r--api/test-current.txt1
-rw-r--r--core/java/android/os/storage/StorageManager.java79
-rw-r--r--core/java/com/android/internal/os/FuseAppLoop.java303
-rw-r--r--core/java/com/android/internal/os/FuseUnavailableMountException.java26
-rw-r--r--core/jni/com_android_internal_os_FuseAppLoop.cpp224
-rw-r--r--core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java4
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java59
-rw-r--r--services/core/java/com/android/server/storage/AppFuseBridge.java44
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();