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 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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
mFuseAppLoop.unregisterCallback(fileId);
|
||||||
return pfd;
|
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;
|
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() {
|
||||||
|
|||||||
@@ -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) {
|
||||||
mCallbackMap.remove(id);
|
synchronized (mLock) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
entry.opened = true;
|
if (mInstance != 0) {
|
||||||
// Use inode as file handle. It's OK because AppFuse does not allow to open the same
|
native_replyOpen(mInstance, unique, /* fh */ inode);
|
||||||
// file twice.
|
entry.opened = true;
|
||||||
return (int) inode;
|
return mBytesMap.startUsing(entry.getThreadId());
|
||||||
} 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;
|
||||||
synchronized(mLock) {
|
if (errno != OsConstants.ENOSYS) {
|
||||||
try {
|
return -errno;
|
||||||
getCallbackEntryOrThrowLocked(inode).callback.onFsync();
|
|
||||||
return 0;
|
|
||||||
} catch (ErrnoException exp) {
|
|
||||||
return getError(exp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 <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());
|
||||||
int32_t OnOpen(uint64_t inode) override {
|
if (buffer == nullptr) {
|
||||||
return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode));
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer));
|
mBuffers.insert(std::make_pair(inode, std::unique_ptr<LocalBytes>(
|
||||||
CHECK(!mEnv->ExceptionCheck());
|
new LocalBytes(mEnv, buffer))));
|
||||||
|
|
||||||
return checkException(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override {
|
void OnFsync(uint64_t unique, uint64_t inode) override {
|
||||||
CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
|
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());
|
CHECK(!mEnv->ExceptionCheck());
|
||||||
|
|
||||||
return checkException(mEnv->CallIntMethod(
|
mEnv->CallVoidMethod(
|
||||||
mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get()));
|
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) {
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
Callback callback(env, self);
|
||||||
|
reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Start(&callback);
|
||||||
|
}
|
||||||
|
|
||||||
if (!callback.Init()) {
|
void com_android_internal_os_FuseAppLoop_replySimple(
|
||||||
LOG(ERROR) << "Failed to init callback";
|
JNIEnv* env, jobject self, jlong ptr, jlong unique, jint result) {
|
||||||
return JNI_FALSE;
|
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[] = {
|
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
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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 {
|
class AppFuseMountScope extends AppFuseBridge.MountScope {
|
||||||
public AppFuseMountScope(int uid, int pid, int mountId)
|
boolean opened = false;
|
||||||
throws NativeDaemonConnectorException {
|
|
||||||
super(uid, pid, mountId, mountAppFuse(uid, mountId));
|
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
|
@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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
} catch (ErrnoException e) {
|
throw new FuseUnavailableMountException(mountId);
|
||||||
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();
|
||||||
|
|||||||
Reference in New Issue
Block a user