com_android_mtp_AppFuse.cpp revision 91e3b50636f48f0860fa7576f185fb36ec4e6dc7
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
34namespace {
35
36constexpr int min(int a, int b) {
37    return a < b ? a : b;
38}
39
40// Maximum number of bytes to write in one request.
41constexpr size_t MAX_WRITE = 256 * 1024;
42
43// Largest possible request.
44// The request size is bounded by the maximum size of a FUSE_WRITE request
45// because it has the largest possible data payload.
46constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) +
47        sizeof(struct fuse_write_in) + MAX_WRITE;
48
49static jclass app_fuse_class;
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 {
82public:
83    AppFuse(JNIEnv* /*env*/, jobject /*self*/) {
84    }
85
86    bool handle_fuse_request(int fd, const FuseRequest& req) {
87        ALOGV("Request op=%d", req.header().opcode);
88        switch (req.header().opcode) {
89            // TODO: Handle more operations that are enough to provide seekable
90            // FD.
91            case FUSE_INIT:
92                invoke_handler(fd, req, &AppFuse::handle_fuse_init);
93                return true;
94            case FUSE_GETATTR:
95                invoke_handler(fd, req, &AppFuse::handle_fuse_getattr);
96                return true;
97            case FUSE_FORGET:
98                return false;
99            default: {
100                ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n",
101                      req.header().opcode,
102                      req.header().unique,
103                      req.header().nodeid);
104                fuse_reply(fd, req.header().unique, -ENOSYS, NULL, 0);
105                return true;
106            }
107        }
108    }
109
110private:
111    int handle_fuse_init(const fuse_in_header&,
112                         const fuse_init_in* in,
113                         fuse_init_out* out,
114                         size_t* reply_size) {
115        // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
116        // defined (fuse version 7.6). The structure is the same from 7.6 through
117        // 7.22. Beginning with 7.23, the structure increased in size and added
118        // new parameters.
119        if (in->major != FUSE_KERNEL_VERSION || in->minor < 6) {
120            ALOGE("Fuse kernel version mismatch: Kernel version %d.%d, "
121                  "Expected at least %d.6",
122                  in->major, in->minor, FUSE_KERNEL_VERSION);
123            return -1;
124        }
125
126        // We limit ourselves to 15 because we don't handle BATCH_FORGET yet
127        out->minor = min(in->minor, 15);
128#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
129        // FUSE_KERNEL_VERSION >= 23.
130
131        // If the kernel only works on minor revs older than or equal to 22,
132        // then use the older structure size since this code only uses the 7.22
133        // version of the structure.
134        if (in->minor <= 22) {
135            *reply_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
136        }
137#else
138        // Don't drop this line to prevent an 'unused' compile error.
139        *reply_size = sizeof(fuse_init_out);
140#endif
141
142        out->major = FUSE_KERNEL_VERSION;
143        out->max_readahead = in->max_readahead;
144        out->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
145        out->max_background = 32;
146        out->congestion_threshold = 32;
147        out->max_write = MAX_WRITE;
148
149        return 0;
150    }
151
152    int handle_fuse_getattr(const fuse_in_header& header,
153                            const fuse_getattr_in* /* in */,
154                            fuse_attr_out* out,
155                            size_t* /*unused*/) {
156        if (header.nodeid != 1) {
157            return -ENOENT;
158        }
159        out->attr_valid = 1000 * 60 * 10;
160        out->attr.ino = header.nodeid;
161        out->attr.mode = S_IFDIR | 0777;
162        out->attr.size = 0;
163        return 0;
164    }
165
166    template <typename T, typename S>
167    void invoke_handler(int fd,
168                        const FuseRequest& request,
169                        int (AppFuse::*handler)(const fuse_in_header&,
170                                                const T*,
171                                                S*,
172                                                size_t*),
173                        size_t reply_size = sizeof(S)) {
174        char reply_data[reply_size];
175        memset(reply_data, 0, reply_size);
176        const int reply_code = (this->*handler)(
177                request.header(),
178                static_cast<const T*>(request.data()),
179                reinterpret_cast<S*>(reply_data),
180                &reply_size);
181        fuse_reply(
182                fd,
183                request.header().unique,
184                reply_code,
185                reply_data,
186                reply_size);
187    }
188
189    static void fuse_reply(int fd, int unique, int reply_code, void* reply_data,
190                           size_t reply_size) {
191        // Don't send any data for error case.
192        if (reply_code != 0) {
193            reply_size = 0;
194        }
195
196        struct fuse_out_header hdr;
197        hdr.len = reply_size + sizeof(hdr);
198        hdr.error = reply_code;
199        hdr.unique = unique;
200
201        struct iovec vec[2];
202        vec[0].iov_base = &hdr;
203        vec[0].iov_len = sizeof(hdr);
204        vec[1].iov_base = reply_data;
205        vec[1].iov_len = reply_size;
206
207        const int res = writev(fd, vec, reply_size != 0 ? 2 : 1);
208        if (res < 0) {
209            ALOGE("*** REPLY FAILED *** %d\n", errno);
210        }
211    }
212};
213
214jboolean com_android_mtp_AppFuse_start_app_fuse_loop(
215        JNIEnv* env, jobject self, jint jfd) {
216    ScopedFd fd(dup(static_cast<int>(jfd)));
217    AppFuse appfuse(env, self);
218
219    ALOGD("Start fuse loop.");
220    while (true) {
221        FuseRequest request;
222        const ssize_t result = TEMP_FAILURE_RETRY(
223                read(fd, request.buffer, sizeof(request.buffer)));
224        if (result < 0) {
225            if (errno == ENODEV) {
226                ALOGE("Someone stole our marbles!\n");
227                return JNI_FALSE;
228            }
229            ALOGE("Failed to read bytes from FD: errno=%d\n", errno);
230            continue;
231        }
232
233        const size_t length = static_cast<size_t>(result);
234        if (length < sizeof(struct fuse_in_header)) {
235            ALOGE("request too short: len=%zu\n", length);
236            continue;
237        }
238
239        if (request.header().len != length) {
240            ALOGE("malformed header: len=%zu, hdr->len=%u\n",
241                  length, request.header().len);
242            continue;
243        }
244
245        if (!appfuse.handle_fuse_request(fd, request)) {
246            return JNI_TRUE;
247        }
248    }
249}
250
251static const JNINativeMethod gMethods[] = {
252    {
253        "native_start_app_fuse_loop",
254        "(I)Z",
255        (void *) com_android_mtp_AppFuse_start_app_fuse_loop
256    }
257};
258
259}
260
261jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
262    JNIEnv* env = nullptr;
263    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
264        ALOGE("ERROR: GetEnv failed\n");
265        return -1;
266
267    }
268    assert(env != nullptr);
269
270    jclass clazz = env->FindClass("com/android/mtp/AppFuse");
271    if (clazz == nullptr) {
272        ALOGE("Can't find com/android/mtp/AppFuse");
273        return -1;
274    }
275    app_fuse_class = static_cast<jclass>(env->NewGlobalRef(clazz));
276    if (app_fuse_class == nullptr) {
277        ALOGE("Can't obtain global reference for com/android/mtp/AppFuse");
278        return -1;
279    }
280
281    const int result = android::AndroidRuntime::registerNativeMethods(
282            env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods));
283    if (result < 0) {
284        return -1;
285    }
286
287    return JNI_VERSION_1_4;
288}
289