Add AppFuse class and its JNI module.
BUG=25756145
Change-Id: I10597e3377cf860412e006a118cd979b6f108af3
diff --git a/packages/MtpDocumentsProvider/Android.mk b/packages/MtpDocumentsProvider/Android.mk
index 3c2fa36..67bbf78 100644
--- a/packages/MtpDocumentsProvider/Android.mk
+++ b/packages/MtpDocumentsProvider/Android.mk
@@ -6,6 +6,7 @@
LOCAL_PACKAGE_NAME := MtpDocumentsProvider
LOCAL_CERTIFICATE := media
LOCAL_PRIVILEGED_MODULE := true
+LOCAL_JNI_SHARED_LIBRARIES := libappfuse_jni
include $(BUILD_PACKAGE)
-include $(LOCAL_PATH)/tests/Android.mk
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/packages/MtpDocumentsProvider/jni/Android.mk b/packages/MtpDocumentsProvider/jni/Android.mk
new file mode 100644
index 0000000..d545b14
--- /dev/null
+++ b/packages/MtpDocumentsProvider/jni/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ com_android_mtp_AppFuse.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libandroid_runtime \
+ libnativehelper \
+ liblog
+
+LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
+LOCAL_MODULE := libappfuse_jni
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
new file mode 100644
index 0000000..dbce609
--- /dev/null
+++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2015 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 specic language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "AppFuseJNI"
+#include "utils/Log.h"
+
+#include <assert.h>
+#include <dirent.h>
+#include <inttypes.h>
+
+#include <linux/fuse.h>
+#include <sys/stat.h>
+
+#include <map>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+namespace {
+
+constexpr int min(int a, int b) {
+ return a < b ? a : b;
+}
+
+// Maximum number of bytes to write in one request.
+constexpr size_t MAX_WRITE = 256 * 1024;
+
+// Largest possible request.
+// The request size is bounded by the maximum size of a FUSE_WRITE request
+// because it has the largest possible data payload.
+constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_write_in) + MAX_WRITE;
+
+static jclass app_fuse_class;
+
+struct FuseRequest {
+ char buffer[MAX_REQUEST_SIZE];
+ FuseRequest() {}
+ const struct fuse_in_header& header() const {
+ return *(const struct fuse_in_header*) buffer;
+ }
+ const void* data() const {
+ return (buffer + sizeof(struct fuse_in_header));
+ }
+ size_t data_length() const {
+ return header().len - sizeof(struct fuse_in_header);
+ }
+};
+
+/**
+ * The class is used to access AppFuse class in Java from fuse handlers.
+ */
+class AppFuse {
+public:
+ AppFuse(JNIEnv* /*env*/, jobject /*self*/) {
+ }
+
+ void 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;
+ case FUSE_GETATTR:
+ invoke_handler(fd, req, &AppFuse::handle_fuse_getattr);
+ break;
+ 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;
+ }
+ }
+ }
+
+private:
+ int handle_fuse_init(const fuse_in_header&,
+ const fuse_init_in* in,
+ fuse_init_out* out,
+ size_t* reply_size) {
+ // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
+ // defined (fuse version 7.6). The structure is the same from 7.6 through
+ // 7.22. Beginning with 7.23, the structure increased in size and added
+ // new parameters.
+ if (in->major != FUSE_KERNEL_VERSION || in->minor < 6) {
+ ALOGE("Fuse kernel version mismatch: Kernel version %d.%d, "
+ "Expected at least %d.6",
+ in->major, in->minor, FUSE_KERNEL_VERSION);
+ return -1;
+ }
+
+ // We limit ourselves to 15 because we don't handle BATCH_FORGET yet
+ out->minor = min(in->minor, 15);
+#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
+ // FUSE_KERNEL_VERSION >= 23.
+
+ // If the kernel only works on minor revs older than or equal to 22,
+ // then use the older structure size since this code only uses the 7.22
+ // version of the structure.
+ if (in->minor <= 22) {
+ *reply_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
+ }
+#endif
+
+ out->major = FUSE_KERNEL_VERSION;
+ out->max_readahead = in->max_readahead;
+ out->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
+ out->max_background = 32;
+ out->congestion_threshold = 32;
+ out->max_write = MAX_WRITE;
+
+ return 0;
+ }
+
+ int handle_fuse_getattr(const fuse_in_header& header,
+ const fuse_getattr_in* /* in */,
+ fuse_attr_out* out,
+ size_t* /*unused*/) {
+ if (header.nodeid != 1) {
+ return -ENOENT;
+ }
+ out->attr_valid = 1000 * 60 * 10;
+ out->attr.ino = header.nodeid;
+ out->attr.mode = S_IFDIR | 0777;
+ out->attr.size = 0;
+ return 0;
+ }
+
+ template <typename T, typename S>
+ void invoke_handler(int fd,
+ const FuseRequest& request,
+ int (AppFuse::*handler)(const fuse_in_header&,
+ const T*,
+ S*,
+ size_t*),
+ size_t reply_size = sizeof(S)) {
+ char reply_data[reply_size];
+ memset(reply_data, 0, reply_size);
+ const int reply_code = (this->*handler)(
+ request.header(),
+ static_cast<const T*>(request.data()),
+ reinterpret_cast<S*>(reply_data),
+ &reply_size);
+ fuse_reply(
+ fd,
+ request.header().unique,
+ reply_code,
+ reply_data,
+ reply_size);
+ }
+
+ static void fuse_reply(int fd, int unique, int reply_code, void* reply_data,
+ size_t reply_size) {
+ // Don't send any data for error case.
+ if (reply_code != 0) {
+ reply_size = 0;
+ }
+
+ struct fuse_out_header hdr;
+ hdr.len = reply_size + sizeof(hdr);
+ hdr.error = reply_code;
+ hdr.unique = unique;
+
+ struct iovec vec[2];
+ vec[0].iov_base = &hdr;
+ vec[0].iov_len = sizeof(hdr);
+ vec[1].iov_base = reply_data;
+ vec[1].iov_len = reply_size;
+
+ const int res = writev(fd, vec, reply_size != 0 ? 2 : 1);
+ if (res < 0) {
+ ALOGE("*** REPLY FAILED *** %d\n", errno);
+ }
+ }
+};
+
+jboolean com_android_mtp_AppFuse_start_app_fuse_loop(
+ JNIEnv* env, jobject self, jint jfd) {
+ const int fd = static_cast<int>(jfd);
+ AppFuse appfuse(env, self);
+
+ ALOGD("Start fuse loop.");
+ while (true) {
+ FuseRequest request;
+ const ssize_t result = TEMP_FAILURE_RETRY(
+ read(fd, request.buffer, sizeof(request.buffer)));
+ if (result < 0) {
+ if (errno == ENODEV) {
+ ALOGE("Someone stole our marbles!\n");
+ return false;
+ }
+ ALOGE("Failed to read bytes from FD: errno=%d\n", errno);
+ continue;
+ }
+
+ const size_t length = static_cast<size_t>(result);
+ if (length < sizeof(struct fuse_in_header)) {
+ ALOGE("request too short: len=%zu\n", length);
+ continue;
+ }
+
+ if (request.header().len != length) {
+ ALOGE("malformed header: len=%zu, hdr->len=%u\n",
+ length, request.header().len);
+ continue;
+ }
+
+ appfuse.handle_fuse_request(fd, request);
+ }
+}
+
+static const JNINativeMethod gMethods[] = {
+ {
+ "native_start_app_fuse_loop",
+ "(I)Z",
+ (void *) com_android_mtp_AppFuse_start_app_fuse_loop
+ }
+};
+
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+ JNIEnv* env = nullptr;
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("ERROR: GetEnv failed\n");
+ return -1;
+
+ }
+ assert(env != nullptr);
+
+ jclass clazz = env->FindClass("com/android/mtp/AppFuse");
+ if (clazz == nullptr) {
+ ALOGE("Can't find com/android/mtp/AppFuse");
+ return -1;
+ }
+ app_fuse_class = static_cast<jclass>(env->NewGlobalRef(clazz));
+ if (app_fuse_class == nullptr) {
+ ALOGE("Can't obtain global reference for com/android/mtp/AppFuse");
+ return -1;
+ }
+
+ const int result = android::AndroidRuntime::registerNativeMethods(
+ env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods));
+ if (result < 0) {
+ return -1;
+ }
+
+ return JNI_VERSION_1_4;
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
new file mode 100644
index 0000000..e9edeb9
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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.mtp;
+
+import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import android.os.Process;
+
+/**
+ * TODO: Remove VisibleForTesting class.
+ */
+@VisibleForTesting
+public class AppFuse {
+ static {
+ System.loadLibrary("appfuse_jni");
+ }
+
+ private final String mName;
+ private final Thread mMessageThread;
+ private ParcelFileDescriptor mDeviceFd;
+
+ @VisibleForTesting
+ AppFuse(String name) {
+ mName = name;
+ mMessageThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ native_start_app_fuse_loop(mDeviceFd.getFd());
+ }
+ });
+ }
+
+ @VisibleForTesting
+ void mount(StorageManager storageManager) {
+ mDeviceFd = storageManager.mountAppFuse(mName);
+ mMessageThread.start();
+ }
+
+ @VisibleForTesting
+ File getMountPoint() {
+ return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName);
+ }
+
+ private native boolean native_start_app_fuse_loop(int fd);
+}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
new file mode 100644
index 0000000..a145756
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.mtp;
+
+import android.os.storage.StorageManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.File;
+
+@SmallTest
+public class AppFuseTest extends AndroidTestCase {
+ /**
+ * TODO: Enable this test after adding SELinux policies for appfuse.
+ */
+ public void testBasic() {
+ final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
+ final AppFuse appFuse = new AppFuse("test");
+ appFuse.mount(storageManager);
+ final File file = appFuse.getMountPoint();
+ assertTrue(file.isDirectory());
+ }
+}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 184f890..fbb31a5 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2813,8 +2813,17 @@
@Override
public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
- // TODO: Invoke vold to mount app fuse.
- throw new UnsupportedOperationException();
+ try {
+ final NativeDaemonEvent event =
+ mConnector.execute("appfuse", "mount", Binder.getCallingUid(), name);
+ if (event.getFileDescriptors() == null) {
+ Log.e(TAG, "AppFuse FD from vold is null.");
+ return null;
+ }
+ return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
}
@Override