com_android_mtp_AppFuse.cpp revision cc9a7d78d519aa25b4afbc96afd401be75696dda
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specic language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_NDEBUG 0 18#define LOG_TAG "AppFuseJNI" 19#include "utils/Log.h" 20 21#include <assert.h> 22#include <dirent.h> 23#include <inttypes.h> 24 25#include <linux/fuse.h> 26#include <sys/stat.h> 27 28#include <map> 29 30#include "jni.h" 31#include "JNIHelp.h" 32#include "android_runtime/AndroidRuntime.h" 33#include "nativehelper/ScopedPrimitiveArray.h" 34 35namespace { 36 37// Maximum number of bytes to write in one request. 38constexpr size_t MAX_WRITE = 256 * 1024; 39constexpr size_t NUM_MAX_HANDLES = 1024; 40 41// Largest possible request. 42// The request size is bounded by the maximum size of a FUSE_WRITE request 43// because it has the largest possible data payload. 44constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) + 45 sizeof(struct fuse_write_in) + MAX_WRITE; 46 47static jclass app_fuse_class; 48static jmethodID app_fuse_get_file_size; 49static jmethodID app_fuse_get_object_bytes; 50 51struct FuseRequest { 52 char buffer[MAX_REQUEST_SIZE]; 53 FuseRequest() {} 54 const struct fuse_in_header& header() const { 55 return *(const struct fuse_in_header*) buffer; 56 } 57 const void* data() const { 58 return (buffer + sizeof(struct fuse_in_header)); 59 } 60 size_t data_length() const { 61 return header().len - sizeof(struct fuse_in_header); 62 } 63}; 64 65class ScopedFd { 66 int mFd; 67 68public: 69 explicit ScopedFd(int fd) : mFd(fd) {} 70 ~ScopedFd() { 71 close(mFd); 72 } 73 operator int() { 74 return mFd; 75 } 76}; 77 78/** 79 * The class is used to access AppFuse class in Java from fuse handlers. 80 */ 81class AppFuse { 82 JNIEnv* env_; 83 jobject self_; 84 85 // Map between file handle and inode. 86 std::map<uint32_t, uint64_t> handles_; 87 uint32_t handle_counter_; 88 89public: 90 AppFuse(JNIEnv* env, jobject self) : 91 env_(env), self_(self), handle_counter_(0) {} 92 93 bool handle_fuse_request(int fd, const FuseRequest& req) { 94 ALOGV("Request op=%d", req.header().opcode); 95 switch (req.header().opcode) { 96 // TODO: Handle more operations that are enough to provide seekable 97 // FD. 98 case FUSE_LOOKUP: 99 invoke_handler(fd, req, &AppFuse::handle_fuse_lookup); 100 return true; 101 case FUSE_INIT: 102 invoke_handler(fd, req, &AppFuse::handle_fuse_init); 103 return true; 104 case FUSE_GETATTR: 105 invoke_handler(fd, req, &AppFuse::handle_fuse_getattr); 106 return true; 107 case FUSE_FORGET: 108 return false; 109 case FUSE_OPEN: 110 invoke_handler(fd, req, &AppFuse::handle_fuse_open); 111 return true; 112 case FUSE_READ: 113 invoke_handler(fd, req, &AppFuse::handle_fuse_read, 8192); 114 return true; 115 case FUSE_RELEASE: 116 invoke_handler(fd, req, &AppFuse::handle_fuse_release, 0); 117 return true; 118 case FUSE_FLUSH: 119 invoke_handler(fd, req, &AppFuse::handle_fuse_flush, 0); 120 return true; 121 default: { 122 ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n", 123 req.header().opcode, 124 req.header().unique, 125 req.header().nodeid); 126 fuse_reply(fd, req.header().unique, -ENOSYS, NULL, 0); 127 return true; 128 } 129 } 130 } 131 132private: 133 int handle_fuse_lookup(const fuse_in_header& header, 134 const char* name, 135 fuse_entry_out* out, 136 uint32_t* /*unused*/) { 137 if (header.nodeid != 1) { 138 return -ENOENT; 139 } 140 141 const int n = atoi(name); 142 if (n == 0) { 143 return -ENOENT; 144 } 145 146 int64_t size = get_file_size(n); 147 if (size < 0) { 148 return -ENOENT; 149 } 150 151 out->nodeid = n; 152 out->attr_valid = 10; 153 out->entry_valid = 10; 154 out->attr.ino = n; 155 out->attr.mode = S_IFREG | 0777; 156 out->attr.size = size; 157 return 0; 158 } 159 160 int handle_fuse_init(const fuse_in_header&, 161 const fuse_init_in* in, 162 fuse_init_out* out, 163 uint32_t* reply_size) { 164 // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out 165 // defined (fuse version 7.6). The structure is the same from 7.6 through 166 // 7.22. Beginning with 7.23, the structure increased in size and added 167 // new parameters. 168 if (in->major != FUSE_KERNEL_VERSION || in->minor < 6) { 169 ALOGE("Fuse kernel version mismatch: Kernel version %d.%d, " 170 "Expected at least %d.6", 171 in->major, in->minor, FUSE_KERNEL_VERSION); 172 return -1; 173 } 174 175 // We limit ourselves to 15 because we don't handle BATCH_FORGET yet 176 out->minor = std::min(in->minor, 15u); 177#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) 178 // FUSE_KERNEL_VERSION >= 23. 179 180 // If the kernel only works on minor revs older than or equal to 22, 181 // then use the older structure size since this code only uses the 7.22 182 // version of the structure. 183 if (in->minor <= 22) { 184 *reply_size = FUSE_COMPAT_22_INIT_OUT_SIZE; 185 } 186#else 187 // Don't drop this line to prevent an 'unused' compile error. 188 *reply_size = sizeof(fuse_init_out); 189#endif 190 191 out->major = FUSE_KERNEL_VERSION; 192 out->max_readahead = in->max_readahead; 193 out->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES; 194 out->max_background = 32; 195 out->congestion_threshold = 32; 196 out->max_write = MAX_WRITE; 197 198 return 0; 199 } 200 201 int handle_fuse_getattr(const fuse_in_header& header, 202 const fuse_getattr_in* /* in */, 203 fuse_attr_out* out, 204 uint32_t* /*unused*/) { 205 if (header.nodeid != 1) { 206 return -ENOENT; 207 } 208 out->attr_valid = 1000 * 60 * 10; 209 out->attr.ino = header.nodeid; 210 out->attr.mode = S_IFDIR | 0777; 211 out->attr.size = 0; 212 return 0; 213 } 214 215 int handle_fuse_open(const fuse_in_header& header, 216 const fuse_open_in* /* in */, 217 fuse_open_out* out, 218 uint32_t* /*unused*/) { 219 if (handles_.size() >= NUM_MAX_HANDLES) { 220 // Too many open files. 221 return -EMFILE; 222 } 223 uint32_t handle; 224 do { 225 handle = handle_counter_++; 226 } while (handles_.count(handle) != 0); 227 228 handles_.insert(std::make_pair(handle, header.nodeid)); 229 out->fh = handle; 230 return 0; 231 } 232 233 int handle_fuse_read(const fuse_in_header& /* header */, 234 const fuse_read_in* in, 235 void* out, 236 uint32_t* reply_size) { 237 const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh); 238 if (it == handles_.end()) { 239 return -EBADF; 240 } 241 const int64_t result = get_object_bytes( 242 it->second, 243 in->offset, 244 in->size, 245 out); 246 if (result < 0) { 247 return -EIO; 248 } 249 *reply_size = static_cast<size_t>(result); 250 return 0; 251 } 252 253 int handle_fuse_release(const fuse_in_header& /* header */, 254 const fuse_release_in* in, 255 void* /* out */, 256 uint32_t* /* reply_size */) { 257 handles_.erase(in->fh); 258 return 0; 259 } 260 261 int handle_fuse_flush(const fuse_in_header& /* header */, 262 const void* /* in */, 263 void* /* out */, 264 uint32_t* /* reply_size */) { 265 return 0; 266 } 267 268 template <typename T, typename S> 269 void invoke_handler(int fd, 270 const FuseRequest& request, 271 int (AppFuse::*handler)(const fuse_in_header&, 272 const T*, 273 S*, 274 uint32_t*), 275 uint32_t reply_size = sizeof(S)) { 276 char reply_data[reply_size]; 277 memset(reply_data, 0, reply_size); 278 const int reply_code = (this->*handler)( 279 request.header(), 280 static_cast<const T*>(request.data()), 281 reinterpret_cast<S*>(reply_data), 282 &reply_size); 283 fuse_reply( 284 fd, 285 request.header().unique, 286 reply_code, 287 reply_data, 288 reply_size); 289 } 290 291 int64_t get_file_size(int inode) { 292 return static_cast<int64_t>(env_->CallLongMethod( 293 self_, 294 app_fuse_get_file_size, 295 static_cast<int>(inode))); 296 } 297 298 int64_t get_object_bytes( 299 int inode, 300 uint64_t offset, 301 uint32_t size, 302 void* buf) { 303 const uint32_t read_size = static_cast<uint32_t>(std::min( 304 static_cast<uint64_t>(size), 305 get_file_size(inode) - offset)); 306 const jbyteArray array = (jbyteArray) env_->CallObjectMethod( 307 self_, 308 app_fuse_get_object_bytes, 309 inode, 310 offset, 311 read_size); 312 if (array == nullptr) { 313 return -1; 314 } 315 ScopedByteArrayRO bytes(env_, array); 316 if (bytes.size() != read_size || bytes.get() == nullptr) { 317 return -1; 318 } 319 320 memcpy(buf, bytes.get(), read_size); 321 return read_size; 322 } 323 324 static void fuse_reply(int fd, int unique, int reply_code, void* reply_data, 325 size_t reply_size) { 326 // Don't send any data for error case. 327 if (reply_code != 0) { 328 reply_size = 0; 329 } 330 331 struct fuse_out_header hdr; 332 hdr.len = reply_size + sizeof(hdr); 333 hdr.error = reply_code; 334 hdr.unique = unique; 335 336 struct iovec vec[2]; 337 vec[0].iov_base = &hdr; 338 vec[0].iov_len = sizeof(hdr); 339 vec[1].iov_base = reply_data; 340 vec[1].iov_len = reply_size; 341 342 const int res = writev(fd, vec, reply_size != 0 ? 2 : 1); 343 if (res < 0) { 344 ALOGE("*** REPLY FAILED *** %d\n", errno); 345 } 346 } 347}; 348 349jboolean com_android_mtp_AppFuse_start_app_fuse_loop( 350 JNIEnv* env, jobject self, jint jfd) { 351 ScopedFd fd(dup(static_cast<int>(jfd))); 352 AppFuse appfuse(env, self); 353 354 ALOGD("Start fuse loop."); 355 while (true) { 356 FuseRequest request; 357 358 const ssize_t result = TEMP_FAILURE_RETRY( 359 read(fd, request.buffer, sizeof(request.buffer))); 360 if (result < 0) { 361 if (errno == ENODEV) { 362 ALOGE("Someone stole our marbles!\n"); 363 return JNI_FALSE; 364 } 365 ALOGE("Failed to read bytes from FD: errno=%d\n", errno); 366 continue; 367 } 368 369 const size_t length = static_cast<size_t>(result); 370 if (length < sizeof(struct fuse_in_header)) { 371 ALOGE("request too short: len=%zu\n", length); 372 continue; 373 } 374 375 if (request.header().len != length) { 376 ALOGE("malformed header: len=%zu, hdr->len=%u\n", 377 length, request.header().len); 378 continue; 379 } 380 381 if (!appfuse.handle_fuse_request(fd, request)) { 382 return JNI_TRUE; 383 } 384 } 385} 386 387static const JNINativeMethod gMethods[] = { 388 { 389 "native_start_app_fuse_loop", 390 "(I)Z", 391 (void *) com_android_mtp_AppFuse_start_app_fuse_loop 392 } 393}; 394 395} 396 397jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { 398 JNIEnv* env = nullptr; 399 if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 400 ALOGE("ERROR: GetEnv failed\n"); 401 return -1; 402 403 } 404 assert(env != nullptr); 405 406 jclass clazz = env->FindClass("com/android/mtp/AppFuse"); 407 if (clazz == nullptr) { 408 ALOGE("Can't find com/android/mtp/AppFuse"); 409 return -1; 410 } 411 412 app_fuse_class = static_cast<jclass>(env->NewGlobalRef(clazz)); 413 if (app_fuse_class == nullptr) { 414 ALOGE("Can't obtain global reference for com/android/mtp/AppFuse"); 415 return -1; 416 } 417 418 app_fuse_get_file_size = env->GetMethodID( 419 app_fuse_class, "getFileSize", "(I)J"); 420 if (app_fuse_get_file_size == nullptr) { 421 ALOGE("Can't find getFileSize"); 422 return -1; 423 } 424 425 app_fuse_get_object_bytes = env->GetMethodID( 426 app_fuse_class, "getObjectBytes", "(IJI)[B"); 427 if (app_fuse_get_object_bytes == nullptr) { 428 ALOGE("Can't find getObjectBytes"); 429 return -1; 430 } 431 432 const int result = android::AndroidRuntime::registerNativeMethods( 433 env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods)); 434 if (result < 0) { 435 return -1; 436 } 437 438 return JNI_VERSION_1_4; 439} 440