Merge "Allow apps to process ProxyFDCallback asynchrnously." into oc-dev

This commit is contained in:
TreeHugger Robot
2017-03-29 04:41:38 +00:00
committed by Android (Google) Code Review
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

@@ -34731,6 +34731,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();