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:
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user