diff options
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()); } } |