Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 35693da2 authored by Daichi Hirono's avatar Daichi Hirono
Browse files

Implement FUSE_WRITE command in app fuse.

The CL adds a handler for FUSE_WRITE command which invokes a Java
handler.

BUG=23093747

Change-Id: I1903fca6b5663e6241ad540a89fe812310ba6810
parent 11c95703
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) +
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 jfieldID app_fuse_buffer;

// NOTE:
@@ -140,6 +141,9 @@ public:
            case FUSE_READ:
                invoke_handler(fd, req, &AppFuse::handle_fuse_read);
                return true;
            case FUSE_WRITE:
                invoke_handler(fd, req, &AppFuse::handle_fuse_write);
                return true;
            case FUSE_RELEASE:
                invoke_handler(fd, req, &AppFuse::handle_fuse_release);
                return true;
@@ -289,6 +293,29 @@ private:
        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(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 */) {
@@ -355,6 +382,27 @@ private:
        return read_size;
    }

    int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer,
                           uint32_t* written_size) {
        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);
        }
        *written_size = env_->CallIntMethod(
                self_, app_fuse_write_object_bytes, inode, offset, size, array.get());
        if (env_->ExceptionCheck()) {
            env_->ExceptionClear();
            return -EIO;
        }
        return 0;
    }

    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.
@@ -469,6 +517,28 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
        return -1;
    }

    app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I");
    if (app_fuse_write_object_bytes == nullptr) {
        ALOGE("Can't find getWriteObjectBytes");
        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) {
+36 −4
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ public class AppFuse {
     * The value is copied from sdcard.c.
     */
    static final int MAX_READ = 128 * 1024;
    static final int MAX_WRITE = 256 * 1024;

    private final String mName;
    private final Callback mCallback;
@@ -47,7 +48,7 @@ public class AppFuse {
     * Buffer for read bytes request.
     * Don't use the buffer from the out of AppFuseMessageThread.
     */
    private byte[] mBuffer = new byte[MAX_READ];
    private byte[] mBuffer = new byte[Math.max(MAX_READ, MAX_WRITE)];

    private Thread mMessageThread;
    private ParcelFileDescriptor mDeviceFd;
@@ -79,11 +80,22 @@ public class AppFuse {
        }
    }

    public ParcelFileDescriptor openFile(int i) throws FileNotFoundException {
    /**
     * Opens a file on app fuse and returns ParcelFileDescriptor.
     *
     * @param i ID for opened file.
     * @param mode Mode for opening file.
     * @see ParcelFileDescriptor#MODE_READ_ONLY
     * @see ParcelFileDescriptor#MODE_WRITE_ONLY
     */
    public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException {
        Preconditions.checkArgument(
                mode == ParcelFileDescriptor.MODE_READ_ONLY ||
                mode == ParcelFileDescriptor.MODE_WRITE_ONLY);
        return ParcelFileDescriptor.open(new File(
                getMountPoint(),
                Integer.toString(i)),
                ParcelFileDescriptor.MODE_READ_ONLY);
                mode);
    }

    File getMountPoint() {
@@ -100,7 +112,7 @@ public class AppFuse {
        long getFileSize(int inode) throws FileNotFoundException;

        /**
         * Returns flie bytes for the give inode.
         * Returns file bytes for the give inode.
         * @param inode
         * @param offset Offset for file bytes.
         * @param size Size for file bytes.
@@ -109,6 +121,17 @@ public class AppFuse {
         * @throws IOException
         */
        long readObjectBytes(int inode, long offset, long size, byte[] bytes) throws IOException;

        /**
         * Handles writing bytes for the give inode.
         * @param inode
         * @param offset Offset for file bytes.
         * @param size Size for file bytes.
         * @param bytes Buffer to store file bytes.
         * @return Number of read bytes. Must not be negative.
         * @throws IOException
         */
        int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException;
    }

    @UsedByNative("com_android_mtp_AppFuse.cpp")
@@ -138,6 +161,15 @@ public class AppFuse {
        }
    }

    @UsedByNative("com_android_mtp_AppFuse.cpp")
    @WorkerThread
    private /* unsgined */ int writeObjectBytes(int inode,
                                                /* unsigned */ long offset,
                                                /* unsigned */ int size,
                                                byte[] bytes) throws IOException {
        return mCallback.writeObjectBytes(inode, offset, size, bytes);
    }

    private native boolean native_start_app_fuse_loop(int fd);

    private class AppFuseMessageThread extends Thread {
+9 −1
Original line number Diff line number Diff line
@@ -242,7 +242,8 @@ public class MtpDocumentsProvider extends DocumentsProvider {
                    // extension.
                    if (MtpDeviceRecord.isPartialReadSupported(
                            device.operationsSupported, fileSize)) {
                        return mAppFuse.openFile(Integer.parseInt(documentId));
                        return mAppFuse.openFile(
                                Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY);
                    } else {
                        return getPipeManager(identifier).readDocument(mMtpManager, identifier);
                    }
@@ -606,5 +607,12 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        public long getFileSize(int inode) throws FileNotFoundException {
            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
        }

        @Override
        public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
                throws IOException {
            // TODO: Implement it.
            throw new IOException();
        }
    }
}
+89 −6
Original line number Diff line number Diff line
@@ -56,7 +56,8 @@ public class AppFuseTest extends AndroidTestCase {
                    }
                });
        appFuse.mount(storageManager);
        final ParcelFileDescriptor fd = appFuse.openFile(INODE);
        final ParcelFileDescriptor fd = appFuse.openFile(
                INODE, ParcelFileDescriptor.MODE_READ_ONLY);
        fd.close();
        appFuse.close();
    }
@@ -67,11 +68,21 @@ public class AppFuseTest extends AndroidTestCase {
        final AppFuse appFuse = new AppFuse("test", new TestCallback());
        appFuse.mount(storageManager);
        try {
            appFuse.openFile(INODE);
            appFuse.openFile(INODE, ParcelFileDescriptor.MODE_READ_ONLY);
            fail();
        } catch (Throwable t) {
            assertTrue(t instanceof FileNotFoundException);
        } catch (FileNotFoundException exp) {}
        appFuse.close();
    }

    public void testOpenFile_illegalMode() throws IOException {
        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
        final int INODE = 10;
        final AppFuse appFuse = new AppFuse("test", new TestCallback());
        appFuse.mount(storageManager);
        try {
            appFuse.openFile(INODE, ParcelFileDescriptor.MODE_READ_WRITE);
            fail();
        } catch (IllegalArgumentException exp) {}
        appFuse.close();
    }

@@ -105,7 +116,8 @@ public class AppFuseTest extends AndroidTestCase {
                    }
                });
        appFuse.mount(storageManager);
        final ParcelFileDescriptor fd = appFuse.openFile(fileInode);
        final ParcelFileDescriptor fd = appFuse.openFile(
                fileInode, ParcelFileDescriptor.MODE_READ_ONLY);
        try (final ParcelFileDescriptor.AutoCloseInputStream stream =
                new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
            final byte[] buffer = new byte[1024];
@@ -115,6 +127,71 @@ public class AppFuseTest extends AndroidTestCase {
        appFuse.close();
    }

    public void testWriteFile() throws IOException {
        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
        final int INODE = 10;
        final byte[] resultBytes = new byte[5];
        final AppFuse appFuse = new AppFuse(
                "test",
                new TestCallback() {
                    @Override
                    public long getFileSize(int inode) throws FileNotFoundException {
                        if (inode != INODE) {
                            throw new FileNotFoundException();
                        }
                        return resultBytes.length;
                    }

                    @Override
                    public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) {
                        for (int i = 0; i < size; i++) {
                            resultBytes[(int)(offset + i)] = bytes[i];
                        }
                        return size;
                    }
                });
        appFuse.mount(storageManager);
        final ParcelFileDescriptor fd = appFuse.openFile(
                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY);
        try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
                new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
            stream.write('a');
            stream.write('b');
            stream.write('c');
            stream.write('d');
            stream.write('e');
        }
        final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' };
        assertTrue(Arrays.equals(BYTES, resultBytes));
        appFuse.close();
    }

    public void testWriteFile_writeError() throws IOException {
        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
        final int INODE = 10;
        final AppFuse appFuse = new AppFuse(
                "test",
                new TestCallback() {
                    @Override
                    public long getFileSize(int inode) throws FileNotFoundException {
                        if (inode != INODE) {
                            throw new FileNotFoundException();
                        }
                        return 5;
                    }
                });
        appFuse.mount(storageManager);
        final ParcelFileDescriptor fd = appFuse.openFile(
                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY);
        try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
                new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
            stream.write('a');
            fail();
        } catch (IOException e) {
        }
        appFuse.close();
    }

    private static class TestCallback implements AppFuse.Callback {
        @Override
        public long getFileSize(int inode) throws FileNotFoundException {
@@ -126,5 +203,11 @@ public class AppFuseTest extends AndroidTestCase {
                throws IOException {
            throw new IOException();
        }

        @Override
        public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
                throws IOException {
            throw new IOException();
        }
    }
}