Merge "Implement FUSE_WRITE command in app fuse." into nyc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
44e2a69585
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user