C++程序  |  571行  |  18.2 KB

/*
 * 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 <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:
   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;
}