diff options
| author | 2016-01-20 19:09:25 +0900 | |
|---|---|---|
| committer | 2016-03-25 11:18:44 +0900 | |
| commit | 09ece6c68bdaf3a04b517f04dff6a3272b54b2b2 (patch) | |
| tree | be9ab18294ca97e21db90c14ec372ca81a160031 | |
| parent | fe952f3a0b0ad5c481fa3e52385866f777a4d6e2 (diff) | |
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
(cherry picked from commit 35693da25af11583053d4af6a70d4acbf446978d)
5 files changed, 208 insertions, 12 deletions
diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp index 2b44d514fde7..1d4ed1dc5c5b 100644 --- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp @@ -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) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java index 38435f4afa47..1fd471cb48e2 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java @@ -38,8 +38,12 @@ public class AppFuse { * Max read amount specified at the FUSE kernel implementation. * The value is copied from sdcard.c. */ + @UsedByNative("com_android_mtp_AppFuse.cpp") static final int MAX_READ = 128 * 1024; + @UsedByNative("com_android_mtp_AppFuse.cpp") + static final int MAX_WRITE = 256 * 1024; + private final String mName; private final Callback mCallback; @@ -47,7 +51,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 +83,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 +115,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 +124,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 +164,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 { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index cf5bee5d727d..4152d1e84de1 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -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(); + } } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java b/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java index a7f295f670b3..2ded92540627 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java @@ -22,7 +22,7 @@ import java.lang.annotation.Target; /** * Annotation that shows the method is used by JNI. */ -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.FIELD}) public @interface UsedByNative { /** * JNI file name that uses the method. diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java index c0973bd73514..3b925068c50c 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java @@ -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(); + } } } |