/* * 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 #include #include #include #include #include #include #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 class FuseResponse { size_t size_; T* const buffer_; public: FuseResponse(void* buffer) : size_(0), buffer_(static_cast(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 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* 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* 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* 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* 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* out) { if (in->size > MAX_READ) { return -EINVAL; } const std::map::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* out) { if (in->size > MAX_WRITE) { return -EINVAL; } const std::map::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(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* /* 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* /* out */) { return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh)); } template void invoke_handler(int fd, FuseRequest* request, int (AppFuse::*handler)(const fuse_in_header&, const T*, FuseResponse*)) { FuseResponse response(request->data()); const int reply_code = (this->*handler)( request->header(), static_cast(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(env_->CallLongMethod( self_, app_fuse_get_file_size, static_cast(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(inode), static_cast(offset), static_cast(size)); if (read_size <= 0) { return read_size; } ScopedLocalRef array( env_, static_cast(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 array( env_, static_cast(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(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(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(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(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(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(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; }