Merge "Allow apps to process ProxyFDCallback asynchrnously." into oc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
76c32a35a9
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user