Merge "Implement FUSE operations in AppFuse JNI."

This commit is contained in:
Daichi Hirono
2016-01-07 01:21:07 +00:00
committed by Android (Google) Code Review
4 changed files with 301 additions and 25 deletions

View File

@@ -30,15 +30,13 @@
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include "nativehelper/ScopedPrimitiveArray.h"
namespace {
constexpr int min(int a, int b) {
return a < b ? a : b;
}
// Maximum number of bytes to write in one request.
constexpr size_t MAX_WRITE = 256 * 1024;
constexpr size_t NUM_MAX_HANDLES = 1024;
// Largest possible request.
// The request size is bounded by the maximum size of a FUSE_WRITE request
@@ -47,6 +45,8 @@ constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) +
sizeof(struct fuse_write_in) + MAX_WRITE;
static jclass app_fuse_class;
static jmethodID app_fuse_get_file_size;
static jmethodID app_fuse_get_object_bytes;
struct FuseRequest {
char buffer[MAX_REQUEST_SIZE];
@@ -79,15 +79,25 @@ public:
* The class is used to access AppFuse class in Java from fuse handlers.
*/
class AppFuse {
JNIEnv* env_;
jobject self_;
// Map between file handle and inode.
std::map<uint32_t, uint64_t> handles_;
uint32_t handle_counter_;
public:
AppFuse(JNIEnv* /*env*/, jobject /*self*/) {
}
AppFuse(JNIEnv* env, jobject self) :
env_(env), self_(self), handle_counter_(0) {}
bool handle_fuse_request(int fd, const FuseRequest& req) {
ALOGV("Request op=%d", req.header().opcode);
switch (req.header().opcode) {
// TODO: Handle more operations that are enough to provide seekable
// FD.
case FUSE_LOOKUP:
invoke_handler(fd, req, &AppFuse::handle_fuse_lookup);
return true;
case FUSE_INIT:
invoke_handler(fd, req, &AppFuse::handle_fuse_init);
return true;
@@ -96,6 +106,18 @@ public:
return true;
case FUSE_FORGET:
return false;
case FUSE_OPEN:
invoke_handler(fd, req, &AppFuse::handle_fuse_open);
return true;
case FUSE_READ:
invoke_handler(fd, req, &AppFuse::handle_fuse_read, 8192);
return true;
case FUSE_RELEASE:
invoke_handler(fd, req, &AppFuse::handle_fuse_release, 0);
return true;
case FUSE_FLUSH:
invoke_handler(fd, req, &AppFuse::handle_fuse_flush, 0);
return true;
default: {
ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n",
req.header().opcode,
@@ -108,10 +130,37 @@ public:
}
private:
int handle_fuse_lookup(const fuse_in_header& header,
const char* name,
fuse_entry_out* out,
uint32_t* /*unused*/) {
if (header.nodeid != 1) {
return -ENOENT;
}
const int n = atoi(name);
if (n == 0) {
return -ENOENT;
}
int64_t size = get_file_size(n);
if (size < 0) {
return -ENOENT;
}
out->nodeid = n;
out->attr_valid = 10;
out->entry_valid = 10;
out->attr.ino = n;
out->attr.mode = S_IFREG | 0777;
out->attr.size = size;
return 0;
}
int handle_fuse_init(const fuse_in_header&,
const fuse_init_in* in,
fuse_init_out* out,
size_t* reply_size) {
uint32_t* reply_size) {
// Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
// defined (fuse version 7.6). The structure is the same from 7.6 through
// 7.22. Beginning with 7.23, the structure increased in size and added
@@ -124,7 +173,7 @@ private:
}
// We limit ourselves to 15 because we don't handle BATCH_FORGET yet
out->minor = min(in->minor, 15);
out->minor = std::min(in->minor, 15u);
#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
// FUSE_KERNEL_VERSION >= 23.
@@ -152,7 +201,7 @@ private:
int handle_fuse_getattr(const fuse_in_header& header,
const fuse_getattr_in* /* in */,
fuse_attr_out* out,
size_t* /*unused*/) {
uint32_t* /*unused*/) {
if (header.nodeid != 1) {
return -ENOENT;
}
@@ -163,14 +212,67 @@ private:
return 0;
}
int handle_fuse_open(const fuse_in_header& header,
const fuse_open_in* /* in */,
fuse_open_out* out,
uint32_t* /*unused*/) {
if (handles_.size() >= NUM_MAX_HANDLES) {
// Too many open files.
return -EMFILE;
}
uint32_t handle;
do {
handle = handle_counter_++;
} while (handles_.count(handle) != 0);
handles_.insert(std::make_pair(handle, header.nodeid));
out->fh = handle;
return 0;
}
int handle_fuse_read(const fuse_in_header& /* header */,
const fuse_read_in* in,
void* out,
uint32_t* reply_size) {
const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh);
if (it == handles_.end()) {
return -EBADF;
}
const int64_t result = get_object_bytes(
it->second,
in->offset,
in->size,
out);
if (result < 0) {
return -EIO;
}
*reply_size = static_cast<size_t>(result);
return 0;
}
int handle_fuse_release(const fuse_in_header& /* header */,
const fuse_release_in* in,
void* /* out */,
uint32_t* /* reply_size */) {
handles_.erase(in->fh);
return 0;
}
int handle_fuse_flush(const fuse_in_header& /* header */,
const void* /* in */,
void* /* out */,
uint32_t* /* reply_size */) {
return 0;
}
template <typename T, typename S>
void invoke_handler(int fd,
const FuseRequest& request,
int (AppFuse::*handler)(const fuse_in_header&,
const T*,
S*,
size_t*),
size_t reply_size = sizeof(S)) {
uint32_t*),
uint32_t reply_size = sizeof(S)) {
char reply_data[reply_size];
memset(reply_data, 0, reply_size);
const int reply_code = (this->*handler)(
@@ -186,6 +288,39 @@ private:
reply_size);
}
int64_t get_file_size(int inode) {
return static_cast<int64_t>(env_->CallLongMethod(
self_,
app_fuse_get_file_size,
static_cast<int>(inode)));
}
int64_t get_object_bytes(
int inode,
uint64_t offset,
uint32_t size,
void* buf) {
const uint32_t read_size = static_cast<uint32_t>(std::min(
static_cast<uint64_t>(size),
get_file_size(inode) - offset));
const jbyteArray array = (jbyteArray) env_->CallObjectMethod(
self_,
app_fuse_get_object_bytes,
inode,
offset,
read_size);
if (array == nullptr) {
return -1;
}
ScopedByteArrayRO bytes(env_, array);
if (bytes.size() != read_size || bytes.get() == nullptr) {
return -1;
}
memcpy(buf, bytes.get(), read_size);
return read_size;
}
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.
@@ -219,6 +354,7 @@ jboolean com_android_mtp_AppFuse_start_app_fuse_loop(
ALOGD("Start fuse loop.");
while (true) {
FuseRequest request;
const ssize_t result = TEMP_FAILURE_RETRY(
read(fd, request.buffer, sizeof(request.buffer)));
if (result < 0) {
@@ -272,12 +408,27 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
ALOGE("Can't find com/android/mtp/AppFuse");
return -1;
}
app_fuse_class = static_cast<jclass>(env->NewGlobalRef(clazz));
if (app_fuse_class == nullptr) {
ALOGE("Can't obtain global reference for com/android/mtp/AppFuse");
return -1;
}
app_fuse_get_file_size = env->GetMethodID(
app_fuse_class, "getFileSize", "(I)J");
if (app_fuse_get_file_size == nullptr) {
ALOGE("Can't find getFileSize");
return -1;
}
app_fuse_get_object_bytes = env->GetMethodID(
app_fuse_class, "getObjectBytes", "(IJI)[B");
if (app_fuse_get_object_bytes == nullptr) {
ALOGE("Can't find getObjectBytes");
return -1;
}
const int result = android::AndroidRuntime::registerNativeMethods(
env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods));
if (result < 0) {

View File

@@ -17,16 +17,14 @@
package com.android.mtp;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.storage.StorageManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.os.Process;
/**
* TODO: Remove VisibleForTesting class.
*/
@@ -37,12 +35,14 @@ public class AppFuse {
}
private final String mName;
private final Callback mCallback;
private final Thread mMessageThread;
private ParcelFileDescriptor mDeviceFd;
@VisibleForTesting
AppFuse(String name) {
AppFuse(String name, Callback callback) {
mName = name;
mCallback = callback;
mMessageThread = new Thread(new Runnable() {
@Override
public void run() {
@@ -72,10 +72,45 @@ public class AppFuse {
}
}
/**
* @param i
* @throws FileNotFoundException
*/
@VisibleForTesting
public ParcelFileDescriptor openFile(int i) throws FileNotFoundException {
return ParcelFileDescriptor.open(new File(
getMountPoint(),
Integer.toString(i)),
ParcelFileDescriptor.MODE_READ_ONLY);
}
@VisibleForTesting
File getMountPoint() {
return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName);
}
static interface Callback {
long getFileSize(int inode) throws FileNotFoundException;
byte[] getObjectBytes(int inode, long offset, int size) throws IOException;
}
@VisibleForTesting
private long getFileSize(int inode) {
try {
return mCallback.getFileSize(inode);
} catch (IOException e) {
return -1;
}
}
@VisibleForTesting
private byte[] getObjectBytes(int inode, long offset, int size) {
try {
return mCallback.getObjectBytes(inode, offset, size);
} catch (IOException e) {
return null;
}
}
private native boolean native_start_app_fuse_loop(int fd);
}

View File

@@ -16,24 +16,27 @@
package com.android.mtp;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.Os;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.MediumTest;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
@SmallTest
/**
* TODO: Enable this test after adding SELinux policies for appfuse.
*/
@MediumTest
public class AppFuseTest extends AndroidTestCase {
/**
* TODO: Enable this test after adding SELinux policies for appfuse.
* @throws ErrnoException
* @throws InterruptedException
*/
public void disabled_testBasic() throws ErrnoException, InterruptedException {
public void disabled_testMount() throws ErrnoException, InterruptedException {
final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
final AppFuse appFuse = new AppFuse("test");
final AppFuse appFuse = new AppFuse("test", new TestCallback());
appFuse.mount(storageManager);
final File file = appFuse.getMountPoint();
assertTrue(file.isDirectory());
@@ -41,4 +44,86 @@ public class AppFuseTest extends AndroidTestCase {
appFuse.close();
assertTrue(1 != Os.stat(file.getPath()).st_ino);
}
public void disabled_testOpenFile() 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) {
return 1024;
}
throw new FileNotFoundException();
}
});
appFuse.mount(storageManager);
final ParcelFileDescriptor fd = appFuse.openFile(INODE);
fd.close();
appFuse.close();
}
public void disabled_testOpenFile_error() {
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);
fail();
} catch (Throwable t) {
assertTrue(t instanceof FileNotFoundException);
}
appFuse.close();
}
public void disabled_testReadFile() throws IOException {
final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
final int INODE = 10;
final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' };
final AppFuse appFuse = new AppFuse(
"test",
new TestCallback() {
@Override
public long getFileSize(int inode) throws FileNotFoundException {
if (inode == INODE) {
return BYTES.length;
}
return super.getFileSize(inode);
}
@Override
public byte[] getObjectBytes(int inode, long offset, int size)
throws IOException {
if (inode == INODE) {
return Arrays.copyOfRange(BYTES, (int) offset, (int) offset + size);
}
return super.getObjectBytes(inode, offset, size);
}
});
appFuse.mount(storageManager);
final ParcelFileDescriptor fd = appFuse.openFile(INODE);
try (final ParcelFileDescriptor.AutoCloseInputStream stream =
new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
final byte[] buffer = new byte[1024];
final int size = stream.read(buffer, 0, buffer.length);
assertEquals(5, size);
}
appFuse.close();
}
private static class TestCallback implements AppFuse.Callback {
@Override
public long getFileSize(int inode) throws FileNotFoundException {
throw new FileNotFoundException();
}
@Override
public byte[] getObjectBytes(int inode, long offset, int size)
throws IOException {
throw new IOException();
}
}
}

View File

@@ -203,4 +203,9 @@ public class TestMtpManager extends MtpManager {
}
return Arrays.copyOf(result, count);
}
@Override
byte[] getObject(int deviceId, int objectHandle, int expectedSize) throws IOException {
return mImportFileBytes.get(pack(deviceId, objectHandle));
}
}