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 4f156065c8)
This commit is contained in:
Daichi Hirono
2017-02-08 16:20:20 +09:00
parent c8d8c27c68
commit 812c95d37d
10 changed files with 502 additions and 268 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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;
}
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<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();
}
}
}

View File

@@ -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");
}
}

View File

@@ -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));
}
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<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 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;
}
mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer));
CHECK(!mEnv->ExceptionCheck());
return checkException(result);
mBuffers.insert(std::make_pair(inode, std::unique_ptr<LocalBytes>(
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<uint32_t>(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<const jbyte*>(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<uint32_t>(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<uint32_t>(fuse::kFuseMaxWrite));
auto it = mBuffers.find(inode);
CHECK(it != mBuffers.end());
jbyteArray const javaBuffer = it->second->get();
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

View File

@@ -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());
}

View File

@@ -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;
}
}

View File

@@ -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();