Merge "Implement FUSE_WRITE command in app fuse." into nyc-dev

am: 44e2a69

* commit '44e2a69585261bd62b55500728bc26ddf99fcf59':
  Implement FUSE_WRITE command in app fuse.
This commit is contained in:
Daichi Hirono
2016-03-25 03:35:24 +00:00
committed by android-build-merger
5 changed files with 208 additions and 12 deletions

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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();
}
}
}

View File

@@ -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.

View File

@@ -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();
}
}
}