Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d66ae533 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Use public version of AppFuse in MtpDocumentsProvider."

parents aba7c9af e80ea384
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -6,7 +6,6 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := MtpDocumentsProvider
LOCAL_CERTIFICATE := media
LOCAL_PRIVILEGED_MODULE := true
LOCAL_JNI_SHARED_LIBRARIES := libappfuse_jni
LOCAL_PROGUARD_FLAG_FILES := proguard.flags

# Only enable asserts on userdebug/eng builds
+0 −15
Original line number Diff line number Diff line
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)
+0 −571
Original line number Diff line number Diff line
/*
 * 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_TAG "AppFuseJNI"
#include "utils/Log.h"

#include <assert.h>
#include <dirent.h>
#include <inttypes.h>

#include <linux/fuse.h>
#include <sys/stat.h>
#include <sys/uio.h>

#include <map>

#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include "nativehelper/ScopedPrimitiveArray.h"
#include "nativehelper/ScopedLocalRef.h"

namespace {

// The numbers came from sdcard.c.
// Maximum number of bytes to write/read in one request/one reply.
constexpr size_t MAX_WRITE = 256 * 1024;
constexpr size_t MAX_READ = 128 * 1024;

constexpr size_t NUM_MAX_HANDLES = 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 > MAX_READ ? MAX_WRITE : MAX_READ);

static jclass app_fuse_class;
static jmethodID app_fuse_get_file_size;
static jmethodID app_fuse_read_object_bytes;
static jmethodID app_fuse_write_object_bytes;
static jmethodID app_fuse_flush_file_handle;
static jmethodID app_fuse_close_file_handle;
static jfieldID app_fuse_buffer;

// NOTE:
// FuseRequest and FuseResponse shares the same buffer to save memory usage, so the handlers must
// not access input buffer after writing data to output buffer.
struct FuseRequest {
    char buffer[MAX_REQUEST_SIZE];
    FuseRequest() {}
    const struct fuse_in_header& header() const {
        return *(const struct fuse_in_header*) buffer;
    }
    void* data() {
        return (buffer + sizeof(struct fuse_in_header));
    }
    size_t data_length() const {
        return header().len - sizeof(struct fuse_in_header);
    }
};

template<typename T>
class FuseResponse {
   size_t size_;
   T* const buffer_;
public:
   explicit FuseResponse(void* buffer) : size_(0), buffer_(static_cast<T*>(buffer)) {}

   void prepare_buffer(size_t size = sizeof(T)) {
       memset(buffer_, 0, size);
       size_ = size;
   }

   void set_size(size_t size) {
       size_ = size;
   }

   size_t size() const { return size_; }
   T* data() const { return buffer_; }
};

class ScopedFd {
    int mFd;

public:
    explicit ScopedFd(int fd) : mFd(fd) {}
    ~ScopedFd() {
        close(mFd);
    }
    operator int() {
        return mFd;
    }
};

/**
 * Fuse implementation consists of handlers parsing FUSE commands.
 */
class AppFuse {
    JNIEnv* env_;
    jobject self_;

    // Map between file handle and inode.
    std::map<uint32_t, uint64_t> handles_;
    uint32_t handle_counter_;

public:
    AppFuse(JNIEnv* env, jobject self) :
        env_(env), self_(self), handle_counter_(0) {}

    void handle_fuse_request(int fd, 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_LOOKUP:
                invoke_handler(fd, req, &AppFuse::handle_fuse_lookup);
                return;
            case FUSE_FORGET:
                // Return without replying.
                return;
            case FUSE_INIT:
                invoke_handler(fd, req, &AppFuse::handle_fuse_init);
                return;
            case FUSE_GETATTR:
                invoke_handler(fd, req, &AppFuse::handle_fuse_getattr);
                return;
            case FUSE_OPEN:
                invoke_handler(fd, req, &AppFuse::handle_fuse_open);
                return;
            case FUSE_READ:
                invoke_handler(fd, req, &AppFuse::handle_fuse_read);
                return;
            case FUSE_WRITE:
                invoke_handler(fd, req, &AppFuse::handle_fuse_write);
                return;
            case FUSE_RELEASE:
                invoke_handler(fd, req, &AppFuse::handle_fuse_release);
                return;
            case FUSE_FLUSH:
                invoke_handler(fd, req, &AppFuse::handle_fuse_flush);
                return;
            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);
                return;
            }
        }
    }

private:
    int handle_fuse_lookup(const fuse_in_header& header,
                           const char* name,
                           FuseResponse<fuse_entry_out>* out) {
        if (header.nodeid != 1) {
            return -ENOENT;
        }

        const int n = atoi(name);
        if (n == 0) {
            return -ENOENT;
        }

        int64_t size = get_file_size(n);
        if (size < 0) {
            return -ENOENT;
        }

        out->prepare_buffer();
        out->data()->nodeid = n;
        out->data()->attr_valid = 10;
        out->data()->entry_valid = 10;
        out->data()->attr.ino = n;
        out->data()->attr.mode = S_IFREG | 0777;
        out->data()->attr.size = size;
        return 0;
    }

    int handle_fuse_init(const fuse_in_header&,
                         const fuse_init_in* in,
                         FuseResponse<fuse_init_out>* out) {
        // 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;
        }

        // Before writing |out|, we need to copy data from |in|.
        const uint32_t minor = in->minor;
        const uint32_t max_readahead = in->max_readahead;

        // We limit ourselves to 15 because we don't handle BATCH_FORGET yet
        size_t response_size = sizeof(fuse_init_out);
#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 (minor <= 22) {
            response_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
        }
#endif
        out->prepare_buffer(response_size);
        out->data()->major = FUSE_KERNEL_VERSION;
        out->data()->minor = std::min(minor, 15u);
        out->data()->max_readahead = max_readahead;
        out->data()->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
        out->data()->max_background = 32;
        out->data()->congestion_threshold = 32;
        out->data()->max_write = MAX_WRITE;

        return 0;
    }

    int handle_fuse_getattr(const fuse_in_header& header,
                            const fuse_getattr_in* /* in */,
                            FuseResponse<fuse_attr_out>* out) {
        out->prepare_buffer();
        out->data()->attr_valid = 10;
        out->data()->attr.ino = header.nodeid;
        if (header.nodeid == 1) {
            out->data()->attr.mode = S_IFDIR | 0777;
            out->data()->attr.size = 0;
        } else {
            int64_t size = get_file_size(header.nodeid);
            if (size < 0) {
                return -ENOENT;
            }
            out->data()->attr.mode = S_IFREG | 0777;
            out->data()->attr.size = size;
        }

        return 0;
    }

    int handle_fuse_open(const fuse_in_header& header,
                         const fuse_open_in* /* in */,
                         FuseResponse<fuse_open_out>* out) {
        if (handles_.size() >= NUM_MAX_HANDLES) {
            // Too many open files.
            return -EMFILE;
        }
        uint32_t handle;
        do {
           handle = handle_counter_++;
        } while (handles_.count(handle) != 0);
        handles_.insert(std::make_pair(handle, header.nodeid));

        out->prepare_buffer();
        out->data()->fh = handle;
        return 0;
    }

    int handle_fuse_read(const fuse_in_header& /* header */,
                         const fuse_read_in* in,
                         FuseResponse<void>* out) {
        if (in->size > MAX_READ) {
            return -EINVAL;
        }
        const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh);
        if (it == handles_.end()) {
            return -EBADF;
        }
        uint64_t offset = in->offset;
        uint32_t size = in->size;

        // Overwrite the size after writing data.
        out->prepare_buffer(0);
        const int64_t result = get_object_bytes(it->second, offset, size, out->data());
        if (result < 0) {
            return result;
        }
        out->set_size(result);
        return 0;
    }

    int handle_fuse_write(const fuse_in_header& /* header */,
                          const fuse_write_in* in,
                          FuseResponse<fuse_write_out>* out) {
        if (in->size > MAX_WRITE) {
            return -EINVAL;
        }
        const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh);
        if (it == handles_.end()) {
            return -EBADF;
        }
        const uint64_t offset = in->offset;
        const uint32_t size = in->size;
        const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in);
        uint32_t written_size;
        const int result = write_object_bytes(
                in->fh, it->second, offset, size, buffer, &written_size);
        if (result < 0) {
            return result;
        }
        out->prepare_buffer();
        out->data()->size = written_size;
        return 0;
    }

    int handle_fuse_release(const fuse_in_header& /* header */,
                            const fuse_release_in* in,
                            FuseResponse<void>* /* out */) {
        handles_.erase(in->fh);
        return env_->CallIntMethod(self_, app_fuse_close_file_handle, file_handle_to_jlong(in->fh));
    }

    int handle_fuse_flush(const fuse_in_header& /* header */,
                          const fuse_flush_in* in,
                          FuseResponse<void>* /* out */) {
        return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh));
    }

    template <typename T, typename S>
    void invoke_handler(int fd,
                        FuseRequest* request,
                        int (AppFuse::*handler)(const fuse_in_header&,
                                                const T*,
                                                FuseResponse<S>*)) {
        FuseResponse<S> response(request->data());
        const int reply_code = (this->*handler)(
                request->header(),
                static_cast<const T*>(request->data()),
                &response);
        fuse_reply(
                fd,
                request->header().unique,
                reply_code,
                request->data(),
                response.size());
    }

    int64_t get_file_size(int inode) {
        return static_cast<int64_t>(env_->CallLongMethod(
                self_,
                app_fuse_get_file_size,
                static_cast<int>(inode)));
    }

    int64_t get_object_bytes(
            int inode,
            uint64_t offset,
            uint32_t size,
            void* buf) {
        const jlong read_size = env_->CallLongMethod(
                self_,
                app_fuse_read_object_bytes,
                static_cast<jint>(inode),
                static_cast<jlong>(offset),
                static_cast<jlong>(size));
        if (read_size <= 0) {
            return read_size;
        }
        ScopedLocalRef<jbyteArray> array(
                env_, static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer)));
        if (array.get() == nullptr) {
            return -EFAULT;
        }
        ScopedByteArrayRO bytes(env_, array.get());
        if (bytes.get() == nullptr) {
            return -ENOMEM;
        }
        memcpy(buf, bytes.get(), read_size);
        return read_size;
    }

    int write_object_bytes(uint64_t handle, int inode, uint64_t offset, uint32_t size,
                           const void* buffer, uint32_t* written_size) {
        static_assert(sizeof(uint64_t) <= sizeof(jlong),
                      "jlong must be able to express any uint64_t values");
        ScopedLocalRef<jbyteArray> array(
                env_,
                static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer)));
        {
            ScopedByteArrayRW bytes(env_, array.get());
            if (bytes.get() == nullptr) {
                return -EIO;
            }
            memcpy(bytes.get(), buffer, size);
        }
        const int result = env_->CallIntMethod(
                self_,
                app_fuse_write_object_bytes,
                file_handle_to_jlong(handle),
                inode,
                offset,
                size,
                array.get());
        if (result < 0) {
            return result;
        }
        *written_size = result;
        return 0;
    }

    static jlong file_handle_to_jlong(uint64_t handle) {
        static_assert(
                sizeof(uint64_t) <= sizeof(jlong),
                "jlong must be able to express any uint64_t values");
        return static_cast<jlong>(handle);
    }

    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);
        }
    }
};

void com_android_mtp_AppFuse_start_app_fuse_loop(JNIEnv* env, jobject self, jint jfd) {
    ScopedFd fd(static_cast<int>(jfd));
    AppFuse appfuse(env, self);

    ALOGV("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) {
                ALOGV("AppFuse was unmounted.\n");
                return;
            }
            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)V",
        (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;
    }

    app_fuse_get_file_size = env->GetMethodID(
            app_fuse_class, "getFileSize", "(I)J");
    if (app_fuse_get_file_size == nullptr) {
        ALOGE("Can't find getFileSize");
        return -1;
    }

    app_fuse_read_object_bytes = env->GetMethodID(
            app_fuse_class, "readObjectBytes", "(IJJ)J");
    if (app_fuse_read_object_bytes == nullptr) {
        ALOGE("Can't find readObjectBytes");
        return -1;
    }

    app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(JIJI[B)I");
    if (app_fuse_write_object_bytes == nullptr) {
        ALOGE("Can't find writeObjectBytes");
        return -1;
    }

    app_fuse_flush_file_handle = env->GetMethodID(app_fuse_class, "flushFileHandle", "(J)I");
    if (app_fuse_flush_file_handle == nullptr) {
        ALOGE("Can't find flushFileHandle");
        return -1;
    }

    app_fuse_close_file_handle = env->GetMethodID(app_fuse_class, "closeFileHandle", "(J)I");
    if (app_fuse_close_file_handle == nullptr) {
        ALOGE("Can't find closeFileHandle");
        return -1;
    }

    app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B");
    if (app_fuse_buffer == nullptr) {
        ALOGE("Can't find mBuffer");
        return -1;
    }

    const jfieldID read_max_fied = env->GetStaticFieldID(app_fuse_class, "MAX_READ", "I");
    if (static_cast<int>(env->GetStaticIntField(app_fuse_class, read_max_fied)) != MAX_READ) {
        return -1;
    }

    const jfieldID write_max_fied = env->GetStaticFieldID(app_fuse_class, "MAX_WRITE", "I");
    if (static_cast<int>(env->GetStaticIntField(app_fuse_class, write_max_fied)) != MAX_WRITE) {
        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;
}
+17 −47
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.ProxyFileDescriptorCallback;
import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.Os;
@@ -36,38 +37,13 @@ import org.junit.Test;

@RunWith(JUnit4.class)
public class AppFusePerfTest {
    final static int SIZE = 10 * 1024 * 1024;  // 10MB

    @Test
    @LargeTest
    public void testReadWriteFile() throws IOException {
        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
        final StorageManager storageManager = context.getSystemService(StorageManager.class);
        final int INODE = 10;
        final int SIZE = 10 * 1024 * 1024;  // 10MB
        final AppFuse appFuse = new AppFuse(
                "test",
                new TestCallback() {
                    @Override
                    public long getFileSize(int inode) throws FileNotFoundException {
                        if (inode != INODE) {
                            throw new FileNotFoundException();
                        }
                        return SIZE;
                    }

                    @Override
                    public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
                            throws IOException {
                        return size;
                    }

                    @Override
                    public int writeObjectBytes(
                            long fileHandle, int inode, long offset, int size, byte[] bytes) {
                        return size;
                    }
                });

        appFuse.mount(storageManager);

        final byte[] bytes = new byte[SIZE];
        final int SAMPLES = 100;
@@ -75,22 +51,20 @@ public class AppFusePerfTest {
        final double[] writeTime = new double[SAMPLES];

        for (int i = 0; i < SAMPLES; i++) {
            final ParcelFileDescriptor fd = appFuse.openFile(
                    INODE,
                    ParcelFileDescriptor.MODE_READ_ONLY);
            final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor(
                    ParcelFileDescriptor.MODE_READ_ONLY, new TestCallback());
            try (final ParcelFileDescriptor.AutoCloseInputStream stream =
                    new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
                final long startTime = System.nanoTime();
                stream.read(bytes);
                readTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0;
            }

        }

        for (int i = 0; i < SAMPLES; i++) {
            final ParcelFileDescriptor fd = appFuse.openFile(
                    INODE,
                    ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
            final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor(
                    ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE,
                    new TestCallback());
            try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
                    new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
                final long startTime = System.nanoTime();
@@ -99,8 +73,6 @@ public class AppFusePerfTest {
            }
        }

        appFuse.close();

        double readAverage = 0;
        double writeAverage = 0;
        double readSquaredAverage = 0;
@@ -127,28 +99,26 @@ public class AppFusePerfTest {
        InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, results);
    }

    private static class TestCallback implements AppFuse.Callback {
    private static class TestCallback extends ProxyFileDescriptorCallback {
        @Override
        public long getFileSize(int inode) throws FileNotFoundException {
            throw new FileNotFoundException();
        public long onGetSize() throws ErrnoException {
            return SIZE;
        }

        @Override
        public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
                throws IOException {
            throw new IOException();
        public int onRead(long offset, int size, byte[] data) throws ErrnoException {
            return size;
        }

        @Override
        public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
                throws IOException {
            throw new IOException();
        public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
            return size;
        }

        @Override
        public void flushFileHandle(long fileHandle) throws IOException {}
        public void onFsync() throws ErrnoException {}

        @Override
        public void closeFileHandle(long fileHandle) {}
        public void onRelease() {}
    }
}
+0 −258

File deleted.

Preview size limit exceeded, changes collapsed.

Loading