summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Daichi Hirono <hirono@google.com> 2016-01-20 19:09:25 +0900
committer Daichi Hirono <hirono@google.com> 2016-03-25 11:18:44 +0900
commit09ece6c68bdaf3a04b517f04dff6a3272b54b2b2 (patch)
treebe9ab18294ca97e21db90c14ec372ca81a160031
parentfe952f3a0b0ad5c481fa3e52385866f777a4d6e2 (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)
-rw-r--r--packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp70
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java43
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java10
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java2
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java95
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();
+ }
}
}