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

This commit is contained in:
Daichi Hirono
2016-03-25 03:29:51 +00:00
committed by Android (Google) Code Review
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 jclass app_fuse_class;
static jmethodID app_fuse_get_file_size; static jmethodID app_fuse_get_file_size;
static jmethodID app_fuse_read_object_bytes; static jmethodID app_fuse_read_object_bytes;
static jmethodID app_fuse_write_object_bytes;
static jfieldID app_fuse_buffer; static jfieldID app_fuse_buffer;
// NOTE: // NOTE:
@@ -140,6 +141,9 @@ public:
case FUSE_READ: case FUSE_READ:
invoke_handler(fd, req, &AppFuse::handle_fuse_read); invoke_handler(fd, req, &AppFuse::handle_fuse_read);
return true; return true;
case FUSE_WRITE:
invoke_handler(fd, req, &AppFuse::handle_fuse_write);
return true;
case FUSE_RELEASE: case FUSE_RELEASE:
invoke_handler(fd, req, &AppFuse::handle_fuse_release); invoke_handler(fd, req, &AppFuse::handle_fuse_release);
return true; return true;
@@ -289,6 +293,29 @@ private:
return 0; 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 */, int handle_fuse_release(const fuse_in_header& /* header */,
const fuse_release_in* in, const fuse_release_in* in,
FuseResponse<void>* /* out */) { FuseResponse<void>* /* out */) {
@@ -355,6 +382,27 @@ private:
return read_size; 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, static void fuse_reply(int fd, int unique, int reply_code, void* reply_data,
size_t reply_size) { size_t reply_size) {
// Don't send any data for error case. // Don't send any data for error case.
@@ -469,6 +517,28 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
return -1; 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( const int result = android::AndroidRuntime::registerNativeMethods(
env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods)); env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods));
if (result < 0) { if (result < 0) {

View File

@@ -38,8 +38,12 @@ public class AppFuse {
* Max read amount specified at the FUSE kernel implementation. * Max read amount specified at the FUSE kernel implementation.
* The value is copied from sdcard.c. * The value is copied from sdcard.c.
*/ */
@UsedByNative("com_android_mtp_AppFuse.cpp")
static final int MAX_READ = 128 * 1024; 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 String mName;
private final Callback mCallback; private final Callback mCallback;
@@ -47,7 +51,7 @@ public class AppFuse {
* Buffer for read bytes request. * Buffer for read bytes request.
* Don't use the buffer from the out of AppFuseMessageThread. * 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 Thread mMessageThread;
private ParcelFileDescriptor mDeviceFd; 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( return ParcelFileDescriptor.open(new File(
getMountPoint(), getMountPoint(),
Integer.toString(i)), Integer.toString(i)),
ParcelFileDescriptor.MODE_READ_ONLY); mode);
} }
File getMountPoint() { File getMountPoint() {
@@ -100,7 +115,7 @@ public class AppFuse {
long getFileSize(int inode) throws FileNotFoundException; long getFileSize(int inode) throws FileNotFoundException;
/** /**
* Returns flie bytes for the give inode. * Returns file bytes for the give inode.
* @param inode * @param inode
* @param offset Offset for file bytes. * @param offset Offset for file bytes.
* @param size Size for file bytes. * @param size Size for file bytes.
@@ -109,6 +124,17 @@ public class AppFuse {
* @throws IOException * @throws IOException
*/ */
long readObjectBytes(int inode, long offset, long size, byte[] bytes) 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") @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 native boolean native_start_app_fuse_loop(int fd);
private class AppFuseMessageThread extends Thread { private class AppFuseMessageThread extends Thread {

View File

@@ -242,7 +242,8 @@ public class MtpDocumentsProvider extends DocumentsProvider {
// extension. // extension.
if (MtpDeviceRecord.isPartialReadSupported( if (MtpDeviceRecord.isPartialReadSupported(
device.operationsSupported, fileSize)) { device.operationsSupported, fileSize)) {
return mAppFuse.openFile(Integer.parseInt(documentId)); return mAppFuse.openFile(
Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY);
} else { } else {
return getPipeManager(identifier).readDocument(mMtpManager, identifier); return getPipeManager(identifier).readDocument(mMtpManager, identifier);
} }
@@ -606,5 +607,12 @@ public class MtpDocumentsProvider extends DocumentsProvider {
public long getFileSize(int inode) throws FileNotFoundException { public long getFileSize(int inode) throws FileNotFoundException {
return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); 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. * Annotation that shows the method is used by JNI.
*/ */
@Target(ElementType.METHOD) @Target({ElementType.METHOD, ElementType.FIELD})
public @interface UsedByNative { public @interface UsedByNative {
/** /**
* JNI file name that uses the method. * JNI file name that uses the method.

View File

@@ -56,7 +56,8 @@ public class AppFuseTest extends AndroidTestCase {
} }
}); });
appFuse.mount(storageManager); appFuse.mount(storageManager);
final ParcelFileDescriptor fd = appFuse.openFile(INODE); final ParcelFileDescriptor fd = appFuse.openFile(
INODE, ParcelFileDescriptor.MODE_READ_ONLY);
fd.close(); fd.close();
appFuse.close(); appFuse.close();
} }
@@ -67,11 +68,21 @@ public class AppFuseTest extends AndroidTestCase {
final AppFuse appFuse = new AppFuse("test", new TestCallback()); final AppFuse appFuse = new AppFuse("test", new TestCallback());
appFuse.mount(storageManager); appFuse.mount(storageManager);
try { try {
appFuse.openFile(INODE); appFuse.openFile(INODE, ParcelFileDescriptor.MODE_READ_ONLY);
fail(); fail();
} catch (Throwable t) { } catch (FileNotFoundException exp) {}
assertTrue(t instanceof FileNotFoundException); 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(); appFuse.close();
} }
@@ -105,7 +116,8 @@ public class AppFuseTest extends AndroidTestCase {
} }
}); });
appFuse.mount(storageManager); appFuse.mount(storageManager);
final ParcelFileDescriptor fd = appFuse.openFile(fileInode); final ParcelFileDescriptor fd = appFuse.openFile(
fileInode, ParcelFileDescriptor.MODE_READ_ONLY);
try (final ParcelFileDescriptor.AutoCloseInputStream stream = try (final ParcelFileDescriptor.AutoCloseInputStream stream =
new ParcelFileDescriptor.AutoCloseInputStream(fd)) { new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
final byte[] buffer = new byte[1024]; final byte[] buffer = new byte[1024];
@@ -115,6 +127,71 @@ public class AppFuseTest extends AndroidTestCase {
appFuse.close(); 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 { private static class TestCallback implements AppFuse.Callback {
@Override @Override
public long getFileSize(int inode) throws FileNotFoundException { public long getFileSize(int inode) throws FileNotFoundException {
@@ -126,5 +203,11 @@ public class AppFuseTest extends AndroidTestCase {
throws IOException { throws IOException {
throw new IOException(); throw new IOException();
} }
@Override
public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
throws IOException {
throw new IOException();
}
} }
} }