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