Loading core/java/android/os/ParcelFileDescriptor.java +13 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +24 −7 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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; } } } Loading Loading @@ -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."); Loading @@ -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; Loading @@ -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; } } } Loading packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +18 −0 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -54,6 +57,21 @@ public class AppFuse { mMessageThread.start(); } @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); Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +8 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } } services/core/java/com/android/server/MountService.java +21 −6 Original line number Diff line number Diff line Loading @@ -2813,17 +2813,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; 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."); } } return new ParcelFileDescriptor(event.getFileDescriptors()[0]); }); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } catch (IOException e) { throw new RemoteException(e.getMessage()); } } Loading Loading
core/java/android/os/ParcelFileDescriptor.java +13 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading
packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +24 −7 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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; } } } Loading Loading @@ -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."); Loading @@ -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; Loading @@ -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; } } } Loading
packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +18 −0 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -54,6 +57,21 @@ public class AppFuse { mMessageThread.start(); } @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); Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +8 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } }
services/core/java/com/android/server/MountService.java +21 −6 Original line number Diff line number Diff line Loading @@ -2813,17 +2813,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; 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."); } } return new ParcelFileDescriptor(event.getFileDescriptors()[0]); }); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } catch (IOException e) { throw new RemoteException(e.getMessage()); } } Loading