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

am: 76c32a35a9

Change-Id: I9e17751f706f9e0869eefafb77bf1704335f5116
This commit is contained in:
Daichi Hirono
2017-03-29 04:49:43 +00:00
committed by android-build-merger
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 isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); 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) 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 setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
method public void setCacheBehaviorTombstone(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); 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 isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); 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) 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 setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
method public void setCacheBehaviorTombstone(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); 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 isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener); 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) 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 setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
method public void setCacheBehaviorTombstone(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); 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.logging.MetricsLogger;
import com.android.internal.os.AppFuseMount; import com.android.internal.os.AppFuseMount;
import com.android.internal.os.FuseAppLoop; 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.RoSystemProperties;
import com.android.internal.os.SomeArgs; import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
@@ -82,6 +84,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import libcore.io.IoUtils;
/** /**
* StorageManager is the interface to the systems storage service. The storage * StorageManager is the interface to the systems storage service. The storage
@@ -1390,53 +1393,52 @@ public class StorageManager {
/** {@hide} */ /** {@hide} */
@VisibleForTesting @VisibleForTesting
public @NonNull ParcelFileDescriptor openProxyFileDescriptor( public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory) int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory)
throws IOException { throws IOException {
MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1); MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1);
// Retry is needed because the mount point mFuseAppLoop is using may be unmounted before // 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 // invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
// the bridge by calling mountProxyFileDescriptorBridge. // the bridge by calling mountProxyFileDescriptorBridge.
int retry = 3; while (true) {
while (retry-- > 0) {
try { try {
synchronized (mFuseAppLoopLock) { synchronized (mFuseAppLoopLock) {
boolean newlyCreated = false;
if (mFuseAppLoop == null) { if (mFuseAppLoop == null) {
final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge(); final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
if (mount == null) { if (mount == null) {
Log.e(TAG, "Failed to open proxy file bridge."); throw new IOException("Failed to mount proxy bridge");
throw new IOException("Failed to open proxy file 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 { try {
final int fileId = mFuseAppLoop.registerCallback(callback); final int fileId = mFuseAppLoop.registerCallback(callback, handler);
final ParcelFileDescriptor pfd = final ParcelFileDescriptor pfd = mStorageManager.openProxyFileDescriptor(
mStorageManager.openProxyFileDescriptor(
mFuseAppLoop.getMountPointId(), fileId, mode); mFuseAppLoop.getMountPointId(), fileId, mode);
if (pfd != null) { if (pfd == null) {
return pfd;
}
// Probably the bridge is being unmounted but mFuseAppLoop has not been
// noticed it yet.
mFuseAppLoop.unregisterCallback(fileId); mFuseAppLoop.unregisterCallback(fileId);
} catch (FuseAppLoop.UnmountedException error) { throw new FuseUnavailableMountException(
Log.d(TAG, "mFuseAppLoop has been already unmounted."); 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);
}
mFuseAppLoop = null; mFuseAppLoop = null;
continue; continue;
} }
} }
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
} catch (RemoteException e) { } 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_WRITE_ONLY}, or
* {@link ParcelFileDescriptor#MODE_READ_WRITE} * {@link ParcelFileDescriptor#MODE_READ_WRITE}
* @param callback Callback to process file operation requests issued on returned file * @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. * @return Seekable ParcelFileDescriptor.
* @throws IOException * @throws IOException
*/ */
public @NonNull ParcelFileDescriptor openProxyFileDescriptor( public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
int mode, ProxyFileDescriptorCallback callback) int mode, ProxyFileDescriptorCallback callback)
throws IOException { 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} */ /** {@hide} */
@VisibleForTesting @VisibleForTesting
public int getProxyFileDescriptorMountPointId() { public int getProxyFileDescriptorMountPointId() {

View File

@@ -19,16 +19,16 @@ package com.android.internal.os;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.os.ProxyFileDescriptorCallback; import android.os.ProxyFileDescriptorCallback;
import android.os.Handler;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.OsConstants; import android.system.OsConstants;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import java.util.HashMap;
import java.io.IOException; import java.util.Map;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
public class FuseAppLoop { public class FuseAppLoop {
@@ -42,14 +42,21 @@ public class FuseAppLoop {
return new Thread(r, TAG); return new Thread(r, TAG);
} }
}; };
private static final int FUSE_OK = 0;
private final Object mLock = new Object(); private final Object mLock = new Object();
private final int mMountPointId; private final int mMountPointId;
private final Thread mThread; private final Thread mThread;
private final Handler mDefaultHandler;
private static final int CMD_FSYNC = 1;
@GuardedBy("mLock") @GuardedBy("mLock")
private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>(); 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. * 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. * 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") @GuardedBy("mLock")
private int mNextInode = MIN_INODE; private int mNextInode = MIN_INODE;
private FuseAppLoop( @GuardedBy("mLock")
private long mInstance;
public FuseAppLoop(
int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) { int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
mMountPointId = mountPointId; mMountPointId = mountPointId;
final int rawFd = fd.detachFd();
if (factory == null) { if (factory == null) {
factory = sDefaultThreadFactory; factory = sDefaultThreadFactory;
} }
mThread = factory.newThread(new Runnable() { mInstance = native_new(fd.detachFd());
@Override mThread = factory.newThread(() -> {
public void run() { native_start(mInstance);
// rawFd is closed by native_start_loop. Java code does not need to close it. synchronized (mLock) {
native_start_loop(rawFd); native_delete(mInstance);
mInstance = 0;
mBytesMap.clear();
} }
}); });
mThread.start();
mDefaultHandler = null;
} }
public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd, public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
@Nullable ThreadFactory factory) { @NonNull Handler handler) throws FuseUnavailableMountException {
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();
}
synchronized (mLock) { synchronized (mLock) {
if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) { Preconditions.checkNotNull(callback);
throw new IOException("Too many opened files."); 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; int id;
while (true) { while (true) {
@@ -101,118 +110,171 @@ public class FuseAppLoop {
break; break;
} }
} }
mCallbackMap.put(id, new CallbackEntry(callback)); mCallbackMap.put(id, new CallbackEntry(callback, handler));
return id; return id;
} }
} }
public void unregisterCallback(int id) { public void unregisterCallback(int id) {
synchronized (mLock) {
mCallbackMap.remove(id); mCallbackMap.remove(id);
} }
}
public int getMountPointId() { public int getMountPointId() {
return mMountPointId; return mMountPointId;
} }
private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException { // Defined in fuse.h
final CallbackEntry entry = mCallbackMap.get(checkInode(inode)); private static final int FUSE_LOOKUP = 1;
if (entry != null) { private static final int FUSE_GETATTR = 3;
return entry; private static final int FUSE_OPEN = 14;
} else { private static final int FUSE_READ = 15;
throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT); 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. // Called by JNI.
@SuppressWarnings("unused") @SuppressWarnings("unused")
private long onGetSize(long inode) { private void onCommand(int command, long unique, long inode, long offset, int size,
byte[] data) {
synchronized(mLock) { synchronized(mLock) {
try { try {
return getCallbackEntryOrThrowLocked(inode).callback.onGetSize(); final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
} catch (ErrnoException exp) { entry.postRunnable(() -> {
return getError(exp); 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. // Called by JNI.
@SuppressWarnings("unused") @SuppressWarnings("unused")
private int onOpen(long inode) { private byte[] onOpen(long unique, long inode) {
synchronized (mLock) { synchronized (mLock) {
try { try {
final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode); final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
if (entry.opened) { if (entry.opened) {
throw new ErrnoException("onOpen", OsConstants.EMFILE); throw new ErrnoException("onOpen", OsConstants.EMFILE);
} }
if (mInstance != 0) {
native_replyOpen(mInstance, unique, /* fh */ inode);
entry.opened = true; entry.opened = true;
// Use inode as file handle. It's OK because AppFuse does not allow to open the same return mBytesMap.startUsing(entry.getThreadId());
// file twice.
return (int) inode;
} catch (ErrnoException exp) {
return getError(exp);
} }
} catch (ErrnoException error) {
replySimpleLocked(unique, getError(error));
}
return null;
} }
} }
// Called by JNI. private static int getError(@NonNull Exception error) {
@SuppressWarnings("unused") if (error instanceof ErrnoException) {
private int onFsync(long inode) { 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) { synchronized (mLock) {
try { replySimpleLocked(unique, result);
getCallbackEntryOrThrowLocked(inode).callback.onFsync();
return 0;
} catch (ErrnoException exp) {
return getError(exp);
}
} }
} }
// Called by JNI. private void replySimpleLocked(long unique, int result) {
@SuppressWarnings("unused") if (mInstance != 0) {
private int onRelease(long inode) { native_replySimple(mInstance, unique, result);
synchronized(mLock) {
try {
getCallbackEntryOrThrowLocked(inode).callback.onRelease();
return 0;
} catch (ErrnoException exp) {
return getError(exp);
} finally {
mCallbackMap.remove(checkInode(inode));
}
} }
} }
// Called by JNI. native long native_new(int fd);
@SuppressWarnings("unused") native void native_delete(long ptr);
private int onRead(long inode, long offset, int size, byte[] bytes) { native void native_start(long ptr);
synchronized(mLock) {
try {
return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
} catch (ErrnoException exp) {
return getError(exp);
}
}
}
// Called by JNI. native void native_replySimple(long ptr, long unique, int result);
@SuppressWarnings("unused") native void native_replyOpen(long ptr, long unique, long fh);
private int onWrite(long inode, long offset, int size, byte[] bytes) { native void native_replyLookup(long ptr, long unique, long inode, long size);
synchronized(mLock) { native void native_replyGetAttr(long ptr, long unique, long inode, long size);
try { native void native_replyWrite(long ptr, long unique, int size);
return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes); native void native_replyRead(long ptr, long unique, int size, byte[] 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);
private static int checkInode(long inode) { private static int checkInode(long inode) {
Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode"); Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
@@ -223,10 +285,61 @@ public class FuseAppLoop {
private static class CallbackEntry { private static class CallbackEntry {
final ProxyFileDescriptorCallback callback; final ProxyFileDescriptorCallback callback;
final Handler handler;
boolean opened; boolean opened;
CallbackEntry(ProxyFileDescriptorCallback callback) {
Preconditions.checkNotNull(callback); CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
this.callback = callback; 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 <stdlib.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <map>
#include <memory>
#include <android_runtime/Log.h> #include <android_runtime/Log.h>
#include <android-base/logging.h> #include <android-base/logging.h>
#include <android-base/unique_fd.h> #include <android-base/unique_fd.h>
#include <jni.h> #include <jni.h>
#include <libappfuse/FuseAppLoop.h> #include <libappfuse/FuseAppLoop.h>
#include <nativehelper/ScopedLocalRef.h> #include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include "core_jni_helpers.h" #include "core_jni_helpers.h"
namespace android { namespace android {
namespace { namespace {
constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop"; constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop";
jclass gFuseAppLoopClass; jclass gFuseAppLoopClass;
jmethodID gOnGetSizeMethod; jmethodID gOnCommandMethod;
jmethodID gOnOpenMethod; jmethodID gOnOpenMethod;
jmethodID gOnFsyncMethod;
jmethodID gOnReleaseMethod;
jmethodID gOnReadMethod;
jmethodID gOnWriteMethod;
class Callback : public fuse::FuseAppLoopCallback { class Callback : public fuse::FuseAppLoopCallback {
private: private:
static constexpr size_t kBufferSize = std::max(fuse::kFuseMaxWrite, fuse::kFuseMaxRead); typedef ScopedLocalRef<jbyteArray> LocalBytes;
static_assert(kBufferSize <= INT32_MAX, "kBufferSize should be fit in int32_t.");
JNIEnv* const mEnv; JNIEnv* const mEnv;
jobject const mSelf; jobject const mSelf;
ScopedLocalRef<jbyteArray> mJniBuffer; std::map<uint64_t, std::unique_ptr<LocalBytes>> mBuffers;
template <typename T>
T checkException(T result) const {
if (mEnv->ExceptionCheck()) {
LOGE_EX(mEnv, nullptr);
mEnv->ExceptionClear();
return -EIO;
}
return result;
}
public: public:
Callback(JNIEnv* env, jobject self) : Callback(JNIEnv* env, jobject self) :
mEnv(env), mEnv(env), mSelf(self) {}
mSelf(self),
mJniBuffer(env, nullptr) {}
bool Init() { void OnLookup(uint64_t unique, uint64_t inode) override {
mJniBuffer.reset(mEnv->NewByteArray(kBufferSize)); mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_LOOKUP, unique, inode, 0, 0, nullptr);
return mJniBuffer.get(); CHECK(!mEnv->ExceptionCheck());
} }
bool IsActive() override { void OnGetAttr(uint64_t unique, uint64_t inode) override {
return true; mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_GETATTR, unique, inode, 0, 0, nullptr);
CHECK(!mEnv->ExceptionCheck());
} }
int64_t OnGetSize(uint64_t inode) override { void OnOpen(uint64_t unique, uint64_t inode) override {
return checkException(mEnv->CallLongMethod(mSelf, gOnGetSizeMethod, inode)); 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 { mBuffers.insert(std::make_pair(inode, std::unique_ptr<LocalBytes>(
return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode)); new LocalBytes(mEnv, buffer))));
} }
int32_t OnFsync(uint64_t inode) override { void OnFsync(uint64_t unique, uint64_t inode) override {
return checkException(mEnv->CallIntMethod(mSelf, gOnFsyncMethod, inode)); mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_FSYNC, unique, inode, 0, 0, nullptr);
CHECK(!mEnv->ExceptionCheck());
} }
int32_t OnRelease(uint64_t inode) override { void OnRelease(uint64_t unique, uint64_t inode) override {
return checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)); 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 { void OnRead(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size) override {
CHECK_LE(size, static_cast<uint32_t>(kBufferSize)); CHECK_LE(size, static_cast<uint32_t>(fuse::kFuseMaxRead));
const int32_t result = checkException(mEnv->CallIntMethod(
mSelf, gOnReadMethod, inode, offset, size, mJniBuffer.get())); auto it = mBuffers.find(inode);
if (result <= 0) { CHECK(it != mBuffers.end());
return result;
} mEnv->CallVoidMethod(
if (result > static_cast<int32_t>(size)) { mSelf, gOnCommandMethod, FUSE_READ, unique, inode, offset, size,
LOG(ERROR) << "Returned size is too large."; it->second->get());
return -EIO; CHECK(!mEnv->ExceptionCheck());
} }
mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer)); 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()); CHECK(!mEnv->ExceptionCheck());
return checkException(result); mEnv->CallVoidMethod(
} mSelf, gOnCommandMethod, FUSE_WRITE, unique, inode, offset, size, javaBuffer);
int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override {
CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast<const jbyte*>(buffer));
CHECK(!mEnv->ExceptionCheck()); CHECK(!mEnv->ExceptionCheck());
return checkException(mEnv->CallIntMethod(
mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get()));
} }
}; };
jboolean com_android_internal_os_FuseAppLoop_start_loop(JNIEnv* env, jobject self, jint jfd) { jlong com_android_internal_os_FuseAppLoop_new(JNIEnv* env, jobject self, jint jfd) {
base::unique_fd fd(jfd); return reinterpret_cast<jlong>(new fuse::FuseAppLoop(base::unique_fd(jfd)));
Callback callback(env, self);
if (!callback.Init()) {
LOG(ERROR) << "Failed to init callback";
return JNI_FALSE;
} }
return fuse::StartFuseAppLoop(fd.release(), &callback); 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);
}
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();
}
}
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[] = { const JNINativeMethod methods[] = {
{ {
"native_start_loop", "native_new",
"(I)Z", "(I)J",
(void *) com_android_internal_os_FuseAppLoop_start_loop 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 } // namespace
int register_com_android_internal_os_FuseAppLoop(JNIEnv* env) { int register_com_android_internal_os_FuseAppLoop(JNIEnv* env) {
gFuseAppLoopClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME)); gFuseAppLoopClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
gOnGetSizeMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onGetSize", "(J)J"); gOnCommandMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onCommand", "(IJJJI[B)V");
gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(J)I"); gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(JJ)[B");
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");
RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods)); RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods));
return 0; return 0;
} }
} // namespace android } // namespace android

View File

@@ -264,7 +264,7 @@ public class StorageManagerIntegrationTest extends StorageManagerBaseTest {
final MyThreadFactory factory = new MyThreadFactory(); final MyThreadFactory factory = new MyThreadFactory();
int firstMountId; int firstMountId;
try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor( 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()); assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
firstMountId = mSm.getProxyFileDescriptorMountPointId(); firstMountId = mSm.getProxyFileDescriptorMountPointId();
assertNotSame(-1, firstMountId); assertNotSame(-1, firstMountId);
@@ -276,7 +276,7 @@ public class StorageManagerIntegrationTest extends StorageManagerBaseTest {
// StorageManager should mount another bridge on the next open request. // StorageManager should mount another bridge on the next open request.
try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor( 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(Thread.State.TERMINATED, factory.thread.getState());
assertNotSame(firstMountId, mSm.getProxyFileDescriptorMountPointId()); 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.app.IMediaContainerService;
import com.android.internal.os.AppFuseMount; import com.android.internal.os.AppFuseMount;
import com.android.internal.os.FuseAppLoop; import com.android.internal.os.FuseAppLoop;
import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.SomeArgs; import com.android.internal.os.SomeArgs;
import com.android.internal.os.Zygote; import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils; import com.android.internal.util.ArrayUtils;
@@ -3007,10 +3008,18 @@ class StorageManagerService extends IStorageManager.Stub
} }
} }
private ParcelFileDescriptor mountAppFuse(int uid, int mountId) class AppFuseMountScope extends AppFuseBridge.MountScope {
throws NativeDaemonConnectorException { 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( final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute(
"appfuse", "mount", uid, Process.myPid(), mountId); "appfuse", "mount", uid, Process.myPid(), mountId);
opened = true;
if (event.getFileDescriptors() == null || if (event.getFileDescriptors() == null ||
event.getFileDescriptors().length == 0) { event.getFileDescriptors().length == 0) {
throw new NativeDaemonConnectorException("Cannot obtain device FD"); throw new NativeDaemonConnectorException("Cannot obtain device FD");
@@ -3018,21 +3027,17 @@ class StorageManagerService extends IStorageManager.Stub
return new ParcelFileDescriptor(event.getFileDescriptors()[0]); 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 @Override
public void close() throws Exception { public void close() throws Exception {
super.close(); if (opened) {
mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId); mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
opened = false;
}
} }
} }
@Override @Override
public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException { public @Nullable AppFuseMount mountProxyFileDescriptorBridge() {
Slog.v(TAG, "mountProxyFileDescriptorBridge"); Slog.v(TAG, "mountProxyFileDescriptorBridge");
final int uid = Binder.getCallingUid(); final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid(); final int pid = Binder.getCallingPid();
@@ -3049,12 +3054,12 @@ class StorageManagerService extends IStorageManager.Stub
final int name = mNextAppFuseName++; final int name = mNextAppFuseName++;
try { try {
return new AppFuseMount( return new AppFuseMount(
name, name, mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name)));
mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name))); } catch (FuseUnavailableMountException e) {
} catch (AppFuseBridge.BridgeException e) {
if (newlyCreated) { if (newlyCreated) {
// If newly created bridge fails, it's a real error. // 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. // It seems the thread of mAppFuseBridge has already been terminated.
mAppFuseBridge = null; mAppFuseBridge = null;
@@ -3067,19 +3072,21 @@ class StorageManagerService extends IStorageManager.Stub
} }
@Override @Override
public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode) public @Nullable ParcelFileDescriptor openProxyFileDescriptor(
throws RemoteException { int mountId, int fileId, int mode) {
Slog.v(TAG, "mountProxyFileDescriptorBridge"); Slog.v(TAG, "mountProxyFileDescriptor");
final int pid = Binder.getCallingPid(); final int pid = Binder.getCallingPid();
try { try {
synchronized (mAppFuseLock) { synchronized (mAppFuseLock) {
if (mAppFuseBridge == null) { 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); return mAppFuseBridge.openFile(pid, mountId, fileId, mode);
} }
} catch (FileNotFoundException | SecurityException | InterruptedException error) { } catch (FuseUnavailableMountException | InterruptedException error) {
throw new RemoteException(error.getMessage()); 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.system.Os;
import android.util.SparseArray; import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import com.android.server.NativeDaemonConnectorException;
import libcore.io.IoUtils; import libcore.io.IoUtils;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -54,17 +56,17 @@ public class AppFuseBridge implements Runnable {
} }
public ParcelFileDescriptor addBridge(MountScope mountScope) public ParcelFileDescriptor addBridge(MountScope mountScope)
throws BridgeException { throws FuseUnavailableMountException, NativeDaemonConnectorException {
try { try {
synchronized (this) { synchronized (this) {
Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0); Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0);
if (mNativeLoop == 0) { if (mNativeLoop == 0) {
throw new BridgeException("The thread has already been terminated"); throw new FuseUnavailableMountException(mountScope.mountId);
} }
final int fd = native_add_bridge( final int fd = native_add_bridge(
mNativeLoop, mountScope.mountId, mountScope.deviceFd.detachFd()); mNativeLoop, mountScope.mountId, mountScope.open().detachFd());
if (fd == -1) { if (fd == -1) {
throw new BridgeException("Failed to invoke native_add_bridge"); throw new FuseUnavailableMountException(mountScope.mountId);
} }
final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd); final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd);
mScopes.put(mountScope.mountId, mountScope); 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) public ParcelFileDescriptor openFile(int pid, int mountId, int fileId, int mode)
throws FileNotFoundException, SecurityException, InterruptedException { throws FuseUnavailableMountException, InterruptedException {
final MountScope scope; final MountScope scope;
synchronized (this) { synchronized (this) {
scope = mScopes.get(mountId); scope = mScopes.get(mountId);
if (scope == null) { if (scope == null) {
throw new FileNotFoundException("Cannot find mount point"); throw new FuseUnavailableMountException(mountId);
} }
} }
if (scope.pid != pid) { if (scope.pid != pid) {
@@ -99,17 +101,14 @@ public class AppFuseBridge implements Runnable {
} }
final boolean result = scope.waitForMount(); final boolean result = scope.waitForMount();
if (result == false) { if (result == false) {
throw new FileNotFoundException("Mount failed"); throw new FuseUnavailableMountException(mountId);
} }
try { try {
if (Os.stat(scope.mountPoint.getPath()).st_ino != 1) { return ParcelFileDescriptor.open(
throw new FileNotFoundException("Could not find bridge mount point."); new File(scope.mountPoint, String.valueOf(fileId)), mode);
} catch (FileNotFoundException error) {
throw new FuseUnavailableMountException(mountId);
} }
} 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);
} }
// Used by com_android_server_storage_AppFuse.cpp. // 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 uid;
public final int pid; public final int pid;
public final int mountId; public final int mountId;
public final ParcelFileDescriptor deviceFd;
public final File mountPoint; public final File mountPoint;
private final CountDownLatch mMounted = new CountDownLatch(1); private final CountDownLatch mMounted = new CountDownLatch(1);
private boolean mMountResult = false; 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.uid = uid;
this.pid = pid; this.pid = pid;
this.mountId = mountId; this.mountId = mountId;
this.deviceFd = deviceFd;
this.mountPoint = new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId)); this.mountPoint = new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId));
} }
@@ -161,16 +158,7 @@ public class AppFuseBridge implements Runnable {
return mMountResult; return mMountResult;
} }
@Override public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException;
public void close() throws Exception {
deviceFd.close();
}
}
public static class BridgeException extends Exception {
public BridgeException(String message) {
super(message);
}
} }
private native long native_new(); private native long native_new();