summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java13
-rw-r--r--packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp31
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java18
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java9
-rw-r--r--services/core/java/com/android/server/MountService.java27
5 files changed, 84 insertions, 14 deletions
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 135f369f895f..d5491d3a808a 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -233,6 +233,19 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
final FileDescriptor fd = openInternal(file, mode);
if (fd == null) return null;
+ return fromFd(fd, handler, listener);
+ }
+
+ /** {@hide} */
+ public static ParcelFileDescriptor fromFd(
+ FileDescriptor fd, Handler handler, final OnCloseListener listener) throws IOException {
+ if (handler == null) {
+ throw new IllegalArgumentException("Handler must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener must not be null");
+ }
+
final FileDescriptor[] comm = createCommSocketPair();
final ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd, comm[0]);
final MessageQueue queue = handler.getLooper().getQueue();
diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
index 9267f4cfa251..f592a1fb1ae1 100644
--- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
+++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
@@ -62,6 +62,19 @@ struct FuseRequest {
}
};
+class ScopedFd {
+ int mFd;
+
+public:
+ explicit ScopedFd(int fd) : mFd(fd) {}
+ ~ScopedFd() {
+ close(mFd);
+ }
+ operator int() {
+ return mFd;
+ }
+};
+
/**
* The class is used to access AppFuse class in Java from fuse handlers.
*/
@@ -70,24 +83,26 @@ public:
AppFuse(JNIEnv* /*env*/, jobject /*self*/) {
}
- void handle_fuse_request(int fd, const FuseRequest& req) {
+ bool handle_fuse_request(int fd, const FuseRequest& req) {
ALOGV("Request op=%d", req.header().opcode);
switch (req.header().opcode) {
// TODO: Handle more operations that are enough to provide seekable
// FD.
case FUSE_INIT:
invoke_handler(fd, req, &AppFuse::handle_fuse_init);
- break;
+ return true;
case FUSE_GETATTR:
invoke_handler(fd, req, &AppFuse::handle_fuse_getattr);
- break;
+ return true;
+ case FUSE_FORGET:
+ return false;
default: {
ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n",
req.header().opcode,
req.header().unique,
req.header().nodeid);
fuse_reply(fd, req.header().unique, -ENOSYS, NULL, 0);
- break;
+ return true;
}
}
}
@@ -198,7 +213,7 @@ private:
jboolean com_android_mtp_AppFuse_start_app_fuse_loop(
JNIEnv* env, jobject self, jint jfd) {
- const int fd = static_cast<int>(jfd);
+ ScopedFd fd(dup(static_cast<int>(jfd)));
AppFuse appfuse(env, self);
ALOGD("Start fuse loop.");
@@ -209,7 +224,7 @@ jboolean com_android_mtp_AppFuse_start_app_fuse_loop(
if (result < 0) {
if (errno == ENODEV) {
ALOGE("Someone stole our marbles!\n");
- return false;
+ return JNI_FALSE;
}
ALOGE("Failed to read bytes from FD: errno=%d\n", errno);
continue;
@@ -227,7 +242,9 @@ jboolean com_android_mtp_AppFuse_start_app_fuse_loop(
continue;
}
- appfuse.handle_fuse_request(fd, request);
+ if (!appfuse.handle_fuse_request(fd, request)) {
+ return JNI_TRUE;
+ }
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
index e9edeb9ca8fa..2c09ad135266 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
@@ -18,10 +18,13 @@ package com.android.mtp;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
+import java.io.IOException;
+
import android.os.Process;
/**
@@ -55,6 +58,21 @@ public class AppFuse {
}
@VisibleForTesting
+ void close() {
+ try {
+ // Remote side of ParcelFileDescriptor is tracking the close of mDeviceFd, and unmount
+ // the corresponding fuse file system. The mMessageThread will receive FUSE_FORGET, and
+ // then terminate itself.
+ mDeviceFd.close();
+ mMessageThread.join();
+ } catch (IOException exp) {
+ Log.e(MtpDocumentsProvider.TAG, "Failed to close device FD.", exp);
+ } catch (InterruptedException exp) {
+ Log.e(MtpDocumentsProvider.TAG, "Failed to terminate message thread.", exp);
+ }
+ }
+
+ @VisibleForTesting
File getMountPoint() {
return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName);
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
index a14575682389..b66d8ebec212 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
@@ -17,6 +17,8 @@
package com.android.mtp;
import android.os.storage.StorageManager;
+import android.system.ErrnoException;
+import android.system.Os;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@@ -26,12 +28,17 @@ import java.io.File;
public class AppFuseTest extends AndroidTestCase {
/**
* TODO: Enable this test after adding SELinux policies for appfuse.
+ * @throws ErrnoException
+ * @throws InterruptedException
*/
- public void testBasic() {
+ public void disabled_testBasic() throws ErrnoException, InterruptedException {
final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
final AppFuse appFuse = new AppFuse("test");
appFuse.mount(storageManager);
final File file = appFuse.getMountPoint();
assertTrue(file.isDirectory());
+ assertEquals(1, Os.stat(file.getPath()).st_ino);
+ appFuse.close();
+ assertTrue(1 != Os.stat(file.getPath()).st_ino);
}
}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index fbb31a51a01b..96a8db86559d 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2812,17 +2812,32 @@ class MountService extends IMountService.Stub
}
@Override
- public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+ public ParcelFileDescriptor mountAppFuse(final String name) throws RemoteException {
try {
+ final int uid = Binder.getCallingUid();
final NativeDaemonEvent event =
- mConnector.execute("appfuse", "mount", Binder.getCallingUid(), name);
+ mConnector.execute("appfuse", "mount", uid, name);
if (event.getFileDescriptors() == null) {
- Log.e(TAG, "AppFuse FD from vold is null.");
- return null;
- }
- return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
+ throw new RemoteException("AppFuse FD from vold is null.");
+ }
+ return ParcelFileDescriptor.fromFd(
+ event.getFileDescriptors()[0],
+ mHandler,
+ new ParcelFileDescriptor.OnCloseListener() {
+ @Override
+ public void onClose(IOException e) {
+ try {
+ final NativeDaemonEvent event = mConnector.execute(
+ "appfuse", "unmount", uid, name);
+ } catch (NativeDaemonConnectorException unmountException) {
+ Log.e(TAG, "Failed to unmount appfuse.");
+ }
+ }
+ });
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
+ } catch (IOException e) {
+ throw new RemoteException(e.getMessage());
}
}