summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Daichi Hirono <hirono@google.com> 2015-12-16 09:24:16 +0900
committer Daichi Hirono <hirono@google.com> 2015-12-22 13:41:33 +0900
commit91e3b50636f48f0860fa7576f185fb36ec4e6dc7 (patch)
treebe34adb71b749726bb4ddb73d22fbb973b6cb8b7
parent01cf378e045dafe136cda39f5559ae1c36119f5b (diff)
Unmount appfuse when the device FD is closed.
The CL lets MountService to observe device FD, and request unmount to vold when the device FD was closed, or remote application providing appfuse is crashed. BUG=25756420 Change-Id: I7990694d32affa7f89e3f40badb25098d74d744d
-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());
}
}